@camstack/addon-provider-homeassistant 1.2.145 → 1.2.146
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon.js +5 -4
- package/dist/addon.mjs +5 -4
- package/dist/{dist-C_Vy4lAi.js → dist-BTJhdlL4.js} +2195 -1394
- package/dist/{dist-CN0BXfTz.mjs → dist-D2jDnKLv.mjs} +2195 -1395
- package/dist/ha-export/ha-export.addon.js +1 -1
- package/dist/ha-export/ha-export.addon.mjs +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
require("node:crypto");
|
|
2
|
+
//#region ../types/dist/event-category-C5xZWqz6.mjs
|
|
2
3
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
3
4
|
EventCategory["SystemBoot"] = "system.boot";
|
|
4
5
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -362,6 +363,16 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
362
363
|
*/
|
|
363
364
|
EventCategory["DeviceStateChanged"] = "device.state-changed";
|
|
364
365
|
/**
|
|
366
|
+
* The NATIVE shadow of a cap a composition has claimed fields of moved
|
|
367
|
+
* (D663). Payload: `{ deviceId, capName, native }` — what the provider
|
|
368
|
+
* wrote, BEFORE the owned fields are overlaid. Owner-only (D224): it is for
|
|
369
|
+
* the composer's native self-reads, and the public event routers refuse it
|
|
370
|
+
* (`isOwnerOnlyEventCategory`) so no consumer surface ever shows a second
|
|
371
|
+
* truth beside the merged `DeviceStateChanged.slice`. Emitted only under a
|
|
372
|
+
* `replace` claim, one per native write of that cap.
|
|
373
|
+
*/
|
|
374
|
+
EventCategory["DeviceNativeShadowChanged"] = "device.native-shadow-changed";
|
|
375
|
+
/**
|
|
365
376
|
* Frame occupancy for a camera CHANGED — a tracked object was gained or
|
|
366
377
|
* lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
|
|
367
378
|
*
|
|
@@ -5359,7 +5370,7 @@ var ZodIssueCode = {
|
|
|
5359
5370
|
var ZodFirstPartyTypeKind;
|
|
5360
5371
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5361
5372
|
//#endregion
|
|
5362
|
-
//#region ../types/dist/sleep-
|
|
5373
|
+
//#region ../types/dist/sleep-jkRQgtSc.mjs
|
|
5363
5374
|
/**
|
|
5364
5375
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5365
5376
|
* window to float samples (D455).
|
|
@@ -7060,324 +7071,6 @@ function event(data) {
|
|
|
7060
7071
|
var StaticDirOutputSchema$1 = object({ staticDir: string() });
|
|
7061
7072
|
var VersionOutputSchema$1 = object({ version: string() });
|
|
7062
7073
|
method(_void(), StaticDirOutputSchema$1, { auth: "admin" }), method(_void(), VersionOutputSchema$1, { auth: "admin" });
|
|
7063
|
-
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
7064
|
-
DeviceType["Camera"] = "camera";
|
|
7065
|
-
DeviceType["Hub"] = "hub";
|
|
7066
|
-
DeviceType["Light"] = "light";
|
|
7067
|
-
DeviceType["Siren"] = "siren";
|
|
7068
|
-
DeviceType["Switch"] = "switch";
|
|
7069
|
-
DeviceType["Sensor"] = "sensor";
|
|
7070
|
-
DeviceType["Thermostat"] = "thermostat";
|
|
7071
|
-
/** Air-conditioner / heat-pump climate device (HVAC) — shares the
|
|
7072
|
-
* `climate-control` cap surface with `Thermostat` but renders a
|
|
7073
|
-
* dedicated AC-appropriate control UI (mode chips, fan speed,
|
|
7074
|
-
* independent vertical/horizontal swing). Sources: native Gree, and
|
|
7075
|
-
* reusable by other AC integrations. */
|
|
7076
|
-
DeviceType["Climate"] = "climate";
|
|
7077
|
-
DeviceType["Button"] = "button";
|
|
7078
|
-
/** Generic stateless event emitter — carries a device's EXACT declared
|
|
7079
|
-
* event vocabulary verbatim (no normalization). Installed with the
|
|
7080
|
-
* `event-emitter` cap. Sources: HA `event.*` entities (structured) and
|
|
7081
|
-
* HA bus events (e.g. `zha_event`, generic). */
|
|
7082
|
-
DeviceType["EventEmitter"] = "event-emitter";
|
|
7083
|
-
/** Firmware/software update entity — current vs available version,
|
|
7084
|
-
* updatable flag, update state, and an install action. Installed with
|
|
7085
|
-
* the `update` cap. Sources: Homematic firmware-update channels (and
|
|
7086
|
-
* reusable by other providers, e.g. HA `update.*` entities). */
|
|
7087
|
-
DeviceType["Update"] = "update";
|
|
7088
|
-
DeviceType["Generic"] = "generic";
|
|
7089
|
-
/** Generic notification delivery target (HA `notify.<service>`, future
|
|
7090
|
-
* Telegram / Discord / ntfy / SMTP, …). One device per delivery
|
|
7091
|
-
* endpoint; the `notifier` cap defines the send surface. */
|
|
7092
|
-
DeviceType["Notifier"] = "notifier";
|
|
7093
|
-
/** Pre-recorded action sequence with optional parameters
|
|
7094
|
-
* (HA `script.*`). Runnable via `script-runner` cap. */
|
|
7095
|
-
DeviceType["Script"] = "script";
|
|
7096
|
-
/** Automation rule (HA `automation.*`) — enable/disable + manual
|
|
7097
|
-
* trigger surface exposed via `automation-control` cap. */
|
|
7098
|
-
DeviceType["Automation"] = "automation";
|
|
7099
|
-
/** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
|
|
7100
|
-
DeviceType["Lock"] = "lock";
|
|
7101
|
-
/** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
|
|
7102
|
-
* `valve.*`). `cover` cap with sub-roles for variant. */
|
|
7103
|
-
DeviceType["Cover"] = "cover";
|
|
7104
|
-
/** Pipe / water / gas valve with open/close/stop and optional
|
|
7105
|
-
* position (HA `valve.*`). `valve` cap — a cover-sibling actuator
|
|
7106
|
-
* modelled on the same open/closed lifecycle. */
|
|
7107
|
-
DeviceType["Valve"] = "valve";
|
|
7108
|
-
/** Humidifier / dehumidifier with on/off + target humidity + mode
|
|
7109
|
-
* (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
|
|
7110
|
-
* modelled on the same target / mode lifecycle. */
|
|
7111
|
-
DeviceType["Humidifier"] = "humidifier";
|
|
7112
|
-
/** Water heater / boiler with target temperature + operation mode +
|
|
7113
|
-
* away mode (HA `water_heater.*`). `water-heater` cap — a
|
|
7114
|
-
* climate-family actuator. */
|
|
7115
|
-
DeviceType["WaterHeater"] = "water-heater";
|
|
7116
|
-
/** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
|
|
7117
|
-
DeviceType["Fan"] = "fan";
|
|
7118
|
-
/** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
|
|
7119
|
-
* the camera surface — those use `Camera`. `media-player` cap. */
|
|
7120
|
-
DeviceType["MediaPlayer"] = "media-player";
|
|
7121
|
-
/** Security panel / alarm system (HA `alarm_control_panel.*`).
|
|
7122
|
-
* `alarm-panel` cap. */
|
|
7123
|
-
DeviceType["AlarmPanel"] = "alarm-panel";
|
|
7124
|
-
/** Generic user-settable input (HA `number` / `input_number` / `select`
|
|
7125
|
-
* / `input_select` / `text` / `input_text` / `input_datetime`).
|
|
7126
|
-
* Sub-type via `DeviceRole`: NumericControl / SelectControl /
|
|
7127
|
-
* TextControl / DateTimeControl. */
|
|
7128
|
-
DeviceType["Control"] = "control";
|
|
7129
|
-
/** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
|
|
7130
|
-
* `presence` cap. */
|
|
7131
|
-
DeviceType["Presence"] = "presence";
|
|
7132
|
-
/** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
|
|
7133
|
-
* `weather` cap. */
|
|
7134
|
-
DeviceType["Weather"] = "weather";
|
|
7135
|
-
/** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
|
|
7136
|
-
DeviceType["Vacuum"] = "vacuum";
|
|
7137
|
-
/** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
|
|
7138
|
-
* `lawn-mower-control` cap. */
|
|
7139
|
-
DeviceType["LawnMower"] = "lawn-mower";
|
|
7140
|
-
/** Physical HA device group — parent container for entity-children
|
|
7141
|
-
* adopted from a single HA device entry. Not renderable as a
|
|
7142
|
-
* standalone device; exists only to anchor child entities. */
|
|
7143
|
-
DeviceType["Container"] = "container";
|
|
7144
|
-
/** Single still-image entity (HA `image.*`). Read-only display of an
|
|
7145
|
-
* `entity_picture` signed URL the browser loads directly. `image` cap. */
|
|
7146
|
-
DeviceType["Image"] = "image";
|
|
7147
|
-
/** Smart pet feeder — cloud-connected food dispenser with a bowl food
|
|
7148
|
-
* level, battery, desiccant life, feeding state and manual-feed /
|
|
7149
|
-
* call-pet / maintenance actions. Installed with the `pet-feeder` cap;
|
|
7150
|
-
* dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
|
|
7151
|
-
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
7152
|
-
* integrations sharing the same food/desiccant/hopper surface. */
|
|
7153
|
-
DeviceType["PetFeeder"] = "pet-feeder";
|
|
7154
|
-
return DeviceType;
|
|
7155
|
-
}({});
|
|
7156
|
-
var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
7157
|
-
DeviceFeature["BatteryOperated"] = "battery-operated";
|
|
7158
|
-
DeviceFeature["Rebootable"] = "rebootable";
|
|
7159
|
-
/**
|
|
7160
|
-
* Device supports an on-demand re-sync of its derived spec with its
|
|
7161
|
-
* upstream source — drives the generic Re-sync button. The owning
|
|
7162
|
-
* provider implements the action via the `device-adoption.resync` cap.
|
|
7163
|
-
*/
|
|
7164
|
-
DeviceFeature["Resyncable"] = "resyncable";
|
|
7165
|
-
DeviceFeature["NativeSnapshot"] = "native-snapshot";
|
|
7166
|
-
DeviceFeature["DoorbellButton"] = "doorbell-button";
|
|
7167
|
-
DeviceFeature["TwoWayAudio"] = "two-way-audio";
|
|
7168
|
-
DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
|
|
7169
|
-
/**
|
|
7170
|
-
* Camera supports the on-firmware autotrack subsystem (subject-
|
|
7171
|
-
* following). Distinct from `PanTiltZoom` because not every PTZ
|
|
7172
|
-
* camera ships autotrack — the admin UI uses this flag to gate
|
|
7173
|
-
* the autotrack toggle / settings card without re-deriving from
|
|
7174
|
-
* the cap registry. Mirrors `ptz-autotrack` cap registration:
|
|
7175
|
-
* driver sets this feature when probe confirms the firmware
|
|
7176
|
-
* surface, and registers the cap in the same code path.
|
|
7177
|
-
*/
|
|
7178
|
-
DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
|
|
7179
|
-
/**
|
|
7180
|
-
* Accessory exposes a "trigger on motion" toggle — the parent camera's
|
|
7181
|
-
* motion detection automatically activates this device. Mirrors
|
|
7182
|
-
* `motion-trigger` cap registration: drivers set this feature in the
|
|
7183
|
-
* same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
|
|
7184
|
-
*
|
|
7185
|
-
* Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
|
|
7186
|
-
* fast scalar without binding fetch), notifier rules, and `listAll`
|
|
7187
|
-
* filters that want "all devices with on-motion behaviour".
|
|
7188
|
-
*/
|
|
7189
|
-
DeviceFeature["MotionTrigger"] = "motion-trigger";
|
|
7190
|
-
/** Light supports rgb-triplet color via `color` cap. */
|
|
7191
|
-
DeviceFeature["LightColorRgb"] = "light-color-rgb";
|
|
7192
|
-
/** Light supports HSV color via `color` cap. */
|
|
7193
|
-
DeviceFeature["LightColorHsv"] = "light-color-hsv";
|
|
7194
|
-
/** Light supports color-temperature (mired) via `color` cap. */
|
|
7195
|
-
DeviceFeature["LightColorMired"] = "light-color-mired";
|
|
7196
|
-
/** Thermostat supports a `heat_cool` dual setpoint (targetLow +
|
|
7197
|
-
* targetHigh). Gates the range slider UI. */
|
|
7198
|
-
DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
|
|
7199
|
-
/** Thermostat exposes target humidity and/or current humidity
|
|
7200
|
-
* readings. Gates the humidity controls. */
|
|
7201
|
-
DeviceFeature["ClimateHumidity"] = "climate-humidity";
|
|
7202
|
-
/** Thermostat exposes a fan-mode selector. */
|
|
7203
|
-
DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
|
|
7204
|
-
/** Thermostat exposes preset modes (eco / away / sleep / vendor). */
|
|
7205
|
-
DeviceFeature["ClimatePreset"] = "climate-preset";
|
|
7206
|
-
/** Thermostat exposes a vertical louver swing toggle. Gates the
|
|
7207
|
-
* vertical-swing switch in the climate UI. Independent of horizontal. */
|
|
7208
|
-
DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
|
|
7209
|
-
/** Thermostat exposes a horizontal louver swing toggle. Gates the
|
|
7210
|
-
* horizontal-swing switch in the climate UI. Independent of vertical. */
|
|
7211
|
-
DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
|
|
7212
|
-
/** Cover exposes intermediate position control (0..100). Gates the
|
|
7213
|
-
* position slider UI. */
|
|
7214
|
-
DeviceFeature["CoverPositionable"] = "cover-positionable";
|
|
7215
|
-
/** Cover exposes slat-tilt control. Gates the tilt slider UI. */
|
|
7216
|
-
DeviceFeature["CoverTilt"] = "cover-tilt";
|
|
7217
|
-
/** Valve exposes intermediate position control (0..100). Gates the
|
|
7218
|
-
* position slider / drag surface UI. */
|
|
7219
|
-
DeviceFeature["ValvePositionable"] = "valve-positionable";
|
|
7220
|
-
/** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
|
|
7221
|
-
DeviceFeature["FanSpeed"] = "fan-speed";
|
|
7222
|
-
/** Fan exposes a preset mode selector. */
|
|
7223
|
-
DeviceFeature["FanPreset"] = "fan-preset";
|
|
7224
|
-
/** Fan exposes blade direction (forward/reverse) — typical of
|
|
7225
|
-
* ceiling fans. */
|
|
7226
|
-
DeviceFeature["FanDirection"] = "fan-direction";
|
|
7227
|
-
/** Fan exposes an oscillation toggle. */
|
|
7228
|
-
DeviceFeature["FanOscillating"] = "fan-oscillating";
|
|
7229
|
-
/** Lock requires a PIN code on lock/unlock. Gates the code-entry
|
|
7230
|
-
* field on the UI lock-controls panel. */
|
|
7231
|
-
DeviceFeature["LockPinRequired"] = "lock-pin-required";
|
|
7232
|
-
/** Lock supports a latch-release ("open door") action distinct from
|
|
7233
|
-
* unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
|
|
7234
|
-
* `supported_features`. Gates the Open Door button in the UI. */
|
|
7235
|
-
DeviceFeature["LockOpen"] = "lock-open";
|
|
7236
|
-
/** Media player exposes a seek-to-position surface. */
|
|
7237
|
-
DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
|
|
7238
|
-
/** Media player exposes a volume-level setter. */
|
|
7239
|
-
DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
|
|
7240
|
-
/** Media player exposes a mute toggle distinct from volume=0. */
|
|
7241
|
-
DeviceFeature["MediaPlayerMute"] = "media-player-mute";
|
|
7242
|
-
/** Media player exposes a shuffle toggle. */
|
|
7243
|
-
DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
|
|
7244
|
-
/** Media player exposes a repeat mode (off / all / one). */
|
|
7245
|
-
DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
|
|
7246
|
-
/** Media player exposes a source / input selector. */
|
|
7247
|
-
DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
|
|
7248
|
-
/** Media player exposes a play-arbitrary-media surface (URL / id). */
|
|
7249
|
-
DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
|
|
7250
|
-
/** Media player exposes next-track. */
|
|
7251
|
-
DeviceFeature["MediaPlayerNext"] = "media-player-next";
|
|
7252
|
-
/** Media player exposes previous-track. */
|
|
7253
|
-
DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
|
|
7254
|
-
/** Media player exposes stop distinct from pause. */
|
|
7255
|
-
DeviceFeature["MediaPlayerStop"] = "media-player-stop";
|
|
7256
|
-
/** Alarm panel requires a PIN code on arm/disarm. */
|
|
7257
|
-
DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
|
|
7258
|
-
/** Presence device carries GPS coordinates (lat/lng/accuracy) in
|
|
7259
|
-
* addition to a textual location. */
|
|
7260
|
-
DeviceFeature["PresenceGps"] = "presence-gps";
|
|
7261
|
-
/** Notifier accepts an inline / URL image attachment. */
|
|
7262
|
-
DeviceFeature["NotifierImage"] = "notifier-image";
|
|
7263
|
-
/** Notifier accepts a priority hint (high/normal/low). */
|
|
7264
|
-
DeviceFeature["NotifierPriority"] = "notifier-priority";
|
|
7265
|
-
/** Notifier accepts a free-form `data` payload for platform-specific
|
|
7266
|
-
* fields. */
|
|
7267
|
-
DeviceFeature["NotifierData"] = "notifier-data";
|
|
7268
|
-
/** Notifier supports interactive action buttons / callbacks. */
|
|
7269
|
-
DeviceFeature["NotifierActions"] = "notifier-actions";
|
|
7270
|
-
/** Notifier supports per-call recipient targeting (multi-user). */
|
|
7271
|
-
DeviceFeature["NotifierRecipients"] = "notifier-recipients";
|
|
7272
|
-
/** Script runner accepts a variables map on each run invocation. */
|
|
7273
|
-
DeviceFeature["ScriptVariables"] = "script-variables";
|
|
7274
|
-
/** Automation `trigger` accepts a skipCondition flag — fires the
|
|
7275
|
-
* automation's actions while bypassing its condition block. */
|
|
7276
|
-
DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
|
|
7277
|
-
/** Robot vacuum exposes a live cleaning map (image child). Gates the
|
|
7278
|
-
* map tile in the vacuum UI. */
|
|
7279
|
-
DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
|
|
7280
|
-
/** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
|
|
7281
|
-
* switches group. */
|
|
7282
|
-
DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
|
|
7283
|
-
/** Robot mower exposes a live mowing map (SVG image child). Gates the
|
|
7284
|
-
* mower map tile. */
|
|
7285
|
-
DeviceFeature["MowerHasMap"] = "mower-has-map";
|
|
7286
|
-
/** Robot mower exposes targeted mowing (all-area / zones / edges /
|
|
7287
|
-
* spots) — gates the Mow action + map selectors. */
|
|
7288
|
-
DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
|
|
7289
|
-
return DeviceFeature;
|
|
7290
|
-
}({});
|
|
7291
|
-
/**
|
|
7292
|
-
* Semantic role a device plays within its parent. Populated by driver
|
|
7293
|
-
* addons when creating accessory devices (Reolink siren/floodlight/
|
|
7294
|
-
* PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
|
|
7295
|
-
* admin UI to pick icons, labels, and widgets — a `Switch` with
|
|
7296
|
-
* `role: Floodlight` renders as a bulb with a brightness slider,
|
|
7297
|
-
* whereas a `Switch` with `role: Siren` renders as a klaxon.
|
|
7298
|
-
*
|
|
7299
|
-
* Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
|
|
7300
|
-
* sqlite as a nullable TEXT column — old rows keep working unchanged.
|
|
7301
|
-
*/
|
|
7302
|
-
var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
7303
|
-
DeviceRole["Siren"] = "siren";
|
|
7304
|
-
DeviceRole["Floodlight"] = "floodlight";
|
|
7305
|
-
DeviceRole["Spotlight"] = "spotlight";
|
|
7306
|
-
DeviceRole["PirSensor"] = "pir-sensor";
|
|
7307
|
-
DeviceRole["Chime"] = "chime";
|
|
7308
|
-
DeviceRole["Autotrack"] = "autotrack";
|
|
7309
|
-
DeviceRole["Nightvision"] = "nightvision";
|
|
7310
|
-
DeviceRole["PrivacyMask"] = "privacy-mask";
|
|
7311
|
-
DeviceRole["Doorbell"] = "doorbell";
|
|
7312
|
-
/** Virtual HA toggle (input_boolean.*) — distinguishable from a
|
|
7313
|
-
* real Switch device for UI rendering / export adapters. */
|
|
7314
|
-
DeviceRole["BinaryHelper"] = "binary-helper";
|
|
7315
|
-
/** Generic motion / occupancy / moving event source. Distinct from
|
|
7316
|
-
* the camera accessory PirSensor role: that one is a camera child;
|
|
7317
|
-
* this is a standalone HA / 3rd-party motion sensor. */
|
|
7318
|
-
DeviceRole["MotionSensor"] = "motion-sensor";
|
|
7319
|
-
DeviceRole["ContactSensor"] = "contact-sensor";
|
|
7320
|
-
DeviceRole["LeakSensor"] = "leak-sensor";
|
|
7321
|
-
DeviceRole["SmokeSensor"] = "smoke-sensor";
|
|
7322
|
-
DeviceRole["COSensor"] = "co-sensor";
|
|
7323
|
-
DeviceRole["GasSensor"] = "gas-sensor";
|
|
7324
|
-
DeviceRole["TamperSensor"] = "tamper-sensor";
|
|
7325
|
-
DeviceRole["VibrationSensor"] = "vibration-sensor";
|
|
7326
|
-
DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
|
|
7327
|
-
DeviceRole["SoundSensor"] = "sound-sensor";
|
|
7328
|
-
/** Fallback for `binary_sensor` without a known `device_class`. */
|
|
7329
|
-
DeviceRole["BinarySensor"] = "binary-sensor";
|
|
7330
|
-
DeviceRole["TemperatureSensor"] = "temperature-sensor";
|
|
7331
|
-
DeviceRole["HumiditySensor"] = "humidity-sensor";
|
|
7332
|
-
DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
|
|
7333
|
-
DeviceRole["PressureSensor"] = "pressure-sensor";
|
|
7334
|
-
/** Wind speed or direction (weather-station `wind-sensor` cap). */
|
|
7335
|
-
DeviceRole["WindSensor"] = "wind-sensor";
|
|
7336
|
-
/** Rain accumulation or rate (weather-station `rain-sensor` cap). */
|
|
7337
|
-
DeviceRole["RainSensor"] = "rain-sensor";
|
|
7338
|
-
/** UV index (weather-station `uv-sensor` cap). */
|
|
7339
|
-
DeviceRole["UvSensor"] = "uv-sensor";
|
|
7340
|
-
/** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
|
|
7341
|
-
* Distinct from AmbientLightSensor (lux). */
|
|
7342
|
-
DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
|
|
7343
|
-
/** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
|
|
7344
|
-
DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
|
|
7345
|
-
DeviceRole["PowerSensor"] = "power-sensor";
|
|
7346
|
-
DeviceRole["EnergySensor"] = "energy-sensor";
|
|
7347
|
-
DeviceRole["VoltageSensor"] = "voltage-sensor";
|
|
7348
|
-
DeviceRole["CurrentSensor"] = "current-sensor";
|
|
7349
|
-
DeviceRole["AirQualitySensor"] = "air-quality-sensor";
|
|
7350
|
-
/** Battery level (numeric % via `sensor` OR low-bool via
|
|
7351
|
-
* `binary_sensor` — the cap distinguishes via the value type). */
|
|
7352
|
-
DeviceRole["BatterySensor"] = "battery-sensor";
|
|
7353
|
-
/** Fallback for `sensor` numeric without a known `device_class`. */
|
|
7354
|
-
DeviceRole["NumericSensor"] = "numeric-sensor";
|
|
7355
|
-
/** String / enum state (HA `sensor` with `state_class: enum` or
|
|
7356
|
-
* `attributes.options`). */
|
|
7357
|
-
DeviceRole["EnumSensor"] = "enum-sensor";
|
|
7358
|
-
/** Date / timestamp state (HA `sensor` with `device_class: timestamp`
|
|
7359
|
-
* or `date`). The slice carries the raw ISO string verbatim (hosted on
|
|
7360
|
-
* the `enum-sensor` cap); the UI renders it locale-formatted. */
|
|
7361
|
-
DeviceRole["DateTimeSensor"] = "datetime-sensor";
|
|
7362
|
-
/** Last-resort fallback when nothing else matches. */
|
|
7363
|
-
DeviceRole["GenericSensor"] = "generic-sensor";
|
|
7364
|
-
DeviceRole["NumericControl"] = "numeric-control";
|
|
7365
|
-
DeviceRole["SelectControl"] = "select-control";
|
|
7366
|
-
DeviceRole["TextControl"] = "text-control";
|
|
7367
|
-
DeviceRole["DateTimeControl"] = "datetime-control";
|
|
7368
|
-
/** Mobile push notifier (HA `notify.mobile_app_*`) — supports
|
|
7369
|
-
* rich features (image, priority, channel routing). */
|
|
7370
|
-
DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
|
|
7371
|
-
/** Chat / messaging service (HA `notify.telegram_*`,
|
|
7372
|
-
* `notify.discord_*`, etc.). */
|
|
7373
|
-
DeviceRole["MessagingNotifier"] = "messaging-notifier";
|
|
7374
|
-
/** Email-based delivery (HA `notify.smtp`, etc.). */
|
|
7375
|
-
DeviceRole["EmailNotifier"] = "email-notifier";
|
|
7376
|
-
/** Fallback when the notifier service name doesn't match a known
|
|
7377
|
-
* pattern. */
|
|
7378
|
-
DeviceRole["GenericNotifier"] = "generic-notifier";
|
|
7379
|
-
return DeviceRole;
|
|
7380
|
-
}({});
|
|
7381
7074
|
var StaticDirOutputSchema = object({ staticDir: string() });
|
|
7382
7075
|
var VersionOutputSchema = object({ version: string() });
|
|
7383
7076
|
method(_void(), StaticDirOutputSchema, { auth: "admin" }), method(_void(), VersionOutputSchema, { auth: "admin" });
|
|
@@ -7445,7 +7138,325 @@ method(object({ deviceId: number() }), array(StreamSourceEntrySchema)), method(o
|
|
|
7445
7138
|
auth: "admin"
|
|
7446
7139
|
}), method(object({ deviceId: number() }), unknown().nullable()), method(object({ deviceId: number() }), RawStateResultSchema.nullable(), { auth: "protected" });
|
|
7447
7140
|
//#endregion
|
|
7448
|
-
//#region ../types/dist/err-msg-
|
|
7141
|
+
//#region ../types/dist/err-msg-DX6i_MY4.mjs
|
|
7142
|
+
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
7143
|
+
DeviceType["Camera"] = "camera";
|
|
7144
|
+
DeviceType["Hub"] = "hub";
|
|
7145
|
+
DeviceType["Light"] = "light";
|
|
7146
|
+
DeviceType["Siren"] = "siren";
|
|
7147
|
+
DeviceType["Switch"] = "switch";
|
|
7148
|
+
DeviceType["Sensor"] = "sensor";
|
|
7149
|
+
DeviceType["Thermostat"] = "thermostat";
|
|
7150
|
+
/** Air-conditioner / heat-pump climate device (HVAC) — shares the
|
|
7151
|
+
* `climate-control` cap surface with `Thermostat` but renders a
|
|
7152
|
+
* dedicated AC-appropriate control UI (mode chips, fan speed,
|
|
7153
|
+
* independent vertical/horizontal swing). Sources: native Gree, and
|
|
7154
|
+
* reusable by other AC integrations. */
|
|
7155
|
+
DeviceType["Climate"] = "climate";
|
|
7156
|
+
DeviceType["Button"] = "button";
|
|
7157
|
+
/** Generic stateless event emitter — carries a device's EXACT declared
|
|
7158
|
+
* event vocabulary verbatim (no normalization). Installed with the
|
|
7159
|
+
* `event-emitter` cap. Sources: HA `event.*` entities (structured) and
|
|
7160
|
+
* HA bus events (e.g. `zha_event`, generic). */
|
|
7161
|
+
DeviceType["EventEmitter"] = "event-emitter";
|
|
7162
|
+
/** Firmware/software update entity — current vs available version,
|
|
7163
|
+
* updatable flag, update state, and an install action. Installed with
|
|
7164
|
+
* the `update` cap. Sources: Homematic firmware-update channels (and
|
|
7165
|
+
* reusable by other providers, e.g. HA `update.*` entities). */
|
|
7166
|
+
DeviceType["Update"] = "update";
|
|
7167
|
+
DeviceType["Generic"] = "generic";
|
|
7168
|
+
/** Generic notification delivery target (HA `notify.<service>`, future
|
|
7169
|
+
* Telegram / Discord / ntfy / SMTP, …). One device per delivery
|
|
7170
|
+
* endpoint; the `notifier` cap defines the send surface. */
|
|
7171
|
+
DeviceType["Notifier"] = "notifier";
|
|
7172
|
+
/** Pre-recorded action sequence with optional parameters
|
|
7173
|
+
* (HA `script.*`). Runnable via `script-runner` cap. */
|
|
7174
|
+
DeviceType["Script"] = "script";
|
|
7175
|
+
/** Automation rule (HA `automation.*`) — enable/disable + manual
|
|
7176
|
+
* trigger surface exposed via `automation-control` cap. */
|
|
7177
|
+
DeviceType["Automation"] = "automation";
|
|
7178
|
+
/** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
|
|
7179
|
+
DeviceType["Lock"] = "lock";
|
|
7180
|
+
/** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
|
|
7181
|
+
* `valve.*`). `cover` cap with sub-roles for variant. */
|
|
7182
|
+
DeviceType["Cover"] = "cover";
|
|
7183
|
+
/** Pipe / water / gas valve with open/close/stop and optional
|
|
7184
|
+
* position (HA `valve.*`). `valve` cap — a cover-sibling actuator
|
|
7185
|
+
* modelled on the same open/closed lifecycle. */
|
|
7186
|
+
DeviceType["Valve"] = "valve";
|
|
7187
|
+
/** Humidifier / dehumidifier with on/off + target humidity + mode
|
|
7188
|
+
* (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
|
|
7189
|
+
* modelled on the same target / mode lifecycle. */
|
|
7190
|
+
DeviceType["Humidifier"] = "humidifier";
|
|
7191
|
+
/** Water heater / boiler with target temperature + operation mode +
|
|
7192
|
+
* away mode (HA `water_heater.*`). `water-heater` cap — a
|
|
7193
|
+
* climate-family actuator. */
|
|
7194
|
+
DeviceType["WaterHeater"] = "water-heater";
|
|
7195
|
+
/** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
|
|
7196
|
+
DeviceType["Fan"] = "fan";
|
|
7197
|
+
/** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
|
|
7198
|
+
* the camera surface — those use `Camera`. `media-player` cap. */
|
|
7199
|
+
DeviceType["MediaPlayer"] = "media-player";
|
|
7200
|
+
/** Security panel / alarm system (HA `alarm_control_panel.*`).
|
|
7201
|
+
* `alarm-panel` cap. */
|
|
7202
|
+
DeviceType["AlarmPanel"] = "alarm-panel";
|
|
7203
|
+
/** Generic user-settable input (HA `number` / `input_number` / `select`
|
|
7204
|
+
* / `input_select` / `text` / `input_text` / `input_datetime`).
|
|
7205
|
+
* Sub-type via `DeviceRole`: NumericControl / SelectControl /
|
|
7206
|
+
* TextControl / DateTimeControl. */
|
|
7207
|
+
DeviceType["Control"] = "control";
|
|
7208
|
+
/** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
|
|
7209
|
+
* `presence` cap. */
|
|
7210
|
+
DeviceType["Presence"] = "presence";
|
|
7211
|
+
/** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
|
|
7212
|
+
* `weather` cap. */
|
|
7213
|
+
DeviceType["Weather"] = "weather";
|
|
7214
|
+
/** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
|
|
7215
|
+
DeviceType["Vacuum"] = "vacuum";
|
|
7216
|
+
/** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
|
|
7217
|
+
* `lawn-mower-control` cap. */
|
|
7218
|
+
DeviceType["LawnMower"] = "lawn-mower";
|
|
7219
|
+
/** Physical HA device group — parent container for entity-children
|
|
7220
|
+
* adopted from a single HA device entry. Not renderable as a
|
|
7221
|
+
* standalone device; exists only to anchor child entities. */
|
|
7222
|
+
DeviceType["Container"] = "container";
|
|
7223
|
+
/** Single still-image entity (HA `image.*`). Read-only display of an
|
|
7224
|
+
* `entity_picture` signed URL the browser loads directly. `image` cap. */
|
|
7225
|
+
DeviceType["Image"] = "image";
|
|
7226
|
+
/** Smart pet feeder — cloud-connected food dispenser with a bowl food
|
|
7227
|
+
* level, battery, desiccant life, feeding state and manual-feed /
|
|
7228
|
+
* call-pet / maintenance actions. Installed with the `pet-feeder` cap;
|
|
7229
|
+
* dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
|
|
7230
|
+
* native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
|
|
7231
|
+
* integrations sharing the same food/desiccant/hopper surface. */
|
|
7232
|
+
DeviceType["PetFeeder"] = "pet-feeder";
|
|
7233
|
+
return DeviceType;
|
|
7234
|
+
}({});
|
|
7235
|
+
var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
7236
|
+
DeviceFeature["BatteryOperated"] = "battery-operated";
|
|
7237
|
+
DeviceFeature["Rebootable"] = "rebootable";
|
|
7238
|
+
/**
|
|
7239
|
+
* Device supports an on-demand re-sync of its derived spec with its
|
|
7240
|
+
* upstream source — drives the generic Re-sync button. The owning
|
|
7241
|
+
* provider implements the action via the `device-adoption.resync` cap.
|
|
7242
|
+
*/
|
|
7243
|
+
DeviceFeature["Resyncable"] = "resyncable";
|
|
7244
|
+
DeviceFeature["NativeSnapshot"] = "native-snapshot";
|
|
7245
|
+
DeviceFeature["DoorbellButton"] = "doorbell-button";
|
|
7246
|
+
DeviceFeature["TwoWayAudio"] = "two-way-audio";
|
|
7247
|
+
DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
|
|
7248
|
+
/**
|
|
7249
|
+
* Camera supports the on-firmware autotrack subsystem (subject-
|
|
7250
|
+
* following). Distinct from `PanTiltZoom` because not every PTZ
|
|
7251
|
+
* camera ships autotrack — the admin UI uses this flag to gate
|
|
7252
|
+
* the autotrack toggle / settings card without re-deriving from
|
|
7253
|
+
* the cap registry. Mirrors `ptz-autotrack` cap registration:
|
|
7254
|
+
* driver sets this feature when probe confirms the firmware
|
|
7255
|
+
* surface, and registers the cap in the same code path.
|
|
7256
|
+
*/
|
|
7257
|
+
DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
|
|
7258
|
+
/**
|
|
7259
|
+
* Accessory exposes a "trigger on motion" toggle — the parent camera's
|
|
7260
|
+
* motion detection automatically activates this device. Mirrors
|
|
7261
|
+
* `motion-trigger` cap registration: drivers set this feature in the
|
|
7262
|
+
* same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
|
|
7263
|
+
*
|
|
7264
|
+
* Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
|
|
7265
|
+
* fast scalar without binding fetch), notifier rules, and `listAll`
|
|
7266
|
+
* filters that want "all devices with on-motion behaviour".
|
|
7267
|
+
*/
|
|
7268
|
+
DeviceFeature["MotionTrigger"] = "motion-trigger";
|
|
7269
|
+
/** Light supports rgb-triplet color via `color` cap. */
|
|
7270
|
+
DeviceFeature["LightColorRgb"] = "light-color-rgb";
|
|
7271
|
+
/** Light supports HSV color via `color` cap. */
|
|
7272
|
+
DeviceFeature["LightColorHsv"] = "light-color-hsv";
|
|
7273
|
+
/** Light supports color-temperature (mired) via `color` cap. */
|
|
7274
|
+
DeviceFeature["LightColorMired"] = "light-color-mired";
|
|
7275
|
+
/** Thermostat supports a `heat_cool` dual setpoint (targetLow +
|
|
7276
|
+
* targetHigh). Gates the range slider UI. */
|
|
7277
|
+
DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
|
|
7278
|
+
/** Thermostat exposes target humidity and/or current humidity
|
|
7279
|
+
* readings. Gates the humidity controls. */
|
|
7280
|
+
DeviceFeature["ClimateHumidity"] = "climate-humidity";
|
|
7281
|
+
/** Thermostat exposes a fan-mode selector. */
|
|
7282
|
+
DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
|
|
7283
|
+
/** Thermostat exposes preset modes (eco / away / sleep / vendor). */
|
|
7284
|
+
DeviceFeature["ClimatePreset"] = "climate-preset";
|
|
7285
|
+
/** Thermostat exposes a vertical louver swing toggle. Gates the
|
|
7286
|
+
* vertical-swing switch in the climate UI. Independent of horizontal. */
|
|
7287
|
+
DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
|
|
7288
|
+
/** Thermostat exposes a horizontal louver swing toggle. Gates the
|
|
7289
|
+
* horizontal-swing switch in the climate UI. Independent of vertical. */
|
|
7290
|
+
DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
|
|
7291
|
+
/** Cover exposes intermediate position control (0..100). Gates the
|
|
7292
|
+
* position slider UI. */
|
|
7293
|
+
DeviceFeature["CoverPositionable"] = "cover-positionable";
|
|
7294
|
+
/** Cover exposes slat-tilt control. Gates the tilt slider UI. */
|
|
7295
|
+
DeviceFeature["CoverTilt"] = "cover-tilt";
|
|
7296
|
+
/** Valve exposes intermediate position control (0..100). Gates the
|
|
7297
|
+
* position slider / drag surface UI. */
|
|
7298
|
+
DeviceFeature["ValvePositionable"] = "valve-positionable";
|
|
7299
|
+
/** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
|
|
7300
|
+
DeviceFeature["FanSpeed"] = "fan-speed";
|
|
7301
|
+
/** Fan exposes a preset mode selector. */
|
|
7302
|
+
DeviceFeature["FanPreset"] = "fan-preset";
|
|
7303
|
+
/** Fan exposes blade direction (forward/reverse) — typical of
|
|
7304
|
+
* ceiling fans. */
|
|
7305
|
+
DeviceFeature["FanDirection"] = "fan-direction";
|
|
7306
|
+
/** Fan exposes an oscillation toggle. */
|
|
7307
|
+
DeviceFeature["FanOscillating"] = "fan-oscillating";
|
|
7308
|
+
/** Lock requires a PIN code on lock/unlock. Gates the code-entry
|
|
7309
|
+
* field on the UI lock-controls panel. */
|
|
7310
|
+
DeviceFeature["LockPinRequired"] = "lock-pin-required";
|
|
7311
|
+
/** Lock supports a latch-release ("open door") action distinct from
|
|
7312
|
+
* unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
|
|
7313
|
+
* `supported_features`. Gates the Open Door button in the UI. */
|
|
7314
|
+
DeviceFeature["LockOpen"] = "lock-open";
|
|
7315
|
+
/** Media player exposes a seek-to-position surface. */
|
|
7316
|
+
DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
|
|
7317
|
+
/** Media player exposes a volume-level setter. */
|
|
7318
|
+
DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
|
|
7319
|
+
/** Media player exposes a mute toggle distinct from volume=0. */
|
|
7320
|
+
DeviceFeature["MediaPlayerMute"] = "media-player-mute";
|
|
7321
|
+
/** Media player exposes a shuffle toggle. */
|
|
7322
|
+
DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
|
|
7323
|
+
/** Media player exposes a repeat mode (off / all / one). */
|
|
7324
|
+
DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
|
|
7325
|
+
/** Media player exposes a source / input selector. */
|
|
7326
|
+
DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
|
|
7327
|
+
/** Media player exposes a play-arbitrary-media surface (URL / id). */
|
|
7328
|
+
DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
|
|
7329
|
+
/** Media player exposes next-track. */
|
|
7330
|
+
DeviceFeature["MediaPlayerNext"] = "media-player-next";
|
|
7331
|
+
/** Media player exposes previous-track. */
|
|
7332
|
+
DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
|
|
7333
|
+
/** Media player exposes stop distinct from pause. */
|
|
7334
|
+
DeviceFeature["MediaPlayerStop"] = "media-player-stop";
|
|
7335
|
+
/** Alarm panel requires a PIN code on arm/disarm. */
|
|
7336
|
+
DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
|
|
7337
|
+
/** Presence device carries GPS coordinates (lat/lng/accuracy) in
|
|
7338
|
+
* addition to a textual location. */
|
|
7339
|
+
DeviceFeature["PresenceGps"] = "presence-gps";
|
|
7340
|
+
/** Notifier accepts an inline / URL image attachment. */
|
|
7341
|
+
DeviceFeature["NotifierImage"] = "notifier-image";
|
|
7342
|
+
/** Notifier accepts a priority hint (high/normal/low). */
|
|
7343
|
+
DeviceFeature["NotifierPriority"] = "notifier-priority";
|
|
7344
|
+
/** Notifier accepts a free-form `data` payload for platform-specific
|
|
7345
|
+
* fields. */
|
|
7346
|
+
DeviceFeature["NotifierData"] = "notifier-data";
|
|
7347
|
+
/** Notifier supports interactive action buttons / callbacks. */
|
|
7348
|
+
DeviceFeature["NotifierActions"] = "notifier-actions";
|
|
7349
|
+
/** Notifier supports per-call recipient targeting (multi-user). */
|
|
7350
|
+
DeviceFeature["NotifierRecipients"] = "notifier-recipients";
|
|
7351
|
+
/** Script runner accepts a variables map on each run invocation. */
|
|
7352
|
+
DeviceFeature["ScriptVariables"] = "script-variables";
|
|
7353
|
+
/** Automation `trigger` accepts a skipCondition flag — fires the
|
|
7354
|
+
* automation's actions while bypassing its condition block. */
|
|
7355
|
+
DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
|
|
7356
|
+
/** Robot vacuum exposes a live cleaning map (image child). Gates the
|
|
7357
|
+
* map tile in the vacuum UI. */
|
|
7358
|
+
DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
|
|
7359
|
+
/** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
|
|
7360
|
+
* switches group. */
|
|
7361
|
+
DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
|
|
7362
|
+
/** Robot mower exposes a live mowing map (SVG image child). Gates the
|
|
7363
|
+
* mower map tile. */
|
|
7364
|
+
DeviceFeature["MowerHasMap"] = "mower-has-map";
|
|
7365
|
+
/** Robot mower exposes targeted mowing (all-area / zones / edges /
|
|
7366
|
+
* spots) — gates the Mow action + map selectors. */
|
|
7367
|
+
DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
|
|
7368
|
+
return DeviceFeature;
|
|
7369
|
+
}({});
|
|
7370
|
+
/**
|
|
7371
|
+
* Semantic role a device plays within its parent. Populated by driver
|
|
7372
|
+
* addons when creating accessory devices (Reolink siren/floodlight/
|
|
7373
|
+
* PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
|
|
7374
|
+
* admin UI to pick icons, labels, and widgets — a `Switch` with
|
|
7375
|
+
* `role: Floodlight` renders as a bulb with a brightness slider,
|
|
7376
|
+
* whereas a `Switch` with `role: Siren` renders as a klaxon.
|
|
7377
|
+
*
|
|
7378
|
+
* Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
|
|
7379
|
+
* sqlite as a nullable TEXT column — old rows keep working unchanged.
|
|
7380
|
+
*/
|
|
7381
|
+
var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
7382
|
+
DeviceRole["Siren"] = "siren";
|
|
7383
|
+
DeviceRole["Floodlight"] = "floodlight";
|
|
7384
|
+
DeviceRole["Spotlight"] = "spotlight";
|
|
7385
|
+
DeviceRole["PirSensor"] = "pir-sensor";
|
|
7386
|
+
DeviceRole["Chime"] = "chime";
|
|
7387
|
+
DeviceRole["Autotrack"] = "autotrack";
|
|
7388
|
+
DeviceRole["Nightvision"] = "nightvision";
|
|
7389
|
+
DeviceRole["PrivacyMask"] = "privacy-mask";
|
|
7390
|
+
DeviceRole["Doorbell"] = "doorbell";
|
|
7391
|
+
/** Virtual HA toggle (input_boolean.*) — distinguishable from a
|
|
7392
|
+
* real Switch device for UI rendering / export adapters. */
|
|
7393
|
+
DeviceRole["BinaryHelper"] = "binary-helper";
|
|
7394
|
+
/** Generic motion / occupancy / moving event source. Distinct from
|
|
7395
|
+
* the camera accessory PirSensor role: that one is a camera child;
|
|
7396
|
+
* this is a standalone HA / 3rd-party motion sensor. */
|
|
7397
|
+
DeviceRole["MotionSensor"] = "motion-sensor";
|
|
7398
|
+
DeviceRole["ContactSensor"] = "contact-sensor";
|
|
7399
|
+
DeviceRole["LeakSensor"] = "leak-sensor";
|
|
7400
|
+
DeviceRole["SmokeSensor"] = "smoke-sensor";
|
|
7401
|
+
DeviceRole["COSensor"] = "co-sensor";
|
|
7402
|
+
DeviceRole["GasSensor"] = "gas-sensor";
|
|
7403
|
+
DeviceRole["TamperSensor"] = "tamper-sensor";
|
|
7404
|
+
DeviceRole["VibrationSensor"] = "vibration-sensor";
|
|
7405
|
+
DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
|
|
7406
|
+
DeviceRole["SoundSensor"] = "sound-sensor";
|
|
7407
|
+
/** Fallback for `binary_sensor` without a known `device_class`. */
|
|
7408
|
+
DeviceRole["BinarySensor"] = "binary-sensor";
|
|
7409
|
+
DeviceRole["TemperatureSensor"] = "temperature-sensor";
|
|
7410
|
+
DeviceRole["HumiditySensor"] = "humidity-sensor";
|
|
7411
|
+
DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
|
|
7412
|
+
DeviceRole["PressureSensor"] = "pressure-sensor";
|
|
7413
|
+
/** Wind speed or direction (weather-station `wind-sensor` cap). */
|
|
7414
|
+
DeviceRole["WindSensor"] = "wind-sensor";
|
|
7415
|
+
/** Rain accumulation or rate (weather-station `rain-sensor` cap). */
|
|
7416
|
+
DeviceRole["RainSensor"] = "rain-sensor";
|
|
7417
|
+
/** UV index (weather-station `uv-sensor` cap). */
|
|
7418
|
+
DeviceRole["UvSensor"] = "uv-sensor";
|
|
7419
|
+
/** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
|
|
7420
|
+
* Distinct from AmbientLightSensor (lux). */
|
|
7421
|
+
DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
|
|
7422
|
+
/** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
|
|
7423
|
+
DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
|
|
7424
|
+
DeviceRole["PowerSensor"] = "power-sensor";
|
|
7425
|
+
DeviceRole["EnergySensor"] = "energy-sensor";
|
|
7426
|
+
DeviceRole["VoltageSensor"] = "voltage-sensor";
|
|
7427
|
+
DeviceRole["CurrentSensor"] = "current-sensor";
|
|
7428
|
+
DeviceRole["AirQualitySensor"] = "air-quality-sensor";
|
|
7429
|
+
/** Battery level (numeric % via `sensor` OR low-bool via
|
|
7430
|
+
* `binary_sensor` — the cap distinguishes via the value type). */
|
|
7431
|
+
DeviceRole["BatterySensor"] = "battery-sensor";
|
|
7432
|
+
/** Fallback for `sensor` numeric without a known `device_class`. */
|
|
7433
|
+
DeviceRole["NumericSensor"] = "numeric-sensor";
|
|
7434
|
+
/** String / enum state (HA `sensor` with `state_class: enum` or
|
|
7435
|
+
* `attributes.options`). */
|
|
7436
|
+
DeviceRole["EnumSensor"] = "enum-sensor";
|
|
7437
|
+
/** Date / timestamp state (HA `sensor` with `device_class: timestamp`
|
|
7438
|
+
* or `date`). The slice carries the raw ISO string verbatim (hosted on
|
|
7439
|
+
* the `enum-sensor` cap); the UI renders it locale-formatted. */
|
|
7440
|
+
DeviceRole["DateTimeSensor"] = "datetime-sensor";
|
|
7441
|
+
/** Last-resort fallback when nothing else matches. */
|
|
7442
|
+
DeviceRole["GenericSensor"] = "generic-sensor";
|
|
7443
|
+
DeviceRole["NumericControl"] = "numeric-control";
|
|
7444
|
+
DeviceRole["SelectControl"] = "select-control";
|
|
7445
|
+
DeviceRole["TextControl"] = "text-control";
|
|
7446
|
+
DeviceRole["DateTimeControl"] = "datetime-control";
|
|
7447
|
+
/** Mobile push notifier (HA `notify.mobile_app_*`) — supports
|
|
7448
|
+
* rich features (image, priority, channel routing). */
|
|
7449
|
+
DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
|
|
7450
|
+
/** Chat / messaging service (HA `notify.telegram_*`,
|
|
7451
|
+
* `notify.discord_*`, etc.). */
|
|
7452
|
+
DeviceRole["MessagingNotifier"] = "messaging-notifier";
|
|
7453
|
+
/** Email-based delivery (HA `notify.smtp`, etc.). */
|
|
7454
|
+
DeviceRole["EmailNotifier"] = "email-notifier";
|
|
7455
|
+
/** Fallback when the notifier service name doesn't match a known
|
|
7456
|
+
* pattern. */
|
|
7457
|
+
DeviceRole["GenericNotifier"] = "generic-notifier";
|
|
7458
|
+
return DeviceRole;
|
|
7459
|
+
}({});
|
|
7449
7460
|
/**
|
|
7450
7461
|
import { errMsg } from '@camstack/types'
|
|
7451
7462
|
* Extract a human-readable message from an unknown error value.
|
|
@@ -7456,6 +7467,757 @@ function errMsg(err) {
|
|
|
7456
7467
|
if (typeof err === "string") return err;
|
|
7457
7468
|
return String(err);
|
|
7458
7469
|
}
|
|
7470
|
+
//#endregion
|
|
7471
|
+
//#region ../types/dist/composition-CPFIlFfw.mjs
|
|
7472
|
+
/**
|
|
7473
|
+
* Error types for the safe expression engine. Two distinct classes so callers
|
|
7474
|
+
* can tell a compile-time (grammar) failure from a runtime (evaluation)
|
|
7475
|
+
* failure — both are non-fatal to the host: read paths degrade to "skip link".
|
|
7476
|
+
*/
|
|
7477
|
+
/** Thrown by the tokenizer / parser. Carries a 0-based source `position` when
|
|
7478
|
+
* the failure is anchored to a character (author-facing inline feedback). */
|
|
7479
|
+
var ExpressionParseError = class extends Error {
|
|
7480
|
+
position;
|
|
7481
|
+
constructor(message, position) {
|
|
7482
|
+
super(message);
|
|
7483
|
+
this.name = "ExpressionParseError";
|
|
7484
|
+
this.position = position;
|
|
7485
|
+
}
|
|
7486
|
+
};
|
|
7487
|
+
/** Thrown by the evaluator (unknown identifier, type mismatch, non-finite
|
|
7488
|
+
* result, unknown builtin, step-budget exceeded). */
|
|
7489
|
+
var ExpressionEvalError = class extends Error {
|
|
7490
|
+
constructor(message) {
|
|
7491
|
+
super(message);
|
|
7492
|
+
this.name = "ExpressionEvalError";
|
|
7493
|
+
}
|
|
7494
|
+
};
|
|
7495
|
+
function asFiniteNumber(value, name, index) {
|
|
7496
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
|
|
7497
|
+
return value;
|
|
7498
|
+
}
|
|
7499
|
+
function asString(value, name, index) {
|
|
7500
|
+
if (typeof value !== "string") throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a string`);
|
|
7501
|
+
return value;
|
|
7502
|
+
}
|
|
7503
|
+
function finiteResult(value, name) {
|
|
7504
|
+
if (!Number.isFinite(value)) throw new ExpressionEvalError(`${name}: produced a non-finite result`);
|
|
7505
|
+
return value;
|
|
7506
|
+
}
|
|
7507
|
+
function allFiniteNumbers(args, name) {
|
|
7508
|
+
return args.map((a, idx) => asFiniteNumber(a, name, idx));
|
|
7509
|
+
}
|
|
7510
|
+
function asBoolean(value, name, index) {
|
|
7511
|
+
if (typeof value !== "boolean") throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a boolean`);
|
|
7512
|
+
return value;
|
|
7513
|
+
}
|
|
7514
|
+
/** A plain decimal (optional sign, fraction, exponent) — never hex, never `Infinity`, never a unit suffix. */
|
|
7515
|
+
var DECIMAL = /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;
|
|
7516
|
+
/**
|
|
7517
|
+
* `number(x)`: a finite number, or a string that IS one, as a number; every
|
|
7518
|
+
* other value — `''`, `'unavailable'`, a boolean, null — is `null`
|
|
7519
|
+
* (unavailable, never 0: D393). The one bridge from a string-valued source (an
|
|
7520
|
+
* HA sensor with a unit and no device_class is an `enum-sensor`) to a number.
|
|
7521
|
+
*/
|
|
7522
|
+
function toNumberOrNull(value) {
|
|
7523
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
7524
|
+
if (typeof value !== "string") return null;
|
|
7525
|
+
const trimmed = value.trim();
|
|
7526
|
+
if (!DECIMAL.test(trimmed)) return null;
|
|
7527
|
+
const parsed = Number(trimmed);
|
|
7528
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
7529
|
+
}
|
|
7530
|
+
var INF = Number.POSITIVE_INFINITY;
|
|
7531
|
+
var table = {
|
|
7532
|
+
min: {
|
|
7533
|
+
minArgs: 1,
|
|
7534
|
+
maxArgs: INF,
|
|
7535
|
+
apply: (args) => finiteResult(Math.min(...allFiniteNumbers(args, "min")), "min")
|
|
7536
|
+
},
|
|
7537
|
+
max: {
|
|
7538
|
+
minArgs: 1,
|
|
7539
|
+
maxArgs: INF,
|
|
7540
|
+
apply: (args) => finiteResult(Math.max(...allFiniteNumbers(args, "max")), "max")
|
|
7541
|
+
},
|
|
7542
|
+
abs: {
|
|
7543
|
+
minArgs: 1,
|
|
7544
|
+
maxArgs: 1,
|
|
7545
|
+
apply: (args) => finiteResult(Math.abs(asFiniteNumber(args[0], "abs", 0)), "abs")
|
|
7546
|
+
},
|
|
7547
|
+
floor: {
|
|
7548
|
+
minArgs: 1,
|
|
7549
|
+
maxArgs: 1,
|
|
7550
|
+
apply: (args) => finiteResult(Math.floor(asFiniteNumber(args[0], "floor", 0)), "floor")
|
|
7551
|
+
},
|
|
7552
|
+
ceil: {
|
|
7553
|
+
minArgs: 1,
|
|
7554
|
+
maxArgs: 1,
|
|
7555
|
+
apply: (args) => finiteResult(Math.ceil(asFiniteNumber(args[0], "ceil", 0)), "ceil")
|
|
7556
|
+
},
|
|
7557
|
+
sqrt: {
|
|
7558
|
+
minArgs: 1,
|
|
7559
|
+
maxArgs: 1,
|
|
7560
|
+
apply: (args) => finiteResult(Math.sqrt(asFiniteNumber(args[0], "sqrt", 0)), "sqrt")
|
|
7561
|
+
},
|
|
7562
|
+
round: {
|
|
7563
|
+
minArgs: 1,
|
|
7564
|
+
maxArgs: 2,
|
|
7565
|
+
apply: (args) => {
|
|
7566
|
+
const x = asFiniteNumber(args[0], "round", 0);
|
|
7567
|
+
const digits = args.length > 1 ? Math.trunc(asFiniteNumber(args[1], "round", 1)) : 0;
|
|
7568
|
+
if (digits < 0 || digits > 100) throw new ExpressionEvalError("round: digits must be between 0 and 100");
|
|
7569
|
+
const factor = 10 ** digits;
|
|
7570
|
+
return finiteResult(Math.round(x * factor) / factor, "round");
|
|
7571
|
+
}
|
|
7572
|
+
},
|
|
7573
|
+
pow: {
|
|
7574
|
+
minArgs: 2,
|
|
7575
|
+
maxArgs: 2,
|
|
7576
|
+
apply: (args) => finiteResult(asFiniteNumber(args[0], "pow", 0) ** asFiniteNumber(args[1], "pow", 1), "pow")
|
|
7577
|
+
},
|
|
7578
|
+
clamp: {
|
|
7579
|
+
minArgs: 3,
|
|
7580
|
+
maxArgs: 3,
|
|
7581
|
+
apply: (args) => {
|
|
7582
|
+
const x = asFiniteNumber(args[0], "clamp", 0);
|
|
7583
|
+
const lo = asFiniteNumber(args[1], "clamp", 1);
|
|
7584
|
+
const hi = asFiniteNumber(args[2], "clamp", 2);
|
|
7585
|
+
if (lo > hi) throw new ExpressionEvalError("clamp: lower bound is greater than upper bound");
|
|
7586
|
+
return finiteResult(Math.min(hi, Math.max(lo, x)), "clamp");
|
|
7587
|
+
}
|
|
7588
|
+
},
|
|
7589
|
+
avg: {
|
|
7590
|
+
minArgs: 1,
|
|
7591
|
+
maxArgs: INF,
|
|
7592
|
+
apply: (args) => {
|
|
7593
|
+
const nums = allFiniteNumbers(args, "avg");
|
|
7594
|
+
return finiteResult(nums.reduce((acc, v) => acc + v, 0) / nums.length, "avg");
|
|
7595
|
+
}
|
|
7596
|
+
},
|
|
7597
|
+
sum: {
|
|
7598
|
+
minArgs: 1,
|
|
7599
|
+
maxArgs: INF,
|
|
7600
|
+
apply: (args) => finiteResult(allFiniteNumbers(args, "sum").reduce((acc, v) => acc + v, 0), "sum")
|
|
7601
|
+
},
|
|
7602
|
+
coalesce: {
|
|
7603
|
+
minArgs: 1,
|
|
7604
|
+
maxArgs: INF,
|
|
7605
|
+
apply: (args) => {
|
|
7606
|
+
for (const a of args) if (a !== null) return a;
|
|
7607
|
+
return null;
|
|
7608
|
+
}
|
|
7609
|
+
},
|
|
7610
|
+
age: {
|
|
7611
|
+
minArgs: 2,
|
|
7612
|
+
maxArgs: 2,
|
|
7613
|
+
apply: (args) => finiteResult(asFiniteNumber(args[0], "age", 0) - asFiniteNumber(args[1], "age", 1), "age")
|
|
7614
|
+
},
|
|
7615
|
+
convert: {
|
|
7616
|
+
minArgs: 3,
|
|
7617
|
+
maxArgs: 3,
|
|
7618
|
+
apply: (args, hooks) => {
|
|
7619
|
+
const x = asFiniteNumber(args[0], "convert", 0);
|
|
7620
|
+
const from = asString(args[1], "convert", 1).trim();
|
|
7621
|
+
const to = asString(args[2], "convert", 2).trim();
|
|
7622
|
+
if (hooks.convert) {
|
|
7623
|
+
const out = hooks.convert(x, from, to);
|
|
7624
|
+
if (out === null) throw new ExpressionEvalError(`convert: cannot convert '${from}' to '${to}'`);
|
|
7625
|
+
return finiteResult(out, "convert");
|
|
7626
|
+
}
|
|
7627
|
+
if (from === to) return x;
|
|
7628
|
+
throw new ExpressionEvalError("convert: unit conversion table not installed");
|
|
7629
|
+
}
|
|
7630
|
+
},
|
|
7631
|
+
any: {
|
|
7632
|
+
minArgs: 1,
|
|
7633
|
+
maxArgs: INF,
|
|
7634
|
+
apply: (args) => args.map((a, i) => asBoolean(a, "any", i)).some((b) => b)
|
|
7635
|
+
},
|
|
7636
|
+
all: {
|
|
7637
|
+
minArgs: 1,
|
|
7638
|
+
maxArgs: INF,
|
|
7639
|
+
apply: (args) => args.map((a, i) => asBoolean(a, "all", i)).every((b) => b)
|
|
7640
|
+
},
|
|
7641
|
+
within: {
|
|
7642
|
+
minArgs: 2,
|
|
7643
|
+
maxArgs: 2,
|
|
7644
|
+
apply: (args, hooks) => {
|
|
7645
|
+
const windowMs = asFiniteNumber(args[1], "within", 1);
|
|
7646
|
+
if (windowMs < 0) throw new ExpressionEvalError("within: the window must not be negative");
|
|
7647
|
+
const at = args[0];
|
|
7648
|
+
if (at === null) return false;
|
|
7649
|
+
const ts = asFiniteNumber(at, "within", 0);
|
|
7650
|
+
const now = hooks.now;
|
|
7651
|
+
if (now === void 0 || !Number.isFinite(now)) throw new ExpressionEvalError("within: no clock was supplied to this evaluation");
|
|
7652
|
+
const inside = now - ts <= windowMs;
|
|
7653
|
+
if (inside) hooks.noteDeadline?.(ts + windowMs + 1);
|
|
7654
|
+
return inside;
|
|
7655
|
+
}
|
|
7656
|
+
},
|
|
7657
|
+
number: {
|
|
7658
|
+
minArgs: 1,
|
|
7659
|
+
maxArgs: 1,
|
|
7660
|
+
apply: (args) => toNumberOrNull(args[0])
|
|
7661
|
+
},
|
|
7662
|
+
latest: {
|
|
7663
|
+
minArgs: 1,
|
|
7664
|
+
maxArgs: INF,
|
|
7665
|
+
apply: (args) => {
|
|
7666
|
+
const present = args.flatMap((a, i) => a === null ? [] : [asFiniteNumber(a, "latest", i)]);
|
|
7667
|
+
return present.length === 0 ? null : finiteResult(Math.max(...present), "latest");
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
};
|
|
7671
|
+
Object.freeze(Object.assign(Object.create(null), table));
|
|
7672
|
+
/** The set of valid builtin names — used by the parser to reject unknown
|
|
7673
|
+
* callees at parse time (immediate author feedback). */
|
|
7674
|
+
var EXPRESSION_BUILTIN_NAMES = new Set(Object.keys(table));
|
|
7675
|
+
/**
|
|
7676
|
+
* Resource-bound constants for the safe expression engine.
|
|
7677
|
+
*
|
|
7678
|
+
* Every bound is defense-in-depth: the grammar is non-Turing-complete (no
|
|
7679
|
+
* loops, recursion, lambdas or member access — see `ast.ts`), so evaluation is
|
|
7680
|
+
* O(nodeCount) by construction. These caps merely put a hard ceiling on the
|
|
7681
|
+
* work a single author-supplied expression can request, so a hostile or
|
|
7682
|
+
* accidental pathological string can never spend unbounded CPU/memory.
|
|
7683
|
+
*/
|
|
7684
|
+
/** Max source length (chars) — checked BEFORE tokenizing so a huge string is
|
|
7685
|
+
* rejected without allocation. */
|
|
7686
|
+
var MAX_EXPRESSION_SOURCE_LENGTH = 2048;
|
|
7687
|
+
/** A legal binding / identifier name. */
|
|
7688
|
+
var EXPRESSION_IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7689
|
+
/** Binding names an author may NOT use: `now` is auto-injected; the literal
|
|
7690
|
+
* keywords lex as values, not identifiers, so binding to them is meaningless. */
|
|
7691
|
+
var RESERVED_BINDING_NAMES = new Set([
|
|
7692
|
+
"now",
|
|
7693
|
+
"true",
|
|
7694
|
+
"false",
|
|
7695
|
+
"null"
|
|
7696
|
+
]);
|
|
7697
|
+
/**
|
|
7698
|
+
* Tokenizer for the safe expression mini-language. Hand-rolled, single-pass,
|
|
7699
|
+
* zero-dependency. The grammar is deliberately boring: decimal numbers,
|
|
7700
|
+
* single/double-quoted strings with a tiny escape set, identifiers, the three
|
|
7701
|
+
* value keywords (`true`/`false`/`null`) and a fixed punctuator set. Anything
|
|
7702
|
+
* outside that — a bare `.`, `=`, `[`, `]`, `{`, `}`, `;`, backtick, `&`, `|` —
|
|
7703
|
+
* is a parse error with a source position, so member access / assignment /
|
|
7704
|
+
* template literals are lexically impossible.
|
|
7705
|
+
*/
|
|
7706
|
+
var KEYWORDS = new Set([
|
|
7707
|
+
"true",
|
|
7708
|
+
"false",
|
|
7709
|
+
"null"
|
|
7710
|
+
]);
|
|
7711
|
+
function isDigit(ch) {
|
|
7712
|
+
return ch >= "0" && ch <= "9";
|
|
7713
|
+
}
|
|
7714
|
+
function isIdentStart(ch) {
|
|
7715
|
+
return ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z" || ch === "_";
|
|
7716
|
+
}
|
|
7717
|
+
function isIdentPart(ch) {
|
|
7718
|
+
return isIdentStart(ch) || isDigit(ch);
|
|
7719
|
+
}
|
|
7720
|
+
function isWhitespace(ch) {
|
|
7721
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
|
|
7722
|
+
}
|
|
7723
|
+
/** Tokenize `source` into a flat token list ending with a single `eof` token.
|
|
7724
|
+
* Throws `ExpressionParseError` on any illegal character or unterminated
|
|
7725
|
+
* string. */
|
|
7726
|
+
function tokenize(source) {
|
|
7727
|
+
if (source.length > 2048) throw new ExpressionParseError(`expression too long (${source.length} > ${MAX_EXPRESSION_SOURCE_LENGTH} chars)`, 0);
|
|
7728
|
+
const tokens = [];
|
|
7729
|
+
let i = 0;
|
|
7730
|
+
const n = source.length;
|
|
7731
|
+
while (i < n) {
|
|
7732
|
+
const ch = source[i];
|
|
7733
|
+
if (isWhitespace(ch)) {
|
|
7734
|
+
i += 1;
|
|
7735
|
+
continue;
|
|
7736
|
+
}
|
|
7737
|
+
if (isDigit(ch)) {
|
|
7738
|
+
const start = i;
|
|
7739
|
+
while (i < n && isDigit(source[i])) i += 1;
|
|
7740
|
+
if (i < n && source[i] === ".") {
|
|
7741
|
+
if (i + 1 >= n || !isDigit(source[i + 1])) throw new ExpressionParseError("malformed number: decimal point needs a digit", i);
|
|
7742
|
+
i += 1;
|
|
7743
|
+
while (i < n && isDigit(source[i])) i += 1;
|
|
7744
|
+
}
|
|
7745
|
+
const text = source.slice(start, i);
|
|
7746
|
+
const value = Number(text);
|
|
7747
|
+
if (!Number.isFinite(value)) throw new ExpressionParseError(`malformed number: '${text}'`, start);
|
|
7748
|
+
tokens.push({
|
|
7749
|
+
type: "number",
|
|
7750
|
+
value,
|
|
7751
|
+
pos: start
|
|
7752
|
+
});
|
|
7753
|
+
continue;
|
|
7754
|
+
}
|
|
7755
|
+
if (ch === "'" || ch === "\"") {
|
|
7756
|
+
const quote = ch;
|
|
7757
|
+
const start = i;
|
|
7758
|
+
i += 1;
|
|
7759
|
+
let out = "";
|
|
7760
|
+
let closed = false;
|
|
7761
|
+
while (i < n) {
|
|
7762
|
+
const c = source[i];
|
|
7763
|
+
if (c === "\\") {
|
|
7764
|
+
const next = i + 1 < n ? source[i + 1] : "";
|
|
7765
|
+
if (next === "\\" || next === "'" || next === "\"") {
|
|
7766
|
+
out += next;
|
|
7767
|
+
i += 2;
|
|
7768
|
+
continue;
|
|
7769
|
+
}
|
|
7770
|
+
throw new ExpressionParseError(`invalid string escape: '\\${next}'`, i);
|
|
7771
|
+
}
|
|
7772
|
+
if (c === quote) {
|
|
7773
|
+
closed = true;
|
|
7774
|
+
i += 1;
|
|
7775
|
+
break;
|
|
7776
|
+
}
|
|
7777
|
+
out += c;
|
|
7778
|
+
i += 1;
|
|
7779
|
+
}
|
|
7780
|
+
if (!closed) throw new ExpressionParseError("unterminated string literal", start);
|
|
7781
|
+
tokens.push({
|
|
7782
|
+
type: "string",
|
|
7783
|
+
value: out,
|
|
7784
|
+
pos: start
|
|
7785
|
+
});
|
|
7786
|
+
continue;
|
|
7787
|
+
}
|
|
7788
|
+
if (isIdentStart(ch)) {
|
|
7789
|
+
const start = i;
|
|
7790
|
+
while (i < n && isIdentPart(source[i])) i += 1;
|
|
7791
|
+
const text = source.slice(start, i);
|
|
7792
|
+
if (KEYWORDS.has(text)) tokens.push({
|
|
7793
|
+
type: "keyword",
|
|
7794
|
+
keyword: keywordOf(text),
|
|
7795
|
+
pos: start
|
|
7796
|
+
});
|
|
7797
|
+
else tokens.push({
|
|
7798
|
+
type: "identifier",
|
|
7799
|
+
name: text,
|
|
7800
|
+
pos: start
|
|
7801
|
+
});
|
|
7802
|
+
continue;
|
|
7803
|
+
}
|
|
7804
|
+
const two = i + 1 < n ? source.slice(i, i + 2) : "";
|
|
7805
|
+
if (two === "<=" || two === ">=" || two === "==" || two === "!=" || two === "&&" || two === "||") {
|
|
7806
|
+
tokens.push({
|
|
7807
|
+
type: "punct",
|
|
7808
|
+
punct: two,
|
|
7809
|
+
pos: i
|
|
7810
|
+
});
|
|
7811
|
+
i += 2;
|
|
7812
|
+
continue;
|
|
7813
|
+
}
|
|
7814
|
+
if (isSinglePunct(ch)) {
|
|
7815
|
+
tokens.push({
|
|
7816
|
+
type: "punct",
|
|
7817
|
+
punct: ch,
|
|
7818
|
+
pos: i
|
|
7819
|
+
});
|
|
7820
|
+
i += 1;
|
|
7821
|
+
continue;
|
|
7822
|
+
}
|
|
7823
|
+
throw new ExpressionParseError(`unexpected character '${ch}'`, i);
|
|
7824
|
+
}
|
|
7825
|
+
tokens.push({
|
|
7826
|
+
type: "eof",
|
|
7827
|
+
pos: n
|
|
7828
|
+
});
|
|
7829
|
+
return tokens;
|
|
7830
|
+
}
|
|
7831
|
+
function keywordOf(text) {
|
|
7832
|
+
if (text === "true") return "true";
|
|
7833
|
+
if (text === "false") return "false";
|
|
7834
|
+
return "null";
|
|
7835
|
+
}
|
|
7836
|
+
function isSinglePunct(ch) {
|
|
7837
|
+
return ch === "(" || ch === ")" || ch === "," || ch === "?" || ch === ":" || ch === "+" || ch === "-" || ch === "*" || ch === "/" || ch === "%" || ch === "!" || ch === "<" || ch === ">";
|
|
7838
|
+
}
|
|
7839
|
+
/**
|
|
7840
|
+
* Pratt (precedence-climbing) parser for the safe expression mini-language.
|
|
7841
|
+
*
|
|
7842
|
+
* Precedence (low → high): ternary `?:` (right-assoc) → `||` → `&&` → equality
|
|
7843
|
+
* → relational → additive → multiplicative → unary `! -` → call / primary.
|
|
7844
|
+
* Calls are ONLY `IDENT '(' args? ')'` at primary position — the callee is a
|
|
7845
|
+
* string validated against the builtin table at parse time, so an unknown
|
|
7846
|
+
* function is rejected immediately (author feedback) and a persisted expression
|
|
7847
|
+
* that references a since-removed builtin degrades at read.
|
|
7848
|
+
*
|
|
7849
|
+
* A node counter caps total AST size (`MAX_EXPRESSION_AST_NODES`) and call
|
|
7850
|
+
* arity is capped (`MAX_EXPRESSION_CALL_ARGS`) — both raise `ExpressionParseError`.
|
|
7851
|
+
*/
|
|
7852
|
+
/** Binary/logical operator precedence (higher binds tighter). */
|
|
7853
|
+
var BINARY_PRECEDENCE = {
|
|
7854
|
+
"||": 1,
|
|
7855
|
+
"&&": 2,
|
|
7856
|
+
"==": 3,
|
|
7857
|
+
"!=": 3,
|
|
7858
|
+
"<": 4,
|
|
7859
|
+
"<=": 4,
|
|
7860
|
+
">": 4,
|
|
7861
|
+
">=": 4,
|
|
7862
|
+
"+": 5,
|
|
7863
|
+
"-": 5,
|
|
7864
|
+
"*": 6,
|
|
7865
|
+
"/": 6,
|
|
7866
|
+
"%": 6
|
|
7867
|
+
};
|
|
7868
|
+
function isLogicalOp(op) {
|
|
7869
|
+
return op === "&&" || op === "||";
|
|
7870
|
+
}
|
|
7871
|
+
function isBinaryOp(op) {
|
|
7872
|
+
return op === "+" || op === "-" || op === "*" || op === "/" || op === "%" || op === "==" || op === "!=" || op === "<" || op === "<=" || op === ">" || op === ">=";
|
|
7873
|
+
}
|
|
7874
|
+
var Parser = class {
|
|
7875
|
+
tokens;
|
|
7876
|
+
pos = 0;
|
|
7877
|
+
nodeCount = 0;
|
|
7878
|
+
identifiers = /* @__PURE__ */ new Set();
|
|
7879
|
+
callees = /* @__PURE__ */ new Set();
|
|
7880
|
+
constructor(tokens) {
|
|
7881
|
+
this.tokens = tokens;
|
|
7882
|
+
}
|
|
7883
|
+
parse() {
|
|
7884
|
+
const ast = this.parseTernary();
|
|
7885
|
+
const tok = this.peek();
|
|
7886
|
+
if (tok.type !== "eof") throw new ExpressionParseError("unexpected trailing input", tok.pos);
|
|
7887
|
+
return {
|
|
7888
|
+
ast,
|
|
7889
|
+
identifiers: this.identifiers,
|
|
7890
|
+
callees: this.callees,
|
|
7891
|
+
nodeCount: this.nodeCount
|
|
7892
|
+
};
|
|
7893
|
+
}
|
|
7894
|
+
peek() {
|
|
7895
|
+
return this.tokens[this.pos];
|
|
7896
|
+
}
|
|
7897
|
+
next() {
|
|
7898
|
+
return this.tokens[this.pos++];
|
|
7899
|
+
}
|
|
7900
|
+
/** Consume a punctuator token, erroring if the next token isn't it. */
|
|
7901
|
+
expectPunct(punct) {
|
|
7902
|
+
const tok = this.peek();
|
|
7903
|
+
if (tok.type !== "punct" || tok.punct !== punct) throw new ExpressionParseError(`expected '${punct}'`, tok.pos);
|
|
7904
|
+
this.pos += 1;
|
|
7905
|
+
}
|
|
7906
|
+
matchPunct(punct) {
|
|
7907
|
+
const tok = this.peek();
|
|
7908
|
+
if (tok.type === "punct" && tok.punct === punct) {
|
|
7909
|
+
this.pos += 1;
|
|
7910
|
+
return true;
|
|
7911
|
+
}
|
|
7912
|
+
return false;
|
|
7913
|
+
}
|
|
7914
|
+
countNode() {
|
|
7915
|
+
this.nodeCount += 1;
|
|
7916
|
+
if (this.nodeCount > 256) throw new ExpressionParseError("expression too complex", this.peek().pos);
|
|
7917
|
+
}
|
|
7918
|
+
parseTernary() {
|
|
7919
|
+
const test = this.parseBinary(1);
|
|
7920
|
+
if (this.matchPunct("?")) {
|
|
7921
|
+
const consequent = this.parseTernary();
|
|
7922
|
+
this.expectPunct(":");
|
|
7923
|
+
const alternate = this.parseTernary();
|
|
7924
|
+
this.countNode();
|
|
7925
|
+
return {
|
|
7926
|
+
kind: "conditional",
|
|
7927
|
+
test,
|
|
7928
|
+
consequent,
|
|
7929
|
+
alternate
|
|
7930
|
+
};
|
|
7931
|
+
}
|
|
7932
|
+
return test;
|
|
7933
|
+
}
|
|
7934
|
+
parseBinary(minPrec) {
|
|
7935
|
+
let left = this.parseUnary();
|
|
7936
|
+
for (;;) {
|
|
7937
|
+
const tok = this.peek();
|
|
7938
|
+
if (tok.type !== "punct") break;
|
|
7939
|
+
const prec = BINARY_PRECEDENCE[tok.punct];
|
|
7940
|
+
if (prec === void 0 || prec < minPrec) break;
|
|
7941
|
+
const op = tok.punct;
|
|
7942
|
+
this.pos += 1;
|
|
7943
|
+
const right = this.parseBinary(prec + 1);
|
|
7944
|
+
this.countNode();
|
|
7945
|
+
if (isLogicalOp(op)) left = {
|
|
7946
|
+
kind: "logical",
|
|
7947
|
+
op,
|
|
7948
|
+
left,
|
|
7949
|
+
right
|
|
7950
|
+
};
|
|
7951
|
+
else if (isBinaryOp(op)) left = {
|
|
7952
|
+
kind: "binary",
|
|
7953
|
+
op,
|
|
7954
|
+
left,
|
|
7955
|
+
right
|
|
7956
|
+
};
|
|
7957
|
+
else throw new ExpressionParseError(`unexpected operator '${op}'`, tok.pos);
|
|
7958
|
+
}
|
|
7959
|
+
return left;
|
|
7960
|
+
}
|
|
7961
|
+
parseUnary() {
|
|
7962
|
+
const tok = this.peek();
|
|
7963
|
+
if (tok.type === "punct" && (tok.punct === "!" || tok.punct === "-")) {
|
|
7964
|
+
const op = tok.punct;
|
|
7965
|
+
this.pos += 1;
|
|
7966
|
+
const operand = this.parseUnary();
|
|
7967
|
+
this.countNode();
|
|
7968
|
+
return {
|
|
7969
|
+
kind: "unary",
|
|
7970
|
+
op,
|
|
7971
|
+
operand
|
|
7972
|
+
};
|
|
7973
|
+
}
|
|
7974
|
+
return this.parsePrimary();
|
|
7975
|
+
}
|
|
7976
|
+
parsePrimary() {
|
|
7977
|
+
const tok = this.next();
|
|
7978
|
+
switch (tok.type) {
|
|
7979
|
+
case "number":
|
|
7980
|
+
this.countNode();
|
|
7981
|
+
return {
|
|
7982
|
+
kind: "literal",
|
|
7983
|
+
value: tok.value
|
|
7984
|
+
};
|
|
7985
|
+
case "string":
|
|
7986
|
+
this.countNode();
|
|
7987
|
+
return {
|
|
7988
|
+
kind: "literal",
|
|
7989
|
+
value: tok.value
|
|
7990
|
+
};
|
|
7991
|
+
case "keyword":
|
|
7992
|
+
this.countNode();
|
|
7993
|
+
return {
|
|
7994
|
+
kind: "literal",
|
|
7995
|
+
value: tok.keyword === "null" ? null : tok.keyword === "true"
|
|
7996
|
+
};
|
|
7997
|
+
case "identifier": {
|
|
7998
|
+
const nextTok = this.peek();
|
|
7999
|
+
if (nextTok.type === "punct" && nextTok.punct === "(") return this.parseCall(tok.name, tok.pos);
|
|
8000
|
+
this.identifiers.add(tok.name);
|
|
8001
|
+
this.countNode();
|
|
8002
|
+
return {
|
|
8003
|
+
kind: "identifier",
|
|
8004
|
+
name: tok.name
|
|
8005
|
+
};
|
|
8006
|
+
}
|
|
8007
|
+
case "punct":
|
|
8008
|
+
if (tok.punct === "(") {
|
|
8009
|
+
const inner = this.parseTernary();
|
|
8010
|
+
this.expectPunct(")");
|
|
8011
|
+
return inner;
|
|
8012
|
+
}
|
|
8013
|
+
throw new ExpressionParseError(`unexpected token '${tok.punct}'`, tok.pos);
|
|
8014
|
+
case "eof": throw new ExpressionParseError("unexpected end of expression", tok.pos);
|
|
8015
|
+
}
|
|
8016
|
+
}
|
|
8017
|
+
parseCall(callee, pos) {
|
|
8018
|
+
if (!EXPRESSION_BUILTIN_NAMES.has(callee)) throw new ExpressionParseError(`unknown function '${callee}'`, pos);
|
|
8019
|
+
this.expectPunct("(");
|
|
8020
|
+
const args = [];
|
|
8021
|
+
if (!this.matchPunct(")")) for (;;) {
|
|
8022
|
+
args.push(this.parseTernary());
|
|
8023
|
+
if (args.length > 16) throw new ExpressionParseError(`too many arguments to '${callee}'`, pos);
|
|
8024
|
+
if (this.matchPunct(",")) continue;
|
|
8025
|
+
this.expectPunct(")");
|
|
8026
|
+
break;
|
|
8027
|
+
}
|
|
8028
|
+
this.callees.add(callee);
|
|
8029
|
+
this.countNode();
|
|
8030
|
+
return {
|
|
8031
|
+
kind: "call",
|
|
8032
|
+
callee,
|
|
8033
|
+
args
|
|
8034
|
+
};
|
|
8035
|
+
}
|
|
8036
|
+
};
|
|
8037
|
+
/** Tokenize + parse `source` into a validated `ParsedExpression`. Throws
|
|
8038
|
+
* `ExpressionParseError` on any lexical or grammatical failure. */
|
|
8039
|
+
function parseExpression(source) {
|
|
8040
|
+
return new Parser(tokenize(source)).parse();
|
|
8041
|
+
}
|
|
8042
|
+
/**
|
|
8043
|
+
* LRU compile cache for parsed expressions (spec §2.4 "parse once … LRU keyed
|
|
8044
|
+
* by expr"). The cache stores BOTH successes and failures (negative caching),
|
|
8045
|
+
* so a corrupt persisted string costs exactly one tokenize+parse total — not
|
|
8046
|
+
* one per read on a hot resolve path.
|
|
8047
|
+
*
|
|
8048
|
+
* The cache is a module-level singleton: entries are pure, content-addressed
|
|
8049
|
+
* ASTs keyed by the raw source string, so sharing one instance across all
|
|
8050
|
+
* callers is safe and maximises hit rate.
|
|
8051
|
+
*/
|
|
8052
|
+
var cache = /* @__PURE__ */ new Map();
|
|
8053
|
+
function getCached(source) {
|
|
8054
|
+
const hit = cache.get(source);
|
|
8055
|
+
if (hit !== void 0) {
|
|
8056
|
+
cache.delete(source);
|
|
8057
|
+
cache.set(source, hit);
|
|
8058
|
+
return hit;
|
|
8059
|
+
}
|
|
8060
|
+
let result;
|
|
8061
|
+
try {
|
|
8062
|
+
result = {
|
|
8063
|
+
ok: true,
|
|
8064
|
+
parsed: parseExpression(source)
|
|
8065
|
+
};
|
|
8066
|
+
} catch (err) {
|
|
8067
|
+
result = {
|
|
8068
|
+
ok: false,
|
|
8069
|
+
error: err instanceof ExpressionParseError ? err.message : String(err)
|
|
8070
|
+
};
|
|
8071
|
+
}
|
|
8072
|
+
cache.set(source, result);
|
|
8073
|
+
if (cache.size > 256) {
|
|
8074
|
+
const oldest = cache.keys().next().value;
|
|
8075
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
8076
|
+
}
|
|
8077
|
+
return result;
|
|
8078
|
+
}
|
|
8079
|
+
/** Compile `source`, returning a discriminated result instead of throwing.
|
|
8080
|
+
* Used by read paths that must degrade rather than raise. LRU/negative-cached. */
|
|
8081
|
+
function compileExpressionSafe(source) {
|
|
8082
|
+
return getCached(source);
|
|
8083
|
+
}
|
|
8084
|
+
Object.freeze({});
|
|
8085
|
+
/**
|
|
8086
|
+
* Author-time validation. Returns `null` when the source is valid, else a
|
|
8087
|
+
* human-readable error message. Checks: the expression compiles; binding count
|
|
8088
|
+
* is within `MAX_EXPRESSION_BINDINGS`; every binding name is a legal identifier,
|
|
8089
|
+
* is not reserved (`now`/keywords) and does not shadow a builtin; and every
|
|
8090
|
+
* FREE identifier of the AST is covered by a binding or the injected `now`.
|
|
8091
|
+
*/
|
|
8092
|
+
function validateExpressionSource(src) {
|
|
8093
|
+
const names = Object.keys(src.bindings);
|
|
8094
|
+
if (names.length > 32) return `too many bindings (${names.length} > 32)`;
|
|
8095
|
+
for (const name of names) {
|
|
8096
|
+
if (!EXPRESSION_IDENTIFIER_RE.test(name)) return `invalid binding name '${name}'`;
|
|
8097
|
+
if (RESERVED_BINDING_NAMES.has(name)) return `binding name '${name}' is reserved`;
|
|
8098
|
+
if (EXPRESSION_BUILTIN_NAMES.has(name)) return `binding name '${name}' shadows a builtin function`;
|
|
8099
|
+
}
|
|
8100
|
+
const compiled = compileExpressionSafe(src.expr);
|
|
8101
|
+
if (!compiled.ok) return compiled.error;
|
|
8102
|
+
const bound = new Set(names);
|
|
8103
|
+
for (const id of compiled.parsed.identifiers) {
|
|
8104
|
+
if (id === "now") continue;
|
|
8105
|
+
if (!bound.has(id)) return `expression references unbound identifier '${id}'`;
|
|
8106
|
+
}
|
|
8107
|
+
return null;
|
|
8108
|
+
}
|
|
8109
|
+
/** A composed device's stableId is this prefix plus the block id. */
|
|
8110
|
+
var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
|
|
8111
|
+
/** Longest snippet a `code` source may carry (slice 4). */
|
|
8112
|
+
var MAX_COMPOSITION_CODE_LENGTH = 2e4;
|
|
8113
|
+
var CompositionSourceRefSchema = object({
|
|
8114
|
+
/** The owning addon; `stableId` is unique only within it. */
|
|
8115
|
+
addonId: string().min(1),
|
|
8116
|
+
stableId: string().min(1)
|
|
8117
|
+
});
|
|
8118
|
+
/** One field of one capability of one source device. */
|
|
8119
|
+
var CompositionFieldReadSchema = object({
|
|
8120
|
+
source: CompositionSourceRefSchema,
|
|
8121
|
+
cap: string().min(1),
|
|
8122
|
+
/** Dotted path into the source cap's runtime-state slice. */
|
|
8123
|
+
fieldPath: string().min(1)
|
|
8124
|
+
});
|
|
8125
|
+
/** `from`: copy one source field verbatim. It is also an expression's `from` binding. */
|
|
8126
|
+
var CompositionFromSourceSchema = CompositionFieldReadSchema.extend({ kind: literal("from") });
|
|
8127
|
+
var CompositionBindingSchema = discriminatedUnion("kind", [CompositionFromSourceSchema, object({
|
|
8128
|
+
kind: literal("literal"),
|
|
8129
|
+
value: union([
|
|
8130
|
+
string(),
|
|
8131
|
+
number(),
|
|
8132
|
+
boolean(),
|
|
8133
|
+
_null()
|
|
8134
|
+
])
|
|
8135
|
+
})]);
|
|
8136
|
+
/**
|
|
8137
|
+
* `expression`: a formula over named bindings, in the salvaged expression
|
|
8138
|
+
* engine. Validated at parse by the SAME function every other consumer runs,
|
|
8139
|
+
* so the editor and the store cannot disagree.
|
|
8140
|
+
*/
|
|
8141
|
+
var CompositionExpressionSourceSchema = object({
|
|
8142
|
+
kind: literal("expression"),
|
|
8143
|
+
expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
8144
|
+
bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), CompositionBindingSchema)
|
|
8145
|
+
}).superRefine((src, ctx) => {
|
|
8146
|
+
const err = validateExpressionSource(src);
|
|
8147
|
+
if (err !== null) ctx.addIssue({
|
|
8148
|
+
code: "custom",
|
|
8149
|
+
message: err,
|
|
8150
|
+
path: ["expr"]
|
|
8151
|
+
});
|
|
8152
|
+
});
|
|
8153
|
+
/** `code`: RESERVED for slice 4 (own runner, one-way eject). */
|
|
8154
|
+
var CompositionCodeSourceSchema = object({
|
|
8155
|
+
kind: literal("code"),
|
|
8156
|
+
code: string().min(1).max(MAX_COMPOSITION_CODE_LENGTH)
|
|
8157
|
+
});
|
|
8158
|
+
var CompositionFieldSourceSchema = discriminatedUnion("kind", [
|
|
8159
|
+
CompositionFromSourceSchema,
|
|
8160
|
+
CompositionExpressionSourceSchema,
|
|
8161
|
+
CompositionCodeSourceSchema
|
|
8162
|
+
]);
|
|
8163
|
+
var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
|
|
8164
|
+
kind: literal("forward"),
|
|
8165
|
+
source: CompositionSourceRefSchema,
|
|
8166
|
+
cap: string().min(1),
|
|
8167
|
+
method: string().min(1)
|
|
8168
|
+
}), CompositionCodeSourceSchema]);
|
|
8169
|
+
/** An item key inside an item-array cap (`consumables.items[<key>]`). */
|
|
8170
|
+
var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
8171
|
+
/**
|
|
8172
|
+
* One item of an item-array cap (D663), addressed through the cap's
|
|
8173
|
+
* `status.itemArray` descriptor. `fields` are paths INSIDE the item, nested
|
|
8174
|
+
* allowed (`remaining.value`); the key and label come from the entry itself.
|
|
8175
|
+
*/
|
|
8176
|
+
var CompositionItemEntrySchema = object({
|
|
8177
|
+
label: string().min(1).max(80),
|
|
8178
|
+
fields: record(string().min(1), CompositionFieldSourceSchema)
|
|
8179
|
+
});
|
|
8180
|
+
var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
8181
|
+
kind: literal("fields"),
|
|
8182
|
+
cap: string().min(1),
|
|
8183
|
+
fields: record(string().min(1), CompositionFieldSourceSchema).refine((fields) => Object.keys(fields).length <= 32, { message: `at most 32 fields per capability` }),
|
|
8184
|
+
/**
|
|
8185
|
+
* Items of the cap's item array, keyed by item key (D663). Naming `items`
|
|
8186
|
+
* owns the WHOLE array field: the composed value holds exactly these items.
|
|
8187
|
+
*/
|
|
8188
|
+
items: record(string().regex(COMPOSITION_ITEM_KEY_RE), CompositionItemEntrySchema).refine((items) => Object.keys(items).length <= 16, { message: `at most 16 items` }).optional(),
|
|
8189
|
+
/** RESERVED for slice 3; refused by the validator when non-empty. */
|
|
8190
|
+
commands: record(string().min(1), CompositionCommandTargetSchema).optional()
|
|
8191
|
+
}), object({
|
|
8192
|
+
kind: literal("passthrough"),
|
|
8193
|
+
cap: string().min(1),
|
|
8194
|
+
source: CompositionSourceRefSchema
|
|
8195
|
+
})]);
|
|
8196
|
+
var CompositionSchema = object({
|
|
8197
|
+
target: discriminatedUnion("kind", [object({
|
|
8198
|
+
kind: literal("new"),
|
|
8199
|
+
type: _enum(DeviceType),
|
|
8200
|
+
role: _enum(DeviceRole).optional()
|
|
8201
|
+
}), object({
|
|
8202
|
+
kind: literal("existing"),
|
|
8203
|
+
device: CompositionSourceRefSchema
|
|
8204
|
+
})]),
|
|
8205
|
+
features: array(CompositionFeatureSchema).min(1).max(16)
|
|
8206
|
+
}).superRefine((composition, ctx) => {
|
|
8207
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8208
|
+
composition.features.forEach((feature, index) => {
|
|
8209
|
+
if (seen.has(feature.cap)) ctx.addIssue({
|
|
8210
|
+
code: "custom",
|
|
8211
|
+
message: `capability \`${feature.cap}\` is composed twice — one feature per capability`,
|
|
8212
|
+
path: [
|
|
8213
|
+
"features",
|
|
8214
|
+
index,
|
|
8215
|
+
"cap"
|
|
8216
|
+
]
|
|
8217
|
+
});
|
|
8218
|
+
seen.add(feature.cap);
|
|
8219
|
+
});
|
|
8220
|
+
});
|
|
7459
8221
|
new Set([
|
|
7460
8222
|
"track",
|
|
7461
8223
|
"summary",
|
|
@@ -9507,1023 +10269,480 @@ var DecoderSessionConfigSchema = object({
|
|
|
9507
10269
|
*/
|
|
9508
10270
|
deviceId: number().int().nonnegative().optional(),
|
|
9509
10271
|
/**
|
|
9510
|
-
* Free-form tag for log scoping. Stream-broker uses
|
|
9511
|
-
* `broker:<deviceId>/<profile>`. Decoder session logger surfaces it
|
|
9512
|
-
* on every line so `grep tag=broker:5/high` filters one camera
|
|
9513
|
-
* profile cleanly.
|
|
9514
|
-
*/
|
|
9515
|
-
tag: string().optional(),
|
|
9516
|
-
/**
|
|
9517
|
-
* Where the session delivers decoded frames (Phase 5 / D9):
|
|
9518
|
-
*
|
|
9519
|
-
* - `'callback'` (default) — the legacy pixel path: decoded frames are
|
|
9520
|
-
* buffered as `DecodedFrame`s and drained via `pullFrames`.
|
|
9521
|
-
* - `'shm'` — the shared-memory frame plane: decoded frames are written
|
|
9522
|
-
* into an OS shared-memory ring and drained as zero-pixel
|
|
9523
|
-
* `FrameHandle`s via `pullHandles`. A session is one mode or the
|
|
9524
|
-
* other — `pullFrames` returns nothing for an `'shm'` session and
|
|
9525
|
-
* `pullHandles` returns nothing for a `'callback'` session.
|
|
9526
|
-
*/
|
|
9527
|
-
frameSink: _enum(["callback", "shm"]).default("callback"),
|
|
9528
|
-
/**
|
|
9529
|
-
* Per-camera decoder DEBUG facility. When `true`, a pull-mode session emits
|
|
9530
|
-
* a throttled (~1Hz) structured `decoder debug` line (effective/adaptive fps,
|
|
9531
|
-
* real-time lag, dropped-frame delta, avg decode time, hwaccel). Mirrors the
|
|
9532
|
-
* stream-broker's `streamingDebug` gate — off by default so production logs
|
|
9533
|
-
* stay quiet and the emit path pays zero per-frame cost when disabled.
|
|
9534
|
-
*/
|
|
9535
|
-
debug: boolean().optional()
|
|
9536
|
-
});
|
|
9537
|
-
/**
|
|
9538
|
-
* Per-component log CHANNELS — the gate a hot path consults, and the registry
|
|
9539
|
-
* an addon declares its channels in.
|
|
9540
|
-
*
|
|
9541
|
-
* ## Two axes, deliberately separated
|
|
9542
|
-
*
|
|
9543
|
-
* - **DECLARATION** — which channels exist. Only the addon knows:
|
|
9544
|
-
* `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
|
|
9545
|
-
* baichuan/handshake. A hand-wired central list rots at the first addition,
|
|
9546
|
-
* and rots silently. So a channel is declared where it is consulted, and the
|
|
9547
|
-
* `log-channels` capability enumerates the declarations.
|
|
9548
|
-
* - **VALUE** — at which level, for which scope, until when. That stays ONE
|
|
9549
|
-
* thing: the logging settings document on the `system` cap. Two authorities
|
|
9550
|
-
* over the values is the exact defect
|
|
9551
|
-
* `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
|
|
9552
|
-
* remove; re-introducing it from the cure side would be grotesque.
|
|
9553
|
-
*
|
|
9554
|
-
* Nothing in this file reads a clock, an env var or a store. The registry is
|
|
9555
|
-
* a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
|
|
9556
|
-
* the hot path with a value somebody actually read, and by
|
|
9557
|
-
* {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
|
|
9558
|
-
* never reaches here, so it can neither disarm an armed channel nor arm a
|
|
9559
|
-
* disarmed one (D49).
|
|
9560
|
-
*
|
|
9561
|
-
* ## The canonical call shape
|
|
9562
|
-
*
|
|
9563
|
-
* ```ts
|
|
9564
|
-
* if (CH_RTP.on && CH_RTP.wants(deviceId)) {
|
|
9565
|
-
* CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
|
|
9566
|
-
* }
|
|
9567
|
-
* ```
|
|
9568
|
-
*
|
|
9569
|
-
* `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
|
|
9570
|
-
* read. Disarmed, a call site costs one load and one branch, and the `extras`
|
|
9571
|
-
* object literal is never constructed because it lives inside the branch. It
|
|
9572
|
-
* is the same shape already proven in production at `stream-broker.ts:1650`,
|
|
9573
|
-
* and the same discipline `LoggingGate.allowsDestination` uses for the
|
|
9574
|
-
* destination floor (measured at 1.93 ns/call when off).
|
|
9575
|
-
*
|
|
9576
|
-
* ## Why a channel emits at `info`
|
|
9577
|
-
*
|
|
9578
|
-
* `loki-logging.addon.ts` pins the destination default at `info` and
|
|
9579
|
-
* `loki-destination.ts` drops everything below it, so a line emitted at
|
|
9580
|
-
* `debug` never reaches Loki and the hub's in-memory ring only holds ~35
|
|
9581
|
-
* minutes. A diagnostic that cannot be read an hour later is worse than no
|
|
9582
|
-
* diagnostic, because it looks done. {@link LogChannelGate.log} therefore
|
|
9583
|
-
* emits at the channel's declared level, whose schema floor is `info`.
|
|
9584
|
-
*/
|
|
9585
|
-
/**
|
|
9586
|
-
* The level a channel writes at once armed.
|
|
9587
|
-
*
|
|
9588
|
-
* `debug` is absent ON PURPOSE and not by omission: below `info` the line does
|
|
9589
|
-
* not leave the process for Loki, and the whole point of arming a channel is
|
|
9590
|
-
* to read it later.
|
|
9591
|
-
*/
|
|
9592
|
-
var LogChannelLevelSchema = _enum([
|
|
9593
|
-
"info",
|
|
9594
|
-
"warn",
|
|
9595
|
-
"error"
|
|
9596
|
-
]);
|
|
9597
|
-
/**
|
|
9598
|
-
* What an addon declares about one channel. No value, no state — a
|
|
9599
|
-
* declaration is inert.
|
|
9600
|
-
*/
|
|
9601
|
-
var LogChannelDescriptorSchema = object({
|
|
9602
|
-
/**
|
|
9603
|
-
* Dotted `area.thing`, unique across the workspace. `area` is conventionally
|
|
9604
|
-
* the addon's short name so an operator reading a channel list can tell who
|
|
9605
|
-
* owns it without a second lookup.
|
|
9606
|
-
*/
|
|
9607
|
-
name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
|
|
9608
|
-
/** One sentence: what the operator will SEE after arming it. */
|
|
9609
|
-
description: string().min(1),
|
|
9610
|
-
/** The level its lines are emitted at. Never below `info`. */
|
|
9611
|
-
defaultLevel: LogChannelLevelSchema,
|
|
9612
|
-
/**
|
|
9613
|
-
* Whether this channel can be narrowed to a camera.
|
|
9614
|
-
*
|
|
9615
|
-
* `true` is a PROMISE with two halves, and both must hold: the gate is
|
|
9616
|
-
* consulted with the numeric device id, AND every line the channel admits
|
|
9617
|
-
* carries `tags: { deviceId }` with that same numeric id. The second half is
|
|
9618
|
-
* what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
|
|
9619
|
-
* keeps `deviceId` out of the stream labels for cardinality, so the tag in
|
|
9620
|
-
* the body is the only way to filter.
|
|
9621
|
-
*
|
|
9622
|
-
* A channel whose lines carry the device only in `meta` (or not at all) is
|
|
9623
|
-
* declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
|
|
9624
|
-
* the operator narrows to one camera, sees nothing, and concludes the code
|
|
9625
|
-
* path was never taken.
|
|
9626
|
-
*/
|
|
9627
|
-
perDevice: boolean()
|
|
9628
|
-
});
|
|
9629
|
-
/**
|
|
9630
|
-
* An armed window over one channel, as the document hands it to a mirror.
|
|
9631
|
-
*
|
|
9632
|
-
* A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
|
|
9633
|
-
* expires by itself, which is the one failure a boolean cannot avoid.
|
|
9634
|
-
*/
|
|
9635
|
-
var LogChannelWindowSchema = object({
|
|
9636
|
-
channel: string().min(1),
|
|
9637
|
-
/** Epoch ms the window closes at. */
|
|
9638
|
-
armedUntilMs: number(),
|
|
9639
|
-
/** `null` = every camera. A non-empty list narrows to those numeric ids. */
|
|
9640
|
-
deviceIds: array(number().int()).readonly().nullable()
|
|
9641
|
-
});
|
|
9642
|
-
var ClipModelMetaSchema = object({
|
|
9643
|
-
/** The paired text encoder's catalog id (the embedding-encoder's text catalog). */
|
|
9644
|
-
textModelId: string().min(1),
|
|
9645
|
-
/** Output dimension of BOTH towers. A vector of any other length is refused. */
|
|
9646
|
-
embeddingDim: number().int().positive(),
|
|
9647
|
-
/**
|
|
9648
|
-
* The `vector-store` index this model's vectors live in. ONE feature space
|
|
9649
|
-
* per index: two models share an index only when their vectors are ranked
|
|
9650
|
-
* against each other, which for CLIP means never across families. MobileCLIP
|
|
9651
|
-
* S1 and S2 share `object-clip` for history (separated by the row's
|
|
9652
|
-
* `modelId`); any other model gets its own.
|
|
9653
|
-
*/
|
|
9654
|
-
vectorIndex: string().regex(/^object-clip(-[a-z0-9][a-z0-9-]*)?$/),
|
|
9655
|
-
/**
|
|
9656
|
-
* The text encoder's tokenizer, a declared SIBLING file of its onnx. The name
|
|
9657
|
-
* must be unique per tokenizer: every model's siblings land in ONE flat
|
|
9658
|
-
* models directory, so two different tokenizers under the same name would
|
|
9659
|
-
* silently overwrite each other (whichever model downloaded last wins, and
|
|
9660
|
-
* the other one tokenises with the wrong vocabulary).
|
|
9661
|
-
*/
|
|
9662
|
-
tokenizerFile: string().min(1),
|
|
9663
|
-
/** Token window the text graph takes (SigLIP2: 64; CLIP BPE: 77). */
|
|
9664
|
-
contextLength: number().int().positive(),
|
|
9665
|
-
/** Id the tokenizer pads with. */
|
|
9666
|
-
padId: number().int().nonnegative(),
|
|
9667
|
-
/**
|
|
9668
|
-
* The search cosine floor at `strictness: 'loose'` — calibrated per model,
|
|
9669
|
-
* because cosine bands are a property of the model: 0.2 keeps 96% of matched
|
|
9670
|
-
* COCO caption-image pairs on MobileCLIP S1 and 2.7% on SigLIP2.
|
|
9671
|
-
*/
|
|
9672
|
-
searchMinScore: number().min(0).max(1)
|
|
9673
|
-
});
|
|
9674
|
-
var ClipSearchStrictnessSchema = _enum([
|
|
9675
|
-
"loose",
|
|
9676
|
-
"balanced",
|
|
9677
|
-
"strict"
|
|
9678
|
-
]);
|
|
9679
|
-
var MODEL_FORMATS = [
|
|
9680
|
-
"onnx",
|
|
9681
|
-
"coreml",
|
|
9682
|
-
"openvino",
|
|
9683
|
-
"tflite",
|
|
9684
|
-
"pt",
|
|
9685
|
-
"gguf"
|
|
9686
|
-
];
|
|
9687
|
-
/**
|
|
9688
|
-
* Multi-file format payload.
|
|
9689
|
-
*
|
|
9690
|
-
* - Directory formats (`isDirectory: true`, e.g. `.mlpackage`): files
|
|
9691
|
-
* relative to the directory root — the downloader fetches each from
|
|
9692
|
-
* `{url}/{file}` into `{modelDir}/{file}`. If omitted, it probes the
|
|
9693
|
-
* HuggingFace API (slower).
|
|
9694
|
-
* - Single-file formats (no `isDirectory`, e.g. OpenVINO IR): sibling
|
|
9695
|
-
* files fetched from the SAME remote directory as `url` and stored flat
|
|
9696
|
-
* alongside the main file — e.g. `['camstack-yolov9t.bin']` for the IR
|
|
9697
|
-
* weights next to `camstack-yolov9t.xml`.
|
|
9698
|
-
*/
|
|
9699
|
-
var ModelFormatEntrySchema = object({
|
|
9700
|
-
url: string(),
|
|
9701
|
-
sizeMB: number(),
|
|
9702
|
-
/** Whether this format is a directory bundle (e.g., .mlpackage) rather than a single file */
|
|
9703
|
-
isDirectory: boolean().optional(),
|
|
9704
|
-
/** Multi-file payload (directory members or sibling files). */
|
|
9705
|
-
files: array(string()).readonly().optional(),
|
|
9706
|
-
/** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
|
|
9707
|
-
runtimes: array(_enum(["python"])).readonly().optional()
|
|
9708
|
-
});
|
|
9709
|
-
/**
|
|
9710
|
-
* Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
|
|
9711
|
-
* The downloader fetches from `url` and saves to `{modelsDir}/{filename}`.
|
|
9712
|
-
*/
|
|
9713
|
-
var ModelExtraFileSchema = object({
|
|
9714
|
-
url: string(),
|
|
9715
|
-
filename: string(),
|
|
9716
|
-
sizeMB: number()
|
|
9717
|
-
});
|
|
9718
|
-
/**
|
|
9719
|
-
* Per-format payload map. Modelled as an explicit object (one optional key
|
|
9720
|
-
* per `ModelFormat`) rather than `z.record(enum, …)` — zod v4's enum-keyed
|
|
9721
|
-
* record requires every key, but a catalog entry only ships a subset of
|
|
9722
|
-
* formats.
|
|
9723
|
-
*/
|
|
9724
|
-
var ModelFormatsSchema = object({
|
|
9725
|
-
onnx: ModelFormatEntrySchema.optional(),
|
|
9726
|
-
coreml: ModelFormatEntrySchema.optional(),
|
|
9727
|
-
openvino: ModelFormatEntrySchema.optional(),
|
|
9728
|
-
tflite: ModelFormatEntrySchema.optional(),
|
|
9729
|
-
pt: ModelFormatEntrySchema.optional(),
|
|
9730
|
-
gguf: ModelFormatEntrySchema.optional()
|
|
9731
|
-
});
|
|
9732
|
-
/**
|
|
9733
|
-
* Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
|
|
9734
|
-
* the reduced `PipelineModelOption` returned in `pipeline.getSchema()` so the
|
|
9735
|
-
* grouped Family→Tier→Variant picker renders identically in the config UI and
|
|
9736
|
-
* in the pipeline/device steppers. The flat `id` stays the source of truth for
|
|
9737
|
-
* resolution/download/persistence; this is a presentation overlay resolved back
|
|
9738
|
-
* to an `id`.
|
|
9739
|
-
*/
|
|
9740
|
-
var ModelVariantGroupSchema = object({
|
|
9741
|
-
/** Top-level family, e.g. `yolo26` (later `d-fine`, `rf-detr`). */
|
|
9742
|
-
family: string(),
|
|
9743
|
-
/** Size within the family, e.g. `n` | `s` | `m` | `l`. */
|
|
9744
|
-
tier: string(),
|
|
9745
|
-
/** Quantization axis. Omit ⇒ the fp32 base build. */
|
|
9746
|
-
precision: _enum(["fp32", "int8"]).optional(),
|
|
9747
|
-
/**
|
|
9748
|
-
* Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
|
|
9749
|
-
* latency-optimized export (e.g. ReLU-activation variant) — the slot the
|
|
9750
|
-
* future performance variants plug into.
|
|
9751
|
-
*/
|
|
9752
|
-
optimization: _enum(["standard", "fast"]).optional(),
|
|
9753
|
-
/**
|
|
9754
|
-
* Input-resolution axis (square input side, px). Omit ⇒ the family's native
|
|
9755
|
-
* resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
|
|
9756
|
-
* cheap latency lever — especially on Apple ANE and the Intel N100 — at a
|
|
9757
|
-
* small-object accuracy cost. Mirrors the model's `inputSize` but lifted onto
|
|
9758
|
-
* the group so the selector can offer it as a variant axis.
|
|
9759
|
-
*/
|
|
9760
|
-
resolution: number().int().positive().optional()
|
|
9761
|
-
});
|
|
9762
|
-
var ModelProviderIdSchema = _enum([
|
|
9763
|
-
"camstack",
|
|
9764
|
-
"frigate",
|
|
9765
|
-
"scrypted",
|
|
9766
|
-
"custom"
|
|
9767
|
-
]);
|
|
9768
|
-
var ModelCatalogEntrySchema = object({
|
|
9769
|
-
id: string(),
|
|
9770
|
-
name: string(),
|
|
9771
|
-
description: string(),
|
|
9772
|
-
formats: ModelFormatsSchema,
|
|
9773
|
-
inputSize: object({
|
|
9774
|
-
width: number(),
|
|
9775
|
-
height: number()
|
|
9776
|
-
}),
|
|
9777
|
-
/**
|
|
9778
|
-
* Channel count of the model input tensor. Omit ⇒ 3 (RGB), the default for
|
|
9779
|
-
* every detector / classifier / embedder. Set to 1 for a grayscale CTC text
|
|
9780
|
-
* recognizer (EasyOCR VGG plate-OCR: input `[N,1,H,W]`) so the preprocess
|
|
9781
|
-
* feeds a single-channel, EasyOCR-normalized tensor instead of the default
|
|
9782
|
-
* 3-channel RGB one. Threaded through `PoolModelConfig.inputChannels` to the
|
|
9783
|
-
* Python inference pool.
|
|
9784
|
-
*/
|
|
9785
|
-
inputChannels: number().int().positive().optional(),
|
|
9786
|
-
labels: array(LabelDefinitionSchema).readonly(),
|
|
9787
|
-
inputLayout: _enum(["nchw", "nhwc"]).optional(),
|
|
9788
|
-
/**
|
|
9789
|
-
* `'scrfd'` applies InsightFace's SCRFD input contract, `(x - 127.5) / 128`
|
|
9790
|
-
* (upstream `insightface/model_zoo/scrfd.py`), instead of the historical
|
|
9791
|
-
* plain `/255`. Measured against COCO GT: 188 → 196 faces found on the
|
|
9792
|
-
* `scrfd-2.5g` catalog entry (2026-09-26 model-replacement spike, §2.4-2).
|
|
9793
|
-
*/
|
|
9794
|
-
inputNormalization: _enum([
|
|
9795
|
-
"zero-one",
|
|
9796
|
-
"imagenet",
|
|
9797
|
-
"none",
|
|
9798
|
-
"scrfd"
|
|
9799
|
-
]).optional(),
|
|
9800
|
-
/**
|
|
9801
|
-
* The model already applies softmax IN-GRAPH — its raw output is a
|
|
9802
|
-
* probability distribution, not logits. When set, the `softmax`
|
|
9803
|
-
* postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
|
|
9804
|
-
* probability vector collapses it toward uniform (top-1 score craters far
|
|
9805
|
-
* below its true value, making every confidence gate meaningless). Absent ⇒
|
|
9806
|
-
* the output is raw logits and the postprocessor applies softmax (the normal
|
|
9807
|
-
* case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
|
|
9808
|
-
* TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
|
|
9809
|
-
*/
|
|
9810
|
-
outputProbabilities: boolean().optional(),
|
|
9811
|
-
preprocessMode: _enum(["letterbox", "resize"]).optional(),
|
|
9812
|
-
/**
|
|
9813
|
-
* Per-MODEL postprocessor override. Absent ⇒ the step's own
|
|
9814
|
-
* `StepDefinition.postprocessor` applies (the normal case — every model in a
|
|
9815
|
-
* step shares its decode). Set it when a step hosts models with DIFFERENT raw
|
|
9816
|
-
* output layouts under one slot: e.g. object-detection is `'yolo'` by default,
|
|
9817
|
-
* but a Coral SSD MobileNet build emits the `TFLite_Detection_PostProcess`
|
|
9818
|
-
* 4-tensor layout and needs `'ssd'`. Threaded into `PoolModelConfig.postprocessor`
|
|
9819
|
-
* by the engine factory (`modelEntry.postprocessor ?? def.postprocessor`).
|
|
9820
|
-
*/
|
|
9821
|
-
postprocessor: custom().optional(),
|
|
9822
|
-
/**
|
|
9823
|
-
* When true, the executor produces a landmark-aligned crop (similarity warp
|
|
9824
|
-
* onto the canonical template) before this step runs, instead of a plain
|
|
9825
|
-
* axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
|
|
9826
|
-
* their embeddings are only discriminative on an aligned input. The face
|
|
9827
|
-
* detector that produced the parent detail must emit 5 landmarks.
|
|
9828
|
-
*/
|
|
9829
|
-
faceAlignment: boolean().optional(),
|
|
9830
|
-
/**
|
|
9831
|
-
* Auxiliary files required at runtime (labels JSON, charset dict, etc.).
|
|
9832
|
-
* Downloaded into the same modelsDir alongside the model file.
|
|
9833
|
-
*/
|
|
9834
|
-
extraFiles: array(ModelExtraFileSchema).readonly().optional(),
|
|
9835
|
-
/**
|
|
9836
|
-
* LEGACY entry — retained in the catalog so a persisted operator selection
|
|
9837
|
-
* still RESOLVES (and can be re-activated), but hidden from the selectable
|
|
9838
|
-
* model list and excluded from the auto format-default pick. Set on the
|
|
9839
|
-
* superseded / consolidated models (older lineages, redundant fp16 IRs) so
|
|
9840
|
-
* the active lineup stays the coherent curated ladder without deleting a
|
|
9841
|
-
* model anyone may still be pinned to. `resolveModelForFormat` keeps honoring
|
|
9842
|
-
* an explicit legacy id that has a build for the node's format.
|
|
9843
|
-
*/
|
|
9844
|
-
legacy: boolean().optional(),
|
|
9845
|
-
/**
|
|
9846
|
-
* Measured quality/latency metadata — populated from the benchmark addon on
|
|
9847
|
-
* the real node classes. Absent = not yet measured (most entries today; the
|
|
9848
|
-
* catalog historically carried only `sizeMB`, a poor cross-architecture
|
|
9849
|
-
* speed proxy). `p95LatencyMs` is keyed by node class (e.g. `n100`, `mac`).
|
|
9850
|
-
*/
|
|
9851
|
-
metrics: object({
|
|
9852
|
-
map50: number().optional(),
|
|
9853
|
-
p95LatencyMs: record(string(), number()).optional()
|
|
9854
|
-
}).optional(),
|
|
9855
|
-
/**
|
|
9856
|
-
* SPDX-ish license id of the model weights (e.g. `AGPL-3.0` for Ultralytics
|
|
9857
|
-
* YOLO26, `GPL-3.0` for YOLOv9, `Apache-2.0` for D-FINE/RF-DETR). Matters for
|
|
9858
|
-
* the retraining addon and any future commercial distribution.
|
|
9859
|
-
*/
|
|
9860
|
-
license: string().optional(),
|
|
9861
|
-
/**
|
|
9862
|
-
* Variant-selector grouping. The UI groups models by `family` + `tier` and
|
|
9863
|
-
* offers `precision` / `optimization` as variant axes WITHIN a tier — so all
|
|
9864
|
-
* of a family's sizes and quantizations collapse into one grouped picker
|
|
9865
|
-
* instead of a flat list of `yolo26s`, `yolo26s-int8`, … Absent ⇒ ungrouped
|
|
9866
|
-
* (legacy / custom models) — never shown in the grouped selector. The flat
|
|
9867
|
-
* `id` stays the source of truth for resolution/download/persistence; grouping
|
|
9868
|
-
* is a presentation overlay resolved back to an `id`.
|
|
9869
|
-
*/
|
|
9870
|
-
group: ModelVariantGroupSchema.optional(),
|
|
9871
|
-
/**
|
|
9872
|
-
* Catalog source for the pipeline stepper's provider-first picker. Absent on
|
|
9873
|
-
* built-in CamStack entries (treated as `camstack`) and on registry rows
|
|
9874
|
-
* persisted before this field existed (`inferModelProvider` fills those).
|
|
10272
|
+
* Free-form tag for log scoping. Stream-broker uses
|
|
10273
|
+
* `broker:<deviceId>/<profile>`. Decoder session logger surfaces it
|
|
10274
|
+
* on every line so `grep tag=broker:5/high` filters one camera
|
|
10275
|
+
* profile cleanly.
|
|
9875
10276
|
*/
|
|
9876
|
-
|
|
10277
|
+
tag: string().optional(),
|
|
9877
10278
|
/**
|
|
9878
|
-
*
|
|
9879
|
-
*
|
|
9880
|
-
*
|
|
10279
|
+
* Where the session delivers decoded frames (Phase 5 / D9):
|
|
10280
|
+
*
|
|
10281
|
+
* - `'callback'` (default) — the legacy pixel path: decoded frames are
|
|
10282
|
+
* buffered as `DecodedFrame`s and drained via `pullFrames`.
|
|
10283
|
+
* - `'shm'` — the shared-memory frame plane: decoded frames are written
|
|
10284
|
+
* into an OS shared-memory ring and drained as zero-pixel
|
|
10285
|
+
* `FrameHandle`s via `pullHandles`. A session is one mode or the
|
|
10286
|
+
* other — `pullFrames` returns nothing for an `'shm'` session and
|
|
10287
|
+
* `pullHandles` returns nothing for a `'callback'` session.
|
|
9881
10288
|
*/
|
|
9882
|
-
|
|
10289
|
+
frameSink: _enum(["callback", "shm"]).default("callback"),
|
|
9883
10290
|
/**
|
|
9884
|
-
*
|
|
9885
|
-
*
|
|
9886
|
-
*
|
|
9887
|
-
*
|
|
10291
|
+
* Per-camera decoder DEBUG facility. When `true`, a pull-mode session emits
|
|
10292
|
+
* a throttled (~1Hz) structured `decoder debug` line (effective/adaptive fps,
|
|
10293
|
+
* real-time lag, dropped-frame delta, avg decode time, hwaccel). Mirrors the
|
|
10294
|
+
* stream-broker's `streamingDebug` gate — off by default so production logs
|
|
10295
|
+
* stay quiet and the emit path pays zero per-frame cost when disabled.
|
|
9888
10296
|
*/
|
|
9889
|
-
|
|
9890
|
-
});
|
|
9891
|
-
var ConvertTargetSchema = discriminatedUnion("format", [object({
|
|
9892
|
-
format: literal("openvino"),
|
|
9893
|
-
precisions: array(_enum(["fp16", "int8"])).min(1).readonly()
|
|
9894
|
-
}), object({ format: literal("coreml") })]);
|
|
9895
|
-
var ModelConvertMetadataSchema = object({
|
|
9896
|
-
id: string().regex(/^[a-zA-Z0-9._-]+$/),
|
|
9897
|
-
name: string(),
|
|
9898
|
-
labels: array(LabelDefinitionSchema).readonly(),
|
|
9899
|
-
inputSize: object({
|
|
9900
|
-
width: number(),
|
|
9901
|
-
height: number()
|
|
9902
|
-
}),
|
|
9903
|
-
inputLayout: _enum(["nchw", "nhwc"]).optional(),
|
|
9904
|
-
inputNormalization: _enum([
|
|
9905
|
-
"zero-one",
|
|
9906
|
-
"imagenet",
|
|
9907
|
-
"none",
|
|
9908
|
-
"scrfd"
|
|
9909
|
-
]).optional(),
|
|
9910
|
-
preprocessMode: _enum(["letterbox", "resize"]).optional(),
|
|
9911
|
-
outputFormat: _enum([
|
|
9912
|
-
"yolo",
|
|
9913
|
-
"ssd",
|
|
9914
|
-
"embedding",
|
|
9915
|
-
"classification",
|
|
9916
|
-
"ocr",
|
|
9917
|
-
"segmentation"
|
|
9918
|
-
]),
|
|
9919
|
-
faceAlignment: boolean().optional(),
|
|
9920
|
-
classMap: DetectionCatalogClassMapSchema.optional()
|
|
9921
|
-
});
|
|
9922
|
-
var ConvertResultSchema = object({
|
|
9923
|
-
entry: ModelCatalogEntrySchema,
|
|
9924
|
-
artifacts: array(object({
|
|
9925
|
-
format: _enum(MODEL_FORMATS),
|
|
9926
|
-
precision: _enum(["fp16", "int8"]).optional(),
|
|
9927
|
-
sizeMB: number(),
|
|
9928
|
-
validated: boolean(),
|
|
9929
|
-
files: array(string()).readonly()
|
|
9930
|
-
})).readonly()
|
|
10297
|
+
debug: boolean().optional()
|
|
9931
10298
|
});
|
|
9932
10299
|
/**
|
|
9933
|
-
*
|
|
9934
|
-
*
|
|
9935
|
-
*
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
*
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
*
|
|
9949
|
-
var
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
*
|
|
9957
|
-
*
|
|
9958
|
-
*
|
|
9959
|
-
*
|
|
10300
|
+
* Per-component log CHANNELS — the gate a hot path consults, and the registry
|
|
10301
|
+
* an addon declares its channels in.
|
|
10302
|
+
*
|
|
10303
|
+
* ## Two axes, deliberately separated
|
|
10304
|
+
*
|
|
10305
|
+
* - **DECLARATION** — which channels exist. Only the addon knows:
|
|
10306
|
+
* `stream-broker` knows webrtc/ICE/RTP, `provider-reolink` knows
|
|
10307
|
+
* baichuan/handshake. A hand-wired central list rots at the first addition,
|
|
10308
|
+
* and rots silently. So a channel is declared where it is consulted, and the
|
|
10309
|
+
* `log-channels` capability enumerates the declarations.
|
|
10310
|
+
* - **VALUE** — at which level, for which scope, until when. That stays ONE
|
|
10311
|
+
* thing: the logging settings document on the `system` cap. Two authorities
|
|
10312
|
+
* over the values is the exact defect
|
|
10313
|
+
* `docs/design/plans/2026-08-26-logging-per-componente.md` was written to
|
|
10314
|
+
* remove; re-introducing it from the cure side would be grotesque.
|
|
10315
|
+
*
|
|
10316
|
+
* Nothing in this file reads a clock, an env var or a store. The registry is
|
|
10317
|
+
* a MIRROR: it is moved only by {@link LogChannelRegistry.apply}, called off
|
|
10318
|
+
* the hot path with a value somebody actually read, and by
|
|
10319
|
+
* {@link LogChannelRegistry.tick}, called on a timer. A store read that fails
|
|
10320
|
+
* never reaches here, so it can neither disarm an armed channel nor arm a
|
|
10321
|
+
* disarmed one (D49).
|
|
10322
|
+
*
|
|
10323
|
+
* ## The canonical call shape
|
|
10324
|
+
*
|
|
10325
|
+
* ```ts
|
|
10326
|
+
* if (CH_RTP.on && CH_RTP.wants(deviceId)) {
|
|
10327
|
+
* CH_RTP.log(logger, 'rtp subscriber added', { tags: { deviceId }, meta: { ssrc } })
|
|
10328
|
+
* }
|
|
10329
|
+
* ```
|
|
10330
|
+
*
|
|
10331
|
+
* `on` is a plain boolean FIELD — never a getter — and it is the FIRST thing
|
|
10332
|
+
* read. Disarmed, a call site costs one load and one branch, and the `extras`
|
|
10333
|
+
* object literal is never constructed because it lives inside the branch. It
|
|
10334
|
+
* is the same shape already proven in production at `stream-broker.ts:1650`,
|
|
10335
|
+
* and the same discipline `LoggingGate.allowsDestination` uses for the
|
|
10336
|
+
* destination floor (measured at 1.93 ns/call when off).
|
|
9960
10337
|
*
|
|
9961
|
-
*
|
|
9962
|
-
* - it cannot be polluted (no `__proto__` / `constructor` write reaches it);
|
|
9963
|
-
* - a lookup for `toString` / `hasOwnProperty` / `constructor` finds NOTHING
|
|
9964
|
-
* (there is no `Object.prototype` in the chain), so those names are not
|
|
9965
|
-
* callable — they are simply "unknown function" at parse time.
|
|
10338
|
+
* ## Why a channel emits at `info`
|
|
9966
10339
|
*
|
|
9967
|
-
*
|
|
9968
|
-
*
|
|
9969
|
-
*
|
|
9970
|
-
*
|
|
10340
|
+
* `loki-logging.addon.ts` pins the destination default at `info` and
|
|
10341
|
+
* `loki-destination.ts` drops everything below it, so a line emitted at
|
|
10342
|
+
* `debug` never reaches Loki and the hub's in-memory ring only holds ~35
|
|
10343
|
+
* minutes. A diagnostic that cannot be read an hour later is worse than no
|
|
10344
|
+
* diagnostic, because it looks done. {@link LogChannelGate.log} therefore
|
|
10345
|
+
* emits at the channel's declared level, whose schema floor is `info`.
|
|
9971
10346
|
*/
|
|
9972
|
-
function asFiniteNumber(value, name, index) {
|
|
9973
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
|
|
9974
|
-
return value;
|
|
9975
|
-
}
|
|
9976
|
-
function asString$1(value, name, index) {
|
|
9977
|
-
if (typeof value !== "string") throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a string`);
|
|
9978
|
-
return value;
|
|
9979
|
-
}
|
|
9980
|
-
function finiteResult(value, name) {
|
|
9981
|
-
if (!Number.isFinite(value)) throw new ExpressionEvalError(`${name}: produced a non-finite result`);
|
|
9982
|
-
return value;
|
|
9983
|
-
}
|
|
9984
|
-
function allFiniteNumbers(args, name) {
|
|
9985
|
-
return args.map((a, idx) => asFiniteNumber(a, name, idx));
|
|
9986
|
-
}
|
|
9987
|
-
var INF = Number.POSITIVE_INFINITY;
|
|
9988
|
-
var table = {
|
|
9989
|
-
min: {
|
|
9990
|
-
minArgs: 1,
|
|
9991
|
-
maxArgs: INF,
|
|
9992
|
-
apply: (args) => finiteResult(Math.min(...allFiniteNumbers(args, "min")), "min")
|
|
9993
|
-
},
|
|
9994
|
-
max: {
|
|
9995
|
-
minArgs: 1,
|
|
9996
|
-
maxArgs: INF,
|
|
9997
|
-
apply: (args) => finiteResult(Math.max(...allFiniteNumbers(args, "max")), "max")
|
|
9998
|
-
},
|
|
9999
|
-
abs: {
|
|
10000
|
-
minArgs: 1,
|
|
10001
|
-
maxArgs: 1,
|
|
10002
|
-
apply: (args) => finiteResult(Math.abs(asFiniteNumber(args[0], "abs", 0)), "abs")
|
|
10003
|
-
},
|
|
10004
|
-
floor: {
|
|
10005
|
-
minArgs: 1,
|
|
10006
|
-
maxArgs: 1,
|
|
10007
|
-
apply: (args) => finiteResult(Math.floor(asFiniteNumber(args[0], "floor", 0)), "floor")
|
|
10008
|
-
},
|
|
10009
|
-
ceil: {
|
|
10010
|
-
minArgs: 1,
|
|
10011
|
-
maxArgs: 1,
|
|
10012
|
-
apply: (args) => finiteResult(Math.ceil(asFiniteNumber(args[0], "ceil", 0)), "ceil")
|
|
10013
|
-
},
|
|
10014
|
-
sqrt: {
|
|
10015
|
-
minArgs: 1,
|
|
10016
|
-
maxArgs: 1,
|
|
10017
|
-
apply: (args) => finiteResult(Math.sqrt(asFiniteNumber(args[0], "sqrt", 0)), "sqrt")
|
|
10018
|
-
},
|
|
10019
|
-
round: {
|
|
10020
|
-
minArgs: 1,
|
|
10021
|
-
maxArgs: 2,
|
|
10022
|
-
apply: (args) => {
|
|
10023
|
-
const x = asFiniteNumber(args[0], "round", 0);
|
|
10024
|
-
const digits = args.length > 1 ? Math.trunc(asFiniteNumber(args[1], "round", 1)) : 0;
|
|
10025
|
-
if (digits < 0 || digits > 100) throw new ExpressionEvalError("round: digits must be between 0 and 100");
|
|
10026
|
-
const factor = 10 ** digits;
|
|
10027
|
-
return finiteResult(Math.round(x * factor) / factor, "round");
|
|
10028
|
-
}
|
|
10029
|
-
},
|
|
10030
|
-
pow: {
|
|
10031
|
-
minArgs: 2,
|
|
10032
|
-
maxArgs: 2,
|
|
10033
|
-
apply: (args) => finiteResult(asFiniteNumber(args[0], "pow", 0) ** asFiniteNumber(args[1], "pow", 1), "pow")
|
|
10034
|
-
},
|
|
10035
|
-
clamp: {
|
|
10036
|
-
minArgs: 3,
|
|
10037
|
-
maxArgs: 3,
|
|
10038
|
-
apply: (args) => {
|
|
10039
|
-
const x = asFiniteNumber(args[0], "clamp", 0);
|
|
10040
|
-
const lo = asFiniteNumber(args[1], "clamp", 1);
|
|
10041
|
-
const hi = asFiniteNumber(args[2], "clamp", 2);
|
|
10042
|
-
if (lo > hi) throw new ExpressionEvalError("clamp: lower bound is greater than upper bound");
|
|
10043
|
-
return finiteResult(Math.min(hi, Math.max(lo, x)), "clamp");
|
|
10044
|
-
}
|
|
10045
|
-
},
|
|
10046
|
-
avg: {
|
|
10047
|
-
minArgs: 1,
|
|
10048
|
-
maxArgs: INF,
|
|
10049
|
-
apply: (args) => {
|
|
10050
|
-
const nums = allFiniteNumbers(args, "avg");
|
|
10051
|
-
return finiteResult(nums.reduce((acc, v) => acc + v, 0) / nums.length, "avg");
|
|
10052
|
-
}
|
|
10053
|
-
},
|
|
10054
|
-
sum: {
|
|
10055
|
-
minArgs: 1,
|
|
10056
|
-
maxArgs: INF,
|
|
10057
|
-
apply: (args) => finiteResult(allFiniteNumbers(args, "sum").reduce((acc, v) => acc + v, 0), "sum")
|
|
10058
|
-
},
|
|
10059
|
-
coalesce: {
|
|
10060
|
-
minArgs: 1,
|
|
10061
|
-
maxArgs: INF,
|
|
10062
|
-
apply: (args) => {
|
|
10063
|
-
for (const a of args) if (a !== null) return a;
|
|
10064
|
-
return null;
|
|
10065
|
-
}
|
|
10066
|
-
},
|
|
10067
|
-
age: {
|
|
10068
|
-
minArgs: 2,
|
|
10069
|
-
maxArgs: 2,
|
|
10070
|
-
apply: (args) => finiteResult(asFiniteNumber(args[0], "age", 0) - asFiniteNumber(args[1], "age", 1), "age")
|
|
10071
|
-
},
|
|
10072
|
-
convert: {
|
|
10073
|
-
minArgs: 3,
|
|
10074
|
-
maxArgs: 3,
|
|
10075
|
-
apply: (args, hooks) => {
|
|
10076
|
-
const x = asFiniteNumber(args[0], "convert", 0);
|
|
10077
|
-
const from = asString$1(args[1], "convert", 1).trim();
|
|
10078
|
-
const to = asString$1(args[2], "convert", 2).trim();
|
|
10079
|
-
if (hooks.convert) {
|
|
10080
|
-
const out = hooks.convert(x, from, to);
|
|
10081
|
-
if (out === null) throw new ExpressionEvalError(`convert: cannot convert '${from}' to '${to}'`);
|
|
10082
|
-
return finiteResult(out, "convert");
|
|
10083
|
-
}
|
|
10084
|
-
if (from === to) return x;
|
|
10085
|
-
throw new ExpressionEvalError("convert: unit conversion table not installed");
|
|
10086
|
-
}
|
|
10087
|
-
}
|
|
10088
|
-
};
|
|
10089
|
-
Object.freeze(Object.assign(Object.create(null), table));
|
|
10090
|
-
/** The set of valid builtin names — used by the parser to reject unknown
|
|
10091
|
-
* callees at parse time (immediate author feedback). */
|
|
10092
|
-
var EXPRESSION_BUILTIN_NAMES = new Set(Object.keys(table));
|
|
10093
10347
|
/**
|
|
10094
|
-
*
|
|
10348
|
+
* The level a channel writes at once armed.
|
|
10095
10349
|
*
|
|
10096
|
-
*
|
|
10097
|
-
*
|
|
10098
|
-
*
|
|
10099
|
-
* work a single author-supplied expression can request, so a hostile or
|
|
10100
|
-
* accidental pathological string can never spend unbounded CPU/memory.
|
|
10350
|
+
* `debug` is absent ON PURPOSE and not by omission: below `info` the line does
|
|
10351
|
+
* not leave the process for Loki, and the whole point of arming a channel is
|
|
10352
|
+
* to read it later.
|
|
10101
10353
|
*/
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
var EXPRESSION_IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
10107
|
-
/** Binding names an author may NOT use: `now` is auto-injected; the literal
|
|
10108
|
-
* keywords lex as values, not identifiers, so binding to them is meaningless. */
|
|
10109
|
-
var RESERVED_BINDING_NAMES = new Set([
|
|
10110
|
-
"now",
|
|
10111
|
-
"true",
|
|
10112
|
-
"false",
|
|
10113
|
-
"null"
|
|
10354
|
+
var LogChannelLevelSchema = _enum([
|
|
10355
|
+
"info",
|
|
10356
|
+
"warn",
|
|
10357
|
+
"error"
|
|
10114
10358
|
]);
|
|
10115
10359
|
/**
|
|
10116
|
-
*
|
|
10117
|
-
*
|
|
10118
|
-
* single/double-quoted strings with a tiny escape set, identifiers, the three
|
|
10119
|
-
* value keywords (`true`/`false`/`null`) and a fixed punctuator set. Anything
|
|
10120
|
-
* outside that — a bare `.`, `=`, `[`, `]`, `{`, `}`, `;`, backtick, `&`, `|` —
|
|
10121
|
-
* is a parse error with a source position, so member access / assignment /
|
|
10122
|
-
* template literals are lexically impossible.
|
|
10360
|
+
* What an addon declares about one channel. No value, no state — a
|
|
10361
|
+
* declaration is inert.
|
|
10123
10362
|
*/
|
|
10124
|
-
var
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
}
|
|
10141
|
-
|
|
10142
|
-
*
|
|
10143
|
-
*
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
i += 1;
|
|
10153
|
-
continue;
|
|
10154
|
-
}
|
|
10155
|
-
if (isDigit(ch)) {
|
|
10156
|
-
const start = i;
|
|
10157
|
-
while (i < n && isDigit(source[i])) i += 1;
|
|
10158
|
-
if (i < n && source[i] === ".") {
|
|
10159
|
-
if (i + 1 >= n || !isDigit(source[i + 1])) throw new ExpressionParseError("malformed number: decimal point needs a digit", i);
|
|
10160
|
-
i += 1;
|
|
10161
|
-
while (i < n && isDigit(source[i])) i += 1;
|
|
10162
|
-
}
|
|
10163
|
-
const text = source.slice(start, i);
|
|
10164
|
-
const value = Number(text);
|
|
10165
|
-
if (!Number.isFinite(value)) throw new ExpressionParseError(`malformed number: '${text}'`, start);
|
|
10166
|
-
tokens.push({
|
|
10167
|
-
type: "number",
|
|
10168
|
-
value,
|
|
10169
|
-
pos: start
|
|
10170
|
-
});
|
|
10171
|
-
continue;
|
|
10172
|
-
}
|
|
10173
|
-
if (ch === "'" || ch === "\"") {
|
|
10174
|
-
const quote = ch;
|
|
10175
|
-
const start = i;
|
|
10176
|
-
i += 1;
|
|
10177
|
-
let out = "";
|
|
10178
|
-
let closed = false;
|
|
10179
|
-
while (i < n) {
|
|
10180
|
-
const c = source[i];
|
|
10181
|
-
if (c === "\\") {
|
|
10182
|
-
const next = i + 1 < n ? source[i + 1] : "";
|
|
10183
|
-
if (next === "\\" || next === "'" || next === "\"") {
|
|
10184
|
-
out += next;
|
|
10185
|
-
i += 2;
|
|
10186
|
-
continue;
|
|
10187
|
-
}
|
|
10188
|
-
throw new ExpressionParseError(`invalid string escape: '\\${next}'`, i);
|
|
10189
|
-
}
|
|
10190
|
-
if (c === quote) {
|
|
10191
|
-
closed = true;
|
|
10192
|
-
i += 1;
|
|
10193
|
-
break;
|
|
10194
|
-
}
|
|
10195
|
-
out += c;
|
|
10196
|
-
i += 1;
|
|
10197
|
-
}
|
|
10198
|
-
if (!closed) throw new ExpressionParseError("unterminated string literal", start);
|
|
10199
|
-
tokens.push({
|
|
10200
|
-
type: "string",
|
|
10201
|
-
value: out,
|
|
10202
|
-
pos: start
|
|
10203
|
-
});
|
|
10204
|
-
continue;
|
|
10205
|
-
}
|
|
10206
|
-
if (isIdentStart(ch)) {
|
|
10207
|
-
const start = i;
|
|
10208
|
-
while (i < n && isIdentPart(source[i])) i += 1;
|
|
10209
|
-
const text = source.slice(start, i);
|
|
10210
|
-
if (KEYWORDS.has(text)) tokens.push({
|
|
10211
|
-
type: "keyword",
|
|
10212
|
-
keyword: keywordOf(text),
|
|
10213
|
-
pos: start
|
|
10214
|
-
});
|
|
10215
|
-
else tokens.push({
|
|
10216
|
-
type: "identifier",
|
|
10217
|
-
name: text,
|
|
10218
|
-
pos: start
|
|
10219
|
-
});
|
|
10220
|
-
continue;
|
|
10221
|
-
}
|
|
10222
|
-
const two = i + 1 < n ? source.slice(i, i + 2) : "";
|
|
10223
|
-
if (two === "<=" || two === ">=" || two === "==" || two === "!=" || two === "&&" || two === "||") {
|
|
10224
|
-
tokens.push({
|
|
10225
|
-
type: "punct",
|
|
10226
|
-
punct: two,
|
|
10227
|
-
pos: i
|
|
10228
|
-
});
|
|
10229
|
-
i += 2;
|
|
10230
|
-
continue;
|
|
10231
|
-
}
|
|
10232
|
-
if (isSinglePunct(ch)) {
|
|
10233
|
-
tokens.push({
|
|
10234
|
-
type: "punct",
|
|
10235
|
-
punct: ch,
|
|
10236
|
-
pos: i
|
|
10237
|
-
});
|
|
10238
|
-
i += 1;
|
|
10239
|
-
continue;
|
|
10240
|
-
}
|
|
10241
|
-
throw new ExpressionParseError(`unexpected character '${ch}'`, i);
|
|
10242
|
-
}
|
|
10243
|
-
tokens.push({
|
|
10244
|
-
type: "eof",
|
|
10245
|
-
pos: n
|
|
10246
|
-
});
|
|
10247
|
-
return tokens;
|
|
10248
|
-
}
|
|
10249
|
-
function keywordOf(text) {
|
|
10250
|
-
if (text === "true") return "true";
|
|
10251
|
-
if (text === "false") return "false";
|
|
10252
|
-
return "null";
|
|
10253
|
-
}
|
|
10254
|
-
function isSinglePunct(ch) {
|
|
10255
|
-
return ch === "(" || ch === ")" || ch === "," || ch === "?" || ch === ":" || ch === "+" || ch === "-" || ch === "*" || ch === "/" || ch === "%" || ch === "!" || ch === "<" || ch === ">";
|
|
10256
|
-
}
|
|
10363
|
+
var LogChannelDescriptorSchema = object({
|
|
10364
|
+
/**
|
|
10365
|
+
* Dotted `area.thing`, unique across the workspace. `area` is conventionally
|
|
10366
|
+
* the addon's short name so an operator reading a channel list can tell who
|
|
10367
|
+
* owns it without a second lookup.
|
|
10368
|
+
*/
|
|
10369
|
+
name: string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
|
|
10370
|
+
/** One sentence: what the operator will SEE after arming it. */
|
|
10371
|
+
description: string().min(1),
|
|
10372
|
+
/** The level its lines are emitted at. Never below `info`. */
|
|
10373
|
+
defaultLevel: LogChannelLevelSchema,
|
|
10374
|
+
/**
|
|
10375
|
+
* Whether this channel can be narrowed to a camera.
|
|
10376
|
+
*
|
|
10377
|
+
* `true` is a PROMISE with two halves, and both must hold: the gate is
|
|
10378
|
+
* consulted with the numeric device id, AND every line the channel admits
|
|
10379
|
+
* carries `tags: { deviceId }` with that same numeric id. The second half is
|
|
10380
|
+
* what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
|
|
10381
|
+
* keeps `deviceId` out of the stream labels for cardinality, so the tag in
|
|
10382
|
+
* the body is the only way to filter.
|
|
10383
|
+
*
|
|
10384
|
+
* A channel whose lines carry the device only in `meta` (or not at all) is
|
|
10385
|
+
* declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
|
|
10386
|
+
* the operator narrows to one camera, sees nothing, and concludes the code
|
|
10387
|
+
* path was never taken.
|
|
10388
|
+
*/
|
|
10389
|
+
perDevice: boolean()
|
|
10390
|
+
});
|
|
10257
10391
|
/**
|
|
10258
|
-
*
|
|
10392
|
+
* An armed window over one channel, as the document hands it to a mirror.
|
|
10259
10393
|
*
|
|
10260
|
-
*
|
|
10261
|
-
*
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10394
|
+
* A window is a DEADLINE, never a flag (ADR-0244): a channel somebody forgot
|
|
10395
|
+
* expires by itself, which is the one failure a boolean cannot avoid.
|
|
10396
|
+
*/
|
|
10397
|
+
var LogChannelWindowSchema = object({
|
|
10398
|
+
channel: string().min(1),
|
|
10399
|
+
/** Epoch ms the window closes at. */
|
|
10400
|
+
armedUntilMs: number(),
|
|
10401
|
+
/** `null` = every camera. A non-empty list narrows to those numeric ids. */
|
|
10402
|
+
deviceIds: array(number().int()).readonly().nullable()
|
|
10403
|
+
});
|
|
10404
|
+
var ClipModelMetaSchema = object({
|
|
10405
|
+
/** The paired text encoder's catalog id (the embedding-encoder's text catalog). */
|
|
10406
|
+
textModelId: string().min(1),
|
|
10407
|
+
/** Output dimension of BOTH towers. A vector of any other length is refused. */
|
|
10408
|
+
embeddingDim: number().int().positive(),
|
|
10409
|
+
/**
|
|
10410
|
+
* The `vector-store` index this model's vectors live in. ONE feature space
|
|
10411
|
+
* per index: two models share an index only when their vectors are ranked
|
|
10412
|
+
* against each other, which for CLIP means never across families. MobileCLIP
|
|
10413
|
+
* S1 and S2 share `object-clip` for history (separated by the row's
|
|
10414
|
+
* `modelId`); any other model gets its own.
|
|
10415
|
+
*/
|
|
10416
|
+
vectorIndex: string().regex(/^object-clip(-[a-z0-9][a-z0-9-]*)?$/),
|
|
10417
|
+
/**
|
|
10418
|
+
* The text encoder's tokenizer, a declared SIBLING file of its onnx. The name
|
|
10419
|
+
* must be unique per tokenizer: every model's siblings land in ONE flat
|
|
10420
|
+
* models directory, so two different tokenizers under the same name would
|
|
10421
|
+
* silently overwrite each other (whichever model downloaded last wins, and
|
|
10422
|
+
* the other one tokenises with the wrong vocabulary).
|
|
10423
|
+
*/
|
|
10424
|
+
tokenizerFile: string().min(1),
|
|
10425
|
+
/** Token window the text graph takes (SigLIP2: 64; CLIP BPE: 77). */
|
|
10426
|
+
contextLength: number().int().positive(),
|
|
10427
|
+
/** Id the tokenizer pads with. */
|
|
10428
|
+
padId: number().int().nonnegative(),
|
|
10429
|
+
/**
|
|
10430
|
+
* The search cosine floor at `strictness: 'loose'` — calibrated per model,
|
|
10431
|
+
* because cosine bands are a property of the model: 0.2 keeps 96% of matched
|
|
10432
|
+
* COCO caption-image pairs on MobileCLIP S1 and 2.7% on SigLIP2.
|
|
10433
|
+
*/
|
|
10434
|
+
searchMinScore: number().min(0).max(1)
|
|
10435
|
+
});
|
|
10436
|
+
var ClipSearchStrictnessSchema = _enum([
|
|
10437
|
+
"loose",
|
|
10438
|
+
"balanced",
|
|
10439
|
+
"strict"
|
|
10440
|
+
]);
|
|
10441
|
+
var MODEL_FORMATS = [
|
|
10442
|
+
"onnx",
|
|
10443
|
+
"coreml",
|
|
10444
|
+
"openvino",
|
|
10445
|
+
"tflite",
|
|
10446
|
+
"pt",
|
|
10447
|
+
"gguf"
|
|
10448
|
+
];
|
|
10449
|
+
/**
|
|
10450
|
+
* Multi-file format payload.
|
|
10266
10451
|
*
|
|
10267
|
-
*
|
|
10268
|
-
*
|
|
10452
|
+
* - Directory formats (`isDirectory: true`, e.g. `.mlpackage`): files
|
|
10453
|
+
* relative to the directory root — the downloader fetches each from
|
|
10454
|
+
* `{url}/{file}` into `{modelDir}/{file}`. If omitted, it probes the
|
|
10455
|
+
* HuggingFace API (slower).
|
|
10456
|
+
* - Single-file formats (no `isDirectory`, e.g. OpenVINO IR): sibling
|
|
10457
|
+
* files fetched from the SAME remote directory as `url` and stored flat
|
|
10458
|
+
* alongside the main file — e.g. `['camstack-yolov9t.bin']` for the IR
|
|
10459
|
+
* weights next to `camstack-yolov9t.xml`.
|
|
10269
10460
|
*/
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
"
|
|
10279
|
-
|
|
10280
|
-
"+": 5,
|
|
10281
|
-
"-": 5,
|
|
10282
|
-
"*": 6,
|
|
10283
|
-
"/": 6,
|
|
10284
|
-
"%": 6
|
|
10285
|
-
};
|
|
10286
|
-
function isLogicalOp(op) {
|
|
10287
|
-
return op === "&&" || op === "||";
|
|
10288
|
-
}
|
|
10289
|
-
function isBinaryOp(op) {
|
|
10290
|
-
return op === "+" || op === "-" || op === "*" || op === "/" || op === "%" || op === "==" || op === "!=" || op === "<" || op === "<=" || op === ">" || op === ">=";
|
|
10291
|
-
}
|
|
10292
|
-
var Parser = class {
|
|
10293
|
-
tokens;
|
|
10294
|
-
pos = 0;
|
|
10295
|
-
nodeCount = 0;
|
|
10296
|
-
identifiers = /* @__PURE__ */ new Set();
|
|
10297
|
-
callees = /* @__PURE__ */ new Set();
|
|
10298
|
-
constructor(tokens) {
|
|
10299
|
-
this.tokens = tokens;
|
|
10300
|
-
}
|
|
10301
|
-
parse() {
|
|
10302
|
-
const ast = this.parseTernary();
|
|
10303
|
-
const tok = this.peek();
|
|
10304
|
-
if (tok.type !== "eof") throw new ExpressionParseError("unexpected trailing input", tok.pos);
|
|
10305
|
-
return {
|
|
10306
|
-
ast,
|
|
10307
|
-
identifiers: this.identifiers,
|
|
10308
|
-
callees: this.callees,
|
|
10309
|
-
nodeCount: this.nodeCount
|
|
10310
|
-
};
|
|
10311
|
-
}
|
|
10312
|
-
peek() {
|
|
10313
|
-
return this.tokens[this.pos];
|
|
10314
|
-
}
|
|
10315
|
-
next() {
|
|
10316
|
-
return this.tokens[this.pos++];
|
|
10317
|
-
}
|
|
10318
|
-
/** Consume a punctuator token, erroring if the next token isn't it. */
|
|
10319
|
-
expectPunct(punct) {
|
|
10320
|
-
const tok = this.peek();
|
|
10321
|
-
if (tok.type !== "punct" || tok.punct !== punct) throw new ExpressionParseError(`expected '${punct}'`, tok.pos);
|
|
10322
|
-
this.pos += 1;
|
|
10323
|
-
}
|
|
10324
|
-
matchPunct(punct) {
|
|
10325
|
-
const tok = this.peek();
|
|
10326
|
-
if (tok.type === "punct" && tok.punct === punct) {
|
|
10327
|
-
this.pos += 1;
|
|
10328
|
-
return true;
|
|
10329
|
-
}
|
|
10330
|
-
return false;
|
|
10331
|
-
}
|
|
10332
|
-
countNode() {
|
|
10333
|
-
this.nodeCount += 1;
|
|
10334
|
-
if (this.nodeCount > 256) throw new ExpressionParseError("expression too complex", this.peek().pos);
|
|
10335
|
-
}
|
|
10336
|
-
parseTernary() {
|
|
10337
|
-
const test = this.parseBinary(1);
|
|
10338
|
-
if (this.matchPunct("?")) {
|
|
10339
|
-
const consequent = this.parseTernary();
|
|
10340
|
-
this.expectPunct(":");
|
|
10341
|
-
const alternate = this.parseTernary();
|
|
10342
|
-
this.countNode();
|
|
10343
|
-
return {
|
|
10344
|
-
kind: "conditional",
|
|
10345
|
-
test,
|
|
10346
|
-
consequent,
|
|
10347
|
-
alternate
|
|
10348
|
-
};
|
|
10349
|
-
}
|
|
10350
|
-
return test;
|
|
10351
|
-
}
|
|
10352
|
-
parseBinary(minPrec) {
|
|
10353
|
-
let left = this.parseUnary();
|
|
10354
|
-
for (;;) {
|
|
10355
|
-
const tok = this.peek();
|
|
10356
|
-
if (tok.type !== "punct") break;
|
|
10357
|
-
const prec = BINARY_PRECEDENCE[tok.punct];
|
|
10358
|
-
if (prec === void 0 || prec < minPrec) break;
|
|
10359
|
-
const op = tok.punct;
|
|
10360
|
-
this.pos += 1;
|
|
10361
|
-
const right = this.parseBinary(prec + 1);
|
|
10362
|
-
this.countNode();
|
|
10363
|
-
if (isLogicalOp(op)) left = {
|
|
10364
|
-
kind: "logical",
|
|
10365
|
-
op,
|
|
10366
|
-
left,
|
|
10367
|
-
right
|
|
10368
|
-
};
|
|
10369
|
-
else if (isBinaryOp(op)) left = {
|
|
10370
|
-
kind: "binary",
|
|
10371
|
-
op,
|
|
10372
|
-
left,
|
|
10373
|
-
right
|
|
10374
|
-
};
|
|
10375
|
-
else throw new ExpressionParseError(`unexpected operator '${op}'`, tok.pos);
|
|
10376
|
-
}
|
|
10377
|
-
return left;
|
|
10378
|
-
}
|
|
10379
|
-
parseUnary() {
|
|
10380
|
-
const tok = this.peek();
|
|
10381
|
-
if (tok.type === "punct" && (tok.punct === "!" || tok.punct === "-")) {
|
|
10382
|
-
const op = tok.punct;
|
|
10383
|
-
this.pos += 1;
|
|
10384
|
-
const operand = this.parseUnary();
|
|
10385
|
-
this.countNode();
|
|
10386
|
-
return {
|
|
10387
|
-
kind: "unary",
|
|
10388
|
-
op,
|
|
10389
|
-
operand
|
|
10390
|
-
};
|
|
10391
|
-
}
|
|
10392
|
-
return this.parsePrimary();
|
|
10393
|
-
}
|
|
10394
|
-
parsePrimary() {
|
|
10395
|
-
const tok = this.next();
|
|
10396
|
-
switch (tok.type) {
|
|
10397
|
-
case "number":
|
|
10398
|
-
this.countNode();
|
|
10399
|
-
return {
|
|
10400
|
-
kind: "literal",
|
|
10401
|
-
value: tok.value
|
|
10402
|
-
};
|
|
10403
|
-
case "string":
|
|
10404
|
-
this.countNode();
|
|
10405
|
-
return {
|
|
10406
|
-
kind: "literal",
|
|
10407
|
-
value: tok.value
|
|
10408
|
-
};
|
|
10409
|
-
case "keyword":
|
|
10410
|
-
this.countNode();
|
|
10411
|
-
return {
|
|
10412
|
-
kind: "literal",
|
|
10413
|
-
value: tok.keyword === "null" ? null : tok.keyword === "true"
|
|
10414
|
-
};
|
|
10415
|
-
case "identifier": {
|
|
10416
|
-
const nextTok = this.peek();
|
|
10417
|
-
if (nextTok.type === "punct" && nextTok.punct === "(") return this.parseCall(tok.name, tok.pos);
|
|
10418
|
-
this.identifiers.add(tok.name);
|
|
10419
|
-
this.countNode();
|
|
10420
|
-
return {
|
|
10421
|
-
kind: "identifier",
|
|
10422
|
-
name: tok.name
|
|
10423
|
-
};
|
|
10424
|
-
}
|
|
10425
|
-
case "punct":
|
|
10426
|
-
if (tok.punct === "(") {
|
|
10427
|
-
const inner = this.parseTernary();
|
|
10428
|
-
this.expectPunct(")");
|
|
10429
|
-
return inner;
|
|
10430
|
-
}
|
|
10431
|
-
throw new ExpressionParseError(`unexpected token '${tok.punct}'`, tok.pos);
|
|
10432
|
-
case "eof": throw new ExpressionParseError("unexpected end of expression", tok.pos);
|
|
10433
|
-
}
|
|
10434
|
-
}
|
|
10435
|
-
parseCall(callee, pos) {
|
|
10436
|
-
if (!EXPRESSION_BUILTIN_NAMES.has(callee)) throw new ExpressionParseError(`unknown function '${callee}'`, pos);
|
|
10437
|
-
this.expectPunct("(");
|
|
10438
|
-
const args = [];
|
|
10439
|
-
if (!this.matchPunct(")")) for (;;) {
|
|
10440
|
-
args.push(this.parseTernary());
|
|
10441
|
-
if (args.length > 16) throw new ExpressionParseError(`too many arguments to '${callee}'`, pos);
|
|
10442
|
-
if (this.matchPunct(",")) continue;
|
|
10443
|
-
this.expectPunct(")");
|
|
10444
|
-
break;
|
|
10445
|
-
}
|
|
10446
|
-
this.callees.add(callee);
|
|
10447
|
-
this.countNode();
|
|
10448
|
-
return {
|
|
10449
|
-
kind: "call",
|
|
10450
|
-
callee,
|
|
10451
|
-
args
|
|
10452
|
-
};
|
|
10453
|
-
}
|
|
10454
|
-
};
|
|
10455
|
-
/** Tokenize + parse `source` into a validated `ParsedExpression`. Throws
|
|
10456
|
-
* `ExpressionParseError` on any lexical or grammatical failure. */
|
|
10457
|
-
function parseExpression(source) {
|
|
10458
|
-
return new Parser(tokenize(source)).parse();
|
|
10459
|
-
}
|
|
10461
|
+
var ModelFormatEntrySchema = object({
|
|
10462
|
+
url: string(),
|
|
10463
|
+
sizeMB: number(),
|
|
10464
|
+
/** Whether this format is a directory bundle (e.g., .mlpackage) rather than a single file */
|
|
10465
|
+
isDirectory: boolean().optional(),
|
|
10466
|
+
/** Multi-file payload (directory members or sibling files). */
|
|
10467
|
+
files: array(string()).readonly().optional(),
|
|
10468
|
+
/** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
|
|
10469
|
+
runtimes: array(_enum(["python"])).readonly().optional()
|
|
10470
|
+
});
|
|
10460
10471
|
/**
|
|
10461
|
-
*
|
|
10462
|
-
*
|
|
10463
|
-
* so a corrupt persisted string costs exactly one tokenize+parse total — not
|
|
10464
|
-
* one per read on a hot resolve path.
|
|
10465
|
-
*
|
|
10466
|
-
* The cache is a module-level singleton: entries are pure, content-addressed
|
|
10467
|
-
* ASTs keyed by the raw source string, so sharing one instance across all
|
|
10468
|
-
* callers is safe and maximises hit rate.
|
|
10472
|
+
* Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
|
|
10473
|
+
* The downloader fetches from `url` and saves to `{modelsDir}/{filename}`.
|
|
10469
10474
|
*/
|
|
10470
|
-
var
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
cache.set(source, hit);
|
|
10476
|
-
return hit;
|
|
10477
|
-
}
|
|
10478
|
-
let result;
|
|
10479
|
-
try {
|
|
10480
|
-
result = {
|
|
10481
|
-
ok: true,
|
|
10482
|
-
parsed: parseExpression(source)
|
|
10483
|
-
};
|
|
10484
|
-
} catch (err) {
|
|
10485
|
-
result = {
|
|
10486
|
-
ok: false,
|
|
10487
|
-
error: err instanceof ExpressionParseError ? err.message : String(err)
|
|
10488
|
-
};
|
|
10489
|
-
}
|
|
10490
|
-
cache.set(source, result);
|
|
10491
|
-
if (cache.size > 256) {
|
|
10492
|
-
const oldest = cache.keys().next().value;
|
|
10493
|
-
if (oldest !== void 0) cache.delete(oldest);
|
|
10494
|
-
}
|
|
10495
|
-
return result;
|
|
10496
|
-
}
|
|
10497
|
-
/** Compile `source`, returning a discriminated result instead of throwing.
|
|
10498
|
-
* Used by read paths that must degrade rather than raise. LRU/negative-cached. */
|
|
10499
|
-
function compileExpressionSafe(source) {
|
|
10500
|
-
return getCached(source);
|
|
10501
|
-
}
|
|
10502
|
-
Object.freeze({});
|
|
10475
|
+
var ModelExtraFileSchema = object({
|
|
10476
|
+
url: string(),
|
|
10477
|
+
filename: string(),
|
|
10478
|
+
sizeMB: number()
|
|
10479
|
+
});
|
|
10503
10480
|
/**
|
|
10504
|
-
*
|
|
10505
|
-
*
|
|
10506
|
-
*
|
|
10507
|
-
*
|
|
10508
|
-
* FREE identifier of the AST is covered by a binding or the injected `now`.
|
|
10481
|
+
* Per-format payload map. Modelled as an explicit object (one optional key
|
|
10482
|
+
* per `ModelFormat`) rather than `z.record(enum, …)` — zod v4's enum-keyed
|
|
10483
|
+
* record requires every key, but a catalog entry only ships a subset of
|
|
10484
|
+
* formats.
|
|
10509
10485
|
*/
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10486
|
+
var ModelFormatsSchema = object({
|
|
10487
|
+
onnx: ModelFormatEntrySchema.optional(),
|
|
10488
|
+
coreml: ModelFormatEntrySchema.optional(),
|
|
10489
|
+
openvino: ModelFormatEntrySchema.optional(),
|
|
10490
|
+
tflite: ModelFormatEntrySchema.optional(),
|
|
10491
|
+
pt: ModelFormatEntrySchema.optional(),
|
|
10492
|
+
gguf: ModelFormatEntrySchema.optional()
|
|
10493
|
+
});
|
|
10494
|
+
/**
|
|
10495
|
+
* Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
|
|
10496
|
+
* the reduced `PipelineModelOption` returned in `pipeline.getSchema()` so the
|
|
10497
|
+
* grouped Family→Tier→Variant picker renders identically in the config UI and
|
|
10498
|
+
* in the pipeline/device steppers. The flat `id` stays the source of truth for
|
|
10499
|
+
* resolution/download/persistence; this is a presentation overlay resolved back
|
|
10500
|
+
* to an `id`.
|
|
10501
|
+
*/
|
|
10502
|
+
var ModelVariantGroupSchema = object({
|
|
10503
|
+
/** Top-level family, e.g. `yolo26` (later `d-fine`, `rf-detr`). */
|
|
10504
|
+
family: string(),
|
|
10505
|
+
/** Size within the family, e.g. `n` | `s` | `m` | `l`. */
|
|
10506
|
+
tier: string(),
|
|
10507
|
+
/** Quantization axis. Omit ⇒ the fp32 base build. */
|
|
10508
|
+
precision: _enum(["fp32", "int8"]).optional(),
|
|
10509
|
+
/**
|
|
10510
|
+
* Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
|
|
10511
|
+
* latency-optimized export (e.g. ReLU-activation variant) — the slot the
|
|
10512
|
+
* future performance variants plug into.
|
|
10513
|
+
*/
|
|
10514
|
+
optimization: _enum(["standard", "fast"]).optional(),
|
|
10515
|
+
/**
|
|
10516
|
+
* Input-resolution axis (square input side, px). Omit ⇒ the family's native
|
|
10517
|
+
* resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
|
|
10518
|
+
* cheap latency lever — especially on Apple ANE and the Intel N100 — at a
|
|
10519
|
+
* small-object accuracy cost. Mirrors the model's `inputSize` but lifted onto
|
|
10520
|
+
* the group so the selector can offer it as a variant axis.
|
|
10521
|
+
*/
|
|
10522
|
+
resolution: number().int().positive().optional()
|
|
10523
|
+
});
|
|
10524
|
+
var ModelProviderIdSchema = _enum([
|
|
10525
|
+
"camstack",
|
|
10526
|
+
"frigate",
|
|
10527
|
+
"scrypted",
|
|
10528
|
+
"custom"
|
|
10529
|
+
]);
|
|
10530
|
+
/**
|
|
10531
|
+
* The licence of a model, as a record rather than a bare SPDX string: a
|
|
10532
|
+
* permissive CODE licence routinely sits on top of restrictive WEIGHTS or
|
|
10533
|
+
* DATA, and the obligations (attribution, modification notices, source offers)
|
|
10534
|
+
* are per upstream. A persisted custom-model row written when this field was a
|
|
10535
|
+
* string still parses — the string is lifted into `{ weights }`.
|
|
10536
|
+
*
|
|
10537
|
+
* Every licence surface (the admin "Models and licenses" page,
|
|
10538
|
+
* `THIRD_PARTY_MODELS.md`, the Hugging Face model table) is GENERATED from
|
|
10539
|
+
* these records by `scripts/gen-model-licenses.ts` (D661).
|
|
10540
|
+
*/
|
|
10541
|
+
var ModelLicenseSchema = preprocess((value) => typeof value === "string" ? { weights: value } : value, object({
|
|
10542
|
+
/** SPDX id of the WEIGHTS' terms, a `LicenseRef-*` for non-SPDX terms, or `UNKNOWN`. */
|
|
10543
|
+
weights: string().min(1),
|
|
10544
|
+
/** SPDX id of the upstream code that defines / trained the network. */
|
|
10545
|
+
code: string().min(1).optional(),
|
|
10546
|
+
/** Training data whose terms add obligations (attribution, non-commercial). */
|
|
10547
|
+
data: object({
|
|
10548
|
+
name: string().min(1),
|
|
10549
|
+
terms: string().min(1),
|
|
10550
|
+
url: string().url().optional()
|
|
10551
|
+
}).optional(),
|
|
10552
|
+
/** The project the weights come from. Required on built-in entries (guard). */
|
|
10553
|
+
upstream: object({
|
|
10554
|
+
name: string().min(1),
|
|
10555
|
+
url: string().url()
|
|
10556
|
+
}).optional(),
|
|
10557
|
+
/** Where the licence text governing the weights is published. */
|
|
10558
|
+
url: string().url().optional(),
|
|
10559
|
+
/** Attribution the licence REQUIRES, verbatim. */
|
|
10560
|
+
attribution: string().min(1).optional(),
|
|
10561
|
+
/** What CamStack changed. Required when `hosting` is `camstack-hf` (guard). */
|
|
10562
|
+
modifications: string().min(1).optional(),
|
|
10563
|
+
/** Who serves the file a node downloads. */
|
|
10564
|
+
hosting: _enum([
|
|
10565
|
+
"camstack-hf",
|
|
10566
|
+
"third-party",
|
|
10567
|
+
"built-in"
|
|
10568
|
+
]).optional(),
|
|
10569
|
+
/** True when the weights or their data forbid commercial use. */
|
|
10570
|
+
nonCommercial: boolean().optional()
|
|
10571
|
+
}));
|
|
10572
|
+
var ModelCatalogEntrySchema = object({
|
|
10573
|
+
id: string(),
|
|
10574
|
+
name: string(),
|
|
10575
|
+
description: string(),
|
|
10576
|
+
formats: ModelFormatsSchema,
|
|
10577
|
+
inputSize: object({
|
|
10578
|
+
width: number(),
|
|
10579
|
+
height: number()
|
|
10580
|
+
}),
|
|
10581
|
+
/**
|
|
10582
|
+
* Channel count of the model input tensor. Omit ⇒ 3 (RGB), the default for
|
|
10583
|
+
* every detector / classifier / embedder. Set to 1 for a grayscale CTC text
|
|
10584
|
+
* recognizer (EasyOCR VGG plate-OCR: input `[N,1,H,W]`) so the preprocess
|
|
10585
|
+
* feeds a single-channel, EasyOCR-normalized tensor instead of the default
|
|
10586
|
+
* 3-channel RGB one. Threaded through `PoolModelConfig.inputChannels` to the
|
|
10587
|
+
* Python inference pool.
|
|
10588
|
+
*/
|
|
10589
|
+
inputChannels: number().int().positive().optional(),
|
|
10590
|
+
labels: array(LabelDefinitionSchema).readonly(),
|
|
10591
|
+
inputLayout: _enum(["nchw", "nhwc"]).optional(),
|
|
10592
|
+
/**
|
|
10593
|
+
* `'scrfd'` applies InsightFace's SCRFD input contract, `(x - 127.5) / 128`
|
|
10594
|
+
* (upstream `insightface/model_zoo/scrfd.py`), instead of the historical
|
|
10595
|
+
* plain `/255`. Measured against COCO GT: 188 → 196 faces found on the
|
|
10596
|
+
* `scrfd-2.5g` catalog entry (2026-09-26 model-replacement spike, §2.4-2).
|
|
10597
|
+
*/
|
|
10598
|
+
inputNormalization: _enum([
|
|
10599
|
+
"zero-one",
|
|
10600
|
+
"imagenet",
|
|
10601
|
+
"none",
|
|
10602
|
+
"scrfd"
|
|
10603
|
+
]).optional(),
|
|
10604
|
+
/**
|
|
10605
|
+
* The model already applies softmax IN-GRAPH — its raw output is a
|
|
10606
|
+
* probability distribution, not logits. When set, the `softmax`
|
|
10607
|
+
* postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
|
|
10608
|
+
* probability vector collapses it toward uniform (top-1 score craters far
|
|
10609
|
+
* below its true value, making every confidence gate meaningless). Absent ⇒
|
|
10610
|
+
* the output is raw logits and the postprocessor applies softmax (the normal
|
|
10611
|
+
* case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
|
|
10612
|
+
* TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
|
|
10613
|
+
*/
|
|
10614
|
+
outputProbabilities: boolean().optional(),
|
|
10615
|
+
preprocessMode: _enum(["letterbox", "resize"]).optional(),
|
|
10616
|
+
/**
|
|
10617
|
+
* Per-MODEL postprocessor override. Absent ⇒ the step's own
|
|
10618
|
+
* `StepDefinition.postprocessor` applies (the normal case — every model in a
|
|
10619
|
+
* step shares its decode). Set it when a step hosts models with DIFFERENT raw
|
|
10620
|
+
* output layouts under one slot: e.g. object-detection is `'yolo'` by default,
|
|
10621
|
+
* but a Coral SSD MobileNet build emits the `TFLite_Detection_PostProcess`
|
|
10622
|
+
* 4-tensor layout and needs `'ssd'`. Threaded into `PoolModelConfig.postprocessor`
|
|
10623
|
+
* by the engine factory (`modelEntry.postprocessor ?? def.postprocessor`).
|
|
10624
|
+
*/
|
|
10625
|
+
postprocessor: custom().optional(),
|
|
10626
|
+
/**
|
|
10627
|
+
* Per-MODEL default confidence floor. Absent ⇒ the step's
|
|
10628
|
+
* `StepDefinition.defaultConfidence` applies. A score is a property of one
|
|
10629
|
+
* model's output scale, not of the step slot it sits in: YuNet's
|
|
10630
|
+
* `sqrt(cls·obj)` is not SCRFD's score, and inheriting SCRFD's 0.5 filled the
|
|
10631
|
+
* face gallery with wheels and hands (D662, amends D645). An operator's
|
|
10632
|
+
* explicit value still wins over it. Resolved in ONE place —
|
|
10633
|
+
* `addon-pipeline/.../registry/effective-confidence.ts`.
|
|
10634
|
+
*/
|
|
10635
|
+
defaultConfidence: number().min(0).max(1).optional(),
|
|
10636
|
+
/**
|
|
10637
|
+
* When true, the executor produces a landmark-aligned crop (similarity warp
|
|
10638
|
+
* onto the canonical template) before this step runs, instead of a plain
|
|
10639
|
+
* axis-aligned bbox crop. Required for face-recognition embedders (ArcFace):
|
|
10640
|
+
* their embeddings are only discriminative on an aligned input. The face
|
|
10641
|
+
* detector that produced the parent detail must emit 5 landmarks.
|
|
10642
|
+
*/
|
|
10643
|
+
faceAlignment: boolean().optional(),
|
|
10644
|
+
/**
|
|
10645
|
+
* Auxiliary files required at runtime (labels JSON, charset dict, etc.).
|
|
10646
|
+
* Downloaded into the same modelsDir alongside the model file.
|
|
10647
|
+
*/
|
|
10648
|
+
extraFiles: array(ModelExtraFileSchema).readonly().optional(),
|
|
10649
|
+
/**
|
|
10650
|
+
* LEGACY entry — retained in the catalog so a persisted operator selection
|
|
10651
|
+
* still RESOLVES (and can be re-activated), but hidden from the selectable
|
|
10652
|
+
* model list and excluded from the auto format-default pick. Set on the
|
|
10653
|
+
* superseded / consolidated models (older lineages, redundant fp16 IRs) so
|
|
10654
|
+
* the active lineup stays the coherent curated ladder without deleting a
|
|
10655
|
+
* model anyone may still be pinned to. `resolveModelForFormat` keeps honoring
|
|
10656
|
+
* an explicit legacy id that has a build for the node's format.
|
|
10657
|
+
*/
|
|
10658
|
+
legacy: boolean().optional(),
|
|
10659
|
+
/**
|
|
10660
|
+
* Measured quality/latency metadata — populated from the benchmark addon on
|
|
10661
|
+
* the real node classes. Absent = not yet measured (most entries today; the
|
|
10662
|
+
* catalog historically carried only `sizeMB`, a poor cross-architecture
|
|
10663
|
+
* speed proxy). `p95LatencyMs` is keyed by node class (e.g. `n100`, `mac`).
|
|
10664
|
+
*/
|
|
10665
|
+
metrics: object({
|
|
10666
|
+
map50: number().optional(),
|
|
10667
|
+
p95LatencyMs: record(string(), number()).optional()
|
|
10668
|
+
}).optional(),
|
|
10669
|
+
/**
|
|
10670
|
+
* The model's licence record — see {@link ModelLicenseSchema}. Optional in
|
|
10671
|
+
* the schema (a custom model's author may state none); REQUIRED and complete
|
|
10672
|
+
* on every built-in catalog entry (`scripts/check-catalog-licence.ts`).
|
|
10673
|
+
*/
|
|
10674
|
+
license: ModelLicenseSchema.optional(),
|
|
10675
|
+
/**
|
|
10676
|
+
* Variant-selector grouping. The UI groups models by `family` + `tier` and
|
|
10677
|
+
* offers `precision` / `optimization` as variant axes WITHIN a tier — so all
|
|
10678
|
+
* of a family's sizes and quantizations collapse into one grouped picker
|
|
10679
|
+
* instead of a flat list of `yolo26s`, `yolo26s-int8`, … Absent ⇒ ungrouped
|
|
10680
|
+
* (legacy / custom models) — never shown in the grouped selector. The flat
|
|
10681
|
+
* `id` stays the source of truth for resolution/download/persistence; grouping
|
|
10682
|
+
* is a presentation overlay resolved back to an `id`.
|
|
10683
|
+
*/
|
|
10684
|
+
group: ModelVariantGroupSchema.optional(),
|
|
10685
|
+
/**
|
|
10686
|
+
* Catalog source for the pipeline stepper's provider-first picker. Absent on
|
|
10687
|
+
* built-in CamStack entries (treated as `camstack`) and on registry rows
|
|
10688
|
+
* persisted before this field existed (`inferModelProvider` fills those).
|
|
10689
|
+
*/
|
|
10690
|
+
provider: ModelProviderIdSchema.optional(),
|
|
10691
|
+
/**
|
|
10692
|
+
* Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
|
|
10693
|
+
* applies (Frigate / COCO public catalog). Set on a custom model whose raw
|
|
10694
|
+
* labels already ARE the CamStack macros (Scrypted identity map).
|
|
10695
|
+
*/
|
|
10696
|
+
classMap: DetectionCatalogClassMapSchema.optional(),
|
|
10697
|
+
/**
|
|
10698
|
+
* The model's CLIP feature-space contract (text encoder, dimension, vector
|
|
10699
|
+
* index, token window, search floor) — see `clip-model.ts`. Present ONLY on a
|
|
10700
|
+
* CLIP image encoder; its absence is what "not a CLIP model" means to the
|
|
10701
|
+
* embedding encoder and the semantic-search store (D649).
|
|
10702
|
+
*/
|
|
10703
|
+
clip: ClipModelMetaSchema.optional()
|
|
10704
|
+
});
|
|
10705
|
+
var ConvertTargetSchema = discriminatedUnion("format", [object({
|
|
10706
|
+
format: literal("openvino"),
|
|
10707
|
+
precisions: array(_enum(["fp16", "int8"])).min(1).readonly()
|
|
10708
|
+
}), object({ format: literal("coreml") })]);
|
|
10709
|
+
var ModelConvertMetadataSchema = object({
|
|
10710
|
+
id: string().regex(/^[a-zA-Z0-9._-]+$/),
|
|
10711
|
+
name: string(),
|
|
10712
|
+
labels: array(LabelDefinitionSchema).readonly(),
|
|
10713
|
+
inputSize: object({
|
|
10714
|
+
width: number(),
|
|
10715
|
+
height: number()
|
|
10716
|
+
}),
|
|
10717
|
+
inputLayout: _enum(["nchw", "nhwc"]).optional(),
|
|
10718
|
+
inputNormalization: _enum([
|
|
10719
|
+
"zero-one",
|
|
10720
|
+
"imagenet",
|
|
10721
|
+
"none",
|
|
10722
|
+
"scrfd"
|
|
10723
|
+
]).optional(),
|
|
10724
|
+
preprocessMode: _enum(["letterbox", "resize"]).optional(),
|
|
10725
|
+
outputFormat: _enum([
|
|
10726
|
+
"yolo",
|
|
10727
|
+
"ssd",
|
|
10728
|
+
"embedding",
|
|
10729
|
+
"classification",
|
|
10730
|
+
"ocr",
|
|
10731
|
+
"segmentation"
|
|
10732
|
+
]),
|
|
10733
|
+
faceAlignment: boolean().optional(),
|
|
10734
|
+
classMap: DetectionCatalogClassMapSchema.optional()
|
|
10735
|
+
});
|
|
10736
|
+
var ConvertResultSchema = object({
|
|
10737
|
+
entry: ModelCatalogEntrySchema,
|
|
10738
|
+
artifacts: array(object({
|
|
10739
|
+
format: _enum(MODEL_FORMATS),
|
|
10740
|
+
precision: _enum(["fp16", "int8"]).optional(),
|
|
10741
|
+
sizeMB: number(),
|
|
10742
|
+
validated: boolean(),
|
|
10743
|
+
files: array(string()).readonly()
|
|
10744
|
+
})).readonly()
|
|
10745
|
+
});
|
|
10527
10746
|
var ExpressionBindingSourceSchema = union([
|
|
10528
10747
|
object({
|
|
10529
10748
|
kind: literal("field").optional(),
|
|
@@ -10698,7 +10917,7 @@ var AutomationActionSchema = discriminatedUnion("kind", [
|
|
|
10698
10917
|
code: string().min(1).max(2e4)
|
|
10699
10918
|
})
|
|
10700
10919
|
]);
|
|
10701
|
-
object({
|
|
10920
|
+
var AutomationRecipeSchema = object({
|
|
10702
10921
|
triggers: array(AutomationTriggerSchema),
|
|
10703
10922
|
conditions: AutomationConditionSchema.optional(),
|
|
10704
10923
|
actions: array(AutomationActionSchema)
|
|
@@ -11185,12 +11404,32 @@ var AudioClassificationLabelSchema = object({
|
|
|
11185
11404
|
originalClass: string().optional(),
|
|
11186
11405
|
score: number()
|
|
11187
11406
|
});
|
|
11407
|
+
/**
|
|
11408
|
+
* Did the classifier RUN on this window? (D660)
|
|
11409
|
+
*
|
|
11410
|
+
* `classified` with no labels means the model heard nothing above the
|
|
11411
|
+
* camera's floor. `not-classified` means nobody listened — and until D660 the
|
|
11412
|
+
* two were the same shape (`classification` absent), so a window the global
|
|
11413
|
+
* busy gate threw away read downstream exactly like a quiet one (D393).
|
|
11414
|
+
*/
|
|
11415
|
+
var AudioClassifyOutcomeSchema = discriminatedUnion("state", [object({ state: literal("classified") }), object({
|
|
11416
|
+
state: literal("not-classified"),
|
|
11417
|
+
reason: _enum([
|
|
11418
|
+
"superseded",
|
|
11419
|
+
"error",
|
|
11420
|
+
"timeout",
|
|
11421
|
+
"disposed"
|
|
11422
|
+
])
|
|
11423
|
+
})]);
|
|
11188
11424
|
var AudioAnalysisResultSchema = object({
|
|
11189
11425
|
level: AudioLevelSchema,
|
|
11426
|
+
/** Labels at or above the camera's floor. Absent = none — read `classifyOutcome` to know whether the model ran at all. */
|
|
11190
11427
|
classification: object({
|
|
11191
11428
|
labels: array(AudioClassificationLabelSchema).readonly(),
|
|
11192
11429
|
inferenceMs: number()
|
|
11193
11430
|
}).optional(),
|
|
11431
|
+
/** Whether the classifier ran on this window (D660). */
|
|
11432
|
+
classifyOutcome: AudioClassifyOutcomeSchema,
|
|
11194
11433
|
timestamp: number()
|
|
11195
11434
|
});
|
|
11196
11435
|
var AudioAnalysisSettingsSchema = object({
|
|
@@ -12899,15 +13138,157 @@ var cameraStreamsCapability = {
|
|
|
12899
13138
|
*/
|
|
12900
13139
|
durability: "session"
|
|
12901
13140
|
};
|
|
13141
|
+
/** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
|
|
13142
|
+
var CompositionProblemSchema = object({
|
|
13143
|
+
code: _enum([
|
|
13144
|
+
"reserved-variant",
|
|
13145
|
+
"unknown-capability",
|
|
13146
|
+
"not-composable",
|
|
13147
|
+
"wrong-device-type",
|
|
13148
|
+
"has-commands",
|
|
13149
|
+
"unknown-field",
|
|
13150
|
+
"nested-field",
|
|
13151
|
+
"missing-field",
|
|
13152
|
+
"kind-mismatch",
|
|
13153
|
+
"may-be-null",
|
|
13154
|
+
"source-missing",
|
|
13155
|
+
"source-unreadable",
|
|
13156
|
+
"source-capability-missing",
|
|
13157
|
+
"source-field-unknown",
|
|
13158
|
+
"expression-invalid",
|
|
13159
|
+
"cycle",
|
|
13160
|
+
"duplicate-target",
|
|
13161
|
+
"not-item-array",
|
|
13162
|
+
"item-field-reserved",
|
|
13163
|
+
"target-missing",
|
|
13164
|
+
"target-unreadable",
|
|
13165
|
+
"not-replaceable"
|
|
13166
|
+
]),
|
|
13167
|
+
path: string(),
|
|
13168
|
+
message: string()
|
|
13169
|
+
});
|
|
13170
|
+
var CompositionFieldKindSchema = _enum([
|
|
13171
|
+
"string",
|
|
13172
|
+
"number",
|
|
13173
|
+
"boolean",
|
|
13174
|
+
"enum"
|
|
13175
|
+
]);
|
|
13176
|
+
/**
|
|
13177
|
+
* How a field says "unavailable" (D393, D663): `null` when its schema allows it;
|
|
13178
|
+
* `unknown` — the enum's own word — for an ADDED field that has one; `release`
|
|
13179
|
+
* for a REPLACED field of an existing device, whose claim is released so the
|
|
13180
|
+
* native provider's reading shows again; else the (composed) device goes offline.
|
|
13181
|
+
*/
|
|
13182
|
+
var CompositionUnavailableModeSchema = _enum([
|
|
13183
|
+
"null",
|
|
13184
|
+
"offline",
|
|
13185
|
+
"unknown",
|
|
13186
|
+
"release"
|
|
13187
|
+
]);
|
|
13188
|
+
/**
|
|
13189
|
+
* `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
|
|
13190
|
+
* field of its cap changes value · `constant` reads nothing, computed once.
|
|
13191
|
+
*/
|
|
13192
|
+
var CompositionFieldRoleSchema = _enum([
|
|
13193
|
+
"derived",
|
|
13194
|
+
"clock",
|
|
13195
|
+
"constant"
|
|
13196
|
+
]);
|
|
13197
|
+
/** The item a planned field belongs to; its `path` is then the dotted path INSIDE the item. */
|
|
13198
|
+
var CompositionFieldItemSchema = object({
|
|
13199
|
+
arrayPath: string(),
|
|
13200
|
+
key: string(),
|
|
13201
|
+
label: string()
|
|
13202
|
+
});
|
|
13203
|
+
var CompositionFieldPlanSchema = object({
|
|
13204
|
+
cap: string(),
|
|
13205
|
+
/** Top-level field, or — when `item` is set — the dotted path inside that item. */
|
|
13206
|
+
path: string(),
|
|
13207
|
+
item: CompositionFieldItemSchema.optional(),
|
|
13208
|
+
/** Index into `composition.features` this field's capability came from —
|
|
13209
|
+
* lets a source/cycle problem name its path as `features[N].fields.<path>`
|
|
13210
|
+
* (the same convention `planComposition`'s own problems already use). */
|
|
13211
|
+
featureIndex: number().int().nonnegative(),
|
|
13212
|
+
kind: CompositionFieldKindSchema,
|
|
13213
|
+
nullable: boolean(),
|
|
13214
|
+
required: boolean(),
|
|
13215
|
+
unavailable: CompositionUnavailableModeSchema,
|
|
13216
|
+
role: CompositionFieldRoleSchema,
|
|
13217
|
+
source: CompositionFieldSourceSchema,
|
|
13218
|
+
reads: array(CompositionFieldReadSchema)
|
|
13219
|
+
});
|
|
13220
|
+
/**
|
|
13221
|
+
* What a feature does to its target (D663): `new` builds a capability on a new
|
|
13222
|
+
* device; on an existing device it `add`s a capability the device lacks, or
|
|
13223
|
+
* `replace`s named fields of one it has natively.
|
|
13224
|
+
*/
|
|
13225
|
+
var CompositionFeatureModeSchema = _enum([
|
|
13226
|
+
"new",
|
|
13227
|
+
"add",
|
|
13228
|
+
"replace"
|
|
13229
|
+
]);
|
|
13230
|
+
var CompositionFeaturePlanSchema = object({
|
|
13231
|
+
featureIndex: number().int().nonnegative(),
|
|
13232
|
+
cap: string(),
|
|
13233
|
+
mode: CompositionFeatureModeSchema
|
|
13234
|
+
});
|
|
13235
|
+
var CompositionValidationSchema = object({
|
|
13236
|
+
ok: boolean(),
|
|
13237
|
+
problems: array(CompositionProblemSchema),
|
|
13238
|
+
fields: array(CompositionFieldPlanSchema),
|
|
13239
|
+
features: array(CompositionFeaturePlanSchema)
|
|
13240
|
+
});
|
|
13241
|
+
/**
|
|
13242
|
+
* Mirrors `CoreBlockStatus`, pinned by `core-blocks-source.spec.ts`. `degraded`:
|
|
13243
|
+
* running, but a replaced field is released to the native provider while its
|
|
13244
|
+
* source is unavailable (ruling S1b, D663) — the reason names each such field.
|
|
13245
|
+
*/
|
|
13246
|
+
var CompositionRunStatusSchema = _enum([
|
|
13247
|
+
"stopped",
|
|
13248
|
+
"starting",
|
|
13249
|
+
"running",
|
|
13250
|
+
"degraded",
|
|
13251
|
+
"failed"
|
|
13252
|
+
]);
|
|
13253
|
+
var CompositionFieldStateKindSchema = _enum([
|
|
13254
|
+
"fresh",
|
|
13255
|
+
"stale",
|
|
13256
|
+
"unavailable"
|
|
13257
|
+
]);
|
|
13258
|
+
var CompositionFieldStateSchema = object({
|
|
13259
|
+
cap: string(),
|
|
13260
|
+
path: string(),
|
|
13261
|
+
/** Same as the plan's: two items' `status` are told apart by it. */
|
|
13262
|
+
item: CompositionFieldItemSchema.optional(),
|
|
13263
|
+
state: CompositionFieldStateKindSchema,
|
|
13264
|
+
/** `null` whenever `state === 'unavailable'`; never a plausible stand-in (D393). */
|
|
13265
|
+
value: union([
|
|
13266
|
+
string(),
|
|
13267
|
+
number(),
|
|
13268
|
+
boolean(),
|
|
13269
|
+
_null()
|
|
13270
|
+
]),
|
|
13271
|
+
reason: string().optional()
|
|
13272
|
+
});
|
|
13273
|
+
var CompositionBlockStateSchema = object({
|
|
13274
|
+
blockId: string(),
|
|
13275
|
+
deviceId: number().nullable(),
|
|
13276
|
+
status: CompositionRunStatusSchema,
|
|
13277
|
+
reason: string().optional(),
|
|
13278
|
+
fields: array(CompositionFieldStateSchema)
|
|
13279
|
+
});
|
|
12902
13280
|
/** Where a block runs. The operator chooses — a block driving a device on an
|
|
12903
13281
|
* agent is the reason placement is not fixed to the hub. */
|
|
12904
13282
|
var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
|
|
12905
13283
|
/** What a block's process is doing. Mirrors the addon runner's own lifecycle so
|
|
12906
|
-
* a failing block reads the same way a failing addon does.
|
|
13284
|
+
* a failing block reads the same way a failing addon does. `degraded` is a
|
|
13285
|
+
* composition on an existing device with a field handed back to the native
|
|
13286
|
+
* provider while its source is unavailable (S1b, D663); the error names it. */
|
|
12907
13287
|
var CoreBlockStatusSchema = _enum([
|
|
12908
13288
|
"stopped",
|
|
12909
13289
|
"starting",
|
|
12910
13290
|
"running",
|
|
13291
|
+
"degraded",
|
|
12911
13292
|
"failed"
|
|
12912
13293
|
]);
|
|
12913
13294
|
/** Client-authored fields. */
|
|
@@ -12925,6 +13306,37 @@ var CoreBlockInputSchema = object({
|
|
|
12925
13306
|
*/
|
|
12926
13307
|
integrationId: string().optional()
|
|
12927
13308
|
});
|
|
13309
|
+
/**
|
|
13310
|
+
* What a block IS (D659). Absent means `code` — every row stored before this
|
|
13311
|
+
* union keeps working unchanged.
|
|
13312
|
+
* - `code`: the TypeScript in `code`, compiled and run in its own runner;
|
|
13313
|
+
* - `recipe`: RESERVED (automations), stored shape only; refused at run by name;
|
|
13314
|
+
* - `composition`: declarative, run by the shared `composer`; `code` is `''`.
|
|
13315
|
+
*/
|
|
13316
|
+
var CoreBlockSourceSchema = discriminatedUnion("kind", [
|
|
13317
|
+
object({ kind: literal("code") }),
|
|
13318
|
+
object({
|
|
13319
|
+
kind: literal("recipe"),
|
|
13320
|
+
recipe: AutomationRecipeSchema
|
|
13321
|
+
}),
|
|
13322
|
+
object({
|
|
13323
|
+
kind: literal("composition"),
|
|
13324
|
+
composition: CompositionSchema
|
|
13325
|
+
})
|
|
13326
|
+
]);
|
|
13327
|
+
/**
|
|
13328
|
+
* The client-authored half of a composition block. No `code`, no placement (hub-only; Phase 7).
|
|
13329
|
+
*
|
|
13330
|
+
* `name`: REQUIRED for a `new` target — the block's name IS the device's name
|
|
13331
|
+
* (D659). ABSENT for an `existing` target — a customization has no name of its
|
|
13332
|
+
* own; the server derives one (`customizationBlockName`, D663) and refuses a
|
|
13333
|
+
* client-sent name by name.
|
|
13334
|
+
*/
|
|
13335
|
+
var CompositionBlockInputSchema = object({
|
|
13336
|
+
name: string().min(1).max(120).optional(),
|
|
13337
|
+
enabled: boolean(),
|
|
13338
|
+
composition: CompositionSchema
|
|
13339
|
+
});
|
|
12928
13340
|
/** A stored block. */
|
|
12929
13341
|
var CoreBlockSchema = CoreBlockInputSchema.extend({
|
|
12930
13342
|
id: string(),
|
|
@@ -12940,8 +13352,43 @@ var CoreBlockSchema = CoreBlockInputSchema.extend({
|
|
|
12940
13352
|
*/
|
|
12941
13353
|
lastError: string().optional(),
|
|
12942
13354
|
/** Ms epoch of the last state change. */
|
|
12943
|
-
lastChangedAt: number()
|
|
13355
|
+
lastChangedAt: number(),
|
|
13356
|
+
/** What the block is. Absent means `code` (D659). */
|
|
13357
|
+
source: CoreBlockSourceSchema.optional()
|
|
13358
|
+
});
|
|
13359
|
+
/** A stored composition block and what the composer says about it. */
|
|
13360
|
+
var CompositionViewSchema = object({
|
|
13361
|
+
block: CoreBlockSchema,
|
|
13362
|
+
state: discriminatedUnion("kind", [object({
|
|
13363
|
+
kind: literal("answered"),
|
|
13364
|
+
state: CompositionBlockStateSchema.nullable()
|
|
13365
|
+
}), object({
|
|
13366
|
+
kind: literal("unreachable"),
|
|
13367
|
+
error: string()
|
|
13368
|
+
})])
|
|
12944
13369
|
});
|
|
13370
|
+
/**
|
|
13371
|
+
* A read of the stored compositions. `not-loaded` while the `core_blocks`
|
|
13372
|
+
* table has not been READ: an empty answer then would make "not yet known"
|
|
13373
|
+
* look like "none", and invite the operator to recreate what exists (D315).
|
|
13374
|
+
*/
|
|
13375
|
+
var CompositionsNotLoadedSchema = object({
|
|
13376
|
+
state: literal("not-loaded"),
|
|
13377
|
+
reason: string()
|
|
13378
|
+
});
|
|
13379
|
+
var CompositionListReadSchema = discriminatedUnion("state", [object({
|
|
13380
|
+
state: literal("loaded"),
|
|
13381
|
+
views: array(CompositionViewSchema)
|
|
13382
|
+
}), CompositionsNotLoadedSchema]);
|
|
13383
|
+
var CompositionGetReadSchema = discriminatedUnion("state", [object({
|
|
13384
|
+
state: literal("loaded"),
|
|
13385
|
+
view: CompositionViewSchema.nullable()
|
|
13386
|
+
}), CompositionsNotLoadedSchema]);
|
|
13387
|
+
/** The customization of one existing device, found by its TARGET, or `not-loaded` (D315). */
|
|
13388
|
+
var CompositionCustomizationReadSchema = discriminatedUnion("state", [object({
|
|
13389
|
+
state: literal("loaded"),
|
|
13390
|
+
view: CompositionViewSchema.nullable()
|
|
13391
|
+
}), CompositionsNotLoadedSchema]);
|
|
12945
13392
|
/** What a compile attempt produced. */
|
|
12946
13393
|
var CoreBlockCompileResultSchema = object({
|
|
12947
13394
|
ok: boolean(),
|
|
@@ -12979,7 +13426,75 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
12979
13426
|
}), method(object({}), object({ libs: array(object({
|
|
12980
13427
|
filePath: string(),
|
|
12981
13428
|
content: string()
|
|
12982
|
-
})) }), { auth: "admin" })
|
|
13429
|
+
})) }), { auth: "admin" }), method(object({ block: CompositionBlockInputSchema }), object({ block: CoreBlockSchema }), {
|
|
13430
|
+
kind: "mutation",
|
|
13431
|
+
auth: "admin",
|
|
13432
|
+
caller: "required"
|
|
13433
|
+
}), method(object({
|
|
13434
|
+
blockId: string(),
|
|
13435
|
+
expectedUpdatedAt: number(),
|
|
13436
|
+
block: CompositionBlockInputSchema.partial()
|
|
13437
|
+
}), object({ block: CoreBlockSchema }), {
|
|
13438
|
+
kind: "mutation",
|
|
13439
|
+
auth: "admin",
|
|
13440
|
+
caller: "required"
|
|
13441
|
+
}), method(object({ blockId: string() }), CompositionGetReadSchema, { auth: "admin" }), method(object({}), CompositionListReadSchema, { auth: "admin" }), method(object({
|
|
13442
|
+
addonId: string().min(1),
|
|
13443
|
+
stableId: string().min(1)
|
|
13444
|
+
}), CompositionCustomizationReadSchema, { auth: "admin" }), method(object({
|
|
13445
|
+
composition: CompositionSchema,
|
|
13446
|
+
blockId: string().optional()
|
|
13447
|
+
}), CompositionValidationSchema, {
|
|
13448
|
+
kind: "mutation",
|
|
13449
|
+
auth: "admin",
|
|
13450
|
+
access: "view"
|
|
13451
|
+
});
|
|
13452
|
+
var ComposerBlockSchema = object({
|
|
13453
|
+
id: string().min(1),
|
|
13454
|
+
name: string().min(1).max(120),
|
|
13455
|
+
enabled: boolean(),
|
|
13456
|
+
updatedAt: number(),
|
|
13457
|
+
composition: CompositionSchema
|
|
13458
|
+
});
|
|
13459
|
+
/**
|
|
13460
|
+
* A stored composition row that no longer PARSES — a schema a release
|
|
13461
|
+
* tightened, a row written by a newer build. Sent by id so the composer keeps
|
|
13462
|
+
* its device as a placeholder and reports the block `failed` naming the parse
|
|
13463
|
+
* error. Leaving it out would read, twice, exactly like a delete (D49).
|
|
13464
|
+
*/
|
|
13465
|
+
var ComposerUnreadableBlockSchema = object({
|
|
13466
|
+
id: string().min(1),
|
|
13467
|
+
/** The row's stored name column; null when even that is unreadable (the device keeps the name it has). */
|
|
13468
|
+
name: string().nullable(),
|
|
13469
|
+
/** The parse error, in the operator's words. */
|
|
13470
|
+
error: string()
|
|
13471
|
+
});
|
|
13472
|
+
method(object({
|
|
13473
|
+
/** The shared Blocks integration; null until `core-blocks` has reconciled it. */
|
|
13474
|
+
integrationId: string().nullable(),
|
|
13475
|
+
/** False when the `core_blocks` read failed: then nothing may be withdrawn (D49). */
|
|
13476
|
+
storeLoaded: boolean(),
|
|
13477
|
+
blocks: array(ComposerBlockSchema),
|
|
13478
|
+
/** Operator restarts: rebuild these runtimes and re-resolve their sources. Stores nothing (D62). */
|
|
13479
|
+
restartBlockIds: array(string()),
|
|
13480
|
+
/** Composition rows that exist but do not parse: held, never withdrawn, reported failed. */
|
|
13481
|
+
unreadableBlocks: array(ComposerUnreadableBlockSchema)
|
|
13482
|
+
}), object({
|
|
13483
|
+
blocks: array(object({
|
|
13484
|
+
blockId: string(),
|
|
13485
|
+
status: CompositionRunStatusSchema,
|
|
13486
|
+
reason: string().optional()
|
|
13487
|
+
})),
|
|
13488
|
+
/**
|
|
13489
|
+
* False when the composer declared and withdrew NOTHING (store not loaded,
|
|
13490
|
+
* integration unknown, its own rows unread or read empty once). An empty
|
|
13491
|
+
* apply the caller owes is paid off only by one the composer acted on (D49).
|
|
13492
|
+
*/
|
|
13493
|
+
acted: boolean()
|
|
13494
|
+
}), {
|
|
13495
|
+
kind: "mutation",
|
|
13496
|
+
auth: "admin"
|
|
13497
|
+
}), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
|
|
12983
13498
|
/**
|
|
12984
13499
|
* `custom-model-registry` — collection cap exposing operator-registered
|
|
12985
13500
|
* custom detection models. Each provider (today: `addon-model-studio`)
|
|
@@ -14700,8 +15215,22 @@ var LinkedDeviceSchema = object({
|
|
|
14700
15215
|
* The `linked-devices` extension slot's editable value (D648): the operator's
|
|
14701
15216
|
* pick list, exactly what `_linkedDeviceIds` persists. Tree children are NOT
|
|
14702
15217
|
* in it — they are always linked and are not a pick.
|
|
14703
|
-
|
|
14704
|
-
|
|
15218
|
+
*
|
|
15219
|
+
* `trackedDeviceIds` (D655) is the tracked-event opt-in, exactly what
|
|
15220
|
+
* `_linkedDeviceTrackedIds` persists: which linked devices materialize a
|
|
15221
|
+
* synthetic tracked event on the camera. A read always carries it AS STORED
|
|
15222
|
+
* (an absent key reads as `[]`, which is what the resolver makes of it —
|
|
15223
|
+
* NONE). On a write it is optional: absent leaves the opt-in untouched, so a
|
|
15224
|
+
* card built before D655 cannot erase it by saving the pick list. A present
|
|
15225
|
+
* list REPLACES the opt-in; each id must be a pick, a device the camera links
|
|
15226
|
+
* by structure (the row's `impliedSources`: tree children, a picked
|
|
15227
|
+
* container's child) or already stored — so what was read can always be
|
|
15228
|
+
* written back unchanged, and a NEW opt-in outside the linked set is refused.
|
|
15229
|
+
*/
|
|
15230
|
+
var LinkedDevicesSlotValueSchema = object({
|
|
15231
|
+
deviceIds: array(number().int().positive()),
|
|
15232
|
+
trackedDeviceIds: array(number().int().positive()).optional()
|
|
15233
|
+
});
|
|
14705
15234
|
DeviceType.Camera;
|
|
14706
15235
|
/** One camera's resolved linked set, tagged with the camera it belongs to.
|
|
14707
15236
|
* The batch answer needs the tag; the single-device answer already has it
|
|
@@ -15205,6 +15734,52 @@ method(object({
|
|
|
15205
15734
|
deviceIds: array(number()).readonly(),
|
|
15206
15735
|
caps: array(string()).readonly().optional()
|
|
15207
15736
|
}), record(string(), record(string(), unknown().nullable())));
|
|
15737
|
+
/**
|
|
15738
|
+
* Per-field ownership (D663). A composition may CLAIM fields of a native
|
|
15739
|
+
* cap's runtime-state slice on an existing device: `replace` overlays the
|
|
15740
|
+
* claimed fields on the native slice, `add` builds a slice the device does
|
|
15741
|
+
* not natively have. Declared here — once — so the hub mirror (Task 5) and
|
|
15742
|
+
* the claim methods (Task 6) share one union.
|
|
15743
|
+
*/
|
|
15744
|
+
var FieldClaimModeSchema = _enum(["replace", "add"]);
|
|
15745
|
+
/** What a claim, patch or release answers. A refusal always names its reason. */
|
|
15746
|
+
var ClaimOutcomeSchema = discriminatedUnion("ok", [object({
|
|
15747
|
+
ok: literal(true),
|
|
15748
|
+
changed: boolean()
|
|
15749
|
+
}), object({
|
|
15750
|
+
ok: literal(false),
|
|
15751
|
+
code: _enum([
|
|
15752
|
+
"owned-by-other",
|
|
15753
|
+
"not-claimed",
|
|
15754
|
+
"field-not-claimed",
|
|
15755
|
+
"invalid-slice",
|
|
15756
|
+
"unknown-device",
|
|
15757
|
+
"row-unreadable",
|
|
15758
|
+
"migration-in-flight"
|
|
15759
|
+
]),
|
|
15760
|
+
message: string()
|
|
15761
|
+
})]);
|
|
15762
|
+
/** One claim as `listClaims` reports it. `fields` is the PLANNED set, explicit (ruling S5). */
|
|
15763
|
+
var FieldClaimSchema = object({
|
|
15764
|
+
deviceId: number(),
|
|
15765
|
+
capName: string(),
|
|
15766
|
+
owner: string().min(1),
|
|
15767
|
+
mode: FieldClaimModeSchema,
|
|
15768
|
+
fields: array(string().min(1)).min(1)
|
|
15769
|
+
});
|
|
15770
|
+
/**
|
|
15771
|
+
* What `listClaims` answers. The claims INDEX is a listing projection loaded
|
|
15772
|
+
* from the settings store at boot; until a load has succeeded it is
|
|
15773
|
+
* `not-loaded`, never `[]` — an unanswered question must not look like an
|
|
15774
|
+
* empty answer (D315).
|
|
15775
|
+
*/
|
|
15776
|
+
var ClaimsListingSchema = discriminatedUnion("state", [object({
|
|
15777
|
+
state: literal("loaded"),
|
|
15778
|
+
claims: array(FieldClaimSchema)
|
|
15779
|
+
}), object({
|
|
15780
|
+
state: literal("not-loaded"),
|
|
15781
|
+
reason: string()
|
|
15782
|
+
})]);
|
|
15208
15783
|
method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
|
|
15209
15784
|
deviceId: number(),
|
|
15210
15785
|
capName: string()
|
|
@@ -15212,7 +15787,35 @@ method(object({ deviceId: number() }), record(string(), record(string(), unknown
|
|
|
15212
15787
|
deviceId: number(),
|
|
15213
15788
|
capName: string(),
|
|
15214
15789
|
slice: record(string(), unknown())
|
|
15215
|
-
}), _void(), { kind: "mutation" }), object({
|
|
15790
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
15791
|
+
deviceId: number(),
|
|
15792
|
+
capName: string(),
|
|
15793
|
+
owner: string().min(1),
|
|
15794
|
+
mode: FieldClaimModeSchema,
|
|
15795
|
+
fields: array(string().min(1)).min(1),
|
|
15796
|
+
values: record(string(), unknown())
|
|
15797
|
+
}), ClaimOutcomeSchema, {
|
|
15798
|
+
kind: "mutation",
|
|
15799
|
+
auth: "admin"
|
|
15800
|
+
}), method(object({
|
|
15801
|
+
deviceId: number(),
|
|
15802
|
+
capName: string(),
|
|
15803
|
+
owner: string().min(1),
|
|
15804
|
+
values: record(string(), unknown())
|
|
15805
|
+
}), ClaimOutcomeSchema, {
|
|
15806
|
+
kind: "mutation",
|
|
15807
|
+
auth: "admin"
|
|
15808
|
+
}), method(object({
|
|
15809
|
+
deviceId: number(),
|
|
15810
|
+
capName: string(),
|
|
15811
|
+
owner: string().min(1)
|
|
15812
|
+
}), ClaimOutcomeSchema, {
|
|
15813
|
+
kind: "mutation",
|
|
15814
|
+
auth: "admin"
|
|
15815
|
+
}), method(object({ ownerPrefix: string().optional() }), ClaimsListingSchema, { auth: "admin" }), method(object({
|
|
15816
|
+
deviceId: number(),
|
|
15817
|
+
capName: string()
|
|
15818
|
+
}), record(string(), unknown()).nullable(), { auth: "admin" }), object({
|
|
15216
15819
|
deviceId: number(),
|
|
15217
15820
|
capName: string(),
|
|
15218
15821
|
slice: record(string(), unknown())
|
|
@@ -19184,8 +19787,8 @@ var NcTestResultSchema = object({
|
|
|
19184
19787
|
className: string().optional(),
|
|
19185
19788
|
label: string().optional()
|
|
19186
19789
|
});
|
|
19187
|
-
/** The rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts` is
|
|
19188
|
-
*
|
|
19790
|
+
/** The rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts` is
|
|
19791
|
+
* `z.infer` of this schema — one shape, no twin. `mixed` is DERIVED
|
|
19189
19792
|
* (a stored system rule whose kinds span families, D637) and never authored. */
|
|
19190
19793
|
var NcRuleKindSchema = _enum([
|
|
19191
19794
|
"detection",
|
|
@@ -21100,6 +21703,13 @@ var AudioEventSchema = object({
|
|
|
21100
21703
|
originalClass: string().optional(),
|
|
21101
21704
|
score: number()
|
|
21102
21705
|
}).optional(),
|
|
21706
|
+
/**
|
|
21707
|
+
* Whether the classifier RAN on the window behind this row (D660). Absent
|
|
21708
|
+
* on rows written before D660 — unknown, NOT classified: until then a
|
|
21709
|
+
* window the analyzer's busy gate threw away was stored exactly like one
|
|
21710
|
+
* that heard nothing above the floor.
|
|
21711
|
+
*/
|
|
21712
|
+
classifyOutcome: AudioClassifyOutcomeSchema.optional(),
|
|
21103
21713
|
/** Populated by B5 (recording playback URL for this event). */
|
|
21104
21714
|
mediaUrl: string().optional()
|
|
21105
21715
|
});
|
|
@@ -21812,7 +22422,15 @@ var RebuildObjectEmbeddingsInput = object({
|
|
|
21812
22422
|
* force is logged at start and finish so a deliberately slow pass reads
|
|
21813
22423
|
* differently from a stalled one.
|
|
21814
22424
|
*/
|
|
21815
|
-
pacingMs: number().int().nonnegative().optional()
|
|
22425
|
+
pacingMs: number().int().nonnegative().optional(),
|
|
22426
|
+
/**
|
|
22427
|
+
* Re-embed ONLY the tracks whose vector is ALREADY in the pinned model's
|
|
22428
|
+
* space (D664) — the in-space skip inverted. It exists to redo vectors
|
|
22429
|
+
* written from the wrong box, which a normal pass would skip as done
|
|
22430
|
+
* forever. Requires `maxTracks`: a forced pass without a bound is refused
|
|
22431
|
+
* (`force-requires-max-tracks`).
|
|
22432
|
+
*/
|
|
22433
|
+
force: boolean().optional()
|
|
21816
22434
|
});
|
|
21817
22435
|
/**
|
|
21818
22436
|
* Result of emptying the CLIP index.
|
|
@@ -21848,6 +22466,7 @@ var WipeObjectEmbeddingsResultSchema = object({
|
|
|
21848
22466
|
* - `reads-disagree` — the two reads named different choices.
|
|
21849
22467
|
* - `not-a-clip-model` — the CONFIRMED id has no CLIP metadata, so there is no
|
|
21850
22468
|
* index to fill and no dimension to check against (D649).
|
|
22469
|
+
* - `force-requires-max-tracks` — `force: true` with no `maxTracks` (D664).
|
|
21851
22470
|
*/
|
|
21852
22471
|
var RebuildObjectEmbeddingsRefusalSchema = _enum([
|
|
21853
22472
|
"stores-unavailable",
|
|
@@ -21855,7 +22474,8 @@ var RebuildObjectEmbeddingsRefusalSchema = _enum([
|
|
|
21855
22474
|
"method-unavailable",
|
|
21856
22475
|
"read-failed",
|
|
21857
22476
|
"reads-disagree",
|
|
21858
|
-
"not-a-clip-model"
|
|
22477
|
+
"not-a-clip-model",
|
|
22478
|
+
"force-requires-max-tracks"
|
|
21859
22479
|
]);
|
|
21860
22480
|
/**
|
|
21861
22481
|
* Acknowledgement that a rebuild STARTED.
|
|
@@ -21880,11 +22500,32 @@ var RebuildStatusSchema = object({
|
|
|
21880
22500
|
running: boolean(),
|
|
21881
22501
|
scanned: number(),
|
|
21882
22502
|
rebuilt: number(),
|
|
21883
|
-
/** Tracks whose key frame is gone — nothing to re-embed from.
|
|
22503
|
+
/** Tracks whose key frame is gone — nothing to re-embed from. Pixels ONLY:
|
|
22504
|
+
* `missingEvent` is its own field since D664, never summed in. */
|
|
21884
22505
|
missingKeyFrame: number(),
|
|
21885
|
-
/** Tracks with no
|
|
22506
|
+
/** Tracks with no object event at all (D664). */
|
|
22507
|
+
missingEvent: number(),
|
|
22508
|
+
/** Tracks with no box measured on the pixels to cut — see `boxSource.refused`. */
|
|
21886
22509
|
missingBbox: number(),
|
|
21887
22510
|
/**
|
|
22511
|
+
* Which rule placed each judged track's box (D664): the key frame row's own
|
|
22512
|
+
* `subjectBox`, `track.positions` at exactly its instant (older key frames),
|
|
22513
|
+
* the event box (a key frame recovered from the recording, at the event's
|
|
22514
|
+
* instant), or `refused` by name — the reasons are in the tagged log lines.
|
|
22515
|
+
*/
|
|
22516
|
+
boxSource: object({
|
|
22517
|
+
subjectBox: number(),
|
|
22518
|
+
positions: number(),
|
|
22519
|
+
event: number(),
|
|
22520
|
+
refused: number()
|
|
22521
|
+
}),
|
|
22522
|
+
/** Tracks skipped because their vector is already in the pinned space. */
|
|
22523
|
+
alreadyInSpace: number(),
|
|
22524
|
+
/** Whether the current / last pass was a `force` pass; `null` before any (D664). */
|
|
22525
|
+
force: boolean().nullable(),
|
|
22526
|
+
/** In-space tracks a `force` pass selected for re-embedding. */
|
|
22527
|
+
forcedInSpace: number(),
|
|
22528
|
+
/**
|
|
21888
22529
|
* Tracks an executing node REFUSED rather than broke on — an unreadable key
|
|
21889
22530
|
* frame, a step that threw. Separate from `failed` because the remedy is
|
|
21890
22531
|
* different, and because a whole camera silently contributing zero vectors
|
|
@@ -22389,7 +23030,8 @@ var PipelineModelOptionSchema = object({
|
|
|
22389
23030
|
})),
|
|
22390
23031
|
group: ModelVariantGroupSchema.optional(),
|
|
22391
23032
|
legacy: boolean().optional(),
|
|
22392
|
-
provider: ModelProviderIdSchema.optional()
|
|
23033
|
+
provider: ModelProviderIdSchema.optional(),
|
|
23034
|
+
defaultConfidence: number().optional()
|
|
22393
23035
|
});
|
|
22394
23036
|
var ConfigFieldBridge = custom();
|
|
22395
23037
|
var PipelineAddonSchemaSchema = object({
|
|
@@ -22691,7 +23333,14 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
|
|
|
22691
23333
|
height: number()
|
|
22692
23334
|
}), { kind: "mutation" }), method(object({
|
|
22693
23335
|
stepId: string(),
|
|
22694
|
-
frameId: number().int()
|
|
23336
|
+
frameId: number().int(),
|
|
23337
|
+
/**
|
|
23338
|
+
* The model to time — REQUIRED (D658). Without it the pool answered the
|
|
23339
|
+
* step's ACTIVE variant (the first loaded after boot), so a benchmark
|
|
23340
|
+
* of model B silently timed model A. Callers pass the `rootModelId`
|
|
23341
|
+
* their warmup `runPipeline` reported: the model that actually ran.
|
|
23342
|
+
*/
|
|
23343
|
+
modelId: string().min(1)
|
|
22695
23344
|
}), record(string(), unknown()), { kind: "mutation" }), method(object({ frameId: number().int() }), _void(), { kind: "mutation" }), method(object({ deviceKey: string().optional() }), EffectiveTuningSchema), method(_void(), array(object({
|
|
22696
23345
|
engineKey: string(),
|
|
22697
23346
|
engine: PipelineEngineChoiceSchema,
|
|
@@ -26704,7 +27353,6 @@ var doorbellCapability = {
|
|
|
26704
27353
|
kind: "wrapper",
|
|
26705
27354
|
defaultActive: false,
|
|
26706
27355
|
deviceTypes: [DeviceType.Button, DeviceType.Camera],
|
|
26707
|
-
exposesDeviceSettings: true,
|
|
26708
27356
|
extensionSlots: [DOORBELL_SOURCE_SLOT],
|
|
26709
27357
|
methods: {},
|
|
26710
27358
|
events: {
|
|
@@ -26754,7 +27402,7 @@ var ExtensionSourceSchema = object({
|
|
|
26754
27402
|
/**
|
|
26755
27403
|
* How one picked source stands. `missing` — no such device (deleted, or an id
|
|
26756
27404
|
* that never existed); `foreign` — a device the slot's owner cannot read from
|
|
26757
|
-
* (
|
|
27405
|
+
* (a source owned by another addon); `unavailable` — it exists but is not a
|
|
26758
27406
|
* valid source now (a container, which is never a pick). A source in any of
|
|
26759
27407
|
* these states is SHOWN, never silently dropped or re-pointed.
|
|
26760
27408
|
*/
|
|
@@ -26794,13 +27442,19 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
|
|
|
26794
27442
|
slot: ExtensionSlotIdSchema,
|
|
26795
27443
|
/** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
|
|
26796
27444
|
authority: string(),
|
|
26797
|
-
/** graft: its switch. association: null — removing every source is "off". */
|
|
26798
|
-
enabled: boolean().nullable(),
|
|
26799
|
-
/** graft: the cap registered on the host. association: null. */
|
|
26800
|
-
projects: string().nullable(),
|
|
26801
27445
|
activation: ExtensionSlotActivationSchema.nullable(),
|
|
26802
27446
|
value: ExtensionSlotValueSchema,
|
|
26803
|
-
sources: array(ExtensionSlotSourceSchema)
|
|
27447
|
+
sources: array(ExtensionSlotSourceSchema),
|
|
27448
|
+
/**
|
|
27449
|
+
* Devices the owner links to the host ON ITS OWN, by structure, and that
|
|
27450
|
+
* are therefore not in `value` (D655): a camera's device-tree children, and
|
|
27451
|
+
* the child a picked container stands for (D356 — a container is never a
|
|
27452
|
+
* pick, its children are). Read-only; the editor shows them and may offer
|
|
27453
|
+
* per-device options on them (the linked-devices tracked-event opt-in).
|
|
27454
|
+
* Absent when the owner links nothing beyond the picks, and from an owner
|
|
27455
|
+
* older than D655.
|
|
27456
|
+
*/
|
|
27457
|
+
impliedSources: array(ExtensionSlotSourceSchema).optional()
|
|
26804
27458
|
}), object({
|
|
26805
27459
|
state: literal("unreachable"),
|
|
26806
27460
|
addonId: string(),
|
|
@@ -26821,7 +27475,8 @@ var ExtensionCandidatesSchema = object({ candidates: array(object({
|
|
|
26821
27475
|
/**
|
|
26822
27476
|
* What a write did. `none` — applied in place. `host` / `parent` — the owner
|
|
26823
27477
|
* reloaded the host (or, for an accessory child, its parent) because the
|
|
26824
|
-
* write changed what the host REGISTERS
|
|
27478
|
+
* write changed what the host REGISTERS. A slot never adds a capability to its
|
|
27479
|
+
* host — that is a composition (D663) — so today's owners answer `none`.
|
|
26825
27480
|
*/
|
|
26826
27481
|
var ExtensionSetResultSchema = object({
|
|
26827
27482
|
applied: literal(true),
|
|
@@ -28101,7 +28756,9 @@ var batteryCapability = {
|
|
|
28101
28756
|
DeviceType.Camera,
|
|
28102
28757
|
DeviceType.Sensor,
|
|
28103
28758
|
DeviceType.Button,
|
|
28104
|
-
DeviceType.Switch
|
|
28759
|
+
DeviceType.Switch,
|
|
28760
|
+
DeviceType.PetFeeder,
|
|
28761
|
+
DeviceType.Container
|
|
28105
28762
|
],
|
|
28106
28763
|
methods: {
|
|
28107
28764
|
/**
|
|
@@ -28128,6 +28785,19 @@ wakeForStream: method(object({
|
|
|
28128
28785
|
awoke: boolean(),
|
|
28129
28786
|
durationMs: number()
|
|
28130
28787
|
}), { kind: "mutation" }) },
|
|
28788
|
+
/**
|
|
28789
|
+
* A composed provider (battery ADDED to a pet feeder or container that has
|
|
28790
|
+
* no wake surface) answers `wakeForStream` with the same "unavailable"
|
|
28791
|
+
* result the contract already defines above: "Returns `awoke: false` when
|
|
28792
|
+
* … the cap surface is unavailable." (D663).
|
|
28793
|
+
*/
|
|
28794
|
+
composedMethods: { wakeForStream: {
|
|
28795
|
+
kind: "answer",
|
|
28796
|
+
value: {
|
|
28797
|
+
awoke: false,
|
|
28798
|
+
durationMs: 0
|
|
28799
|
+
}
|
|
28800
|
+
} },
|
|
28131
28801
|
events: {
|
|
28132
28802
|
/**
|
|
28133
28803
|
* Emitted whenever the cached status changes (firmware push OR
|
|
@@ -28143,7 +28813,7 @@ onStatusChanged: { data: object({
|
|
|
28143
28813
|
schema: BatteryStatusSchema,
|
|
28144
28814
|
kind: "push",
|
|
28145
28815
|
empty: {
|
|
28146
|
-
percentage:
|
|
28816
|
+
percentage: null,
|
|
28147
28817
|
charging: "unknown",
|
|
28148
28818
|
sleeping: false,
|
|
28149
28819
|
lastUpdated: 0
|
|
@@ -28885,6 +29555,19 @@ var connectivityCapability = {
|
|
|
28885
29555
|
* provider populates it by guessing (no HA inference). The UI renders a
|
|
28886
29556
|
* "No consumables reported" placeholder when `items` is empty.
|
|
28887
29557
|
*/
|
|
29558
|
+
/** Units a remaining-life counter is reported in. Closed: the panel renders
|
|
29559
|
+
* each. */
|
|
29560
|
+
var ConsumableRemainingUnitSchema = _enum([
|
|
29561
|
+
"days",
|
|
29562
|
+
"hours",
|
|
29563
|
+
"cycles"
|
|
29564
|
+
]);
|
|
29565
|
+
/** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
|
|
29566
|
+
* days). `value` null = unknown (D393). */
|
|
29567
|
+
var ConsumableRemainingSchema = object({
|
|
29568
|
+
value: number().min(0).nullable(),
|
|
29569
|
+
unit: ConsumableRemainingUnitSchema
|
|
29570
|
+
});
|
|
28888
29571
|
/** A single consumable item. Either a continuous `level` (remaining
|
|
28889
29572
|
* life %) or a discrete `status` may be known — both may be null when a
|
|
28890
29573
|
* provider only knows the item exists. `level` and `status` are not
|
|
@@ -28901,7 +29584,10 @@ var ConsumableItemSchema = object({
|
|
|
28901
29584
|
/** Ms epoch of the last replace, when known. */
|
|
28902
29585
|
lastResetAt: number().nullable(),
|
|
28903
29586
|
/** Whether `reset()` is meaningful for this item. */
|
|
28904
|
-
resettable: boolean()
|
|
29587
|
+
resettable: boolean(),
|
|
29588
|
+
/** Remaining life as a count (days/hours/cycles), when a provider reports
|
|
29589
|
+
* it that way instead of — or alongside — `level`. Absent = not reported. */
|
|
29590
|
+
remaining: ConsumableRemainingSchema.optional()
|
|
28905
29591
|
});
|
|
28906
29592
|
var ConsumablesStatusSchema = object({
|
|
28907
29593
|
items: array(ConsumableItemSchema),
|
|
@@ -28931,6 +29617,16 @@ reset: method(object({
|
|
|
28931
29617
|
kind: "mutation",
|
|
28932
29618
|
auth: "admin"
|
|
28933
29619
|
}) },
|
|
29620
|
+
/**
|
|
29621
|
+
* A composed consumable (item added to a device with no native consumables
|
|
29622
|
+
* provider) refuses `reset` by name: "Only meaningful when the item's
|
|
29623
|
+
* `resettable` is true" — a composed item is never resettable until
|
|
29624
|
+
* commands (`forward`) arrive in slice 3 (D663).
|
|
29625
|
+
*/
|
|
29626
|
+
composedMethods: { reset: {
|
|
29627
|
+
kind: "refuse",
|
|
29628
|
+
reason: "a composed consumable is not resettable until commands (forward) arrive — slice 3"
|
|
29629
|
+
} },
|
|
28934
29630
|
status: {
|
|
28935
29631
|
schema: ConsumablesStatusSchema,
|
|
28936
29632
|
kind: "push",
|
|
@@ -38043,6 +38739,42 @@ var zoneRulesCapability = {
|
|
|
38043
38739
|
*/
|
|
38044
38740
|
durability: "restored"
|
|
38045
38741
|
};
|
|
38742
|
+
var NUMBER = {
|
|
38743
|
+
kind: "fixed",
|
|
38744
|
+
result: ["number"]
|
|
38745
|
+
};
|
|
38746
|
+
var BOOLEAN = {
|
|
38747
|
+
kind: "fixed",
|
|
38748
|
+
result: ["boolean"]
|
|
38749
|
+
};
|
|
38750
|
+
Object.freeze({
|
|
38751
|
+
min: NUMBER,
|
|
38752
|
+
max: NUMBER,
|
|
38753
|
+
abs: NUMBER,
|
|
38754
|
+
floor: NUMBER,
|
|
38755
|
+
ceil: NUMBER,
|
|
38756
|
+
sqrt: NUMBER,
|
|
38757
|
+
round: NUMBER,
|
|
38758
|
+
pow: NUMBER,
|
|
38759
|
+
clamp: NUMBER,
|
|
38760
|
+
avg: NUMBER,
|
|
38761
|
+
sum: NUMBER,
|
|
38762
|
+
age: NUMBER,
|
|
38763
|
+
convert: NUMBER,
|
|
38764
|
+
coalesce: { kind: "coalesce" },
|
|
38765
|
+
any: BOOLEAN,
|
|
38766
|
+
all: BOOLEAN,
|
|
38767
|
+
within: BOOLEAN,
|
|
38768
|
+
latest: {
|
|
38769
|
+
kind: "fixed",
|
|
38770
|
+
result: ["number", "null"]
|
|
38771
|
+
},
|
|
38772
|
+
number: {
|
|
38773
|
+
kind: "fixed",
|
|
38774
|
+
result: ["number", "null"]
|
|
38775
|
+
}
|
|
38776
|
+
});
|
|
38777
|
+
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
38046
38778
|
/**
|
|
38047
38779
|
* Accessory device helpers — shared across drivers.
|
|
38048
38780
|
*
|
|
@@ -38186,38 +38918,31 @@ var DeviceConfig = class DeviceConfig {
|
|
|
38186
38918
|
}));
|
|
38187
38919
|
}
|
|
38188
38920
|
};
|
|
38189
|
-
/**
|
|
38190
|
-
* Concrete implementation. Routes every successful write through
|
|
38191
|
-
* `writer(capName, slice)` — the kernel hooks this up to
|
|
38192
|
-
* `device-state.setCapSlice`, the canonical cross-layer write
|
|
38193
|
-
* entrypoint, which handles disk persistence (debounced on the hub)
|
|
38194
|
-
* and mirror updates.
|
|
38195
|
-
*
|
|
38196
|
-
* Schema validation runs in-process before the writer is called —
|
|
38197
|
-
* the round-trip should never carry an invalid slice. `flush()`
|
|
38198
|
-
* awaits any in-flight writer promises so shutdown is lossless.
|
|
38199
|
-
*
|
|
38200
|
-
* `initial` is the persisted blob loaded at boot. Slices for caps
|
|
38201
|
-
* whose schema hasn't been installed yet are kept in-memory verbatim
|
|
38202
|
-
* and validated when the cap registers later.
|
|
38203
|
-
*/
|
|
38204
38921
|
var DeviceRuntimeState = class DeviceRuntimeState {
|
|
38205
38922
|
writer;
|
|
38923
|
+
onWriteFailed;
|
|
38206
38924
|
/** In-flight writer promises tracked so `flush()` can await them. */
|
|
38207
38925
|
pendingWrites = /* @__PURE__ */ new Set();
|
|
38926
|
+
/**
|
|
38927
|
+
* Caps whose last write the hub refused. The equality gate in
|
|
38928
|
+
* `applyCapWrite` is bypassed for these, so the SAME value is re-sent on the
|
|
38929
|
+
* next write instead of being swallowed forever; a landing write clears it.
|
|
38930
|
+
*/
|
|
38931
|
+
failedWrites = /* @__PURE__ */ new Set();
|
|
38208
38932
|
/** Per-cap committed slice — after schema validation when known. */
|
|
38209
38933
|
slices;
|
|
38210
38934
|
/** Per-cap registered schema (set by `installCapSchema`). */
|
|
38211
38935
|
schemas = /* @__PURE__ */ new Map();
|
|
38212
38936
|
listeners = /* @__PURE__ */ new Set();
|
|
38213
38937
|
capListeners = /* @__PURE__ */ new Map();
|
|
38214
|
-
constructor(initial, writer) {
|
|
38938
|
+
constructor(initial, writer, onWriteFailed) {
|
|
38215
38939
|
this.writer = writer;
|
|
38940
|
+
this.onWriteFailed = onWriteFailed;
|
|
38216
38941
|
this.slices = /* @__PURE__ */ new Map();
|
|
38217
38942
|
for (const [k, v] of Object.entries(initial)) if (v && typeof v === "object" && !Array.isArray(v)) this.slices.set(k, { ...v });
|
|
38218
38943
|
}
|
|
38219
|
-
static fromInitial(initial, writer) {
|
|
38220
|
-
return new DeviceRuntimeState(initial, writer);
|
|
38944
|
+
static fromInitial(initial, writer, onWriteFailed) {
|
|
38945
|
+
return new DeviceRuntimeState(initial, writer, onWriteFailed);
|
|
38221
38946
|
}
|
|
38222
38947
|
installCapSchema(capName, schema) {
|
|
38223
38948
|
const existing = this.schemas.get(capName);
|
|
@@ -38261,10 +38986,15 @@ var DeviceRuntimeState = class DeviceRuntimeState {
|
|
|
38261
38986
|
...value
|
|
38262
38987
|
} : { ...value };
|
|
38263
38988
|
const parsed = schema.parse(next);
|
|
38264
|
-
if (shallowEqual(current, parsed)) return;
|
|
38989
|
+
if (shallowEqual(current, parsed) && !this.failedWrites.has(capName)) return;
|
|
38265
38990
|
this.slices.set(capName, parsed);
|
|
38266
38991
|
this.fireListeners([capName]);
|
|
38267
|
-
const writePromise = this.writer(capName, { ...parsed }).
|
|
38992
|
+
const writePromise = this.writer(capName, { ...parsed }).then(() => {
|
|
38993
|
+
this.failedWrites.delete(capName);
|
|
38994
|
+
}).catch((err) => {
|
|
38995
|
+
this.failedWrites.add(capName);
|
|
38996
|
+
this.onWriteFailed?.(capName, err instanceof Error ? err.message : String(err));
|
|
38997
|
+
});
|
|
38268
38998
|
this.pendingWrites.add(writePromise);
|
|
38269
38999
|
writePromise.finally(() => {
|
|
38270
39000
|
this.pendingWrites.delete(writePromise);
|
|
@@ -38541,7 +39271,16 @@ var BaseDevice = class {
|
|
|
38541
39271
|
});
|
|
38542
39272
|
};
|
|
38543
39273
|
const initial = ctx.initialRuntimeState ?? {};
|
|
38544
|
-
|
|
39274
|
+
const onWriteFailed = (cap, error) => {
|
|
39275
|
+
ctx.logger.warn("runtime-state write refused by the hub — the slice is not mirrored; it is re-sent on the next write", {
|
|
39276
|
+
tags: { deviceId: ctx.id },
|
|
39277
|
+
meta: {
|
|
39278
|
+
cap,
|
|
39279
|
+
error
|
|
39280
|
+
}
|
|
39281
|
+
});
|
|
39282
|
+
};
|
|
39283
|
+
this.runtimeState = DeviceRuntimeState.fromInitial(initial, writer, onWriteFailed);
|
|
38545
39284
|
ctx.bindRuntimeState?.(this.runtimeState);
|
|
38546
39285
|
ctx.registerNativeCap?.(deviceStatusCapability, {});
|
|
38547
39286
|
const seed = {
|
|
@@ -41809,6 +42548,18 @@ Object.freeze({
|
|
|
41809
42548
|
addonId: null,
|
|
41810
42549
|
access: "create"
|
|
41811
42550
|
},
|
|
42551
|
+
"composer.applyBlocks": {
|
|
42552
|
+
capName: "composer",
|
|
42553
|
+
capScope: "system",
|
|
42554
|
+
addonId: null,
|
|
42555
|
+
access: "create"
|
|
42556
|
+
},
|
|
42557
|
+
"composer.getStates": {
|
|
42558
|
+
capName: "composer",
|
|
42559
|
+
capScope: "system",
|
|
42560
|
+
addonId: null,
|
|
42561
|
+
access: "view"
|
|
42562
|
+
},
|
|
41812
42563
|
"connectionTest.describeTest": {
|
|
41813
42564
|
capName: "connection-test",
|
|
41814
42565
|
capScope: "system",
|
|
@@ -41869,6 +42620,12 @@ Object.freeze({
|
|
|
41869
42620
|
addonId: null,
|
|
41870
42621
|
access: "create"
|
|
41871
42622
|
},
|
|
42623
|
+
"coreBlocks.createComposition": {
|
|
42624
|
+
capName: "core-blocks",
|
|
42625
|
+
capScope: "system",
|
|
42626
|
+
addonId: null,
|
|
42627
|
+
access: "create"
|
|
42628
|
+
},
|
|
41872
42629
|
"coreBlocks.delete": {
|
|
41873
42630
|
capName: "core-blocks",
|
|
41874
42631
|
capScope: "system",
|
|
@@ -41881,6 +42638,18 @@ Object.freeze({
|
|
|
41881
42638
|
addonId: null,
|
|
41882
42639
|
access: "view"
|
|
41883
42640
|
},
|
|
42641
|
+
"coreBlocks.getComposition": {
|
|
42642
|
+
capName: "core-blocks",
|
|
42643
|
+
capScope: "system",
|
|
42644
|
+
addonId: null,
|
|
42645
|
+
access: "view"
|
|
42646
|
+
},
|
|
42647
|
+
"coreBlocks.getCustomization": {
|
|
42648
|
+
capName: "core-blocks",
|
|
42649
|
+
capScope: "system",
|
|
42650
|
+
addonId: null,
|
|
42651
|
+
access: "view"
|
|
42652
|
+
},
|
|
41884
42653
|
"coreBlocks.getTypeDefs": {
|
|
41885
42654
|
capName: "core-blocks",
|
|
41886
42655
|
capScope: "system",
|
|
@@ -41893,6 +42662,12 @@ Object.freeze({
|
|
|
41893
42662
|
addonId: null,
|
|
41894
42663
|
access: "view"
|
|
41895
42664
|
},
|
|
42665
|
+
"coreBlocks.listCompositions": {
|
|
42666
|
+
capName: "core-blocks",
|
|
42667
|
+
capScope: "system",
|
|
42668
|
+
addonId: null,
|
|
42669
|
+
access: "view"
|
|
42670
|
+
},
|
|
41896
42671
|
"coreBlocks.restart": {
|
|
41897
42672
|
capName: "core-blocks",
|
|
41898
42673
|
capScope: "system",
|
|
@@ -41911,6 +42686,18 @@ Object.freeze({
|
|
|
41911
42686
|
addonId: null,
|
|
41912
42687
|
access: "create"
|
|
41913
42688
|
},
|
|
42689
|
+
"coreBlocks.updateComposition": {
|
|
42690
|
+
capName: "core-blocks",
|
|
42691
|
+
capScope: "system",
|
|
42692
|
+
addonId: null,
|
|
42693
|
+
access: "create"
|
|
42694
|
+
},
|
|
42695
|
+
"coreBlocks.validateComposition": {
|
|
42696
|
+
capName: "core-blocks",
|
|
42697
|
+
capScope: "system",
|
|
42698
|
+
addonId: null,
|
|
42699
|
+
access: "view"
|
|
42700
|
+
},
|
|
41914
42701
|
"cover.close": {
|
|
41915
42702
|
capName: "cover",
|
|
41916
42703
|
capScope: "device",
|
|
@@ -42397,12 +43184,6 @@ Object.freeze({
|
|
|
42397
43184
|
addonId: null,
|
|
42398
43185
|
access: "create"
|
|
42399
43186
|
},
|
|
42400
|
-
"deviceManager.applyDeviceSettingsPatch": {
|
|
42401
|
-
capName: "device-manager",
|
|
42402
|
-
capScope: "system",
|
|
42403
|
-
addonId: null,
|
|
42404
|
-
access: "create"
|
|
42405
|
-
},
|
|
42406
43187
|
"deviceManager.applyInitialMeta": {
|
|
42407
43188
|
capName: "device-manager",
|
|
42408
43189
|
capScope: "system",
|
|
@@ -42505,12 +43286,6 @@ Object.freeze({
|
|
|
42505
43286
|
addonId: null,
|
|
42506
43287
|
access: "view"
|
|
42507
43288
|
},
|
|
42508
|
-
"deviceManager.getDeviceLiveContribution": {
|
|
42509
|
-
capName: "device-manager",
|
|
42510
|
-
capScope: "system",
|
|
42511
|
-
addonId: null,
|
|
42512
|
-
access: "view"
|
|
42513
|
-
},
|
|
42514
43289
|
"deviceManager.getDeviceLiveInfoAggregate": {
|
|
42515
43290
|
capName: "device-manager",
|
|
42516
43291
|
capScope: "system",
|
|
@@ -42523,12 +43298,6 @@ Object.freeze({
|
|
|
42523
43298
|
addonId: null,
|
|
42524
43299
|
access: "view"
|
|
42525
43300
|
},
|
|
42526
|
-
"deviceManager.getDeviceSettingsContribution": {
|
|
42527
|
-
capName: "device-manager",
|
|
42528
|
-
capScope: "system",
|
|
42529
|
-
addonId: null,
|
|
42530
|
-
access: "view"
|
|
42531
|
-
},
|
|
42532
43301
|
"deviceManager.getDeviceStatusAggregate": {
|
|
42533
43302
|
capName: "device-manager",
|
|
42534
43303
|
capScope: "system",
|
|
@@ -43003,6 +43772,12 @@ Object.freeze({
|
|
|
43003
43772
|
addonId: null,
|
|
43004
43773
|
access: "create"
|
|
43005
43774
|
},
|
|
43775
|
+
"deviceState.claimFields": {
|
|
43776
|
+
capName: "device-state",
|
|
43777
|
+
capScope: "system",
|
|
43778
|
+
addonId: null,
|
|
43779
|
+
access: "create"
|
|
43780
|
+
},
|
|
43006
43781
|
"deviceState.getAllSnapshots": {
|
|
43007
43782
|
capName: "device-state",
|
|
43008
43783
|
capScope: "system",
|
|
@@ -43015,38 +43790,44 @@ Object.freeze({
|
|
|
43015
43790
|
addonId: null,
|
|
43016
43791
|
access: "view"
|
|
43017
43792
|
},
|
|
43018
|
-
"deviceState.
|
|
43793
|
+
"deviceState.getNativeCapSlice": {
|
|
43019
43794
|
capName: "device-state",
|
|
43020
43795
|
capScope: "system",
|
|
43021
43796
|
addonId: null,
|
|
43022
43797
|
access: "view"
|
|
43023
43798
|
},
|
|
43024
|
-
"deviceState.
|
|
43799
|
+
"deviceState.getSnapshot": {
|
|
43025
43800
|
capName: "device-state",
|
|
43026
43801
|
capScope: "system",
|
|
43027
43802
|
addonId: null,
|
|
43028
|
-
access: "
|
|
43803
|
+
access: "view"
|
|
43029
43804
|
},
|
|
43030
|
-
"
|
|
43031
|
-
capName: "device-
|
|
43032
|
-
capScope: "
|
|
43805
|
+
"deviceState.listClaims": {
|
|
43806
|
+
capName: "device-state",
|
|
43807
|
+
capScope: "system",
|
|
43033
43808
|
addonId: null,
|
|
43034
43809
|
access: "view"
|
|
43035
43810
|
},
|
|
43036
|
-
"
|
|
43037
|
-
capName: "
|
|
43038
|
-
capScope: "
|
|
43811
|
+
"deviceState.patchOwnedFields": {
|
|
43812
|
+
capName: "device-state",
|
|
43813
|
+
capScope: "system",
|
|
43039
43814
|
addonId: null,
|
|
43040
43815
|
access: "create"
|
|
43041
43816
|
},
|
|
43042
|
-
"
|
|
43043
|
-
capName: "
|
|
43044
|
-
capScope: "
|
|
43817
|
+
"deviceState.releaseClaim": {
|
|
43818
|
+
capName: "device-state",
|
|
43819
|
+
capScope: "system",
|
|
43045
43820
|
addonId: null,
|
|
43046
|
-
access: "
|
|
43821
|
+
access: "create"
|
|
43047
43822
|
},
|
|
43048
|
-
"
|
|
43049
|
-
capName: "
|
|
43823
|
+
"deviceState.setCapSlice": {
|
|
43824
|
+
capName: "device-state",
|
|
43825
|
+
capScope: "system",
|
|
43826
|
+
addonId: null,
|
|
43827
|
+
access: "create"
|
|
43828
|
+
},
|
|
43829
|
+
"deviceStatus.getStatus": {
|
|
43830
|
+
capName: "device-status",
|
|
43050
43831
|
capScope: "device",
|
|
43051
43832
|
addonId: null,
|
|
43052
43833
|
access: "view"
|
|
@@ -48554,16 +49335,36 @@ Object.freeze({
|
|
|
48554
49335
|
form: "single",
|
|
48555
49336
|
optional: false
|
|
48556
49337
|
}],
|
|
49338
|
+
"deviceState.claimFields": [{
|
|
49339
|
+
name: "deviceId",
|
|
49340
|
+
form: "single",
|
|
49341
|
+
optional: false
|
|
49342
|
+
}],
|
|
48557
49343
|
"deviceState.getCapSlice": [{
|
|
48558
49344
|
name: "deviceId",
|
|
48559
49345
|
form: "single",
|
|
48560
49346
|
optional: false
|
|
48561
49347
|
}],
|
|
49348
|
+
"deviceState.getNativeCapSlice": [{
|
|
49349
|
+
name: "deviceId",
|
|
49350
|
+
form: "single",
|
|
49351
|
+
optional: false
|
|
49352
|
+
}],
|
|
48562
49353
|
"deviceState.getSnapshot": [{
|
|
48563
49354
|
name: "deviceId",
|
|
48564
49355
|
form: "single",
|
|
48565
49356
|
optional: false
|
|
48566
49357
|
}],
|
|
49358
|
+
"deviceState.patchOwnedFields": [{
|
|
49359
|
+
name: "deviceId",
|
|
49360
|
+
form: "single",
|
|
49361
|
+
optional: false
|
|
49362
|
+
}],
|
|
49363
|
+
"deviceState.releaseClaim": [{
|
|
49364
|
+
name: "deviceId",
|
|
49365
|
+
form: "single",
|
|
49366
|
+
optional: false
|
|
49367
|
+
}],
|
|
48567
49368
|
"deviceState.setCapSlice": [{
|
|
48568
49369
|
name: "deviceId",
|
|
48569
49370
|
form: "single",
|