@camstack/addon-notifiers 1.1.29 → 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
@@ -7068,6 +7068,17 @@ var ModelCatalogEntrySchema = object({
7068
7068
  "imagenet",
7069
7069
  "none"
7070
7070
  ]).optional(),
7071
+ /**
7072
+ * The model already applies softmax IN-GRAPH — its raw output is a
7073
+ * probability distribution, not logits. When set, the `softmax`
7074
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7075
+ * probability vector collapses it toward uniform (top-1 score craters far
7076
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7077
+ * the output is raw logits and the postprocessor applies softmax (the normal
7078
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7079
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7080
+ */
7081
+ outputProbabilities: boolean().optional(),
7071
7082
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7072
7083
  /**
7073
7084
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -7744,6 +7755,160 @@ var EncodeProfileSchema = object({
7744
7755
  */
7745
7756
  outputArgs: array(string()).optional()
7746
7757
  });
7758
+ var COCO_TO_MACRO = {
7759
+ mapping: {
7760
+ person: "person",
7761
+ bicycle: "vehicle",
7762
+ car: "vehicle",
7763
+ motorcycle: "vehicle",
7764
+ airplane: "vehicle",
7765
+ bus: "vehicle",
7766
+ train: "vehicle",
7767
+ truck: "vehicle",
7768
+ boat: "vehicle",
7769
+ bird: "animal",
7770
+ cat: "animal",
7771
+ dog: "animal",
7772
+ horse: "animal",
7773
+ sheep: "animal",
7774
+ cow: "animal",
7775
+ elephant: "animal",
7776
+ bear: "animal",
7777
+ zebra: "animal",
7778
+ giraffe: "animal",
7779
+ suitcase: "package",
7780
+ backpack: "package",
7781
+ handbag: "package"
7782
+ },
7783
+ preserveOriginal: false
7784
+ };
7785
+ var AUDIO_MACRO_LABELS = [
7786
+ {
7787
+ id: "speech",
7788
+ name: "Speech",
7789
+ icon: "🗣️"
7790
+ },
7791
+ {
7792
+ id: "scream",
7793
+ name: "Scream / Shout",
7794
+ icon: "😱"
7795
+ },
7796
+ {
7797
+ id: "crying",
7798
+ name: "Crying / Baby",
7799
+ icon: "😢"
7800
+ },
7801
+ {
7802
+ id: "laughter",
7803
+ name: "Laughter",
7804
+ icon: "😂"
7805
+ },
7806
+ {
7807
+ id: "music",
7808
+ name: "Music",
7809
+ icon: "🎵"
7810
+ },
7811
+ {
7812
+ id: "dog",
7813
+ name: "Dog",
7814
+ icon: "🐕"
7815
+ },
7816
+ {
7817
+ id: "cat",
7818
+ name: "Cat",
7819
+ icon: "🐈"
7820
+ },
7821
+ {
7822
+ id: "bird",
7823
+ name: "Bird",
7824
+ icon: "🐦"
7825
+ },
7826
+ {
7827
+ id: "animal",
7828
+ name: "Animal (other)",
7829
+ icon: "🐾"
7830
+ },
7831
+ {
7832
+ id: "alarm",
7833
+ name: "Alarm / Siren",
7834
+ icon: "🚨"
7835
+ },
7836
+ {
7837
+ id: "doorbell",
7838
+ name: "Doorbell / Knock",
7839
+ icon: "🔔"
7840
+ },
7841
+ {
7842
+ id: "glass_breaking",
7843
+ name: "Glass Breaking",
7844
+ icon: "💥"
7845
+ },
7846
+ {
7847
+ id: "gunshot",
7848
+ name: "Gunshot / Explosion",
7849
+ icon: "💣"
7850
+ },
7851
+ {
7852
+ id: "vehicle",
7853
+ name: "Vehicle",
7854
+ icon: "🚗"
7855
+ },
7856
+ {
7857
+ id: "siren",
7858
+ name: "Emergency Siren",
7859
+ icon: "🚑"
7860
+ },
7861
+ {
7862
+ id: "fire",
7863
+ name: "Fire / Smoke",
7864
+ icon: "🔥"
7865
+ },
7866
+ {
7867
+ id: "water",
7868
+ name: "Water",
7869
+ icon: "💧"
7870
+ },
7871
+ {
7872
+ id: "wind",
7873
+ name: "Wind / Weather",
7874
+ icon: "🌬️"
7875
+ },
7876
+ {
7877
+ id: "door",
7878
+ name: "Door",
7879
+ icon: "🚪"
7880
+ },
7881
+ {
7882
+ id: "footsteps",
7883
+ name: "Footsteps",
7884
+ icon: "👣"
7885
+ },
7886
+ {
7887
+ id: "crowd",
7888
+ name: "Crowd / Chatter",
7889
+ icon: "👥"
7890
+ },
7891
+ {
7892
+ id: "telephone",
7893
+ name: "Telephone",
7894
+ icon: "📞"
7895
+ },
7896
+ {
7897
+ id: "engine",
7898
+ name: "Engine / Motor",
7899
+ icon: "⚙️"
7900
+ },
7901
+ {
7902
+ id: "tools",
7903
+ name: "Tools / Construction",
7904
+ icon: "🔨"
7905
+ },
7906
+ {
7907
+ id: "silence",
7908
+ name: "Silence",
7909
+ icon: "🤫"
7910
+ }
7911
+ ];
7747
7912
  var YAMNET_TO_MACRO = {
7748
7913
  mapping: {
7749
7914
  Speech: "speech",
@@ -8010,6 +8175,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8010
8175
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8011
8176
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8012
8177
  /**
8178
+ * Unified event-kind taxonomy — THE single source of truth for
8179
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8180
+ * icon }`.
8181
+ *
8182
+ * This dictionary folds together what used to be scattered across four
8183
+ * copies:
8184
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8185
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8186
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8187
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8188
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8189
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8190
+ *
8191
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8192
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8193
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8194
+ * color or an icon: they read this dictionary (server descriptors carry the
8195
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8196
+ *
8197
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8198
+ */
8199
+ var TAXONOMY_COLORS = {
8200
+ motion: "#f59e0b",
8201
+ audio: "#06b6d4",
8202
+ person: "#22c55e",
8203
+ vehicle: "#3b82f6",
8204
+ animal: "#f97316",
8205
+ package: "#a855f7",
8206
+ sensor: "#8b5cf6",
8207
+ control: "#10b981",
8208
+ genericDetection: "#64748b"
8209
+ };
8210
+ var DETECTION_SUB_COLORS = {
8211
+ car: "#f59e0b",
8212
+ truck: "#d97706",
8213
+ bus: "#b45309",
8214
+ motorcycle: "#eab308",
8215
+ bicycle: "#ca8a04",
8216
+ airplane: "#60a5fa",
8217
+ boat: "#2563eb",
8218
+ train: "#1d4ed8",
8219
+ bird: "#14b8a6",
8220
+ dog: "#84cc16",
8221
+ cat: "#f97316",
8222
+ horse: "#a16207",
8223
+ sheep: "#a3a3a3",
8224
+ cow: "#78716c",
8225
+ elephant: "#6b7280",
8226
+ bear: "#7c2d12",
8227
+ zebra: "#404040",
8228
+ giraffe: "#d4a373"
8229
+ };
8230
+ function titleCase(id) {
8231
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8232
+ }
8233
+ var entries = /* @__PURE__ */ new Map();
8234
+ function macro(kind, category, color, iconId, label) {
8235
+ entries.set(kind, {
8236
+ kind,
8237
+ parentKind: null,
8238
+ level: "macro",
8239
+ category,
8240
+ color,
8241
+ iconId,
8242
+ labelKey: `eventKind.${kind}`,
8243
+ label
8244
+ });
8245
+ }
8246
+ function sub(kind, parentKind, category, color, iconId, label) {
8247
+ entries.set(kind, {
8248
+ kind,
8249
+ parentKind,
8250
+ level: "sub",
8251
+ category,
8252
+ color,
8253
+ iconId,
8254
+ labelKey: `eventKind.${kind}`,
8255
+ label
8256
+ });
8257
+ }
8258
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8259
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8260
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8261
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8262
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8263
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8264
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8265
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8266
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8267
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8268
+ if (entries.has(cocoClass)) continue;
8269
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8270
+ }
8271
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8272
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8273
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8274
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8275
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8276
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8277
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8278
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8279
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8280
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8281
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8282
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8283
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8284
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8285
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8286
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8287
+ sub("button", "control", "control", "#10b981", "button", "Button");
8288
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8289
+ for (const l of AUDIO_MACRO_LABELS) {
8290
+ const kind = `audio-${l.id}`;
8291
+ if (entries.has(kind)) continue;
8292
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8293
+ }
8294
+ /** The complete taxonomy dictionary, keyed by kind. */
8295
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8296
+ /**
8013
8297
  * Error types for the safe expression engine. Two distinct classes so callers
8014
8298
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8015
8299
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -11118,10 +11402,7 @@ var ConfigUISchemaNullableBridge = custom();
11118
11402
  var InferenceCapabilitiesBridge = custom();
11119
11403
  var ModelAvailabilityListBridge = custom();
11120
11404
  var PipelineRunResultBridge = custom();
11121
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
11122
- kind: "mutation",
11123
- auth: "admin"
11124
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
11405
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
11125
11406
  modelId: string(),
11126
11407
  settings: record(string(), unknown()).readonly()
11127
11408
  }))), method(object({ steps: record(string(), object({
@@ -11181,13 +11462,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
11181
11462
  * (inputClasses ≠ null) are skipped and served per-track via
11182
11463
  * pipelineRunner.runDetailSubtree (two-plane design).
11183
11464
  */
11184
- plane: _enum(["full", "frame"]).optional()
11465
+ plane: _enum(["full", "frame"]).optional(),
11466
+ /**
11467
+ * Inference-device selector (Phase 2 multi-device). Format
11468
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
11469
+ * Omitted ⇒ the runner's default device (current single-engine
11470
+ * behaviour). Selects WHICH device pool of the node runs the call.
11471
+ */
11472
+ deviceKey: string().optional()
11185
11473
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
11186
11474
  engine: PipelineEngineChoiceSchema.optional(),
11187
11475
  steps: array(PipelineStepInputSchema).min(1),
11188
11476
  frames: array(FrameInputSchema).min(1).max(255),
11189
11477
  deviceId: number().optional(),
11190
- sessionId: string().optional()
11478
+ sessionId: string().optional(),
11479
+ /**
11480
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
11481
+ * the batch to the Python pool's bench preprocess cache
11482
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
11483
+ * preprocessed ONCE and every later inference is a pure-inference cache
11484
+ * hit — the sustained-throughput run measures inference, not
11485
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
11486
+ * full preprocess every call, correct). Fresh per sustained run;
11487
+ * released via `uncacheFrame`.
11488
+ */
11489
+ frameId: number().int().nonnegative().optional(),
11490
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
11491
+ deviceKey: string().optional()
11191
11492
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
11192
11493
  data: _instanceof(Uint8Array),
11193
11494
  width: number().int().positive(),
@@ -11219,8 +11520,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
11219
11520
  * - `runtime` — main camera-serving engine (no idle TTL).
11220
11521
  * - `warm-override` — benchmark/test override held in the warm
11221
11522
  * cache; auto-disposed after the idle TTL.
11523
+ * - `device-pool` — a concurrent per-device pool (Phase 2
11524
+ * multi-device, keyed by `deviceKey`) resolved
11525
+ * via `resolveDeviceFactory`. Runs alongside the
11526
+ * `runtime` engine on a DIFFERENT accelerator
11527
+ * (NPU / iGPU / Coral) — this is how the
11528
+ * Engines tab shows all pools running at once.
11222
11529
  */
11223
- kind: _enum(["runtime", "warm-override"]),
11530
+ kind: _enum([
11531
+ "runtime",
11532
+ "warm-override",
11533
+ "device-pool"
11534
+ ]),
11224
11535
  /** Native pid of the underlying Python pool (null when no pool). */
11225
11536
  poolPid: number().nullable(),
11226
11537
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -11562,7 +11873,14 @@ var RunnerCameraConfigSchema = object({
11562
11873
  * camera's detect node differs from its source-owner (P2d, gated by the
11563
11874
  * `remoteSourcingNodes` rollout setting).
11564
11875
  */
11565
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
11876
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
11877
+ /**
11878
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
11879
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
11880
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
11881
+ * this only selects WHICH device pool of that node runs the session.
11882
+ */
11883
+ deviceKey: string().optional()
11566
11884
  });
11567
11885
  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;
11568
11886
  /**
@@ -11583,6 +11901,19 @@ var RunnerLocalLoadSchema = object({
11583
11901
  avgInferenceTimeMs: number(),
11584
11902
  /** Total queue depth across motion + detection queues. */
11585
11903
  queueDepthTotal: number(),
11904
+ /**
11905
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
11906
+ * this runner currently has attached cameras on, so the orchestrator's second
11907
+ * `balance()` pass (over a node's devices) weights on real per-pool session
11908
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
11909
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
11910
+ */
11911
+ devices: array(object({
11912
+ deviceKey: string(),
11913
+ backend: string(),
11914
+ attachedCameras: number(),
11915
+ queueDepthTotal: number()
11916
+ })).default([]),
11586
11917
  /** Hardware capability flags reported by this node. */
11587
11918
  hardware: object({
11588
11919
  hasGpu: boolean(),
@@ -13058,6 +13389,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
13058
13389
  kind: "mutation",
13059
13390
  auth: "admin"
13060
13391
  });
13392
+ 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;
13393
+ new Set(Object.values(DeviceType));
13061
13394
  /**
13062
13395
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
13063
13396
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -16185,17 +16518,30 @@ var EventKindCategorySchema = _enum([
16185
16518
  "audio",
16186
16519
  "detection",
16187
16520
  "sensor",
16521
+ "control",
16188
16522
  "custom",
16189
16523
  "package"
16190
16524
  ]);
16525
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16526
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
16191
16527
  var EventKindDescriptorSchema = object({
16192
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16528
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
16193
16529
  kind: string(),
16530
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16531
+ labelKey: string(),
16532
+ /** English fallback label (kept for clients that don't translate). */
16194
16533
  label: string(),
16195
16534
  /** Hex color for timeline/legend rendering. */
16196
16535
  color: string(),
16536
+ /** Dictionary id → lucide component on the UI side. */
16537
+ iconId: string(),
16538
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
16197
16539
  icon: EventKindIconSchema,
16198
16540
  category: EventKindCategorySchema,
16541
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16542
+ parentKind: string().nullable(),
16543
+ /** Derived from `parentKind`, explicit for the client tree. */
16544
+ level: EventKindLevelSchema,
16199
16545
  /** Which cap + device contributes this kind. For built-ins the camera
16200
16546
  * itself; for sensor kinds the LINKED source device. */
16201
16547
  source: object({
@@ -16268,11 +16614,21 @@ var TrackAudioLabelSchema = object({
16268
16614
  firstAt: number(),
16269
16615
  lastAt: number()
16270
16616
  });
16617
+ /**
16618
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16619
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16620
+ * linked sensor/control state change (no positions; carries a snapshot). The
16621
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16622
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16623
+ */
16624
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16271
16625
  var TrackSchema = object({
16272
16626
  trackId: string(),
16273
16627
  deviceId: number(),
16274
16628
  className: string(),
16275
16629
  label: string().optional(),
16630
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16631
+ source: TrackSourceSchema.optional(),
16276
16632
  firstSeen: number(),
16277
16633
  lastSeen: number(),
16278
16634
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16649,6 +17005,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16649
17005
  eventId: string(),
16650
17006
  timestamp: number()
16651
17007
  });
17008
+ /**
17009
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
17010
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
17011
+ * caps into per-camera event-kind descriptors.
17012
+ *
17013
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
17014
+ * is NOT duplicated here — every entry is derived from the single
17015
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
17016
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
17017
+ * control cap means adding one line here (and a taxonomy entry); the anti-
17018
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
17019
+ * eventful cap is missing.
17020
+ */
17021
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
17022
+ var LEGACY_ICON = {
17023
+ motion: "motion",
17024
+ audio: "audio",
17025
+ person: "person",
17026
+ vehicle: "vehicle",
17027
+ animal: "animal",
17028
+ package: "package",
17029
+ door: "door",
17030
+ pir: "pir",
17031
+ smoke: "smoke",
17032
+ water: "water",
17033
+ button: "button",
17034
+ generic: "generic",
17035
+ gas: "smoke",
17036
+ vibration: "generic",
17037
+ tamper: "generic",
17038
+ presence: "person",
17039
+ lock: "generic",
17040
+ siren: "generic",
17041
+ switch: "generic",
17042
+ doorbell: "button"
17043
+ };
17044
+ function legacyIcon(iconId) {
17045
+ return LEGACY_ICON[iconId] ?? "generic";
17046
+ }
17047
+ /**
17048
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
17049
+ * The anti-drift guard cross-checks this against the eventful caps declared
17050
+ * in `packages/types/src/capabilities/*.cap.ts`.
17051
+ */
17052
+ var CAP_TO_KIND = {
17053
+ contact: "contact",
17054
+ motion: "motion-sensor",
17055
+ smoke: "smoke",
17056
+ flood: "flood",
17057
+ gas: "gas",
17058
+ "carbon-monoxide": "carbon-monoxide",
17059
+ vibration: "vibration",
17060
+ tamper: "tamper",
17061
+ presence: "presence",
17062
+ "enum-sensor": "enum-sensor",
17063
+ "event-emitter": "device-event",
17064
+ "lock-control": "lock",
17065
+ switch: "switch",
17066
+ button: "button",
17067
+ doorbell: "doorbell"
17068
+ };
17069
+ function buildDescriptor(capName, kind) {
17070
+ const t = EVENT_TAXONOMY[kind];
17071
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
17072
+ return {
17073
+ ...t,
17074
+ icon: legacyIcon(t.iconId)
17075
+ };
17076
+ }
17077
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16652
17078
  var CameraPipelineConfigSchema = object({
16653
17079
  engine: PipelineEngineChoiceSchema.optional(),
16654
17080
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16667,13 +17093,11 @@ var PipelineTemplateSchema = object({
16667
17093
  createdAt: string(),
16668
17094
  updatedAt: string()
16669
17095
  });
16670
- var AgentAddonConfigSchema = object({
16671
- enabled: boolean(),
17096
+ var DeviceStepConfigSchema = object({
16672
17097
  modelId: string().optional(),
16673
- settings: record(string(), unknown()).readonly()
17098
+ settings: record(string(), unknown()).optional()
16674
17099
  });
16675
17100
  var AgentPipelineSettingsSchema = object({
16676
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
16677
17101
  maxCameras: number().int().nonnegative().nullable().default(null),
16678
17102
  /** Per-node detection weight (relative share for the quota balancer). */
16679
17103
  detectWeight: number().positive().optional(),
@@ -16697,7 +17121,22 @@ var AgentPipelineSettingsSchema = object({
16697
17121
  * it already uses to reach the hub). Set this only when the auto-detected
16698
17122
  * address is wrong (multi-homed host, NAT, custom interface).
16699
17123
  */
16700
- reachableHost: string().optional()
17124
+ reachableHost: string().optional(),
17125
+ /**
17126
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
17127
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
17128
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
17129
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
17130
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
17131
+ * the default model/settings for every camera landing on that accelerator;
17132
+ * a stepId absent ⇒ the step uses that device's format default.
17133
+ */
17134
+ inferenceDevices: record(string(), object({
17135
+ enabled: boolean(),
17136
+ weight: number().positive().optional(),
17137
+ maxSessions: number().int().positive().optional(),
17138
+ steps: record(string(), DeviceStepConfigSchema).optional()
17139
+ })).optional()
16701
17140
  });
16702
17141
  var CameraPipelineForAgentSchema = object({
16703
17142
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16707,14 +17146,13 @@ var CameraPipelineForAgentSchema = object({
16707
17146
  }).nullable()
16708
17147
  });
16709
17148
  var CameraStepOverridePatchSchema = object({
16710
- enabled: boolean().optional(),
16711
17149
  modelId: string().optional(),
16712
17150
  settings: record(string(), unknown()).readonly().optional()
16713
17151
  });
16714
17152
  var CameraPipelineSettingsSchema = object({
16715
17153
  pinnedAgentNodeId: string().optional(),
16716
17154
  stepToggles: record(string(), boolean()).optional(),
16717
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
17155
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
16718
17156
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
16719
17157
  });
16720
17158
  /**
@@ -16928,6 +17366,44 @@ var CameraStatusSchema = object({
16928
17366
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
16929
17367
  fetchedAt: number()
16930
17368
  });
17369
+ var NodeInferenceDeviceSchema = object({
17370
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
17371
+ key: string(),
17372
+ backend: string(),
17373
+ device: string(),
17374
+ format: _enum(MODEL_FORMATS),
17375
+ /** Whether the node's live probe reports the device as usable right now. */
17376
+ available: boolean(),
17377
+ /**
17378
+ * Whether this device participates in dispatch. AUTO default (spec C2):
17379
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
17380
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
17381
+ * not a balanced target). An explicit stored value always wins; a stored-only
17382
+ * (unavailable) key keeps its stored value.
17383
+ */
17384
+ enabled: boolean(),
17385
+ /** Relative balancer weight for the enabled device (default 1). */
17386
+ weight: number(),
17387
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
17388
+ maxSessions: number().nullable(),
17389
+ /** Object-detection model the executor defaults to for this deviceKey. */
17390
+ defaultModelId: string(),
17391
+ /**
17392
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
17393
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
17394
+ * Absent/empty ⇒ no base (every step uses its device format default). The
17395
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
17396
+ * available per format; this is the stored selection that becomes the
17397
+ * default for EVERY camera landing on this accelerator.
17398
+ */
17399
+ steps: record(string(), DeviceStepConfigSchema).optional()
17400
+ });
17401
+ var NodeInferenceDevicesSchema = object({
17402
+ nodeId: string(),
17403
+ /** False when the node's platform-probe was unreachable (no live device set). */
17404
+ reachable: boolean(),
17405
+ devices: array(NodeInferenceDeviceSchema).readonly()
17406
+ });
16931
17407
  method(object({
16932
17408
  deviceId: number(),
16933
17409
  agentNodeId: string()
@@ -16937,7 +17413,13 @@ method(object({
16937
17413
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
16938
17414
  kind: "mutation",
16939
17415
  auth: "admin"
16940
- }), method(_void(), object({ migrated: number() }), {
17416
+ }), method(object({
17417
+ deviceId: number(),
17418
+ deviceKey: string()
17419
+ }), object({ success: literal(true) }), {
17420
+ kind: "mutation",
17421
+ auth: "admin"
17422
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
16941
17423
  kind: "mutation",
16942
17424
  auth: "admin"
16943
17425
  }), 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({
@@ -16971,13 +17453,7 @@ method(object({
16971
17453
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
16972
17454
  nodeId: string(),
16973
17455
  settings: AgentPipelineSettingsSchema
16974
- })).readonly()), method(object({
16975
- agentNodeId: string(),
16976
- defaults: record(string(), AgentAddonConfigSchema)
16977
- }), object({ success: literal(true) }), {
16978
- kind: "mutation",
16979
- auth: "admin"
16980
- }), method(object({ agentNodeId: string() }), object({
17456
+ })).readonly()), method(object({ agentNodeId: string() }), object({
16981
17457
  success: boolean(),
16982
17458
  removed: boolean()
16983
17459
  }), {
@@ -17009,7 +17485,18 @@ method(object({
17009
17485
  }), object({ success: literal(true) }), {
17010
17486
  kind: "mutation",
17011
17487
  auth: "admin"
17012
- }), method(object({ agentNodeId: string() }), object({
17488
+ }), method(object({
17489
+ agentNodeId: string(),
17490
+ inferenceDevices: record(string(), object({
17491
+ enabled: boolean(),
17492
+ weight: number().positive().optional(),
17493
+ maxSessions: number().int().positive().optional(),
17494
+ steps: record(string(), DeviceStepConfigSchema).optional()
17495
+ }))
17496
+ }), object({ success: literal(true) }), {
17497
+ kind: "mutation",
17498
+ auth: "admin"
17499
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
17013
17500
  success: literal(true),
17014
17501
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
17015
17502
  effectiveModelId: string().nullable(),
@@ -17025,9 +17512,10 @@ method(object({
17025
17512
  }), object({ success: literal(true) }), {
17026
17513
  kind: "mutation",
17027
17514
  auth: "admin"
17028
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
17515
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
17029
17516
  deviceId: number(),
17030
17517
  agentNodeId: string(),
17518
+ deviceKey: string(),
17031
17519
  addonId: string(),
17032
17520
  patch: CameraStepOverridePatchSchema.nullable()
17033
17521
  }), object({ success: literal(true) }), {
@@ -17064,14 +17552,13 @@ method(object({
17064
17552
  });
17065
17553
  /**
17066
17554
  * server-management — per-NODE singleton capability for a node's ROOT
17067
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
17068
- * agents).
17555
+ * package lifecycle (runtime-updatable node packages).
17069
17556
  *
17070
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
17071
- * on agents) carries the whole software stack in its npm dep tree, so ONE
17072
- * version describes the node. Updates install into
17073
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
17074
- * starter (probation boot + auto-rollback to N-1).
17557
+ * Every node role runs the SAME root package (`@camstack/server`), which
17558
+ * carries the whole software stack in its npm dep tree, so ONE version
17559
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
17560
+ * on restart via the baked starter (single-copy in-place swap — no probation,
17561
+ * no auto-rollback).
17075
17562
  *
17076
17563
  * Providers:
17077
17564
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -17179,7 +17666,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
17179
17666
  /** Explicit target version; omitted = latest from the registry. */
17180
17667
  version: string().optional() }), ServerUpdateActionResultSchema, {
17181
17668
  kind: "mutation",
17182
- auth: "admin"
17669
+ auth: "admin",
17670
+ timeoutMs: 16 * 6e4
17183
17671
  }), method(_void(), ServerUpdateActionResultSchema, {
17184
17672
  kind: "mutation",
17185
17673
  auth: "admin"
@@ -18223,22 +18711,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
18223
18711
  var RestartAddonResultSchema = unknown();
18224
18712
  var InstallPackageResultSchema = unknown();
18225
18713
  var ReloadPackagesResultSchema = unknown();
18226
- /**
18227
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
18228
- * server restarts so the admin UI can react to the `restartingAt`
18229
- * timestamp (shows reconnect overlay). The transition from
18230
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
18231
- * `system.restart-completed` event after the new process boots.
18232
- *
18233
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
18234
- */
18235
- var UpdateFrameworkPackageResultSchema = object({
18236
- packageName: string(),
18237
- fromVersion: string(),
18238
- toVersion: string(),
18239
- /** Ms-epoch the server scheduled its self-restart. */
18240
- restartingAt: number()
18241
- });
18242
18714
  var BulkUpdateItemStatusSchema = _enum([
18243
18715
  "queued",
18244
18716
  "updating",
@@ -18366,13 +18838,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
18366
18838
  }), object({ success: literal(true) }), {
18367
18839
  kind: "mutation",
18368
18840
  auth: "admin"
18369
- }), method(object({
18370
- packageName: string().min(1),
18371
- version: string().optional(),
18372
- deferRestart: boolean().optional()
18373
- }), UpdateFrameworkPackageResultSchema, {
18374
- kind: "mutation",
18375
- auth: "admin"
18376
18841
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
18377
18842
  kind: "mutation",
18378
18843
  auth: "admin"
@@ -19238,10 +19703,10 @@ var TopologyCategorySchema = object({
19238
19703
  addons: array(TopologyCategoryAddonSchema).readonly()
19239
19704
  });
19240
19705
  /**
19241
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
19242
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
19243
- * version visibility for the Server management surface. Nullable: offline
19244
- * rows and pre-phase-2 nodes report none.
19706
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
19707
+ * root package for every node role) as reported by its `registerNode`
19708
+ * manifest — version visibility for the Server management surface. Nullable:
19709
+ * offline rows and nodes that never reported one.
19245
19710
  */
19246
19711
  var TopologyRootPackageSchema = object({
19247
19712
  name: string(),
@@ -19629,17 +20094,28 @@ var PlatformScoreSchema = object({
19629
20094
  format: _enum([
19630
20095
  "onnx",
19631
20096
  "coreml",
19632
- "openvino"
20097
+ "openvino",
20098
+ "tflite"
19633
20099
  ]),
19634
20100
  score: number(),
19635
20101
  reason: string(),
19636
20102
  available: boolean()
19637
20103
  });
20104
+ var InferenceDeviceDescriptorSchema = object({
20105
+ key: string(),
20106
+ backend: string(),
20107
+ device: string(),
20108
+ format: ModelFormatSchema,
20109
+ runtime: literal("python"),
20110
+ score: number(),
20111
+ available: boolean()
20112
+ });
19638
20113
  var PlatformCapabilitiesSchema = object({
19639
20114
  hardware: HardwareInfoSchema,
19640
20115
  scores: array(PlatformScoreSchema).readonly(),
19641
20116
  bestScore: PlatformScoreSchema,
19642
- pythonPath: string().nullable()
20117
+ pythonPath: string().nullable(),
20118
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
19643
20119
  });
19644
20120
  var ModelRequirementSchema = object({
19645
20121
  modelId: string(),
@@ -20518,12 +20994,6 @@ Object.freeze({
20518
20994
  addonId: null,
20519
20995
  access: "delete"
20520
20996
  },
20521
- "addons.updateFrameworkPackage": {
20522
- capName: "addons",
20523
- capScope: "system",
20524
- addonId: null,
20525
- access: "create"
20526
- },
20527
20997
  "addons.updatePackage": {
20528
20998
  capName: "addons",
20529
20999
  capScope: "system",
@@ -23296,12 +23766,6 @@ Object.freeze({
23296
23766
  addonId: null,
23297
23767
  access: "view"
23298
23768
  },
23299
- "pipelineExecutor.reprobeEngine": {
23300
- capName: "pipeline-executor",
23301
- capScope: "system",
23302
- addonId: null,
23303
- access: "create"
23304
- },
23305
23769
  "pipelineExecutor.runAudioTest": {
23306
23770
  capName: "pipeline-executor",
23307
23771
  capScope: "system",
@@ -23452,6 +23916,12 @@ Object.freeze({
23452
23916
  addonId: null,
23453
23917
  access: "view"
23454
23918
  },
23919
+ "pipelineOrchestrator.getNodeInferenceDevices": {
23920
+ capName: "pipeline-orchestrator",
23921
+ capScope: "system",
23922
+ addonId: null,
23923
+ access: "view"
23924
+ },
23455
23925
  "pipelineOrchestrator.getPipelineAssignment": {
23456
23926
  capName: "pipeline-orchestrator",
23457
23927
  capScope: "system",
@@ -23464,6 +23934,12 @@ Object.freeze({
23464
23934
  addonId: null,
23465
23935
  access: "view"
23466
23936
  },
23937
+ "pipelineOrchestrator.getPipelineDevicePin": {
23938
+ capName: "pipeline-orchestrator",
23939
+ capScope: "system",
23940
+ addonId: null,
23941
+ access: "view"
23942
+ },
23467
23943
  "pipelineOrchestrator.listAgentSettings": {
23468
23944
  capName: "pipeline-orchestrator",
23469
23945
  capScope: "system",
@@ -23506,19 +23982,19 @@ Object.freeze({
23506
23982
  addonId: null,
23507
23983
  access: "create"
23508
23984
  },
23509
- "pipelineOrchestrator.setAgentAddonDefaults": {
23985
+ "pipelineOrchestrator.setAgentCapabilities": {
23510
23986
  capName: "pipeline-orchestrator",
23511
23987
  capScope: "system",
23512
23988
  addonId: null,
23513
23989
  access: "create"
23514
23990
  },
23515
- "pipelineOrchestrator.setAgentCapabilities": {
23991
+ "pipelineOrchestrator.setAgentDetectWeight": {
23516
23992
  capName: "pipeline-orchestrator",
23517
23993
  capScope: "system",
23518
23994
  addonId: null,
23519
23995
  access: "create"
23520
23996
  },
23521
- "pipelineOrchestrator.setAgentDetectWeight": {
23997
+ "pipelineOrchestrator.setAgentInferenceDevices": {
23522
23998
  capName: "pipeline-orchestrator",
23523
23999
  capScope: "system",
23524
24000
  addonId: null,
@@ -23560,6 +24036,12 @@ Object.freeze({
23560
24036
  addonId: null,
23561
24037
  access: "create"
23562
24038
  },
24039
+ "pipelineOrchestrator.setPipelineDevicePin": {
24040
+ capName: "pipeline-orchestrator",
24041
+ capScope: "system",
24042
+ addonId: null,
24043
+ access: "create"
24044
+ },
23563
24045
  "pipelineOrchestrator.unassignAudio": {
23564
24046
  capName: "pipeline-orchestrator",
23565
24047
  capScope: "system",
@@ -25112,32 +25594,6 @@ Object.freeze({
25112
25594
  "network-access": "ingress",
25113
25595
  "smtp-provider": "email"
25114
25596
  });
25115
- var frameworkSwapPackageSchema = object({
25116
- name: string(),
25117
- stagedPath: string(),
25118
- backupPath: string(),
25119
- toVersion: string(),
25120
- fromVersion: string().nullable()
25121
- });
25122
- object({
25123
- jobId: string(),
25124
- taskId: string(),
25125
- packages: array(frameworkSwapPackageSchema),
25126
- requestedAtMs: number(),
25127
- schemaVersion: literal(1)
25128
- });
25129
- object({
25130
- jobId: string(),
25131
- taskId: string(),
25132
- backups: array(object({
25133
- name: string(),
25134
- backupPath: string(),
25135
- livePath: string()
25136
- })),
25137
- appliedAtMs: number(),
25138
- bootAttempts: number(),
25139
- schemaVersion: literal(1)
25140
- });
25141
25597
  var NOTIFIER_ICONS = {
25142
25598
  telegram: {
25143
25599
  contentType: "image/svg+xml",