@camstack/addon-decoder-nodeav 1.1.17 → 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/index.js +560 -104
  2. package/dist/index.mjs +560 -104
  3. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -7080,6 +7080,17 @@ var ModelCatalogEntrySchema = object({
7080
7080
  "imagenet",
7081
7081
  "none"
7082
7082
  ]).optional(),
7083
+ /**
7084
+ * The model already applies softmax IN-GRAPH — its raw output is a
7085
+ * probability distribution, not logits. When set, the `softmax`
7086
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7087
+ * probability vector collapses it toward uniform (top-1 score craters far
7088
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7089
+ * the output is raw logits and the postprocessor applies softmax (the normal
7090
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7091
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7092
+ */
7093
+ outputProbabilities: boolean().optional(),
7083
7094
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7084
7095
  /**
7085
7096
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -7639,6 +7650,160 @@ var EncodeProfileSchema = object({
7639
7650
  */
7640
7651
  outputArgs: array(string()).optional()
7641
7652
  });
7653
+ var COCO_TO_MACRO = {
7654
+ mapping: {
7655
+ person: "person",
7656
+ bicycle: "vehicle",
7657
+ car: "vehicle",
7658
+ motorcycle: "vehicle",
7659
+ airplane: "vehicle",
7660
+ bus: "vehicle",
7661
+ train: "vehicle",
7662
+ truck: "vehicle",
7663
+ boat: "vehicle",
7664
+ bird: "animal",
7665
+ cat: "animal",
7666
+ dog: "animal",
7667
+ horse: "animal",
7668
+ sheep: "animal",
7669
+ cow: "animal",
7670
+ elephant: "animal",
7671
+ bear: "animal",
7672
+ zebra: "animal",
7673
+ giraffe: "animal",
7674
+ suitcase: "package",
7675
+ backpack: "package",
7676
+ handbag: "package"
7677
+ },
7678
+ preserveOriginal: false
7679
+ };
7680
+ var AUDIO_MACRO_LABELS = [
7681
+ {
7682
+ id: "speech",
7683
+ name: "Speech",
7684
+ icon: "🗣️"
7685
+ },
7686
+ {
7687
+ id: "scream",
7688
+ name: "Scream / Shout",
7689
+ icon: "😱"
7690
+ },
7691
+ {
7692
+ id: "crying",
7693
+ name: "Crying / Baby",
7694
+ icon: "😢"
7695
+ },
7696
+ {
7697
+ id: "laughter",
7698
+ name: "Laughter",
7699
+ icon: "😂"
7700
+ },
7701
+ {
7702
+ id: "music",
7703
+ name: "Music",
7704
+ icon: "🎵"
7705
+ },
7706
+ {
7707
+ id: "dog",
7708
+ name: "Dog",
7709
+ icon: "🐕"
7710
+ },
7711
+ {
7712
+ id: "cat",
7713
+ name: "Cat",
7714
+ icon: "🐈"
7715
+ },
7716
+ {
7717
+ id: "bird",
7718
+ name: "Bird",
7719
+ icon: "🐦"
7720
+ },
7721
+ {
7722
+ id: "animal",
7723
+ name: "Animal (other)",
7724
+ icon: "🐾"
7725
+ },
7726
+ {
7727
+ id: "alarm",
7728
+ name: "Alarm / Siren",
7729
+ icon: "🚨"
7730
+ },
7731
+ {
7732
+ id: "doorbell",
7733
+ name: "Doorbell / Knock",
7734
+ icon: "🔔"
7735
+ },
7736
+ {
7737
+ id: "glass_breaking",
7738
+ name: "Glass Breaking",
7739
+ icon: "💥"
7740
+ },
7741
+ {
7742
+ id: "gunshot",
7743
+ name: "Gunshot / Explosion",
7744
+ icon: "💣"
7745
+ },
7746
+ {
7747
+ id: "vehicle",
7748
+ name: "Vehicle",
7749
+ icon: "🚗"
7750
+ },
7751
+ {
7752
+ id: "siren",
7753
+ name: "Emergency Siren",
7754
+ icon: "🚑"
7755
+ },
7756
+ {
7757
+ id: "fire",
7758
+ name: "Fire / Smoke",
7759
+ icon: "🔥"
7760
+ },
7761
+ {
7762
+ id: "water",
7763
+ name: "Water",
7764
+ icon: "💧"
7765
+ },
7766
+ {
7767
+ id: "wind",
7768
+ name: "Wind / Weather",
7769
+ icon: "🌬️"
7770
+ },
7771
+ {
7772
+ id: "door",
7773
+ name: "Door",
7774
+ icon: "🚪"
7775
+ },
7776
+ {
7777
+ id: "footsteps",
7778
+ name: "Footsteps",
7779
+ icon: "👣"
7780
+ },
7781
+ {
7782
+ id: "crowd",
7783
+ name: "Crowd / Chatter",
7784
+ icon: "👥"
7785
+ },
7786
+ {
7787
+ id: "telephone",
7788
+ name: "Telephone",
7789
+ icon: "📞"
7790
+ },
7791
+ {
7792
+ id: "engine",
7793
+ name: "Engine / Motor",
7794
+ icon: "⚙️"
7795
+ },
7796
+ {
7797
+ id: "tools",
7798
+ name: "Tools / Construction",
7799
+ icon: "🔨"
7800
+ },
7801
+ {
7802
+ id: "silence",
7803
+ name: "Silence",
7804
+ icon: "🤫"
7805
+ }
7806
+ ];
7642
7807
  var YAMNET_TO_MACRO = {
7643
7808
  mapping: {
7644
7809
  Speech: "speech",
@@ -7905,6 +8070,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7905
8070
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7906
8071
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7907
8072
  /**
8073
+ * Unified event-kind taxonomy — THE single source of truth for
8074
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8075
+ * icon }`.
8076
+ *
8077
+ * This dictionary folds together what used to be scattered across four
8078
+ * copies:
8079
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8080
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8081
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8082
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8083
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8084
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8085
+ *
8086
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8087
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8088
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8089
+ * color or an icon: they read this dictionary (server descriptors carry the
8090
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8091
+ *
8092
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8093
+ */
8094
+ var TAXONOMY_COLORS = {
8095
+ motion: "#f59e0b",
8096
+ audio: "#06b6d4",
8097
+ person: "#22c55e",
8098
+ vehicle: "#3b82f6",
8099
+ animal: "#f97316",
8100
+ package: "#a855f7",
8101
+ sensor: "#8b5cf6",
8102
+ control: "#10b981",
8103
+ genericDetection: "#64748b"
8104
+ };
8105
+ var DETECTION_SUB_COLORS = {
8106
+ car: "#f59e0b",
8107
+ truck: "#d97706",
8108
+ bus: "#b45309",
8109
+ motorcycle: "#eab308",
8110
+ bicycle: "#ca8a04",
8111
+ airplane: "#60a5fa",
8112
+ boat: "#2563eb",
8113
+ train: "#1d4ed8",
8114
+ bird: "#14b8a6",
8115
+ dog: "#84cc16",
8116
+ cat: "#f97316",
8117
+ horse: "#a16207",
8118
+ sheep: "#a3a3a3",
8119
+ cow: "#78716c",
8120
+ elephant: "#6b7280",
8121
+ bear: "#7c2d12",
8122
+ zebra: "#404040",
8123
+ giraffe: "#d4a373"
8124
+ };
8125
+ function titleCase(id) {
8126
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8127
+ }
8128
+ var entries = /* @__PURE__ */ new Map();
8129
+ function macro(kind, category, color, iconId, label) {
8130
+ entries.set(kind, {
8131
+ kind,
8132
+ parentKind: null,
8133
+ level: "macro",
8134
+ category,
8135
+ color,
8136
+ iconId,
8137
+ labelKey: `eventKind.${kind}`,
8138
+ label
8139
+ });
8140
+ }
8141
+ function sub(kind, parentKind, category, color, iconId, label) {
8142
+ entries.set(kind, {
8143
+ kind,
8144
+ parentKind,
8145
+ level: "sub",
8146
+ category,
8147
+ color,
8148
+ iconId,
8149
+ labelKey: `eventKind.${kind}`,
8150
+ label
8151
+ });
8152
+ }
8153
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8154
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8155
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8156
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8157
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8158
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8159
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8160
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8161
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8162
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8163
+ if (entries.has(cocoClass)) continue;
8164
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8165
+ }
8166
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8167
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8168
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8169
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8170
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8171
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8172
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8173
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8174
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8175
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8176
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8177
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8178
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8179
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8180
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8181
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8182
+ sub("button", "control", "control", "#10b981", "button", "Button");
8183
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8184
+ for (const l of AUDIO_MACRO_LABELS) {
8185
+ const kind = `audio-${l.id}`;
8186
+ if (entries.has(kind)) continue;
8187
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8188
+ }
8189
+ /** The complete taxonomy dictionary, keyed by kind. */
8190
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8191
+ /**
7908
8192
  * Error types for the safe expression engine. Two distinct classes so callers
7909
8193
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7910
8194
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -10820,10 +11104,7 @@ var ConfigUISchemaNullableBridge = custom();
10820
11104
  var InferenceCapabilitiesBridge = custom();
10821
11105
  var ModelAvailabilityListBridge = custom();
10822
11106
  var PipelineRunResultBridge = custom();
10823
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
10824
- kind: "mutation",
10825
- auth: "admin"
10826
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
11107
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
10827
11108
  modelId: string(),
10828
11109
  settings: record(string(), unknown()).readonly()
10829
11110
  }))), method(object({ steps: record(string(), object({
@@ -10883,13 +11164,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10883
11164
  * (inputClasses ≠ null) are skipped and served per-track via
10884
11165
  * pipelineRunner.runDetailSubtree (two-plane design).
10885
11166
  */
10886
- plane: _enum(["full", "frame"]).optional()
11167
+ plane: _enum(["full", "frame"]).optional(),
11168
+ /**
11169
+ * Inference-device selector (Phase 2 multi-device). Format
11170
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
11171
+ * Omitted ⇒ the runner's default device (current single-engine
11172
+ * behaviour). Selects WHICH device pool of the node runs the call.
11173
+ */
11174
+ deviceKey: string().optional()
10887
11175
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
10888
11176
  engine: PipelineEngineChoiceSchema.optional(),
10889
11177
  steps: array(PipelineStepInputSchema).min(1),
10890
11178
  frames: array(FrameInputSchema).min(1).max(255),
10891
11179
  deviceId: number().optional(),
10892
- sessionId: string().optional()
11180
+ sessionId: string().optional(),
11181
+ /**
11182
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
11183
+ * the batch to the Python pool's bench preprocess cache
11184
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
11185
+ * preprocessed ONCE and every later inference is a pure-inference cache
11186
+ * hit — the sustained-throughput run measures inference, not
11187
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
11188
+ * full preprocess every call, correct). Fresh per sustained run;
11189
+ * released via `uncacheFrame`.
11190
+ */
11191
+ frameId: number().int().nonnegative().optional(),
11192
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
11193
+ deviceKey: string().optional()
10893
11194
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
10894
11195
  data: _instanceof(Uint8Array),
10895
11196
  width: number().int().positive(),
@@ -10921,8 +11222,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10921
11222
  * - `runtime` — main camera-serving engine (no idle TTL).
10922
11223
  * - `warm-override` — benchmark/test override held in the warm
10923
11224
  * cache; auto-disposed after the idle TTL.
11225
+ * - `device-pool` — a concurrent per-device pool (Phase 2
11226
+ * multi-device, keyed by `deviceKey`) resolved
11227
+ * via `resolveDeviceFactory`. Runs alongside the
11228
+ * `runtime` engine on a DIFFERENT accelerator
11229
+ * (NPU / iGPU / Coral) — this is how the
11230
+ * Engines tab shows all pools running at once.
10924
11231
  */
10925
- kind: _enum(["runtime", "warm-override"]),
11232
+ kind: _enum([
11233
+ "runtime",
11234
+ "warm-override",
11235
+ "device-pool"
11236
+ ]),
10926
11237
  /** Native pid of the underlying Python pool (null when no pool). */
10927
11238
  poolPid: number().nullable(),
10928
11239
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -11264,7 +11575,14 @@ var RunnerCameraConfigSchema = object({
11264
11575
  * camera's detect node differs from its source-owner (P2d, gated by the
11265
11576
  * `remoteSourcingNodes` rollout setting).
11266
11577
  */
11267
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
11578
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
11579
+ /**
11580
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
11581
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
11582
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
11583
+ * this only selects WHICH device pool of that node runs the session.
11584
+ */
11585
+ deviceKey: string().optional()
11268
11586
  });
11269
11587
  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;
11270
11588
  /**
@@ -11285,6 +11603,19 @@ var RunnerLocalLoadSchema = object({
11285
11603
  avgInferenceTimeMs: number(),
11286
11604
  /** Total queue depth across motion + detection queues. */
11287
11605
  queueDepthTotal: number(),
11606
+ /**
11607
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
11608
+ * this runner currently has attached cameras on, so the orchestrator's second
11609
+ * `balance()` pass (over a node's devices) weights on real per-pool session
11610
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
11611
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
11612
+ */
11613
+ devices: array(object({
11614
+ deviceKey: string(),
11615
+ backend: string(),
11616
+ attachedCameras: number(),
11617
+ queueDepthTotal: number()
11618
+ })).default([]),
11288
11619
  /** Hardware capability flags reported by this node. */
11289
11620
  hardware: object({
11290
11621
  hasGpu: boolean(),
@@ -12760,6 +13091,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
12760
13091
  kind: "mutation",
12761
13092
  auth: "admin"
12762
13093
  });
13094
+ 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;
13095
+ new Set(Object.values(DeviceType));
12763
13096
  /**
12764
13097
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
12765
13098
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -15984,17 +16317,30 @@ var EventKindCategorySchema = _enum([
15984
16317
  "audio",
15985
16318
  "detection",
15986
16319
  "sensor",
16320
+ "control",
15987
16321
  "custom",
15988
16322
  "package"
15989
16323
  ]);
16324
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16325
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15990
16326
  var EventKindDescriptorSchema = object({
15991
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16327
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15992
16328
  kind: string(),
16329
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16330
+ labelKey: string(),
16331
+ /** English fallback label (kept for clients that don't translate). */
15993
16332
  label: string(),
15994
16333
  /** Hex color for timeline/legend rendering. */
15995
16334
  color: string(),
16335
+ /** Dictionary id → lucide component on the UI side. */
16336
+ iconId: string(),
16337
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15996
16338
  icon: EventKindIconSchema,
15997
16339
  category: EventKindCategorySchema,
16340
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16341
+ parentKind: string().nullable(),
16342
+ /** Derived from `parentKind`, explicit for the client tree. */
16343
+ level: EventKindLevelSchema,
15998
16344
  /** Which cap + device contributes this kind. For built-ins the camera
15999
16345
  * itself; for sensor kinds the LINKED source device. */
16000
16346
  source: object({
@@ -16067,11 +16413,21 @@ var TrackAudioLabelSchema = object({
16067
16413
  firstAt: number(),
16068
16414
  lastAt: number()
16069
16415
  });
16416
+ /**
16417
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16418
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16419
+ * linked sensor/control state change (no positions; carries a snapshot). The
16420
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16421
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16422
+ */
16423
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16070
16424
  var TrackSchema = object({
16071
16425
  trackId: string(),
16072
16426
  deviceId: number(),
16073
16427
  className: string(),
16074
16428
  label: string().optional(),
16429
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16430
+ source: TrackSourceSchema.optional(),
16075
16431
  firstSeen: number(),
16076
16432
  lastSeen: number(),
16077
16433
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16448,6 +16804,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16448
16804
  eventId: string(),
16449
16805
  timestamp: number()
16450
16806
  });
16807
+ /**
16808
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16809
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16810
+ * caps into per-camera event-kind descriptors.
16811
+ *
16812
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16813
+ * is NOT duplicated here — every entry is derived from the single
16814
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16815
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16816
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16817
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16818
+ * eventful cap is missing.
16819
+ */
16820
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16821
+ var LEGACY_ICON = {
16822
+ motion: "motion",
16823
+ audio: "audio",
16824
+ person: "person",
16825
+ vehicle: "vehicle",
16826
+ animal: "animal",
16827
+ package: "package",
16828
+ door: "door",
16829
+ pir: "pir",
16830
+ smoke: "smoke",
16831
+ water: "water",
16832
+ button: "button",
16833
+ generic: "generic",
16834
+ gas: "smoke",
16835
+ vibration: "generic",
16836
+ tamper: "generic",
16837
+ presence: "person",
16838
+ lock: "generic",
16839
+ siren: "generic",
16840
+ switch: "generic",
16841
+ doorbell: "button"
16842
+ };
16843
+ function legacyIcon(iconId) {
16844
+ return LEGACY_ICON[iconId] ?? "generic";
16845
+ }
16846
+ /**
16847
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16848
+ * The anti-drift guard cross-checks this against the eventful caps declared
16849
+ * in `packages/types/src/capabilities/*.cap.ts`.
16850
+ */
16851
+ var CAP_TO_KIND = {
16852
+ contact: "contact",
16853
+ motion: "motion-sensor",
16854
+ smoke: "smoke",
16855
+ flood: "flood",
16856
+ gas: "gas",
16857
+ "carbon-monoxide": "carbon-monoxide",
16858
+ vibration: "vibration",
16859
+ tamper: "tamper",
16860
+ presence: "presence",
16861
+ "enum-sensor": "enum-sensor",
16862
+ "event-emitter": "device-event",
16863
+ "lock-control": "lock",
16864
+ switch: "switch",
16865
+ button: "button",
16866
+ doorbell: "doorbell"
16867
+ };
16868
+ function buildDescriptor(capName, kind) {
16869
+ const t = EVENT_TAXONOMY[kind];
16870
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16871
+ return {
16872
+ ...t,
16873
+ icon: legacyIcon(t.iconId)
16874
+ };
16875
+ }
16876
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16451
16877
  var CameraPipelineConfigSchema = object({
16452
16878
  engine: PipelineEngineChoiceSchema.optional(),
16453
16879
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16466,13 +16892,11 @@ var PipelineTemplateSchema = object({
16466
16892
  createdAt: string(),
16467
16893
  updatedAt: string()
16468
16894
  });
16469
- var AgentAddonConfigSchema = object({
16470
- enabled: boolean(),
16895
+ var DeviceStepConfigSchema = object({
16471
16896
  modelId: string().optional(),
16472
- settings: record(string(), unknown()).readonly()
16897
+ settings: record(string(), unknown()).optional()
16473
16898
  });
16474
16899
  var AgentPipelineSettingsSchema = object({
16475
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
16476
16900
  maxCameras: number().int().nonnegative().nullable().default(null),
16477
16901
  /** Per-node detection weight (relative share for the quota balancer). */
16478
16902
  detectWeight: number().positive().optional(),
@@ -16496,7 +16920,22 @@ var AgentPipelineSettingsSchema = object({
16496
16920
  * it already uses to reach the hub). Set this only when the auto-detected
16497
16921
  * address is wrong (multi-homed host, NAT, custom interface).
16498
16922
  */
16499
- reachableHost: string().optional()
16923
+ reachableHost: string().optional(),
16924
+ /**
16925
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
16926
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
16927
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
16928
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
16929
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
16930
+ * the default model/settings for every camera landing on that accelerator;
16931
+ * a stepId absent ⇒ the step uses that device's format default.
16932
+ */
16933
+ inferenceDevices: record(string(), object({
16934
+ enabled: boolean(),
16935
+ weight: number().positive().optional(),
16936
+ maxSessions: number().int().positive().optional(),
16937
+ steps: record(string(), DeviceStepConfigSchema).optional()
16938
+ })).optional()
16500
16939
  });
16501
16940
  var CameraPipelineForAgentSchema = object({
16502
16941
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16506,14 +16945,13 @@ var CameraPipelineForAgentSchema = object({
16506
16945
  }).nullable()
16507
16946
  });
16508
16947
  var CameraStepOverridePatchSchema = object({
16509
- enabled: boolean().optional(),
16510
16948
  modelId: string().optional(),
16511
16949
  settings: record(string(), unknown()).readonly().optional()
16512
16950
  });
16513
16951
  var CameraPipelineSettingsSchema = object({
16514
16952
  pinnedAgentNodeId: string().optional(),
16515
16953
  stepToggles: record(string(), boolean()).optional(),
16516
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
16954
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
16517
16955
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
16518
16956
  });
16519
16957
  /**
@@ -16727,6 +17165,44 @@ var CameraStatusSchema = object({
16727
17165
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
16728
17166
  fetchedAt: number()
16729
17167
  });
17168
+ var NodeInferenceDeviceSchema = object({
17169
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
17170
+ key: string(),
17171
+ backend: string(),
17172
+ device: string(),
17173
+ format: _enum(MODEL_FORMATS),
17174
+ /** Whether the node's live probe reports the device as usable right now. */
17175
+ available: boolean(),
17176
+ /**
17177
+ * Whether this device participates in dispatch. AUTO default (spec C2):
17178
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
17179
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
17180
+ * not a balanced target). An explicit stored value always wins; a stored-only
17181
+ * (unavailable) key keeps its stored value.
17182
+ */
17183
+ enabled: boolean(),
17184
+ /** Relative balancer weight for the enabled device (default 1). */
17185
+ weight: number(),
17186
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
17187
+ maxSessions: number().nullable(),
17188
+ /** Object-detection model the executor defaults to for this deviceKey. */
17189
+ defaultModelId: string(),
17190
+ /**
17191
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
17192
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
17193
+ * Absent/empty ⇒ no base (every step uses its device format default). The
17194
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
17195
+ * available per format; this is the stored selection that becomes the
17196
+ * default for EVERY camera landing on this accelerator.
17197
+ */
17198
+ steps: record(string(), DeviceStepConfigSchema).optional()
17199
+ });
17200
+ var NodeInferenceDevicesSchema = object({
17201
+ nodeId: string(),
17202
+ /** False when the node's platform-probe was unreachable (no live device set). */
17203
+ reachable: boolean(),
17204
+ devices: array(NodeInferenceDeviceSchema).readonly()
17205
+ });
16730
17206
  method(object({
16731
17207
  deviceId: number(),
16732
17208
  agentNodeId: string()
@@ -16736,7 +17212,13 @@ method(object({
16736
17212
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
16737
17213
  kind: "mutation",
16738
17214
  auth: "admin"
16739
- }), method(_void(), object({ migrated: number() }), {
17215
+ }), method(object({
17216
+ deviceId: number(),
17217
+ deviceKey: string()
17218
+ }), object({ success: literal(true) }), {
17219
+ kind: "mutation",
17220
+ auth: "admin"
17221
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
16740
17222
  kind: "mutation",
16741
17223
  auth: "admin"
16742
17224
  }), 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({
@@ -16770,13 +17252,7 @@ method(object({
16770
17252
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
16771
17253
  nodeId: string(),
16772
17254
  settings: AgentPipelineSettingsSchema
16773
- })).readonly()), method(object({
16774
- agentNodeId: string(),
16775
- defaults: record(string(), AgentAddonConfigSchema)
16776
- }), object({ success: literal(true) }), {
16777
- kind: "mutation",
16778
- auth: "admin"
16779
- }), method(object({ agentNodeId: string() }), object({
17255
+ })).readonly()), method(object({ agentNodeId: string() }), object({
16780
17256
  success: boolean(),
16781
17257
  removed: boolean()
16782
17258
  }), {
@@ -16808,7 +17284,18 @@ method(object({
16808
17284
  }), object({ success: literal(true) }), {
16809
17285
  kind: "mutation",
16810
17286
  auth: "admin"
16811
- }), method(object({ agentNodeId: string() }), object({
17287
+ }), method(object({
17288
+ agentNodeId: string(),
17289
+ inferenceDevices: record(string(), object({
17290
+ enabled: boolean(),
17291
+ weight: number().positive().optional(),
17292
+ maxSessions: number().int().positive().optional(),
17293
+ steps: record(string(), DeviceStepConfigSchema).optional()
17294
+ }))
17295
+ }), object({ success: literal(true) }), {
17296
+ kind: "mutation",
17297
+ auth: "admin"
17298
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
16812
17299
  success: literal(true),
16813
17300
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
16814
17301
  effectiveModelId: string().nullable(),
@@ -16824,9 +17311,10 @@ method(object({
16824
17311
  }), object({ success: literal(true) }), {
16825
17312
  kind: "mutation",
16826
17313
  auth: "admin"
16827
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
17314
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
16828
17315
  deviceId: number(),
16829
17316
  agentNodeId: string(),
17317
+ deviceKey: string(),
16830
17318
  addonId: string(),
16831
17319
  patch: CameraStepOverridePatchSchema.nullable()
16832
17320
  }), object({ success: literal(true) }), {
@@ -16863,14 +17351,13 @@ method(object({
16863
17351
  });
16864
17352
  /**
16865
17353
  * server-management — per-NODE singleton capability for a node's ROOT
16866
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
16867
- * agents).
17354
+ * package lifecycle (runtime-updatable node packages).
16868
17355
  *
16869
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
16870
- * on agents) carries the whole software stack in its npm dep tree, so ONE
16871
- * version describes the node. Updates install into
16872
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
16873
- * starter (probation boot + auto-rollback to N-1).
17356
+ * Every node role runs the SAME root package (`@camstack/server`), which
17357
+ * carries the whole software stack in its npm dep tree, so ONE version
17358
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
17359
+ * on restart via the baked starter (single-copy in-place swap — no probation,
17360
+ * no auto-rollback).
16874
17361
  *
16875
17362
  * Providers:
16876
17363
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -16978,7 +17465,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
16978
17465
  /** Explicit target version; omitted = latest from the registry. */
16979
17466
  version: string().optional() }), ServerUpdateActionResultSchema, {
16980
17467
  kind: "mutation",
16981
- auth: "admin"
17468
+ auth: "admin",
17469
+ timeoutMs: 16 * 6e4
16982
17470
  }), method(_void(), ServerUpdateActionResultSchema, {
16983
17471
  kind: "mutation",
16984
17472
  auth: "admin"
@@ -18022,22 +18510,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
18022
18510
  var RestartAddonResultSchema = unknown();
18023
18511
  var InstallPackageResultSchema = unknown();
18024
18512
  var ReloadPackagesResultSchema = unknown();
18025
- /**
18026
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
18027
- * server restarts so the admin UI can react to the `restartingAt`
18028
- * timestamp (shows reconnect overlay). The transition from
18029
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
18030
- * `system.restart-completed` event after the new process boots.
18031
- *
18032
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
18033
- */
18034
- var UpdateFrameworkPackageResultSchema = object({
18035
- packageName: string(),
18036
- fromVersion: string(),
18037
- toVersion: string(),
18038
- /** Ms-epoch the server scheduled its self-restart. */
18039
- restartingAt: number()
18040
- });
18041
18513
  var BulkUpdateItemStatusSchema = _enum([
18042
18514
  "queued",
18043
18515
  "updating",
@@ -18165,13 +18637,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
18165
18637
  }), object({ success: literal(true) }), {
18166
18638
  kind: "mutation",
18167
18639
  auth: "admin"
18168
- }), method(object({
18169
- packageName: string().min(1),
18170
- version: string().optional(),
18171
- deferRestart: boolean().optional()
18172
- }), UpdateFrameworkPackageResultSchema, {
18173
- kind: "mutation",
18174
- auth: "admin"
18175
18640
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
18176
18641
  kind: "mutation",
18177
18642
  auth: "admin"
@@ -19037,10 +19502,10 @@ var TopologyCategorySchema = object({
19037
19502
  addons: array(TopologyCategoryAddonSchema).readonly()
19038
19503
  });
19039
19504
  /**
19040
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
19041
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
19042
- * version visibility for the Server management surface. Nullable: offline
19043
- * rows and pre-phase-2 nodes report none.
19505
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
19506
+ * root package for every node role) as reported by its `registerNode`
19507
+ * manifest — version visibility for the Server management surface. Nullable:
19508
+ * offline rows and nodes that never reported one.
19044
19509
  */
19045
19510
  var TopologyRootPackageSchema = object({
19046
19511
  name: string(),
@@ -19428,17 +19893,28 @@ var PlatformScoreSchema = object({
19428
19893
  format: _enum([
19429
19894
  "onnx",
19430
19895
  "coreml",
19431
- "openvino"
19896
+ "openvino",
19897
+ "tflite"
19432
19898
  ]),
19433
19899
  score: number(),
19434
19900
  reason: string(),
19435
19901
  available: boolean()
19436
19902
  });
19903
+ var InferenceDeviceDescriptorSchema = object({
19904
+ key: string(),
19905
+ backend: string(),
19906
+ device: string(),
19907
+ format: ModelFormatSchema,
19908
+ runtime: literal("python"),
19909
+ score: number(),
19910
+ available: boolean()
19911
+ });
19437
19912
  var PlatformCapabilitiesSchema = object({
19438
19913
  hardware: HardwareInfoSchema,
19439
19914
  scores: array(PlatformScoreSchema).readonly(),
19440
19915
  bestScore: PlatformScoreSchema,
19441
- pythonPath: string().nullable()
19916
+ pythonPath: string().nullable(),
19917
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
19442
19918
  });
19443
19919
  var ModelRequirementSchema = object({
19444
19920
  modelId: string(),
@@ -20317,12 +20793,6 @@ Object.freeze({
20317
20793
  addonId: null,
20318
20794
  access: "delete"
20319
20795
  },
20320
- "addons.updateFrameworkPackage": {
20321
- capName: "addons",
20322
- capScope: "system",
20323
- addonId: null,
20324
- access: "create"
20325
- },
20326
20796
  "addons.updatePackage": {
20327
20797
  capName: "addons",
20328
20798
  capScope: "system",
@@ -23095,12 +23565,6 @@ Object.freeze({
23095
23565
  addonId: null,
23096
23566
  access: "view"
23097
23567
  },
23098
- "pipelineExecutor.reprobeEngine": {
23099
- capName: "pipeline-executor",
23100
- capScope: "system",
23101
- addonId: null,
23102
- access: "create"
23103
- },
23104
23568
  "pipelineExecutor.runAudioTest": {
23105
23569
  capName: "pipeline-executor",
23106
23570
  capScope: "system",
@@ -23251,6 +23715,12 @@ Object.freeze({
23251
23715
  addonId: null,
23252
23716
  access: "view"
23253
23717
  },
23718
+ "pipelineOrchestrator.getNodeInferenceDevices": {
23719
+ capName: "pipeline-orchestrator",
23720
+ capScope: "system",
23721
+ addonId: null,
23722
+ access: "view"
23723
+ },
23254
23724
  "pipelineOrchestrator.getPipelineAssignment": {
23255
23725
  capName: "pipeline-orchestrator",
23256
23726
  capScope: "system",
@@ -23263,6 +23733,12 @@ Object.freeze({
23263
23733
  addonId: null,
23264
23734
  access: "view"
23265
23735
  },
23736
+ "pipelineOrchestrator.getPipelineDevicePin": {
23737
+ capName: "pipeline-orchestrator",
23738
+ capScope: "system",
23739
+ addonId: null,
23740
+ access: "view"
23741
+ },
23266
23742
  "pipelineOrchestrator.listAgentSettings": {
23267
23743
  capName: "pipeline-orchestrator",
23268
23744
  capScope: "system",
@@ -23305,19 +23781,19 @@ Object.freeze({
23305
23781
  addonId: null,
23306
23782
  access: "create"
23307
23783
  },
23308
- "pipelineOrchestrator.setAgentAddonDefaults": {
23784
+ "pipelineOrchestrator.setAgentCapabilities": {
23309
23785
  capName: "pipeline-orchestrator",
23310
23786
  capScope: "system",
23311
23787
  addonId: null,
23312
23788
  access: "create"
23313
23789
  },
23314
- "pipelineOrchestrator.setAgentCapabilities": {
23790
+ "pipelineOrchestrator.setAgentDetectWeight": {
23315
23791
  capName: "pipeline-orchestrator",
23316
23792
  capScope: "system",
23317
23793
  addonId: null,
23318
23794
  access: "create"
23319
23795
  },
23320
- "pipelineOrchestrator.setAgentDetectWeight": {
23796
+ "pipelineOrchestrator.setAgentInferenceDevices": {
23321
23797
  capName: "pipeline-orchestrator",
23322
23798
  capScope: "system",
23323
23799
  addonId: null,
@@ -23359,6 +23835,12 @@ Object.freeze({
23359
23835
  addonId: null,
23360
23836
  access: "create"
23361
23837
  },
23838
+ "pipelineOrchestrator.setPipelineDevicePin": {
23839
+ capName: "pipeline-orchestrator",
23840
+ capScope: "system",
23841
+ addonId: null,
23842
+ access: "create"
23843
+ },
23362
23844
  "pipelineOrchestrator.unassignAudio": {
23363
23845
  capName: "pipeline-orchestrator",
23364
23846
  capScope: "system",
@@ -24911,32 +25393,6 @@ Object.freeze({
24911
25393
  "network-access": "ingress",
24912
25394
  "smtp-provider": "email"
24913
25395
  });
24914
- var frameworkSwapPackageSchema = object({
24915
- name: string(),
24916
- stagedPath: string(),
24917
- backupPath: string(),
24918
- toVersion: string(),
24919
- fromVersion: string().nullable()
24920
- });
24921
- object({
24922
- jobId: string(),
24923
- taskId: string(),
24924
- packages: array(frameworkSwapPackageSchema),
24925
- requestedAtMs: number(),
24926
- schemaVersion: literal(1)
24927
- });
24928
- object({
24929
- jobId: string(),
24930
- taskId: string(),
24931
- backups: array(object({
24932
- name: string(),
24933
- backupPath: string(),
24934
- livePath: string()
24935
- })),
24936
- appliedAtMs: number(),
24937
- bootAttempts: number(),
24938
- schemaVersion: literal(1)
24939
- });
24940
25396
  /**
24941
25397
  * Fixed-capacity ring buffer. When full, push() overwrites the oldest entry.
24942
25398
  * drain() returns up to maxCount items in FIFO order and removes them.