@camstack/addon-provider-homematic 1.1.26 → 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.mjs CHANGED
@@ -7108,6 +7108,17 @@ var ModelCatalogEntrySchema = object({
7108
7108
  "imagenet",
7109
7109
  "none"
7110
7110
  ]).optional(),
7111
+ /**
7112
+ * The model already applies softmax IN-GRAPH — its raw output is a
7113
+ * probability distribution, not logits. When set, the `softmax`
7114
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7115
+ * probability vector collapses it toward uniform (top-1 score craters far
7116
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7117
+ * the output is raw logits and the postprocessor applies softmax (the normal
7118
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7119
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7120
+ */
7121
+ outputProbabilities: boolean().optional(),
7111
7122
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7112
7123
  /**
7113
7124
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -7667,6 +7678,160 @@ var EncodeProfileSchema = object({
7667
7678
  */
7668
7679
  outputArgs: array(string()).optional()
7669
7680
  });
7681
+ var COCO_TO_MACRO = {
7682
+ mapping: {
7683
+ person: "person",
7684
+ bicycle: "vehicle",
7685
+ car: "vehicle",
7686
+ motorcycle: "vehicle",
7687
+ airplane: "vehicle",
7688
+ bus: "vehicle",
7689
+ train: "vehicle",
7690
+ truck: "vehicle",
7691
+ boat: "vehicle",
7692
+ bird: "animal",
7693
+ cat: "animal",
7694
+ dog: "animal",
7695
+ horse: "animal",
7696
+ sheep: "animal",
7697
+ cow: "animal",
7698
+ elephant: "animal",
7699
+ bear: "animal",
7700
+ zebra: "animal",
7701
+ giraffe: "animal",
7702
+ suitcase: "package",
7703
+ backpack: "package",
7704
+ handbag: "package"
7705
+ },
7706
+ preserveOriginal: false
7707
+ };
7708
+ var AUDIO_MACRO_LABELS = [
7709
+ {
7710
+ id: "speech",
7711
+ name: "Speech",
7712
+ icon: "🗣️"
7713
+ },
7714
+ {
7715
+ id: "scream",
7716
+ name: "Scream / Shout",
7717
+ icon: "😱"
7718
+ },
7719
+ {
7720
+ id: "crying",
7721
+ name: "Crying / Baby",
7722
+ icon: "😢"
7723
+ },
7724
+ {
7725
+ id: "laughter",
7726
+ name: "Laughter",
7727
+ icon: "😂"
7728
+ },
7729
+ {
7730
+ id: "music",
7731
+ name: "Music",
7732
+ icon: "🎵"
7733
+ },
7734
+ {
7735
+ id: "dog",
7736
+ name: "Dog",
7737
+ icon: "🐕"
7738
+ },
7739
+ {
7740
+ id: "cat",
7741
+ name: "Cat",
7742
+ icon: "🐈"
7743
+ },
7744
+ {
7745
+ id: "bird",
7746
+ name: "Bird",
7747
+ icon: "🐦"
7748
+ },
7749
+ {
7750
+ id: "animal",
7751
+ name: "Animal (other)",
7752
+ icon: "🐾"
7753
+ },
7754
+ {
7755
+ id: "alarm",
7756
+ name: "Alarm / Siren",
7757
+ icon: "🚨"
7758
+ },
7759
+ {
7760
+ id: "doorbell",
7761
+ name: "Doorbell / Knock",
7762
+ icon: "🔔"
7763
+ },
7764
+ {
7765
+ id: "glass_breaking",
7766
+ name: "Glass Breaking",
7767
+ icon: "💥"
7768
+ },
7769
+ {
7770
+ id: "gunshot",
7771
+ name: "Gunshot / Explosion",
7772
+ icon: "💣"
7773
+ },
7774
+ {
7775
+ id: "vehicle",
7776
+ name: "Vehicle",
7777
+ icon: "🚗"
7778
+ },
7779
+ {
7780
+ id: "siren",
7781
+ name: "Emergency Siren",
7782
+ icon: "🚑"
7783
+ },
7784
+ {
7785
+ id: "fire",
7786
+ name: "Fire / Smoke",
7787
+ icon: "🔥"
7788
+ },
7789
+ {
7790
+ id: "water",
7791
+ name: "Water",
7792
+ icon: "💧"
7793
+ },
7794
+ {
7795
+ id: "wind",
7796
+ name: "Wind / Weather",
7797
+ icon: "🌬️"
7798
+ },
7799
+ {
7800
+ id: "door",
7801
+ name: "Door",
7802
+ icon: "🚪"
7803
+ },
7804
+ {
7805
+ id: "footsteps",
7806
+ name: "Footsteps",
7807
+ icon: "👣"
7808
+ },
7809
+ {
7810
+ id: "crowd",
7811
+ name: "Crowd / Chatter",
7812
+ icon: "👥"
7813
+ },
7814
+ {
7815
+ id: "telephone",
7816
+ name: "Telephone",
7817
+ icon: "📞"
7818
+ },
7819
+ {
7820
+ id: "engine",
7821
+ name: "Engine / Motor",
7822
+ icon: "⚙️"
7823
+ },
7824
+ {
7825
+ id: "tools",
7826
+ name: "Tools / Construction",
7827
+ icon: "🔨"
7828
+ },
7829
+ {
7830
+ id: "silence",
7831
+ name: "Silence",
7832
+ icon: "🤫"
7833
+ }
7834
+ ];
7670
7835
  var YAMNET_TO_MACRO = {
7671
7836
  mapping: {
7672
7837
  Speech: "speech",
@@ -7933,6 +8098,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7933
8098
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7934
8099
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7935
8100
  /**
8101
+ * Unified event-kind taxonomy — THE single source of truth for
8102
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8103
+ * icon }`.
8104
+ *
8105
+ * This dictionary folds together what used to be scattered across four
8106
+ * copies:
8107
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8108
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8109
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8110
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8111
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8112
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8113
+ *
8114
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8115
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8116
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8117
+ * color or an icon: they read this dictionary (server descriptors carry the
8118
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8119
+ *
8120
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8121
+ */
8122
+ var TAXONOMY_COLORS = {
8123
+ motion: "#f59e0b",
8124
+ audio: "#06b6d4",
8125
+ person: "#22c55e",
8126
+ vehicle: "#3b82f6",
8127
+ animal: "#f97316",
8128
+ package: "#a855f7",
8129
+ sensor: "#8b5cf6",
8130
+ control: "#10b981",
8131
+ genericDetection: "#64748b"
8132
+ };
8133
+ var DETECTION_SUB_COLORS = {
8134
+ car: "#f59e0b",
8135
+ truck: "#d97706",
8136
+ bus: "#b45309",
8137
+ motorcycle: "#eab308",
8138
+ bicycle: "#ca8a04",
8139
+ airplane: "#60a5fa",
8140
+ boat: "#2563eb",
8141
+ train: "#1d4ed8",
8142
+ bird: "#14b8a6",
8143
+ dog: "#84cc16",
8144
+ cat: "#f97316",
8145
+ horse: "#a16207",
8146
+ sheep: "#a3a3a3",
8147
+ cow: "#78716c",
8148
+ elephant: "#6b7280",
8149
+ bear: "#7c2d12",
8150
+ zebra: "#404040",
8151
+ giraffe: "#d4a373"
8152
+ };
8153
+ function titleCase(id) {
8154
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8155
+ }
8156
+ var entries = /* @__PURE__ */ new Map();
8157
+ function macro(kind, category, color, iconId, label) {
8158
+ entries.set(kind, {
8159
+ kind,
8160
+ parentKind: null,
8161
+ level: "macro",
8162
+ category,
8163
+ color,
8164
+ iconId,
8165
+ labelKey: `eventKind.${kind}`,
8166
+ label
8167
+ });
8168
+ }
8169
+ function sub(kind, parentKind, category, color, iconId, label) {
8170
+ entries.set(kind, {
8171
+ kind,
8172
+ parentKind,
8173
+ level: "sub",
8174
+ category,
8175
+ color,
8176
+ iconId,
8177
+ labelKey: `eventKind.${kind}`,
8178
+ label
8179
+ });
8180
+ }
8181
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8182
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8183
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8184
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8185
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8186
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8187
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8188
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8189
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8190
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8191
+ if (entries.has(cocoClass)) continue;
8192
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8193
+ }
8194
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8195
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8196
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8197
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8198
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8199
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8200
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8201
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8202
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8203
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8204
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8205
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8206
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8207
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8208
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8209
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8210
+ sub("button", "control", "control", "#10b981", "button", "Button");
8211
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8212
+ for (const l of AUDIO_MACRO_LABELS) {
8213
+ const kind = `audio-${l.id}`;
8214
+ if (entries.has(kind)) continue;
8215
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8216
+ }
8217
+ /** The complete taxonomy dictionary, keyed by kind. */
8218
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8219
+ /**
7936
8220
  * Error types for the safe expression engine. Two distinct classes so callers
7937
8221
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7938
8222
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -12125,10 +12409,7 @@ var ConfigUISchemaNullableBridge = custom();
12125
12409
  var InferenceCapabilitiesBridge = custom();
12126
12410
  var ModelAvailabilityListBridge = custom();
12127
12411
  var PipelineRunResultBridge = custom();
12128
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
12129
- kind: "mutation",
12130
- auth: "admin"
12131
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
12412
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
12132
12413
  modelId: string(),
12133
12414
  settings: record(string(), unknown()).readonly()
12134
12415
  }))), method(object({ steps: record(string(), object({
@@ -12188,13 +12469,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
12188
12469
  * (inputClasses ≠ null) are skipped and served per-track via
12189
12470
  * pipelineRunner.runDetailSubtree (two-plane design).
12190
12471
  */
12191
- plane: _enum(["full", "frame"]).optional()
12472
+ plane: _enum(["full", "frame"]).optional(),
12473
+ /**
12474
+ * Inference-device selector (Phase 2 multi-device). Format
12475
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
12476
+ * Omitted ⇒ the runner's default device (current single-engine
12477
+ * behaviour). Selects WHICH device pool of the node runs the call.
12478
+ */
12479
+ deviceKey: string().optional()
12192
12480
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
12193
12481
  engine: PipelineEngineChoiceSchema.optional(),
12194
12482
  steps: array(PipelineStepInputSchema).min(1),
12195
12483
  frames: array(FrameInputSchema).min(1).max(255),
12196
12484
  deviceId: number().optional(),
12197
- sessionId: string().optional()
12485
+ sessionId: string().optional(),
12486
+ /**
12487
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
12488
+ * the batch to the Python pool's bench preprocess cache
12489
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
12490
+ * preprocessed ONCE and every later inference is a pure-inference cache
12491
+ * hit — the sustained-throughput run measures inference, not
12492
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
12493
+ * full preprocess every call, correct). Fresh per sustained run;
12494
+ * released via `uncacheFrame`.
12495
+ */
12496
+ frameId: number().int().nonnegative().optional(),
12497
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
12498
+ deviceKey: string().optional()
12198
12499
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
12199
12500
  data: _instanceof(Uint8Array),
12200
12501
  width: number().int().positive(),
@@ -12226,8 +12527,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
12226
12527
  * - `runtime` — main camera-serving engine (no idle TTL).
12227
12528
  * - `warm-override` — benchmark/test override held in the warm
12228
12529
  * cache; auto-disposed after the idle TTL.
12530
+ * - `device-pool` — a concurrent per-device pool (Phase 2
12531
+ * multi-device, keyed by `deviceKey`) resolved
12532
+ * via `resolveDeviceFactory`. Runs alongside the
12533
+ * `runtime` engine on a DIFFERENT accelerator
12534
+ * (NPU / iGPU / Coral) — this is how the
12535
+ * Engines tab shows all pools running at once.
12229
12536
  */
12230
- kind: _enum(["runtime", "warm-override"]),
12537
+ kind: _enum([
12538
+ "runtime",
12539
+ "warm-override",
12540
+ "device-pool"
12541
+ ]),
12231
12542
  /** Native pid of the underlying Python pool (null when no pool). */
12232
12543
  poolPid: number().nullable(),
12233
12544
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -12602,7 +12913,14 @@ var RunnerCameraConfigSchema = object({
12602
12913
  * camera's detect node differs from its source-owner (P2d, gated by the
12603
12914
  * `remoteSourcingNodes` rollout setting).
12604
12915
  */
12605
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
12916
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
12917
+ /**
12918
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
12919
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
12920
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
12921
+ * this only selects WHICH device pool of that node runs the session.
12922
+ */
12923
+ deviceKey: string().optional()
12606
12924
  });
12607
12925
  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;
12608
12926
  /**
@@ -12623,6 +12941,19 @@ var RunnerLocalLoadSchema = object({
12623
12941
  avgInferenceTimeMs: number(),
12624
12942
  /** Total queue depth across motion + detection queues. */
12625
12943
  queueDepthTotal: number(),
12944
+ /**
12945
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
12946
+ * this runner currently has attached cameras on, so the orchestrator's second
12947
+ * `balance()` pass (over a node's devices) weights on real per-pool session
12948
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
12949
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
12950
+ */
12951
+ devices: array(object({
12952
+ deviceKey: string(),
12953
+ backend: string(),
12954
+ attachedCameras: number(),
12955
+ queueDepthTotal: number()
12956
+ })).default([]),
12626
12957
  /** Hardware capability flags reported by this node. */
12627
12958
  hardware: object({
12628
12959
  hasGpu: boolean(),
@@ -15771,6 +16102,8 @@ var BaseDeviceProvider = class extends BaseAddon {
15771
16102
  return toDeviceSummary(device, this.addonId);
15772
16103
  }
15773
16104
  };
16105
+ 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;
16106
+ new Set(Object.values(DeviceType));
15774
16107
  /**
15775
16108
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
15776
16109
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -18931,17 +19264,30 @@ var EventKindCategorySchema = _enum([
18931
19264
  "audio",
18932
19265
  "detection",
18933
19266
  "sensor",
19267
+ "control",
18934
19268
  "custom",
18935
19269
  "package"
18936
19270
  ]);
19271
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19272
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
18937
19273
  var EventKindDescriptorSchema = object({
18938
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19274
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
18939
19275
  kind: string(),
19276
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19277
+ labelKey: string(),
19278
+ /** English fallback label (kept for clients that don't translate). */
18940
19279
  label: string(),
18941
19280
  /** Hex color for timeline/legend rendering. */
18942
19281
  color: string(),
19282
+ /** Dictionary id → lucide component on the UI side. */
19283
+ iconId: string(),
19284
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
18943
19285
  icon: EventKindIconSchema,
18944
19286
  category: EventKindCategorySchema,
19287
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19288
+ parentKind: string().nullable(),
19289
+ /** Derived from `parentKind`, explicit for the client tree. */
19290
+ level: EventKindLevelSchema,
18945
19291
  /** Which cap + device contributes this kind. For built-ins the camera
18946
19292
  * itself; for sensor kinds the LINKED source device. */
18947
19293
  source: object({
@@ -19014,11 +19360,21 @@ var TrackAudioLabelSchema = object({
19014
19360
  firstAt: number(),
19015
19361
  lastAt: number()
19016
19362
  });
19363
+ /**
19364
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19365
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19366
+ * linked sensor/control state change (no positions; carries a snapshot). The
19367
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19368
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19369
+ */
19370
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19017
19371
  var TrackSchema = object({
19018
19372
  trackId: string(),
19019
19373
  deviceId: number(),
19020
19374
  className: string(),
19021
19375
  label: string().optional(),
19376
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19377
+ source: TrackSourceSchema.optional(),
19022
19378
  firstSeen: number(),
19023
19379
  lastSeen: number(),
19024
19380
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19395,6 +19751,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19395
19751
  eventId: string(),
19396
19752
  timestamp: number()
19397
19753
  });
19754
+ /**
19755
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19756
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19757
+ * caps into per-camera event-kind descriptors.
19758
+ *
19759
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19760
+ * is NOT duplicated here — every entry is derived from the single
19761
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19762
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19763
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19764
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19765
+ * eventful cap is missing.
19766
+ */
19767
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19768
+ var LEGACY_ICON = {
19769
+ motion: "motion",
19770
+ audio: "audio",
19771
+ person: "person",
19772
+ vehicle: "vehicle",
19773
+ animal: "animal",
19774
+ package: "package",
19775
+ door: "door",
19776
+ pir: "pir",
19777
+ smoke: "smoke",
19778
+ water: "water",
19779
+ button: "button",
19780
+ generic: "generic",
19781
+ gas: "smoke",
19782
+ vibration: "generic",
19783
+ tamper: "generic",
19784
+ presence: "person",
19785
+ lock: "generic",
19786
+ siren: "generic",
19787
+ switch: "generic",
19788
+ doorbell: "button"
19789
+ };
19790
+ function legacyIcon(iconId) {
19791
+ return LEGACY_ICON[iconId] ?? "generic";
19792
+ }
19793
+ /**
19794
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19795
+ * The anti-drift guard cross-checks this against the eventful caps declared
19796
+ * in `packages/types/src/capabilities/*.cap.ts`.
19797
+ */
19798
+ var CAP_TO_KIND = {
19799
+ contact: "contact",
19800
+ motion: "motion-sensor",
19801
+ smoke: "smoke",
19802
+ flood: "flood",
19803
+ gas: "gas",
19804
+ "carbon-monoxide": "carbon-monoxide",
19805
+ vibration: "vibration",
19806
+ tamper: "tamper",
19807
+ presence: "presence",
19808
+ "enum-sensor": "enum-sensor",
19809
+ "event-emitter": "device-event",
19810
+ "lock-control": "lock",
19811
+ switch: "switch",
19812
+ button: "button",
19813
+ doorbell: "doorbell"
19814
+ };
19815
+ function buildDescriptor(capName, kind) {
19816
+ const t = EVENT_TAXONOMY[kind];
19817
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19818
+ return {
19819
+ ...t,
19820
+ icon: legacyIcon(t.iconId)
19821
+ };
19822
+ }
19823
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19398
19824
  var CameraPipelineConfigSchema = object({
19399
19825
  engine: PipelineEngineChoiceSchema.optional(),
19400
19826
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19413,13 +19839,11 @@ var PipelineTemplateSchema = object({
19413
19839
  createdAt: string(),
19414
19840
  updatedAt: string()
19415
19841
  });
19416
- var AgentAddonConfigSchema = object({
19417
- enabled: boolean(),
19842
+ var DeviceStepConfigSchema = object({
19418
19843
  modelId: string().optional(),
19419
- settings: record(string(), unknown()).readonly()
19844
+ settings: record(string(), unknown()).optional()
19420
19845
  });
19421
19846
  var AgentPipelineSettingsSchema = object({
19422
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
19423
19847
  maxCameras: number().int().nonnegative().nullable().default(null),
19424
19848
  /** Per-node detection weight (relative share for the quota balancer). */
19425
19849
  detectWeight: number().positive().optional(),
@@ -19443,7 +19867,22 @@ var AgentPipelineSettingsSchema = object({
19443
19867
  * it already uses to reach the hub). Set this only when the auto-detected
19444
19868
  * address is wrong (multi-homed host, NAT, custom interface).
19445
19869
  */
19446
- reachableHost: string().optional()
19870
+ reachableHost: string().optional(),
19871
+ /**
19872
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
19873
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
19874
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
19875
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
19876
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
19877
+ * the default model/settings for every camera landing on that accelerator;
19878
+ * a stepId absent ⇒ the step uses that device's format default.
19879
+ */
19880
+ inferenceDevices: record(string(), object({
19881
+ enabled: boolean(),
19882
+ weight: number().positive().optional(),
19883
+ maxSessions: number().int().positive().optional(),
19884
+ steps: record(string(), DeviceStepConfigSchema).optional()
19885
+ })).optional()
19447
19886
  });
19448
19887
  var CameraPipelineForAgentSchema = object({
19449
19888
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19453,14 +19892,13 @@ var CameraPipelineForAgentSchema = object({
19453
19892
  }).nullable()
19454
19893
  });
19455
19894
  var CameraStepOverridePatchSchema = object({
19456
- enabled: boolean().optional(),
19457
19895
  modelId: string().optional(),
19458
19896
  settings: record(string(), unknown()).readonly().optional()
19459
19897
  });
19460
19898
  var CameraPipelineSettingsSchema = object({
19461
19899
  pinnedAgentNodeId: string().optional(),
19462
19900
  stepToggles: record(string(), boolean()).optional(),
19463
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
19901
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
19464
19902
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
19465
19903
  });
19466
19904
  /**
@@ -19674,6 +20112,44 @@ var CameraStatusSchema = object({
19674
20112
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
19675
20113
  fetchedAt: number()
19676
20114
  });
20115
+ var NodeInferenceDeviceSchema = object({
20116
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
20117
+ key: string(),
20118
+ backend: string(),
20119
+ device: string(),
20120
+ format: _enum(MODEL_FORMATS),
20121
+ /** Whether the node's live probe reports the device as usable right now. */
20122
+ available: boolean(),
20123
+ /**
20124
+ * Whether this device participates in dispatch. AUTO default (spec C2):
20125
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
20126
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
20127
+ * not a balanced target). An explicit stored value always wins; a stored-only
20128
+ * (unavailable) key keeps its stored value.
20129
+ */
20130
+ enabled: boolean(),
20131
+ /** Relative balancer weight for the enabled device (default 1). */
20132
+ weight: number(),
20133
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
20134
+ maxSessions: number().nullable(),
20135
+ /** Object-detection model the executor defaults to for this deviceKey. */
20136
+ defaultModelId: string(),
20137
+ /**
20138
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
20139
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
20140
+ * Absent/empty ⇒ no base (every step uses its device format default). The
20141
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
20142
+ * available per format; this is the stored selection that becomes the
20143
+ * default for EVERY camera landing on this accelerator.
20144
+ */
20145
+ steps: record(string(), DeviceStepConfigSchema).optional()
20146
+ });
20147
+ var NodeInferenceDevicesSchema = object({
20148
+ nodeId: string(),
20149
+ /** False when the node's platform-probe was unreachable (no live device set). */
20150
+ reachable: boolean(),
20151
+ devices: array(NodeInferenceDeviceSchema).readonly()
20152
+ });
19677
20153
  method(object({
19678
20154
  deviceId: number(),
19679
20155
  agentNodeId: string()
@@ -19683,7 +20159,13 @@ method(object({
19683
20159
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
19684
20160
  kind: "mutation",
19685
20161
  auth: "admin"
19686
- }), method(_void(), object({ migrated: number() }), {
20162
+ }), method(object({
20163
+ deviceId: number(),
20164
+ deviceKey: string()
20165
+ }), object({ success: literal(true) }), {
20166
+ kind: "mutation",
20167
+ auth: "admin"
20168
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
19687
20169
  kind: "mutation",
19688
20170
  auth: "admin"
19689
20171
  }), 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({
@@ -19717,13 +20199,7 @@ method(object({
19717
20199
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
19718
20200
  nodeId: string(),
19719
20201
  settings: AgentPipelineSettingsSchema
19720
- })).readonly()), method(object({
19721
- agentNodeId: string(),
19722
- defaults: record(string(), AgentAddonConfigSchema)
19723
- }), object({ success: literal(true) }), {
19724
- kind: "mutation",
19725
- auth: "admin"
19726
- }), method(object({ agentNodeId: string() }), object({
20202
+ })).readonly()), method(object({ agentNodeId: string() }), object({
19727
20203
  success: boolean(),
19728
20204
  removed: boolean()
19729
20205
  }), {
@@ -19755,7 +20231,18 @@ method(object({
19755
20231
  }), object({ success: literal(true) }), {
19756
20232
  kind: "mutation",
19757
20233
  auth: "admin"
19758
- }), method(object({ agentNodeId: string() }), object({
20234
+ }), method(object({
20235
+ agentNodeId: string(),
20236
+ inferenceDevices: record(string(), object({
20237
+ enabled: boolean(),
20238
+ weight: number().positive().optional(),
20239
+ maxSessions: number().int().positive().optional(),
20240
+ steps: record(string(), DeviceStepConfigSchema).optional()
20241
+ }))
20242
+ }), object({ success: literal(true) }), {
20243
+ kind: "mutation",
20244
+ auth: "admin"
20245
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
19759
20246
  success: literal(true),
19760
20247
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
19761
20248
  effectiveModelId: string().nullable(),
@@ -19771,9 +20258,10 @@ method(object({
19771
20258
  }), object({ success: literal(true) }), {
19772
20259
  kind: "mutation",
19773
20260
  auth: "admin"
19774
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
20261
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
19775
20262
  deviceId: number(),
19776
20263
  agentNodeId: string(),
20264
+ deviceKey: string(),
19777
20265
  addonId: string(),
19778
20266
  patch: CameraStepOverridePatchSchema.nullable()
19779
20267
  }), object({ success: literal(true) }), {
@@ -19810,14 +20298,13 @@ method(object({
19810
20298
  });
19811
20299
  /**
19812
20300
  * server-management — per-NODE singleton capability for a node's ROOT
19813
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
19814
- * agents).
20301
+ * package lifecycle (runtime-updatable node packages).
19815
20302
  *
19816
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
19817
- * on agents) carries the whole software stack in its npm dep tree, so ONE
19818
- * version describes the node. Updates install into
19819
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
19820
- * starter (probation boot + auto-rollback to N-1).
20303
+ * Every node role runs the SAME root package (`@camstack/server`), which
20304
+ * carries the whole software stack in its npm dep tree, so ONE version
20305
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
20306
+ * on restart via the baked starter (single-copy in-place swap — no probation,
20307
+ * no auto-rollback).
19821
20308
  *
19822
20309
  * Providers:
19823
20310
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -19925,7 +20412,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
19925
20412
  /** Explicit target version; omitted = latest from the registry. */
19926
20413
  version: string().optional() }), ServerUpdateActionResultSchema, {
19927
20414
  kind: "mutation",
19928
- auth: "admin"
20415
+ auth: "admin",
20416
+ timeoutMs: 16 * 6e4
19929
20417
  }), method(_void(), ServerUpdateActionResultSchema, {
19930
20418
  kind: "mutation",
19931
20419
  auth: "admin"
@@ -20969,22 +21457,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
20969
21457
  var RestartAddonResultSchema = unknown();
20970
21458
  var InstallPackageResultSchema = unknown();
20971
21459
  var ReloadPackagesResultSchema = unknown();
20972
- /**
20973
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
20974
- * server restarts so the admin UI can react to the `restartingAt`
20975
- * timestamp (shows reconnect overlay). The transition from
20976
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
20977
- * `system.restart-completed` event after the new process boots.
20978
- *
20979
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
20980
- */
20981
- var UpdateFrameworkPackageResultSchema = object({
20982
- packageName: string(),
20983
- fromVersion: string(),
20984
- toVersion: string(),
20985
- /** Ms-epoch the server scheduled its self-restart. */
20986
- restartingAt: number()
20987
- });
20988
21460
  var BulkUpdateItemStatusSchema = _enum([
20989
21461
  "queued",
20990
21462
  "updating",
@@ -21112,13 +21584,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
21112
21584
  }), object({ success: literal(true) }), {
21113
21585
  kind: "mutation",
21114
21586
  auth: "admin"
21115
- }), method(object({
21116
- packageName: string().min(1),
21117
- version: string().optional(),
21118
- deferRestart: boolean().optional()
21119
- }), UpdateFrameworkPackageResultSchema, {
21120
- kind: "mutation",
21121
- auth: "admin"
21122
21587
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
21123
21588
  kind: "mutation",
21124
21589
  auth: "admin"
@@ -21984,10 +22449,10 @@ var TopologyCategorySchema = object({
21984
22449
  addons: array(TopologyCategoryAddonSchema).readonly()
21985
22450
  });
21986
22451
  /**
21987
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
21988
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
21989
- * version visibility for the Server management surface. Nullable: offline
21990
- * rows and pre-phase-2 nodes report none.
22452
+ * The node's runtime-updatable ROOT package (`@camstack/server` — the single
22453
+ * root package for every node role) as reported by its `registerNode`
22454
+ * manifest — version visibility for the Server management surface. Nullable:
22455
+ * offline rows and nodes that never reported one.
21991
22456
  */
21992
22457
  var TopologyRootPackageSchema = object({
21993
22458
  name: string(),
@@ -22375,17 +22840,28 @@ var PlatformScoreSchema = object({
22375
22840
  format: _enum([
22376
22841
  "onnx",
22377
22842
  "coreml",
22378
- "openvino"
22843
+ "openvino",
22844
+ "tflite"
22379
22845
  ]),
22380
22846
  score: number(),
22381
22847
  reason: string(),
22382
22848
  available: boolean()
22383
22849
  });
22850
+ var InferenceDeviceDescriptorSchema = object({
22851
+ key: string(),
22852
+ backend: string(),
22853
+ device: string(),
22854
+ format: ModelFormatSchema,
22855
+ runtime: literal("python"),
22856
+ score: number(),
22857
+ available: boolean()
22858
+ });
22384
22859
  var PlatformCapabilitiesSchema = object({
22385
22860
  hardware: HardwareInfoSchema,
22386
22861
  scores: array(PlatformScoreSchema).readonly(),
22387
22862
  bestScore: PlatformScoreSchema,
22388
- pythonPath: string().nullable()
22863
+ pythonPath: string().nullable(),
22864
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
22389
22865
  });
22390
22866
  var ModelRequirementSchema = object({
22391
22867
  modelId: string(),
@@ -23264,12 +23740,6 @@ Object.freeze({
23264
23740
  addonId: null,
23265
23741
  access: "delete"
23266
23742
  },
23267
- "addons.updateFrameworkPackage": {
23268
- capName: "addons",
23269
- capScope: "system",
23270
- addonId: null,
23271
- access: "create"
23272
- },
23273
23743
  "addons.updatePackage": {
23274
23744
  capName: "addons",
23275
23745
  capScope: "system",
@@ -26042,12 +26512,6 @@ Object.freeze({
26042
26512
  addonId: null,
26043
26513
  access: "view"
26044
26514
  },
26045
- "pipelineExecutor.reprobeEngine": {
26046
- capName: "pipeline-executor",
26047
- capScope: "system",
26048
- addonId: null,
26049
- access: "create"
26050
- },
26051
26515
  "pipelineExecutor.runAudioTest": {
26052
26516
  capName: "pipeline-executor",
26053
26517
  capScope: "system",
@@ -26198,6 +26662,12 @@ Object.freeze({
26198
26662
  addonId: null,
26199
26663
  access: "view"
26200
26664
  },
26665
+ "pipelineOrchestrator.getNodeInferenceDevices": {
26666
+ capName: "pipeline-orchestrator",
26667
+ capScope: "system",
26668
+ addonId: null,
26669
+ access: "view"
26670
+ },
26201
26671
  "pipelineOrchestrator.getPipelineAssignment": {
26202
26672
  capName: "pipeline-orchestrator",
26203
26673
  capScope: "system",
@@ -26210,6 +26680,12 @@ Object.freeze({
26210
26680
  addonId: null,
26211
26681
  access: "view"
26212
26682
  },
26683
+ "pipelineOrchestrator.getPipelineDevicePin": {
26684
+ capName: "pipeline-orchestrator",
26685
+ capScope: "system",
26686
+ addonId: null,
26687
+ access: "view"
26688
+ },
26213
26689
  "pipelineOrchestrator.listAgentSettings": {
26214
26690
  capName: "pipeline-orchestrator",
26215
26691
  capScope: "system",
@@ -26252,19 +26728,19 @@ Object.freeze({
26252
26728
  addonId: null,
26253
26729
  access: "create"
26254
26730
  },
26255
- "pipelineOrchestrator.setAgentAddonDefaults": {
26731
+ "pipelineOrchestrator.setAgentCapabilities": {
26256
26732
  capName: "pipeline-orchestrator",
26257
26733
  capScope: "system",
26258
26734
  addonId: null,
26259
26735
  access: "create"
26260
26736
  },
26261
- "pipelineOrchestrator.setAgentCapabilities": {
26737
+ "pipelineOrchestrator.setAgentDetectWeight": {
26262
26738
  capName: "pipeline-orchestrator",
26263
26739
  capScope: "system",
26264
26740
  addonId: null,
26265
26741
  access: "create"
26266
26742
  },
26267
- "pipelineOrchestrator.setAgentDetectWeight": {
26743
+ "pipelineOrchestrator.setAgentInferenceDevices": {
26268
26744
  capName: "pipeline-orchestrator",
26269
26745
  capScope: "system",
26270
26746
  addonId: null,
@@ -26306,6 +26782,12 @@ Object.freeze({
26306
26782
  addonId: null,
26307
26783
  access: "create"
26308
26784
  },
26785
+ "pipelineOrchestrator.setPipelineDevicePin": {
26786
+ capName: "pipeline-orchestrator",
26787
+ capScope: "system",
26788
+ addonId: null,
26789
+ access: "create"
26790
+ },
26309
26791
  "pipelineOrchestrator.unassignAudio": {
26310
26792
  capName: "pipeline-orchestrator",
26311
26793
  capScope: "system",
@@ -27858,32 +28340,6 @@ Object.freeze({
27858
28340
  "network-access": "ingress",
27859
28341
  "smtp-provider": "email"
27860
28342
  });
27861
- var frameworkSwapPackageSchema = object({
27862
- name: string(),
27863
- stagedPath: string(),
27864
- backupPath: string(),
27865
- toVersion: string(),
27866
- fromVersion: string().nullable()
27867
- });
27868
- object({
27869
- jobId: string(),
27870
- taskId: string(),
27871
- packages: array(frameworkSwapPackageSchema),
27872
- requestedAtMs: number(),
27873
- schemaVersion: literal(1)
27874
- });
27875
- object({
27876
- jobId: string(),
27877
- taskId: string(),
27878
- backups: array(object({
27879
- name: string(),
27880
- backupPath: string(),
27881
- livePath: string()
27882
- })),
27883
- appliedAtMs: number(),
27884
- bootAttempts: number(),
27885
- schemaVersion: literal(1)
27886
- });
27887
28343
  /**
27888
28344
  * Pure fuzzy matcher for adoption location import. Normalized
27889
28345
  * case-insensitive Levenshtein similarity, used to reuse an existing