@camstack/addon-provider-dreo 0.1.22 → 0.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 +561 -105
  2. package/dist/addon.mjs +561 -105
  3. package/package.json +1 -1
package/dist/addon.mjs CHANGED
@@ -7121,6 +7121,17 @@ var ModelCatalogEntrySchema = object({
7121
7121
  "imagenet",
7122
7122
  "none"
7123
7123
  ]).optional(),
7124
+ /**
7125
+ * The model already applies softmax IN-GRAPH — its raw output is a
7126
+ * probability distribution, not logits. When set, the `softmax`
7127
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7128
+ * probability vector collapses it toward uniform (top-1 score craters far
7129
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7130
+ * the output is raw logits and the postprocessor applies softmax (the normal
7131
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7132
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7133
+ */
7134
+ outputProbabilities: boolean().optional(),
7124
7135
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7125
7136
  /**
7126
7137
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -7680,6 +7691,160 @@ var EncodeProfileSchema = object({
7680
7691
  */
7681
7692
  outputArgs: array(string()).optional()
7682
7693
  });
7694
+ var COCO_TO_MACRO = {
7695
+ mapping: {
7696
+ person: "person",
7697
+ bicycle: "vehicle",
7698
+ car: "vehicle",
7699
+ motorcycle: "vehicle",
7700
+ airplane: "vehicle",
7701
+ bus: "vehicle",
7702
+ train: "vehicle",
7703
+ truck: "vehicle",
7704
+ boat: "vehicle",
7705
+ bird: "animal",
7706
+ cat: "animal",
7707
+ dog: "animal",
7708
+ horse: "animal",
7709
+ sheep: "animal",
7710
+ cow: "animal",
7711
+ elephant: "animal",
7712
+ bear: "animal",
7713
+ zebra: "animal",
7714
+ giraffe: "animal",
7715
+ suitcase: "package",
7716
+ backpack: "package",
7717
+ handbag: "package"
7718
+ },
7719
+ preserveOriginal: false
7720
+ };
7721
+ var AUDIO_MACRO_LABELS = [
7722
+ {
7723
+ id: "speech",
7724
+ name: "Speech",
7725
+ icon: "🗣️"
7726
+ },
7727
+ {
7728
+ id: "scream",
7729
+ name: "Scream / Shout",
7730
+ icon: "😱"
7731
+ },
7732
+ {
7733
+ id: "crying",
7734
+ name: "Crying / Baby",
7735
+ icon: "😢"
7736
+ },
7737
+ {
7738
+ id: "laughter",
7739
+ name: "Laughter",
7740
+ icon: "😂"
7741
+ },
7742
+ {
7743
+ id: "music",
7744
+ name: "Music",
7745
+ icon: "🎵"
7746
+ },
7747
+ {
7748
+ id: "dog",
7749
+ name: "Dog",
7750
+ icon: "🐕"
7751
+ },
7752
+ {
7753
+ id: "cat",
7754
+ name: "Cat",
7755
+ icon: "🐈"
7756
+ },
7757
+ {
7758
+ id: "bird",
7759
+ name: "Bird",
7760
+ icon: "🐦"
7761
+ },
7762
+ {
7763
+ id: "animal",
7764
+ name: "Animal (other)",
7765
+ icon: "🐾"
7766
+ },
7767
+ {
7768
+ id: "alarm",
7769
+ name: "Alarm / Siren",
7770
+ icon: "🚨"
7771
+ },
7772
+ {
7773
+ id: "doorbell",
7774
+ name: "Doorbell / Knock",
7775
+ icon: "🔔"
7776
+ },
7777
+ {
7778
+ id: "glass_breaking",
7779
+ name: "Glass Breaking",
7780
+ icon: "💥"
7781
+ },
7782
+ {
7783
+ id: "gunshot",
7784
+ name: "Gunshot / Explosion",
7785
+ icon: "💣"
7786
+ },
7787
+ {
7788
+ id: "vehicle",
7789
+ name: "Vehicle",
7790
+ icon: "🚗"
7791
+ },
7792
+ {
7793
+ id: "siren",
7794
+ name: "Emergency Siren",
7795
+ icon: "🚑"
7796
+ },
7797
+ {
7798
+ id: "fire",
7799
+ name: "Fire / Smoke",
7800
+ icon: "🔥"
7801
+ },
7802
+ {
7803
+ id: "water",
7804
+ name: "Water",
7805
+ icon: "💧"
7806
+ },
7807
+ {
7808
+ id: "wind",
7809
+ name: "Wind / Weather",
7810
+ icon: "🌬️"
7811
+ },
7812
+ {
7813
+ id: "door",
7814
+ name: "Door",
7815
+ icon: "🚪"
7816
+ },
7817
+ {
7818
+ id: "footsteps",
7819
+ name: "Footsteps",
7820
+ icon: "👣"
7821
+ },
7822
+ {
7823
+ id: "crowd",
7824
+ name: "Crowd / Chatter",
7825
+ icon: "👥"
7826
+ },
7827
+ {
7828
+ id: "telephone",
7829
+ name: "Telephone",
7830
+ icon: "📞"
7831
+ },
7832
+ {
7833
+ id: "engine",
7834
+ name: "Engine / Motor",
7835
+ icon: "⚙️"
7836
+ },
7837
+ {
7838
+ id: "tools",
7839
+ name: "Tools / Construction",
7840
+ icon: "🔨"
7841
+ },
7842
+ {
7843
+ id: "silence",
7844
+ name: "Silence",
7845
+ icon: "🤫"
7846
+ }
7847
+ ];
7683
7848
  var YAMNET_TO_MACRO = {
7684
7849
  mapping: {
7685
7850
  Speech: "speech",
@@ -7946,6 +8111,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7946
8111
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7947
8112
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7948
8113
  /**
8114
+ * Unified event-kind taxonomy — THE single source of truth for
8115
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8116
+ * icon }`.
8117
+ *
8118
+ * This dictionary folds together what used to be scattered across four
8119
+ * copies:
8120
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8121
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8122
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8123
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8124
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8125
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8126
+ *
8127
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8128
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8129
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8130
+ * color or an icon: they read this dictionary (server descriptors carry the
8131
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8132
+ *
8133
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8134
+ */
8135
+ var TAXONOMY_COLORS = {
8136
+ motion: "#f59e0b",
8137
+ audio: "#06b6d4",
8138
+ person: "#22c55e",
8139
+ vehicle: "#3b82f6",
8140
+ animal: "#f97316",
8141
+ package: "#a855f7",
8142
+ sensor: "#8b5cf6",
8143
+ control: "#10b981",
8144
+ genericDetection: "#64748b"
8145
+ };
8146
+ var DETECTION_SUB_COLORS = {
8147
+ car: "#f59e0b",
8148
+ truck: "#d97706",
8149
+ bus: "#b45309",
8150
+ motorcycle: "#eab308",
8151
+ bicycle: "#ca8a04",
8152
+ airplane: "#60a5fa",
8153
+ boat: "#2563eb",
8154
+ train: "#1d4ed8",
8155
+ bird: "#14b8a6",
8156
+ dog: "#84cc16",
8157
+ cat: "#f97316",
8158
+ horse: "#a16207",
8159
+ sheep: "#a3a3a3",
8160
+ cow: "#78716c",
8161
+ elephant: "#6b7280",
8162
+ bear: "#7c2d12",
8163
+ zebra: "#404040",
8164
+ giraffe: "#d4a373"
8165
+ };
8166
+ function titleCase(id) {
8167
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8168
+ }
8169
+ var entries = /* @__PURE__ */ new Map();
8170
+ function macro(kind, category, color, iconId, label) {
8171
+ entries.set(kind, {
8172
+ kind,
8173
+ parentKind: null,
8174
+ level: "macro",
8175
+ category,
8176
+ color,
8177
+ iconId,
8178
+ labelKey: `eventKind.${kind}`,
8179
+ label
8180
+ });
8181
+ }
8182
+ function sub(kind, parentKind, category, color, iconId, label) {
8183
+ entries.set(kind, {
8184
+ kind,
8185
+ parentKind,
8186
+ level: "sub",
8187
+ category,
8188
+ color,
8189
+ iconId,
8190
+ labelKey: `eventKind.${kind}`,
8191
+ label
8192
+ });
8193
+ }
8194
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8195
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8196
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8197
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8198
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8199
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8200
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8201
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8202
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8203
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8204
+ if (entries.has(cocoClass)) continue;
8205
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8206
+ }
8207
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8208
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8209
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8210
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8211
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8212
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8213
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8214
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8215
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8216
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8217
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8218
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8219
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8220
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8221
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8222
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8223
+ sub("button", "control", "control", "#10b981", "button", "Button");
8224
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8225
+ for (const l of AUDIO_MACRO_LABELS) {
8226
+ const kind = `audio-${l.id}`;
8227
+ if (entries.has(kind)) continue;
8228
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8229
+ }
8230
+ /** The complete taxonomy dictionary, keyed by kind. */
8231
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8232
+ /**
7949
8233
  * Error types for the safe expression engine. Two distinct classes so callers
7950
8234
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7951
8235
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -12138,10 +12422,7 @@ var ConfigUISchemaNullableBridge = custom();
12138
12422
  var InferenceCapabilitiesBridge = custom();
12139
12423
  var ModelAvailabilityListBridge = custom();
12140
12424
  var PipelineRunResultBridge = custom();
12141
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
12142
- kind: "mutation",
12143
- auth: "admin"
12144
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
12425
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
12145
12426
  modelId: string(),
12146
12427
  settings: record(string(), unknown()).readonly()
12147
12428
  }))), method(object({ steps: record(string(), object({
@@ -12201,13 +12482,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
12201
12482
  * (inputClasses ≠ null) are skipped and served per-track via
12202
12483
  * pipelineRunner.runDetailSubtree (two-plane design).
12203
12484
  */
12204
- plane: _enum(["full", "frame"]).optional()
12485
+ plane: _enum(["full", "frame"]).optional(),
12486
+ /**
12487
+ * Inference-device selector (Phase 2 multi-device). Format
12488
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
12489
+ * Omitted ⇒ the runner's default device (current single-engine
12490
+ * behaviour). Selects WHICH device pool of the node runs the call.
12491
+ */
12492
+ deviceKey: string().optional()
12205
12493
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
12206
12494
  engine: PipelineEngineChoiceSchema.optional(),
12207
12495
  steps: array(PipelineStepInputSchema).min(1),
12208
12496
  frames: array(FrameInputSchema).min(1).max(255),
12209
12497
  deviceId: number().optional(),
12210
- sessionId: string().optional()
12498
+ sessionId: string().optional(),
12499
+ /**
12500
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
12501
+ * the batch to the Python pool's bench preprocess cache
12502
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
12503
+ * preprocessed ONCE and every later inference is a pure-inference cache
12504
+ * hit — the sustained-throughput run measures inference, not
12505
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
12506
+ * full preprocess every call, correct). Fresh per sustained run;
12507
+ * released via `uncacheFrame`.
12508
+ */
12509
+ frameId: number().int().nonnegative().optional(),
12510
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
12511
+ deviceKey: string().optional()
12211
12512
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
12212
12513
  data: _instanceof(Uint8Array),
12213
12514
  width: number().int().positive(),
@@ -12239,8 +12540,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
12239
12540
  * - `runtime` — main camera-serving engine (no idle TTL).
12240
12541
  * - `warm-override` — benchmark/test override held in the warm
12241
12542
  * cache; auto-disposed after the idle TTL.
12543
+ * - `device-pool` — a concurrent per-device pool (Phase 2
12544
+ * multi-device, keyed by `deviceKey`) resolved
12545
+ * via `resolveDeviceFactory`. Runs alongside the
12546
+ * `runtime` engine on a DIFFERENT accelerator
12547
+ * (NPU / iGPU / Coral) — this is how the
12548
+ * Engines tab shows all pools running at once.
12242
12549
  */
12243
- kind: _enum(["runtime", "warm-override"]),
12550
+ kind: _enum([
12551
+ "runtime",
12552
+ "warm-override",
12553
+ "device-pool"
12554
+ ]),
12244
12555
  /** Native pid of the underlying Python pool (null when no pool). */
12245
12556
  poolPid: number().nullable(),
12246
12557
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -12615,7 +12926,14 @@ var RunnerCameraConfigSchema = object({
12615
12926
  * camera's detect node differs from its source-owner (P2d, gated by the
12616
12927
  * `remoteSourcingNodes` rollout setting).
12617
12928
  */
12618
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
12929
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
12930
+ /**
12931
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
12932
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
12933
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
12934
+ * this only selects WHICH device pool of that node runs the session.
12935
+ */
12936
+ deviceKey: string().optional()
12619
12937
  });
12620
12938
  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;
12621
12939
  /**
@@ -12636,6 +12954,19 @@ var RunnerLocalLoadSchema = object({
12636
12954
  avgInferenceTimeMs: number(),
12637
12955
  /** Total queue depth across motion + detection queues. */
12638
12956
  queueDepthTotal: number(),
12957
+ /**
12958
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
12959
+ * this runner currently has attached cameras on, so the orchestrator's second
12960
+ * `balance()` pass (over a node's devices) weights on real per-pool session
12961
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
12962
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
12963
+ */
12964
+ devices: array(object({
12965
+ deviceKey: string(),
12966
+ backend: string(),
12967
+ attachedCameras: number(),
12968
+ queueDepthTotal: number()
12969
+ })).default([]),
12639
12970
  /** Hardware capability flags reported by this node. */
12640
12971
  hardware: object({
12641
12972
  hasGpu: boolean(),
@@ -15784,6 +16115,8 @@ var BaseDeviceProvider = class extends BaseAddon {
15784
16115
  return toDeviceSummary(device, this.addonId);
15785
16116
  }
15786
16117
  };
16118
+ 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;
16119
+ new Set(Object.values(DeviceType));
15787
16120
  /**
15788
16121
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
15789
16122
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -18895,17 +19228,30 @@ var EventKindCategorySchema = _enum([
18895
19228
  "audio",
18896
19229
  "detection",
18897
19230
  "sensor",
19231
+ "control",
18898
19232
  "custom",
18899
19233
  "package"
18900
19234
  ]);
19235
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19236
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
18901
19237
  var EventKindDescriptorSchema = object({
18902
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19238
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
18903
19239
  kind: string(),
19240
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19241
+ labelKey: string(),
19242
+ /** English fallback label (kept for clients that don't translate). */
18904
19243
  label: string(),
18905
19244
  /** Hex color for timeline/legend rendering. */
18906
19245
  color: string(),
19246
+ /** Dictionary id → lucide component on the UI side. */
19247
+ iconId: string(),
19248
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
18907
19249
  icon: EventKindIconSchema,
18908
19250
  category: EventKindCategorySchema,
19251
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19252
+ parentKind: string().nullable(),
19253
+ /** Derived from `parentKind`, explicit for the client tree. */
19254
+ level: EventKindLevelSchema,
18909
19255
  /** Which cap + device contributes this kind. For built-ins the camera
18910
19256
  * itself; for sensor kinds the LINKED source device. */
18911
19257
  source: object({
@@ -18978,11 +19324,21 @@ var TrackAudioLabelSchema = object({
18978
19324
  firstAt: number(),
18979
19325
  lastAt: number()
18980
19326
  });
19327
+ /**
19328
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19329
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19330
+ * linked sensor/control state change (no positions; carries a snapshot). The
19331
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19332
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19333
+ */
19334
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
18981
19335
  var TrackSchema = object({
18982
19336
  trackId: string(),
18983
19337
  deviceId: number(),
18984
19338
  className: string(),
18985
19339
  label: string().optional(),
19340
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19341
+ source: TrackSourceSchema.optional(),
18986
19342
  firstSeen: number(),
18987
19343
  lastSeen: number(),
18988
19344
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19359,6 +19715,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19359
19715
  eventId: string(),
19360
19716
  timestamp: number()
19361
19717
  });
19718
+ /**
19719
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19720
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19721
+ * caps into per-camera event-kind descriptors.
19722
+ *
19723
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19724
+ * is NOT duplicated here — every entry is derived from the single
19725
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19726
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19727
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19728
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19729
+ * eventful cap is missing.
19730
+ */
19731
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19732
+ var LEGACY_ICON = {
19733
+ motion: "motion",
19734
+ audio: "audio",
19735
+ person: "person",
19736
+ vehicle: "vehicle",
19737
+ animal: "animal",
19738
+ package: "package",
19739
+ door: "door",
19740
+ pir: "pir",
19741
+ smoke: "smoke",
19742
+ water: "water",
19743
+ button: "button",
19744
+ generic: "generic",
19745
+ gas: "smoke",
19746
+ vibration: "generic",
19747
+ tamper: "generic",
19748
+ presence: "person",
19749
+ lock: "generic",
19750
+ siren: "generic",
19751
+ switch: "generic",
19752
+ doorbell: "button"
19753
+ };
19754
+ function legacyIcon(iconId) {
19755
+ return LEGACY_ICON[iconId] ?? "generic";
19756
+ }
19757
+ /**
19758
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19759
+ * The anti-drift guard cross-checks this against the eventful caps declared
19760
+ * in `packages/types/src/capabilities/*.cap.ts`.
19761
+ */
19762
+ var CAP_TO_KIND = {
19763
+ contact: "contact",
19764
+ motion: "motion-sensor",
19765
+ smoke: "smoke",
19766
+ flood: "flood",
19767
+ gas: "gas",
19768
+ "carbon-monoxide": "carbon-monoxide",
19769
+ vibration: "vibration",
19770
+ tamper: "tamper",
19771
+ presence: "presence",
19772
+ "enum-sensor": "enum-sensor",
19773
+ "event-emitter": "device-event",
19774
+ "lock-control": "lock",
19775
+ switch: "switch",
19776
+ button: "button",
19777
+ doorbell: "doorbell"
19778
+ };
19779
+ function buildDescriptor(capName, kind) {
19780
+ const t = EVENT_TAXONOMY[kind];
19781
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19782
+ return {
19783
+ ...t,
19784
+ icon: legacyIcon(t.iconId)
19785
+ };
19786
+ }
19787
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19362
19788
  var CameraPipelineConfigSchema = object({
19363
19789
  engine: PipelineEngineChoiceSchema.optional(),
19364
19790
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19377,13 +19803,11 @@ var PipelineTemplateSchema = object({
19377
19803
  createdAt: string(),
19378
19804
  updatedAt: string()
19379
19805
  });
19380
- var AgentAddonConfigSchema = object({
19381
- enabled: boolean(),
19806
+ var DeviceStepConfigSchema = object({
19382
19807
  modelId: string().optional(),
19383
- settings: record(string(), unknown()).readonly()
19808
+ settings: record(string(), unknown()).optional()
19384
19809
  });
19385
19810
  var AgentPipelineSettingsSchema = object({
19386
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
19387
19811
  maxCameras: number().int().nonnegative().nullable().default(null),
19388
19812
  /** Per-node detection weight (relative share for the quota balancer). */
19389
19813
  detectWeight: number().positive().optional(),
@@ -19407,7 +19831,22 @@ var AgentPipelineSettingsSchema = object({
19407
19831
  * it already uses to reach the hub). Set this only when the auto-detected
19408
19832
  * address is wrong (multi-homed host, NAT, custom interface).
19409
19833
  */
19410
- reachableHost: string().optional()
19834
+ reachableHost: string().optional(),
19835
+ /**
19836
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
19837
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
19838
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
19839
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
19840
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
19841
+ * the default model/settings for every camera landing on that accelerator;
19842
+ * a stepId absent ⇒ the step uses that device's format default.
19843
+ */
19844
+ inferenceDevices: record(string(), object({
19845
+ enabled: boolean(),
19846
+ weight: number().positive().optional(),
19847
+ maxSessions: number().int().positive().optional(),
19848
+ steps: record(string(), DeviceStepConfigSchema).optional()
19849
+ })).optional()
19411
19850
  });
19412
19851
  var CameraPipelineForAgentSchema = object({
19413
19852
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19417,14 +19856,13 @@ var CameraPipelineForAgentSchema = object({
19417
19856
  }).nullable()
19418
19857
  });
19419
19858
  var CameraStepOverridePatchSchema = object({
19420
- enabled: boolean().optional(),
19421
19859
  modelId: string().optional(),
19422
19860
  settings: record(string(), unknown()).readonly().optional()
19423
19861
  });
19424
19862
  var CameraPipelineSettingsSchema = object({
19425
19863
  pinnedAgentNodeId: string().optional(),
19426
19864
  stepToggles: record(string(), boolean()).optional(),
19427
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
19865
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
19428
19866
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
19429
19867
  });
19430
19868
  /**
@@ -19638,6 +20076,44 @@ var CameraStatusSchema = object({
19638
20076
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
19639
20077
  fetchedAt: number()
19640
20078
  });
20079
+ var NodeInferenceDeviceSchema = object({
20080
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
20081
+ key: string(),
20082
+ backend: string(),
20083
+ device: string(),
20084
+ format: _enum(MODEL_FORMATS),
20085
+ /** Whether the node's live probe reports the device as usable right now. */
20086
+ available: boolean(),
20087
+ /**
20088
+ * Whether this device participates in dispatch. AUTO default (spec C2):
20089
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
20090
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
20091
+ * not a balanced target). An explicit stored value always wins; a stored-only
20092
+ * (unavailable) key keeps its stored value.
20093
+ */
20094
+ enabled: boolean(),
20095
+ /** Relative balancer weight for the enabled device (default 1). */
20096
+ weight: number(),
20097
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
20098
+ maxSessions: number().nullable(),
20099
+ /** Object-detection model the executor defaults to for this deviceKey. */
20100
+ defaultModelId: string(),
20101
+ /**
20102
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
20103
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
20104
+ * Absent/empty ⇒ no base (every step uses its device format default). The
20105
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
20106
+ * available per format; this is the stored selection that becomes the
20107
+ * default for EVERY camera landing on this accelerator.
20108
+ */
20109
+ steps: record(string(), DeviceStepConfigSchema).optional()
20110
+ });
20111
+ var NodeInferenceDevicesSchema = object({
20112
+ nodeId: string(),
20113
+ /** False when the node's platform-probe was unreachable (no live device set). */
20114
+ reachable: boolean(),
20115
+ devices: array(NodeInferenceDeviceSchema).readonly()
20116
+ });
19641
20117
  method(object({
19642
20118
  deviceId: number(),
19643
20119
  agentNodeId: string()
@@ -19647,7 +20123,13 @@ method(object({
19647
20123
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
19648
20124
  kind: "mutation",
19649
20125
  auth: "admin"
19650
- }), method(_void(), object({ migrated: number() }), {
20126
+ }), method(object({
20127
+ deviceId: number(),
20128
+ deviceKey: string()
20129
+ }), object({ success: literal(true) }), {
20130
+ kind: "mutation",
20131
+ auth: "admin"
20132
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
19651
20133
  kind: "mutation",
19652
20134
  auth: "admin"
19653
20135
  }), 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({
@@ -19681,13 +20163,7 @@ method(object({
19681
20163
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
19682
20164
  nodeId: string(),
19683
20165
  settings: AgentPipelineSettingsSchema
19684
- })).readonly()), method(object({
19685
- agentNodeId: string(),
19686
- defaults: record(string(), AgentAddonConfigSchema)
19687
- }), object({ success: literal(true) }), {
19688
- kind: "mutation",
19689
- auth: "admin"
19690
- }), method(object({ agentNodeId: string() }), object({
20166
+ })).readonly()), method(object({ agentNodeId: string() }), object({
19691
20167
  success: boolean(),
19692
20168
  removed: boolean()
19693
20169
  }), {
@@ -19719,7 +20195,18 @@ method(object({
19719
20195
  }), object({ success: literal(true) }), {
19720
20196
  kind: "mutation",
19721
20197
  auth: "admin"
19722
- }), method(object({ agentNodeId: string() }), object({
20198
+ }), method(object({
20199
+ agentNodeId: string(),
20200
+ inferenceDevices: record(string(), object({
20201
+ enabled: boolean(),
20202
+ weight: number().positive().optional(),
20203
+ maxSessions: number().int().positive().optional(),
20204
+ steps: record(string(), DeviceStepConfigSchema).optional()
20205
+ }))
20206
+ }), object({ success: literal(true) }), {
20207
+ kind: "mutation",
20208
+ auth: "admin"
20209
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
19723
20210
  success: literal(true),
19724
20211
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
19725
20212
  effectiveModelId: string().nullable(),
@@ -19735,9 +20222,10 @@ method(object({
19735
20222
  }), object({ success: literal(true) }), {
19736
20223
  kind: "mutation",
19737
20224
  auth: "admin"
19738
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
20225
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
19739
20226
  deviceId: number(),
19740
20227
  agentNodeId: string(),
20228
+ deviceKey: string(),
19741
20229
  addonId: string(),
19742
20230
  patch: CameraStepOverridePatchSchema.nullable()
19743
20231
  }), object({ success: literal(true) }), {
@@ -19774,14 +20262,13 @@ method(object({
19774
20262
  });
19775
20263
  /**
19776
20264
  * server-management — per-NODE singleton capability for a node's ROOT
19777
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
19778
- * agents).
20265
+ * package lifecycle (runtime-updatable node packages).
19779
20266
  *
19780
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
19781
- * on agents) carries the whole software stack in its npm dep tree, so ONE
19782
- * version describes the node. Updates install into
19783
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
19784
- * starter (probation boot + auto-rollback to N-1).
20267
+ * Every node role runs the SAME root package (`@camstack/server`), which
20268
+ * carries the whole software stack in its npm dep tree, so ONE version
20269
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
20270
+ * on restart via the baked starter (single-copy in-place swap — no probation,
20271
+ * no auto-rollback).
19785
20272
  *
19786
20273
  * Providers:
19787
20274
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -19889,7 +20376,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
19889
20376
  /** Explicit target version; omitted = latest from the registry. */
19890
20377
  version: string().optional() }), ServerUpdateActionResultSchema, {
19891
20378
  kind: "mutation",
19892
- auth: "admin"
20379
+ auth: "admin",
20380
+ timeoutMs: 16 * 6e4
19893
20381
  }), method(_void(), ServerUpdateActionResultSchema, {
19894
20382
  kind: "mutation",
19895
20383
  auth: "admin"
@@ -20933,22 +21421,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
20933
21421
  var RestartAddonResultSchema = unknown();
20934
21422
  var InstallPackageResultSchema = unknown();
20935
21423
  var ReloadPackagesResultSchema = unknown();
20936
- /**
20937
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
20938
- * server restarts so the admin UI can react to the `restartingAt`
20939
- * timestamp (shows reconnect overlay). The transition from
20940
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
20941
- * `system.restart-completed` event after the new process boots.
20942
- *
20943
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
20944
- */
20945
- var UpdateFrameworkPackageResultSchema = object({
20946
- packageName: string(),
20947
- fromVersion: string(),
20948
- toVersion: string(),
20949
- /** Ms-epoch the server scheduled its self-restart. */
20950
- restartingAt: number()
20951
- });
20952
21424
  var BulkUpdateItemStatusSchema = _enum([
20953
21425
  "queued",
20954
21426
  "updating",
@@ -21076,13 +21548,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
21076
21548
  }), object({ success: literal(true) }), {
21077
21549
  kind: "mutation",
21078
21550
  auth: "admin"
21079
- }), method(object({
21080
- packageName: string().min(1),
21081
- version: string().optional(),
21082
- deferRestart: boolean().optional()
21083
- }), UpdateFrameworkPackageResultSchema, {
21084
- kind: "mutation",
21085
- auth: "admin"
21086
21551
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
21087
21552
  kind: "mutation",
21088
21553
  auth: "admin"
@@ -21948,10 +22413,10 @@ var TopologyCategorySchema = object({
21948
22413
  addons: array(TopologyCategoryAddonSchema).readonly()
21949
22414
  });
21950
22415
  /**
21951
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
21952
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
21953
- * version visibility for the Server management surface. Nullable: offline
21954
- * rows and pre-phase-2 nodes report none.
22416
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
22417
+ * root package for every node role) as reported by its `registerNode`
22418
+ * manifest — version visibility for the Server management surface. Nullable:
22419
+ * offline rows and nodes that never reported one.
21955
22420
  */
21956
22421
  var TopologyRootPackageSchema = object({
21957
22422
  name: string(),
@@ -22339,17 +22804,28 @@ var PlatformScoreSchema = object({
22339
22804
  format: _enum([
22340
22805
  "onnx",
22341
22806
  "coreml",
22342
- "openvino"
22807
+ "openvino",
22808
+ "tflite"
22343
22809
  ]),
22344
22810
  score: number(),
22345
22811
  reason: string(),
22346
22812
  available: boolean()
22347
22813
  });
22814
+ var InferenceDeviceDescriptorSchema = object({
22815
+ key: string(),
22816
+ backend: string(),
22817
+ device: string(),
22818
+ format: ModelFormatSchema,
22819
+ runtime: literal("python"),
22820
+ score: number(),
22821
+ available: boolean()
22822
+ });
22348
22823
  var PlatformCapabilitiesSchema = object({
22349
22824
  hardware: HardwareInfoSchema,
22350
22825
  scores: array(PlatformScoreSchema).readonly(),
22351
22826
  bestScore: PlatformScoreSchema,
22352
- pythonPath: string().nullable()
22827
+ pythonPath: string().nullable(),
22828
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
22353
22829
  });
22354
22830
  var ModelRequirementSchema = object({
22355
22831
  modelId: string(),
@@ -23228,12 +23704,6 @@ Object.freeze({
23228
23704
  addonId: null,
23229
23705
  access: "delete"
23230
23706
  },
23231
- "addons.updateFrameworkPackage": {
23232
- capName: "addons",
23233
- capScope: "system",
23234
- addonId: null,
23235
- access: "create"
23236
- },
23237
23707
  "addons.updatePackage": {
23238
23708
  capName: "addons",
23239
23709
  capScope: "system",
@@ -26006,12 +26476,6 @@ Object.freeze({
26006
26476
  addonId: null,
26007
26477
  access: "view"
26008
26478
  },
26009
- "pipelineExecutor.reprobeEngine": {
26010
- capName: "pipeline-executor",
26011
- capScope: "system",
26012
- addonId: null,
26013
- access: "create"
26014
- },
26015
26479
  "pipelineExecutor.runAudioTest": {
26016
26480
  capName: "pipeline-executor",
26017
26481
  capScope: "system",
@@ -26162,6 +26626,12 @@ Object.freeze({
26162
26626
  addonId: null,
26163
26627
  access: "view"
26164
26628
  },
26629
+ "pipelineOrchestrator.getNodeInferenceDevices": {
26630
+ capName: "pipeline-orchestrator",
26631
+ capScope: "system",
26632
+ addonId: null,
26633
+ access: "view"
26634
+ },
26165
26635
  "pipelineOrchestrator.getPipelineAssignment": {
26166
26636
  capName: "pipeline-orchestrator",
26167
26637
  capScope: "system",
@@ -26174,6 +26644,12 @@ Object.freeze({
26174
26644
  addonId: null,
26175
26645
  access: "view"
26176
26646
  },
26647
+ "pipelineOrchestrator.getPipelineDevicePin": {
26648
+ capName: "pipeline-orchestrator",
26649
+ capScope: "system",
26650
+ addonId: null,
26651
+ access: "view"
26652
+ },
26177
26653
  "pipelineOrchestrator.listAgentSettings": {
26178
26654
  capName: "pipeline-orchestrator",
26179
26655
  capScope: "system",
@@ -26216,19 +26692,19 @@ Object.freeze({
26216
26692
  addonId: null,
26217
26693
  access: "create"
26218
26694
  },
26219
- "pipelineOrchestrator.setAgentAddonDefaults": {
26695
+ "pipelineOrchestrator.setAgentCapabilities": {
26220
26696
  capName: "pipeline-orchestrator",
26221
26697
  capScope: "system",
26222
26698
  addonId: null,
26223
26699
  access: "create"
26224
26700
  },
26225
- "pipelineOrchestrator.setAgentCapabilities": {
26701
+ "pipelineOrchestrator.setAgentDetectWeight": {
26226
26702
  capName: "pipeline-orchestrator",
26227
26703
  capScope: "system",
26228
26704
  addonId: null,
26229
26705
  access: "create"
26230
26706
  },
26231
- "pipelineOrchestrator.setAgentDetectWeight": {
26707
+ "pipelineOrchestrator.setAgentInferenceDevices": {
26232
26708
  capName: "pipeline-orchestrator",
26233
26709
  capScope: "system",
26234
26710
  addonId: null,
@@ -26270,6 +26746,12 @@ Object.freeze({
26270
26746
  addonId: null,
26271
26747
  access: "create"
26272
26748
  },
26749
+ "pipelineOrchestrator.setPipelineDevicePin": {
26750
+ capName: "pipeline-orchestrator",
26751
+ capScope: "system",
26752
+ addonId: null,
26753
+ access: "create"
26754
+ },
26273
26755
  "pipelineOrchestrator.unassignAudio": {
26274
26756
  capName: "pipeline-orchestrator",
26275
26757
  capScope: "system",
@@ -27822,32 +28304,6 @@ Object.freeze({
27822
28304
  "network-access": "ingress",
27823
28305
  "smtp-provider": "email"
27824
28306
  });
27825
- var frameworkSwapPackageSchema = object({
27826
- name: string(),
27827
- stagedPath: string(),
27828
- backupPath: string(),
27829
- toVersion: string(),
27830
- fromVersion: string().nullable()
27831
- });
27832
- object({
27833
- jobId: string(),
27834
- taskId: string(),
27835
- packages: array(frameworkSwapPackageSchema),
27836
- requestedAtMs: number(),
27837
- schemaVersion: literal(1)
27838
- });
27839
- object({
27840
- jobId: string(),
27841
- taskId: string(),
27842
- backups: array(object({
27843
- name: string(),
27844
- backupPath: string(),
27845
- livePath: string()
27846
- })),
27847
- appliedAtMs: number(),
27848
- bootAttempts: number(),
27849
- schemaVersion: literal(1)
27850
- });
27851
28307
  //#endregion
27852
28308
  //#region src/config.ts
27853
28309
  /**
@@ -31523,7 +31979,7 @@ var import_websocket = /* @__PURE__ */ __toESM(require_websocket(), 1);
31523
31979
  require_websocket_server();
31524
31980
  var wrapper_default = import_websocket.default;
31525
31981
  //#endregion
31526
- //#region node_modules/@apocaliss92/nodedreo/dist/index.js
31982
+ //#region ../../node_modules/@apocaliss92/nodedreo/dist/index.js
31527
31983
  var DreoError = class extends Error {
31528
31984
  constructor(message) {
31529
31985
  super(message);