@camstack/addon-provider-homeassistant 1.1.31 → 1.2.0

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.
Files changed (3) hide show
  1. package/dist/addon.js +560 -104
  2. package/dist/addon.mjs +560 -104
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -7086,6 +7086,17 @@ var ModelCatalogEntrySchema = object({
7086
7086
  "imagenet",
7087
7087
  "none"
7088
7088
  ]).optional(),
7089
+ /**
7090
+ * The model already applies softmax IN-GRAPH — its raw output is a
7091
+ * probability distribution, not logits. When set, the `softmax`
7092
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7093
+ * probability vector collapses it toward uniform (top-1 score craters far
7094
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7095
+ * the output is raw logits and the postprocessor applies softmax (the normal
7096
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7097
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7098
+ */
7099
+ outputProbabilities: boolean().optional(),
7089
7100
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7090
7101
  /**
7091
7102
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -7762,6 +7773,160 @@ var EncodeProfileSchema = object({
7762
7773
  */
7763
7774
  outputArgs: array(string()).optional()
7764
7775
  });
7776
+ var COCO_TO_MACRO = {
7777
+ mapping: {
7778
+ person: "person",
7779
+ bicycle: "vehicle",
7780
+ car: "vehicle",
7781
+ motorcycle: "vehicle",
7782
+ airplane: "vehicle",
7783
+ bus: "vehicle",
7784
+ train: "vehicle",
7785
+ truck: "vehicle",
7786
+ boat: "vehicle",
7787
+ bird: "animal",
7788
+ cat: "animal",
7789
+ dog: "animal",
7790
+ horse: "animal",
7791
+ sheep: "animal",
7792
+ cow: "animal",
7793
+ elephant: "animal",
7794
+ bear: "animal",
7795
+ zebra: "animal",
7796
+ giraffe: "animal",
7797
+ suitcase: "package",
7798
+ backpack: "package",
7799
+ handbag: "package"
7800
+ },
7801
+ preserveOriginal: false
7802
+ };
7803
+ var AUDIO_MACRO_LABELS = [
7804
+ {
7805
+ id: "speech",
7806
+ name: "Speech",
7807
+ icon: "🗣️"
7808
+ },
7809
+ {
7810
+ id: "scream",
7811
+ name: "Scream / Shout",
7812
+ icon: "😱"
7813
+ },
7814
+ {
7815
+ id: "crying",
7816
+ name: "Crying / Baby",
7817
+ icon: "😢"
7818
+ },
7819
+ {
7820
+ id: "laughter",
7821
+ name: "Laughter",
7822
+ icon: "😂"
7823
+ },
7824
+ {
7825
+ id: "music",
7826
+ name: "Music",
7827
+ icon: "🎵"
7828
+ },
7829
+ {
7830
+ id: "dog",
7831
+ name: "Dog",
7832
+ icon: "🐕"
7833
+ },
7834
+ {
7835
+ id: "cat",
7836
+ name: "Cat",
7837
+ icon: "🐈"
7838
+ },
7839
+ {
7840
+ id: "bird",
7841
+ name: "Bird",
7842
+ icon: "🐦"
7843
+ },
7844
+ {
7845
+ id: "animal",
7846
+ name: "Animal (other)",
7847
+ icon: "🐾"
7848
+ },
7849
+ {
7850
+ id: "alarm",
7851
+ name: "Alarm / Siren",
7852
+ icon: "🚨"
7853
+ },
7854
+ {
7855
+ id: "doorbell",
7856
+ name: "Doorbell / Knock",
7857
+ icon: "🔔"
7858
+ },
7859
+ {
7860
+ id: "glass_breaking",
7861
+ name: "Glass Breaking",
7862
+ icon: "💥"
7863
+ },
7864
+ {
7865
+ id: "gunshot",
7866
+ name: "Gunshot / Explosion",
7867
+ icon: "💣"
7868
+ },
7869
+ {
7870
+ id: "vehicle",
7871
+ name: "Vehicle",
7872
+ icon: "🚗"
7873
+ },
7874
+ {
7875
+ id: "siren",
7876
+ name: "Emergency Siren",
7877
+ icon: "🚑"
7878
+ },
7879
+ {
7880
+ id: "fire",
7881
+ name: "Fire / Smoke",
7882
+ icon: "🔥"
7883
+ },
7884
+ {
7885
+ id: "water",
7886
+ name: "Water",
7887
+ icon: "💧"
7888
+ },
7889
+ {
7890
+ id: "wind",
7891
+ name: "Wind / Weather",
7892
+ icon: "🌬️"
7893
+ },
7894
+ {
7895
+ id: "door",
7896
+ name: "Door",
7897
+ icon: "🚪"
7898
+ },
7899
+ {
7900
+ id: "footsteps",
7901
+ name: "Footsteps",
7902
+ icon: "👣"
7903
+ },
7904
+ {
7905
+ id: "crowd",
7906
+ name: "Crowd / Chatter",
7907
+ icon: "👥"
7908
+ },
7909
+ {
7910
+ id: "telephone",
7911
+ name: "Telephone",
7912
+ icon: "📞"
7913
+ },
7914
+ {
7915
+ id: "engine",
7916
+ name: "Engine / Motor",
7917
+ icon: "⚙️"
7918
+ },
7919
+ {
7920
+ id: "tools",
7921
+ name: "Tools / Construction",
7922
+ icon: "🔨"
7923
+ },
7924
+ {
7925
+ id: "silence",
7926
+ name: "Silence",
7927
+ icon: "🤫"
7928
+ }
7929
+ ];
7765
7930
  var YAMNET_TO_MACRO = {
7766
7931
  mapping: {
7767
7932
  Speech: "speech",
@@ -8028,6 +8193,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8028
8193
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8029
8194
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8030
8195
  /**
8196
+ * Unified event-kind taxonomy — THE single source of truth for
8197
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8198
+ * icon }`.
8199
+ *
8200
+ * This dictionary folds together what used to be scattered across four
8201
+ * copies:
8202
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8203
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8204
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8205
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8206
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8207
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8208
+ *
8209
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8210
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8211
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8212
+ * color or an icon: they read this dictionary (server descriptors carry the
8213
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8214
+ *
8215
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8216
+ */
8217
+ var TAXONOMY_COLORS = {
8218
+ motion: "#f59e0b",
8219
+ audio: "#06b6d4",
8220
+ person: "#22c55e",
8221
+ vehicle: "#3b82f6",
8222
+ animal: "#f97316",
8223
+ package: "#a855f7",
8224
+ sensor: "#8b5cf6",
8225
+ control: "#10b981",
8226
+ genericDetection: "#64748b"
8227
+ };
8228
+ var DETECTION_SUB_COLORS = {
8229
+ car: "#f59e0b",
8230
+ truck: "#d97706",
8231
+ bus: "#b45309",
8232
+ motorcycle: "#eab308",
8233
+ bicycle: "#ca8a04",
8234
+ airplane: "#60a5fa",
8235
+ boat: "#2563eb",
8236
+ train: "#1d4ed8",
8237
+ bird: "#14b8a6",
8238
+ dog: "#84cc16",
8239
+ cat: "#f97316",
8240
+ horse: "#a16207",
8241
+ sheep: "#a3a3a3",
8242
+ cow: "#78716c",
8243
+ elephant: "#6b7280",
8244
+ bear: "#7c2d12",
8245
+ zebra: "#404040",
8246
+ giraffe: "#d4a373"
8247
+ };
8248
+ function titleCase(id) {
8249
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8250
+ }
8251
+ var entries = /* @__PURE__ */ new Map();
8252
+ function macro(kind, category, color, iconId, label) {
8253
+ entries.set(kind, {
8254
+ kind,
8255
+ parentKind: null,
8256
+ level: "macro",
8257
+ category,
8258
+ color,
8259
+ iconId,
8260
+ labelKey: `eventKind.${kind}`,
8261
+ label
8262
+ });
8263
+ }
8264
+ function sub(kind, parentKind, category, color, iconId, label) {
8265
+ entries.set(kind, {
8266
+ kind,
8267
+ parentKind,
8268
+ level: "sub",
8269
+ category,
8270
+ color,
8271
+ iconId,
8272
+ labelKey: `eventKind.${kind}`,
8273
+ label
8274
+ });
8275
+ }
8276
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8277
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8278
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8279
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8280
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8281
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8282
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8283
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8284
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8285
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8286
+ if (entries.has(cocoClass)) continue;
8287
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8288
+ }
8289
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8290
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8291
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8292
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8293
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8294
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8295
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8296
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8297
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8298
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8299
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8300
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8301
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8302
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8303
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8304
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8305
+ sub("button", "control", "control", "#10b981", "button", "Button");
8306
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8307
+ for (const l of AUDIO_MACRO_LABELS) {
8308
+ const kind = `audio-${l.id}`;
8309
+ if (entries.has(kind)) continue;
8310
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8311
+ }
8312
+ /** The complete taxonomy dictionary, keyed by kind. */
8313
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8314
+ /**
8031
8315
  * Error types for the safe expression engine. Two distinct classes so callers
8032
8316
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8033
8317
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -12458,10 +12742,7 @@ var ConfigUISchemaNullableBridge = custom();
12458
12742
  var InferenceCapabilitiesBridge = custom();
12459
12743
  var ModelAvailabilityListBridge = custom();
12460
12744
  var PipelineRunResultBridge = custom();
12461
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
12462
- kind: "mutation",
12463
- auth: "admin"
12464
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
12745
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
12465
12746
  modelId: string(),
12466
12747
  settings: record(string(), unknown()).readonly()
12467
12748
  }))), method(object({ steps: record(string(), object({
@@ -12521,13 +12802,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
12521
12802
  * (inputClasses ≠ null) are skipped and served per-track via
12522
12803
  * pipelineRunner.runDetailSubtree (two-plane design).
12523
12804
  */
12524
- plane: _enum(["full", "frame"]).optional()
12805
+ plane: _enum(["full", "frame"]).optional(),
12806
+ /**
12807
+ * Inference-device selector (Phase 2 multi-device). Format
12808
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
12809
+ * Omitted ⇒ the runner's default device (current single-engine
12810
+ * behaviour). Selects WHICH device pool of the node runs the call.
12811
+ */
12812
+ deviceKey: string().optional()
12525
12813
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
12526
12814
  engine: PipelineEngineChoiceSchema.optional(),
12527
12815
  steps: array(PipelineStepInputSchema).min(1),
12528
12816
  frames: array(FrameInputSchema).min(1).max(255),
12529
12817
  deviceId: number().optional(),
12530
- sessionId: string().optional()
12818
+ sessionId: string().optional(),
12819
+ /**
12820
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
12821
+ * the batch to the Python pool's bench preprocess cache
12822
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
12823
+ * preprocessed ONCE and every later inference is a pure-inference cache
12824
+ * hit — the sustained-throughput run measures inference, not
12825
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
12826
+ * full preprocess every call, correct). Fresh per sustained run;
12827
+ * released via `uncacheFrame`.
12828
+ */
12829
+ frameId: number().int().nonnegative().optional(),
12830
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
12831
+ deviceKey: string().optional()
12531
12832
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
12532
12833
  data: _instanceof(Uint8Array),
12533
12834
  width: number().int().positive(),
@@ -12559,8 +12860,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
12559
12860
  * - `runtime` — main camera-serving engine (no idle TTL).
12560
12861
  * - `warm-override` — benchmark/test override held in the warm
12561
12862
  * cache; auto-disposed after the idle TTL.
12863
+ * - `device-pool` — a concurrent per-device pool (Phase 2
12864
+ * multi-device, keyed by `deviceKey`) resolved
12865
+ * via `resolveDeviceFactory`. Runs alongside the
12866
+ * `runtime` engine on a DIFFERENT accelerator
12867
+ * (NPU / iGPU / Coral) — this is how the
12868
+ * Engines tab shows all pools running at once.
12562
12869
  */
12563
- kind: _enum(["runtime", "warm-override"]),
12870
+ kind: _enum([
12871
+ "runtime",
12872
+ "warm-override",
12873
+ "device-pool"
12874
+ ]),
12564
12875
  /** Native pid of the underlying Python pool (null when no pool). */
12565
12876
  poolPid: number().nullable(),
12566
12877
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -12935,7 +13246,14 @@ var RunnerCameraConfigSchema = object({
12935
13246
  * camera's detect node differs from its source-owner (P2d, gated by the
12936
13247
  * `remoteSourcingNodes` rollout setting).
12937
13248
  */
12938
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
13249
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
13250
+ /**
13251
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
13252
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
13253
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
13254
+ * this only selects WHICH device pool of that node runs the session.
13255
+ */
13256
+ deviceKey: string().optional()
12939
13257
  });
12940
13258
  motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
12941
13259
  /**
@@ -12956,6 +13274,19 @@ var RunnerLocalLoadSchema = object({
12956
13274
  avgInferenceTimeMs: number(),
12957
13275
  /** Total queue depth across motion + detection queues. */
12958
13276
  queueDepthTotal: number(),
13277
+ /**
13278
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
13279
+ * this runner currently has attached cameras on, so the orchestrator's second
13280
+ * `balance()` pass (over a node's devices) weights on real per-pool session
13281
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
13282
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
13283
+ */
13284
+ devices: array(object({
13285
+ deviceKey: string(),
13286
+ backend: string(),
13287
+ attachedCameras: number(),
13288
+ queueDepthTotal: number()
13289
+ })).default([]),
12959
13290
  /** Hardware capability flags reported by this node. */
12960
13291
  hardware: object({
12961
13292
  hasGpu: boolean(),
@@ -16104,6 +16435,8 @@ var BaseDeviceProvider = class extends BaseAddon {
16104
16435
  return toDeviceSummary(device, this.addonId);
16105
16436
  }
16106
16437
  };
16438
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
16439
+ new Set(Object.values(DeviceType));
16107
16440
  /**
16108
16441
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
16109
16442
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -19297,17 +19630,30 @@ var EventKindCategorySchema = _enum([
19297
19630
  "audio",
19298
19631
  "detection",
19299
19632
  "sensor",
19633
+ "control",
19300
19634
  "custom",
19301
19635
  "package"
19302
19636
  ]);
19637
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19638
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19303
19639
  var EventKindDescriptorSchema = object({
19304
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19640
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19305
19641
  kind: string(),
19642
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19643
+ labelKey: string(),
19644
+ /** English fallback label (kept for clients that don't translate). */
19306
19645
  label: string(),
19307
19646
  /** Hex color for timeline/legend rendering. */
19308
19647
  color: string(),
19648
+ /** Dictionary id → lucide component on the UI side. */
19649
+ iconId: string(),
19650
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19309
19651
  icon: EventKindIconSchema,
19310
19652
  category: EventKindCategorySchema,
19653
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19654
+ parentKind: string().nullable(),
19655
+ /** Derived from `parentKind`, explicit for the client tree. */
19656
+ level: EventKindLevelSchema,
19311
19657
  /** Which cap + device contributes this kind. For built-ins the camera
19312
19658
  * itself; for sensor kinds the LINKED source device. */
19313
19659
  source: object({
@@ -19380,11 +19726,21 @@ var TrackAudioLabelSchema = object({
19380
19726
  firstAt: number(),
19381
19727
  lastAt: number()
19382
19728
  });
19729
+ /**
19730
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19731
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19732
+ * linked sensor/control state change (no positions; carries a snapshot). The
19733
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19734
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19735
+ */
19736
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19383
19737
  var TrackSchema = object({
19384
19738
  trackId: string(),
19385
19739
  deviceId: number(),
19386
19740
  className: string(),
19387
19741
  label: string().optional(),
19742
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19743
+ source: TrackSourceSchema.optional(),
19388
19744
  firstSeen: number(),
19389
19745
  lastSeen: number(),
19390
19746
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19761,6 +20117,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19761
20117
  eventId: string(),
19762
20118
  timestamp: number()
19763
20119
  });
20120
+ /**
20121
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
20122
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
20123
+ * caps into per-camera event-kind descriptors.
20124
+ *
20125
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
20126
+ * is NOT duplicated here — every entry is derived from the single
20127
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
20128
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
20129
+ * control cap means adding one line here (and a taxonomy entry); the anti-
20130
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
20131
+ * eventful cap is missing.
20132
+ */
20133
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
20134
+ var LEGACY_ICON = {
20135
+ motion: "motion",
20136
+ audio: "audio",
20137
+ person: "person",
20138
+ vehicle: "vehicle",
20139
+ animal: "animal",
20140
+ package: "package",
20141
+ door: "door",
20142
+ pir: "pir",
20143
+ smoke: "smoke",
20144
+ water: "water",
20145
+ button: "button",
20146
+ generic: "generic",
20147
+ gas: "smoke",
20148
+ vibration: "generic",
20149
+ tamper: "generic",
20150
+ presence: "person",
20151
+ lock: "generic",
20152
+ siren: "generic",
20153
+ switch: "generic",
20154
+ doorbell: "button"
20155
+ };
20156
+ function legacyIcon(iconId) {
20157
+ return LEGACY_ICON[iconId] ?? "generic";
20158
+ }
20159
+ /**
20160
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
20161
+ * The anti-drift guard cross-checks this against the eventful caps declared
20162
+ * in `packages/types/src/capabilities/*.cap.ts`.
20163
+ */
20164
+ var CAP_TO_KIND = {
20165
+ contact: "contact",
20166
+ motion: "motion-sensor",
20167
+ smoke: "smoke",
20168
+ flood: "flood",
20169
+ gas: "gas",
20170
+ "carbon-monoxide": "carbon-monoxide",
20171
+ vibration: "vibration",
20172
+ tamper: "tamper",
20173
+ presence: "presence",
20174
+ "enum-sensor": "enum-sensor",
20175
+ "event-emitter": "device-event",
20176
+ "lock-control": "lock",
20177
+ switch: "switch",
20178
+ button: "button",
20179
+ doorbell: "doorbell"
20180
+ };
20181
+ function buildDescriptor(capName, kind) {
20182
+ const t = EVENT_TAXONOMY[kind];
20183
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
20184
+ return {
20185
+ ...t,
20186
+ icon: legacyIcon(t.iconId)
20187
+ };
20188
+ }
20189
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19764
20190
  var CameraPipelineConfigSchema = object({
19765
20191
  engine: PipelineEngineChoiceSchema.optional(),
19766
20192
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19779,13 +20205,11 @@ var PipelineTemplateSchema = object({
19779
20205
  createdAt: string(),
19780
20206
  updatedAt: string()
19781
20207
  });
19782
- var AgentAddonConfigSchema = object({
19783
- enabled: boolean(),
20208
+ var DeviceStepConfigSchema = object({
19784
20209
  modelId: string().optional(),
19785
- settings: record(string(), unknown()).readonly()
20210
+ settings: record(string(), unknown()).optional()
19786
20211
  });
19787
20212
  var AgentPipelineSettingsSchema = object({
19788
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
19789
20213
  maxCameras: number().int().nonnegative().nullable().default(null),
19790
20214
  /** Per-node detection weight (relative share for the quota balancer). */
19791
20215
  detectWeight: number().positive().optional(),
@@ -19809,7 +20233,22 @@ var AgentPipelineSettingsSchema = object({
19809
20233
  * it already uses to reach the hub). Set this only when the auto-detected
19810
20234
  * address is wrong (multi-homed host, NAT, custom interface).
19811
20235
  */
19812
- reachableHost: string().optional()
20236
+ reachableHost: string().optional(),
20237
+ /**
20238
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
20239
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
20240
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
20241
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
20242
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
20243
+ * the default model/settings for every camera landing on that accelerator;
20244
+ * a stepId absent ⇒ the step uses that device's format default.
20245
+ */
20246
+ inferenceDevices: record(string(), object({
20247
+ enabled: boolean(),
20248
+ weight: number().positive().optional(),
20249
+ maxSessions: number().int().positive().optional(),
20250
+ steps: record(string(), DeviceStepConfigSchema).optional()
20251
+ })).optional()
19813
20252
  });
19814
20253
  var CameraPipelineForAgentSchema = object({
19815
20254
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19819,14 +20258,13 @@ var CameraPipelineForAgentSchema = object({
19819
20258
  }).nullable()
19820
20259
  });
19821
20260
  var CameraStepOverridePatchSchema = object({
19822
- enabled: boolean().optional(),
19823
20261
  modelId: string().optional(),
19824
20262
  settings: record(string(), unknown()).readonly().optional()
19825
20263
  });
19826
20264
  var CameraPipelineSettingsSchema = object({
19827
20265
  pinnedAgentNodeId: string().optional(),
19828
20266
  stepToggles: record(string(), boolean()).optional(),
19829
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
20267
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
19830
20268
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
19831
20269
  });
19832
20270
  /**
@@ -20040,6 +20478,44 @@ var CameraStatusSchema = object({
20040
20478
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
20041
20479
  fetchedAt: number()
20042
20480
  });
20481
+ var NodeInferenceDeviceSchema = object({
20482
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
20483
+ key: string(),
20484
+ backend: string(),
20485
+ device: string(),
20486
+ format: _enum(MODEL_FORMATS),
20487
+ /** Whether the node's live probe reports the device as usable right now. */
20488
+ available: boolean(),
20489
+ /**
20490
+ * Whether this device participates in dispatch. AUTO default (spec C2):
20491
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
20492
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
20493
+ * not a balanced target). An explicit stored value always wins; a stored-only
20494
+ * (unavailable) key keeps its stored value.
20495
+ */
20496
+ enabled: boolean(),
20497
+ /** Relative balancer weight for the enabled device (default 1). */
20498
+ weight: number(),
20499
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
20500
+ maxSessions: number().nullable(),
20501
+ /** Object-detection model the executor defaults to for this deviceKey. */
20502
+ defaultModelId: string(),
20503
+ /**
20504
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
20505
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
20506
+ * Absent/empty ⇒ no base (every step uses its device format default). The
20507
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
20508
+ * available per format; this is the stored selection that becomes the
20509
+ * default for EVERY camera landing on this accelerator.
20510
+ */
20511
+ steps: record(string(), DeviceStepConfigSchema).optional()
20512
+ });
20513
+ var NodeInferenceDevicesSchema = object({
20514
+ nodeId: string(),
20515
+ /** False when the node's platform-probe was unreachable (no live device set). */
20516
+ reachable: boolean(),
20517
+ devices: array(NodeInferenceDeviceSchema).readonly()
20518
+ });
20043
20519
  method(object({
20044
20520
  deviceId: number(),
20045
20521
  agentNodeId: string()
@@ -20049,7 +20525,13 @@ method(object({
20049
20525
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
20050
20526
  kind: "mutation",
20051
20527
  auth: "admin"
20052
- }), method(_void(), object({ migrated: number() }), {
20528
+ }), method(object({
20529
+ deviceId: number(),
20530
+ deviceKey: string()
20531
+ }), object({ success: literal(true) }), {
20532
+ kind: "mutation",
20533
+ auth: "admin"
20534
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
20053
20535
  kind: "mutation",
20054
20536
  auth: "admin"
20055
20537
  }), method(_void(), array(PipelineAssignmentSchema).readonly()), method(object({ deviceId: number() }), PipelineAssignmentSchema.nullable()), method(_void(), array(AgentLoadSummarySchema).readonly()), method(_void(), GlobalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(object({ nodeId: string() }), CapabilityBindingsSchema), method(object({
@@ -20083,13 +20565,7 @@ method(object({
20083
20565
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
20084
20566
  nodeId: string(),
20085
20567
  settings: AgentPipelineSettingsSchema
20086
- })).readonly()), method(object({
20087
- agentNodeId: string(),
20088
- defaults: record(string(), AgentAddonConfigSchema)
20089
- }), object({ success: literal(true) }), {
20090
- kind: "mutation",
20091
- auth: "admin"
20092
- }), method(object({ agentNodeId: string() }), object({
20568
+ })).readonly()), method(object({ agentNodeId: string() }), object({
20093
20569
  success: boolean(),
20094
20570
  removed: boolean()
20095
20571
  }), {
@@ -20121,7 +20597,18 @@ method(object({
20121
20597
  }), object({ success: literal(true) }), {
20122
20598
  kind: "mutation",
20123
20599
  auth: "admin"
20124
- }), method(object({ agentNodeId: string() }), object({
20600
+ }), method(object({
20601
+ agentNodeId: string(),
20602
+ inferenceDevices: record(string(), object({
20603
+ enabled: boolean(),
20604
+ weight: number().positive().optional(),
20605
+ maxSessions: number().int().positive().optional(),
20606
+ steps: record(string(), DeviceStepConfigSchema).optional()
20607
+ }))
20608
+ }), object({ success: literal(true) }), {
20609
+ kind: "mutation",
20610
+ auth: "admin"
20611
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
20125
20612
  success: literal(true),
20126
20613
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
20127
20614
  effectiveModelId: string().nullable(),
@@ -20137,9 +20624,10 @@ method(object({
20137
20624
  }), object({ success: literal(true) }), {
20138
20625
  kind: "mutation",
20139
20626
  auth: "admin"
20140
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
20627
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
20141
20628
  deviceId: number(),
20142
20629
  agentNodeId: string(),
20630
+ deviceKey: string(),
20143
20631
  addonId: string(),
20144
20632
  patch: CameraStepOverridePatchSchema.nullable()
20145
20633
  }), object({ success: literal(true) }), {
@@ -20176,14 +20664,13 @@ method(object({
20176
20664
  });
20177
20665
  /**
20178
20666
  * server-management — per-NODE singleton capability for a node's ROOT
20179
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
20180
- * agents).
20667
+ * package lifecycle (runtime-updatable node packages).
20181
20668
  *
20182
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
20183
- * on agents) carries the whole software stack in its npm dep tree, so ONE
20184
- * version describes the node. Updates install into
20185
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
20186
- * starter (probation boot + auto-rollback to N-1).
20669
+ * Every node role runs the SAME root package (`@camstack/server`), which
20670
+ * carries the whole software stack in its npm dep tree, so ONE version
20671
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
20672
+ * on restart via the baked starter (single-copy in-place swap — no probation,
20673
+ * no auto-rollback).
20187
20674
  *
20188
20675
  * Providers:
20189
20676
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -20291,7 +20778,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
20291
20778
  /** Explicit target version; omitted = latest from the registry. */
20292
20779
  version: string().optional() }), ServerUpdateActionResultSchema, {
20293
20780
  kind: "mutation",
20294
- auth: "admin"
20781
+ auth: "admin",
20782
+ timeoutMs: 16 * 6e4
20295
20783
  }), method(_void(), ServerUpdateActionResultSchema, {
20296
20784
  kind: "mutation",
20297
20785
  auth: "admin"
@@ -21407,22 +21895,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
21407
21895
  var RestartAddonResultSchema = unknown();
21408
21896
  var InstallPackageResultSchema = unknown();
21409
21897
  var ReloadPackagesResultSchema = unknown();
21410
- /**
21411
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
21412
- * server restarts so the admin UI can react to the `restartingAt`
21413
- * timestamp (shows reconnect overlay). The transition from
21414
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
21415
- * `system.restart-completed` event after the new process boots.
21416
- *
21417
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
21418
- */
21419
- var UpdateFrameworkPackageResultSchema = object({
21420
- packageName: string(),
21421
- fromVersion: string(),
21422
- toVersion: string(),
21423
- /** Ms-epoch the server scheduled its self-restart. */
21424
- restartingAt: number()
21425
- });
21426
21898
  var BulkUpdateItemStatusSchema = _enum([
21427
21899
  "queued",
21428
21900
  "updating",
@@ -21550,13 +22022,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
21550
22022
  }), object({ success: literal(true) }), {
21551
22023
  kind: "mutation",
21552
22024
  auth: "admin"
21553
- }), method(object({
21554
- packageName: string().min(1),
21555
- version: string().optional(),
21556
- deferRestart: boolean().optional()
21557
- }), UpdateFrameworkPackageResultSchema, {
21558
- kind: "mutation",
21559
- auth: "admin"
21560
22025
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
21561
22026
  kind: "mutation",
21562
22027
  auth: "admin"
@@ -22439,10 +22904,10 @@ var TopologyCategorySchema = object({
22439
22904
  addons: array(TopologyCategoryAddonSchema).readonly()
22440
22905
  });
22441
22906
  /**
22442
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
22443
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
22444
- * version visibility for the Server management surface. Nullable: offline
22445
- * rows and pre-phase-2 nodes report none.
22907
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
22908
+ * root package for every node role) as reported by its `registerNode`
22909
+ * manifest — version visibility for the Server management surface. Nullable:
22910
+ * offline rows and nodes that never reported one.
22446
22911
  */
22447
22912
  var TopologyRootPackageSchema = object({
22448
22913
  name: string(),
@@ -22830,17 +23295,28 @@ var PlatformScoreSchema = object({
22830
23295
  format: _enum([
22831
23296
  "onnx",
22832
23297
  "coreml",
22833
- "openvino"
23298
+ "openvino",
23299
+ "tflite"
22834
23300
  ]),
22835
23301
  score: number(),
22836
23302
  reason: string(),
22837
23303
  available: boolean()
22838
23304
  });
23305
+ var InferenceDeviceDescriptorSchema = object({
23306
+ key: string(),
23307
+ backend: string(),
23308
+ device: string(),
23309
+ format: ModelFormatSchema,
23310
+ runtime: literal("python"),
23311
+ score: number(),
23312
+ available: boolean()
23313
+ });
22839
23314
  var PlatformCapabilitiesSchema = object({
22840
23315
  hardware: HardwareInfoSchema,
22841
23316
  scores: array(PlatformScoreSchema).readonly(),
22842
23317
  bestScore: PlatformScoreSchema,
22843
- pythonPath: string().nullable()
23318
+ pythonPath: string().nullable(),
23319
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
22844
23320
  });
22845
23321
  var ModelRequirementSchema = object({
22846
23322
  modelId: string(),
@@ -23719,12 +24195,6 @@ Object.freeze({
23719
24195
  addonId: null,
23720
24196
  access: "delete"
23721
24197
  },
23722
- "addons.updateFrameworkPackage": {
23723
- capName: "addons",
23724
- capScope: "system",
23725
- addonId: null,
23726
- access: "create"
23727
- },
23728
24198
  "addons.updatePackage": {
23729
24199
  capName: "addons",
23730
24200
  capScope: "system",
@@ -26497,12 +26967,6 @@ Object.freeze({
26497
26967
  addonId: null,
26498
26968
  access: "view"
26499
26969
  },
26500
- "pipelineExecutor.reprobeEngine": {
26501
- capName: "pipeline-executor",
26502
- capScope: "system",
26503
- addonId: null,
26504
- access: "create"
26505
- },
26506
26970
  "pipelineExecutor.runAudioTest": {
26507
26971
  capName: "pipeline-executor",
26508
26972
  capScope: "system",
@@ -26653,6 +27117,12 @@ Object.freeze({
26653
27117
  addonId: null,
26654
27118
  access: "view"
26655
27119
  },
27120
+ "pipelineOrchestrator.getNodeInferenceDevices": {
27121
+ capName: "pipeline-orchestrator",
27122
+ capScope: "system",
27123
+ addonId: null,
27124
+ access: "view"
27125
+ },
26656
27126
  "pipelineOrchestrator.getPipelineAssignment": {
26657
27127
  capName: "pipeline-orchestrator",
26658
27128
  capScope: "system",
@@ -26665,6 +27135,12 @@ Object.freeze({
26665
27135
  addonId: null,
26666
27136
  access: "view"
26667
27137
  },
27138
+ "pipelineOrchestrator.getPipelineDevicePin": {
27139
+ capName: "pipeline-orchestrator",
27140
+ capScope: "system",
27141
+ addonId: null,
27142
+ access: "view"
27143
+ },
26668
27144
  "pipelineOrchestrator.listAgentSettings": {
26669
27145
  capName: "pipeline-orchestrator",
26670
27146
  capScope: "system",
@@ -26707,19 +27183,19 @@ Object.freeze({
26707
27183
  addonId: null,
26708
27184
  access: "create"
26709
27185
  },
26710
- "pipelineOrchestrator.setAgentAddonDefaults": {
27186
+ "pipelineOrchestrator.setAgentCapabilities": {
26711
27187
  capName: "pipeline-orchestrator",
26712
27188
  capScope: "system",
26713
27189
  addonId: null,
26714
27190
  access: "create"
26715
27191
  },
26716
- "pipelineOrchestrator.setAgentCapabilities": {
27192
+ "pipelineOrchestrator.setAgentDetectWeight": {
26717
27193
  capName: "pipeline-orchestrator",
26718
27194
  capScope: "system",
26719
27195
  addonId: null,
26720
27196
  access: "create"
26721
27197
  },
26722
- "pipelineOrchestrator.setAgentDetectWeight": {
27198
+ "pipelineOrchestrator.setAgentInferenceDevices": {
26723
27199
  capName: "pipeline-orchestrator",
26724
27200
  capScope: "system",
26725
27201
  addonId: null,
@@ -26761,6 +27237,12 @@ Object.freeze({
26761
27237
  addonId: null,
26762
27238
  access: "create"
26763
27239
  },
27240
+ "pipelineOrchestrator.setPipelineDevicePin": {
27241
+ capName: "pipeline-orchestrator",
27242
+ capScope: "system",
27243
+ addonId: null,
27244
+ access: "create"
27245
+ },
26764
27246
  "pipelineOrchestrator.unassignAudio": {
26765
27247
  capName: "pipeline-orchestrator",
26766
27248
  capScope: "system",
@@ -28313,32 +28795,6 @@ Object.freeze({
28313
28795
  "network-access": "ingress",
28314
28796
  "smtp-provider": "email"
28315
28797
  });
28316
- var frameworkSwapPackageSchema = object({
28317
- name: string(),
28318
- stagedPath: string(),
28319
- backupPath: string(),
28320
- toVersion: string(),
28321
- fromVersion: string().nullable()
28322
- });
28323
- object({
28324
- jobId: string(),
28325
- taskId: string(),
28326
- packages: array(frameworkSwapPackageSchema),
28327
- requestedAtMs: number(),
28328
- schemaVersion: literal(1)
28329
- });
28330
- object({
28331
- jobId: string(),
28332
- taskId: string(),
28333
- backups: array(object({
28334
- name: string(),
28335
- backupPath: string(),
28336
- livePath: string()
28337
- })),
28338
- appliedAtMs: number(),
28339
- bootAttempts: number(),
28340
- schemaVersion: literal(1)
28341
- });
28342
28798
  /**
28343
28799
  * Pure fuzzy matcher for adoption location import. Normalized
28344
28800
  * case-insensitive Levenshtein similarity, used to reuse an existing