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