@camstack/addon-remote-storage 1.1.28 → 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.
@@ -7069,6 +7069,17 @@ var ModelCatalogEntrySchema = object({
7069
7069
  "imagenet",
7070
7070
  "none"
7071
7071
  ]).optional(),
7072
+ /**
7073
+ * The model already applies softmax IN-GRAPH — its raw output is a
7074
+ * probability distribution, not logits. When set, the `softmax`
7075
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7076
+ * probability vector collapses it toward uniform (top-1 score craters far
7077
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7078
+ * the output is raw logits and the postprocessor applies softmax (the normal
7079
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7080
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7081
+ */
7082
+ outputProbabilities: boolean().optional(),
7072
7083
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7073
7084
  /**
7074
7085
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -7628,6 +7639,160 @@ var EncodeProfileSchema = object({
7628
7639
  */
7629
7640
  outputArgs: array(string()).optional()
7630
7641
  });
7642
+ var COCO_TO_MACRO = {
7643
+ mapping: {
7644
+ person: "person",
7645
+ bicycle: "vehicle",
7646
+ car: "vehicle",
7647
+ motorcycle: "vehicle",
7648
+ airplane: "vehicle",
7649
+ bus: "vehicle",
7650
+ train: "vehicle",
7651
+ truck: "vehicle",
7652
+ boat: "vehicle",
7653
+ bird: "animal",
7654
+ cat: "animal",
7655
+ dog: "animal",
7656
+ horse: "animal",
7657
+ sheep: "animal",
7658
+ cow: "animal",
7659
+ elephant: "animal",
7660
+ bear: "animal",
7661
+ zebra: "animal",
7662
+ giraffe: "animal",
7663
+ suitcase: "package",
7664
+ backpack: "package",
7665
+ handbag: "package"
7666
+ },
7667
+ preserveOriginal: false
7668
+ };
7669
+ var AUDIO_MACRO_LABELS = [
7670
+ {
7671
+ id: "speech",
7672
+ name: "Speech",
7673
+ icon: "🗣️"
7674
+ },
7675
+ {
7676
+ id: "scream",
7677
+ name: "Scream / Shout",
7678
+ icon: "😱"
7679
+ },
7680
+ {
7681
+ id: "crying",
7682
+ name: "Crying / Baby",
7683
+ icon: "😢"
7684
+ },
7685
+ {
7686
+ id: "laughter",
7687
+ name: "Laughter",
7688
+ icon: "😂"
7689
+ },
7690
+ {
7691
+ id: "music",
7692
+ name: "Music",
7693
+ icon: "🎵"
7694
+ },
7695
+ {
7696
+ id: "dog",
7697
+ name: "Dog",
7698
+ icon: "🐕"
7699
+ },
7700
+ {
7701
+ id: "cat",
7702
+ name: "Cat",
7703
+ icon: "🐈"
7704
+ },
7705
+ {
7706
+ id: "bird",
7707
+ name: "Bird",
7708
+ icon: "🐦"
7709
+ },
7710
+ {
7711
+ id: "animal",
7712
+ name: "Animal (other)",
7713
+ icon: "🐾"
7714
+ },
7715
+ {
7716
+ id: "alarm",
7717
+ name: "Alarm / Siren",
7718
+ icon: "🚨"
7719
+ },
7720
+ {
7721
+ id: "doorbell",
7722
+ name: "Doorbell / Knock",
7723
+ icon: "🔔"
7724
+ },
7725
+ {
7726
+ id: "glass_breaking",
7727
+ name: "Glass Breaking",
7728
+ icon: "💥"
7729
+ },
7730
+ {
7731
+ id: "gunshot",
7732
+ name: "Gunshot / Explosion",
7733
+ icon: "💣"
7734
+ },
7735
+ {
7736
+ id: "vehicle",
7737
+ name: "Vehicle",
7738
+ icon: "🚗"
7739
+ },
7740
+ {
7741
+ id: "siren",
7742
+ name: "Emergency Siren",
7743
+ icon: "🚑"
7744
+ },
7745
+ {
7746
+ id: "fire",
7747
+ name: "Fire / Smoke",
7748
+ icon: "🔥"
7749
+ },
7750
+ {
7751
+ id: "water",
7752
+ name: "Water",
7753
+ icon: "💧"
7754
+ },
7755
+ {
7756
+ id: "wind",
7757
+ name: "Wind / Weather",
7758
+ icon: "🌬️"
7759
+ },
7760
+ {
7761
+ id: "door",
7762
+ name: "Door",
7763
+ icon: "🚪"
7764
+ },
7765
+ {
7766
+ id: "footsteps",
7767
+ name: "Footsteps",
7768
+ icon: "👣"
7769
+ },
7770
+ {
7771
+ id: "crowd",
7772
+ name: "Crowd / Chatter",
7773
+ icon: "👥"
7774
+ },
7775
+ {
7776
+ id: "telephone",
7777
+ name: "Telephone",
7778
+ icon: "📞"
7779
+ },
7780
+ {
7781
+ id: "engine",
7782
+ name: "Engine / Motor",
7783
+ icon: "⚙️"
7784
+ },
7785
+ {
7786
+ id: "tools",
7787
+ name: "Tools / Construction",
7788
+ icon: "🔨"
7789
+ },
7790
+ {
7791
+ id: "silence",
7792
+ name: "Silence",
7793
+ icon: "🤫"
7794
+ }
7795
+ ];
7631
7796
  var YAMNET_TO_MACRO = {
7632
7797
  mapping: {
7633
7798
  Speech: "speech",
@@ -7894,6 +8059,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7894
8059
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7895
8060
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7896
8061
  /**
8062
+ * Unified event-kind taxonomy — THE single source of truth for
8063
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8064
+ * icon }`.
8065
+ *
8066
+ * This dictionary folds together what used to be scattered across four
8067
+ * copies:
8068
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8069
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8070
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8071
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8072
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8073
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8074
+ *
8075
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8076
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8077
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8078
+ * color or an icon: they read this dictionary (server descriptors carry the
8079
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8080
+ *
8081
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8082
+ */
8083
+ var TAXONOMY_COLORS = {
8084
+ motion: "#f59e0b",
8085
+ audio: "#06b6d4",
8086
+ person: "#22c55e",
8087
+ vehicle: "#3b82f6",
8088
+ animal: "#f97316",
8089
+ package: "#a855f7",
8090
+ sensor: "#8b5cf6",
8091
+ control: "#10b981",
8092
+ genericDetection: "#64748b"
8093
+ };
8094
+ var DETECTION_SUB_COLORS = {
8095
+ car: "#f59e0b",
8096
+ truck: "#d97706",
8097
+ bus: "#b45309",
8098
+ motorcycle: "#eab308",
8099
+ bicycle: "#ca8a04",
8100
+ airplane: "#60a5fa",
8101
+ boat: "#2563eb",
8102
+ train: "#1d4ed8",
8103
+ bird: "#14b8a6",
8104
+ dog: "#84cc16",
8105
+ cat: "#f97316",
8106
+ horse: "#a16207",
8107
+ sheep: "#a3a3a3",
8108
+ cow: "#78716c",
8109
+ elephant: "#6b7280",
8110
+ bear: "#7c2d12",
8111
+ zebra: "#404040",
8112
+ giraffe: "#d4a373"
8113
+ };
8114
+ function titleCase(id) {
8115
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8116
+ }
8117
+ var entries = /* @__PURE__ */ new Map();
8118
+ function macro(kind, category, color, iconId, label) {
8119
+ entries.set(kind, {
8120
+ kind,
8121
+ parentKind: null,
8122
+ level: "macro",
8123
+ category,
8124
+ color,
8125
+ iconId,
8126
+ labelKey: `eventKind.${kind}`,
8127
+ label
8128
+ });
8129
+ }
8130
+ function sub(kind, parentKind, category, color, iconId, label) {
8131
+ entries.set(kind, {
8132
+ kind,
8133
+ parentKind,
8134
+ level: "sub",
8135
+ category,
8136
+ color,
8137
+ iconId,
8138
+ labelKey: `eventKind.${kind}`,
8139
+ label
8140
+ });
8141
+ }
8142
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8143
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8144
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8145
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8146
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8147
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8148
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8149
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8150
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8151
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8152
+ if (entries.has(cocoClass)) continue;
8153
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8154
+ }
8155
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8156
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8157
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8158
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8159
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8160
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8161
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8162
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8163
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8164
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8165
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8166
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8167
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8168
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8169
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8170
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8171
+ sub("button", "control", "control", "#10b981", "button", "Button");
8172
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8173
+ for (const l of AUDIO_MACRO_LABELS) {
8174
+ const kind = `audio-${l.id}`;
8175
+ if (entries.has(kind)) continue;
8176
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8177
+ }
8178
+ /** The complete taxonomy dictionary, keyed by kind. */
8179
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8180
+ /**
7897
8181
  * Error types for the safe expression engine. Two distinct classes so callers
7898
8182
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7899
8183
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -10809,10 +11093,7 @@ var ConfigUISchemaNullableBridge = custom();
10809
11093
  var InferenceCapabilitiesBridge = custom();
10810
11094
  var ModelAvailabilityListBridge = custom();
10811
11095
  var PipelineRunResultBridge = custom();
10812
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
10813
- kind: "mutation",
10814
- auth: "admin"
10815
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
11096
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
10816
11097
  modelId: string(),
10817
11098
  settings: record(string(), unknown()).readonly()
10818
11099
  }))), method(object({ steps: record(string(), object({
@@ -10872,13 +11153,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10872
11153
  * (inputClasses ≠ null) are skipped and served per-track via
10873
11154
  * pipelineRunner.runDetailSubtree (two-plane design).
10874
11155
  */
10875
- plane: _enum(["full", "frame"]).optional()
11156
+ plane: _enum(["full", "frame"]).optional(),
11157
+ /**
11158
+ * Inference-device selector (Phase 2 multi-device). Format
11159
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
11160
+ * Omitted ⇒ the runner's default device (current single-engine
11161
+ * behaviour). Selects WHICH device pool of the node runs the call.
11162
+ */
11163
+ deviceKey: string().optional()
10876
11164
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
10877
11165
  engine: PipelineEngineChoiceSchema.optional(),
10878
11166
  steps: array(PipelineStepInputSchema).min(1),
10879
11167
  frames: array(FrameInputSchema).min(1).max(255),
10880
11168
  deviceId: number().optional(),
10881
- sessionId: string().optional()
11169
+ sessionId: string().optional(),
11170
+ /**
11171
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
11172
+ * the batch to the Python pool's bench preprocess cache
11173
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
11174
+ * preprocessed ONCE and every later inference is a pure-inference cache
11175
+ * hit — the sustained-throughput run measures inference, not
11176
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
11177
+ * full preprocess every call, correct). Fresh per sustained run;
11178
+ * released via `uncacheFrame`.
11179
+ */
11180
+ frameId: number().int().nonnegative().optional(),
11181
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
11182
+ deviceKey: string().optional()
10882
11183
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
10883
11184
  data: _instanceof(Uint8Array),
10884
11185
  width: number().int().positive(),
@@ -10910,8 +11211,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10910
11211
  * - `runtime` — main camera-serving engine (no idle TTL).
10911
11212
  * - `warm-override` — benchmark/test override held in the warm
10912
11213
  * cache; auto-disposed after the idle TTL.
11214
+ * - `device-pool` — a concurrent per-device pool (Phase 2
11215
+ * multi-device, keyed by `deviceKey`) resolved
11216
+ * via `resolveDeviceFactory`. Runs alongside the
11217
+ * `runtime` engine on a DIFFERENT accelerator
11218
+ * (NPU / iGPU / Coral) — this is how the
11219
+ * Engines tab shows all pools running at once.
10913
11220
  */
10914
- kind: _enum(["runtime", "warm-override"]),
11221
+ kind: _enum([
11222
+ "runtime",
11223
+ "warm-override",
11224
+ "device-pool"
11225
+ ]),
10915
11226
  /** Native pid of the underlying Python pool (null when no pool). */
10916
11227
  poolPid: number().nullable(),
10917
11228
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -11253,7 +11564,14 @@ var RunnerCameraConfigSchema = object({
11253
11564
  * camera's detect node differs from its source-owner (P2d, gated by the
11254
11565
  * `remoteSourcingNodes` rollout setting).
11255
11566
  */
11256
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
11567
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
11568
+ /**
11569
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
11570
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
11571
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
11572
+ * this only selects WHICH device pool of that node runs the session.
11573
+ */
11574
+ deviceKey: string().optional()
11257
11575
  });
11258
11576
  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;
11259
11577
  /**
@@ -11274,6 +11592,19 @@ var RunnerLocalLoadSchema = object({
11274
11592
  avgInferenceTimeMs: number(),
11275
11593
  /** Total queue depth across motion + detection queues. */
11276
11594
  queueDepthTotal: number(),
11595
+ /**
11596
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
11597
+ * this runner currently has attached cameras on, so the orchestrator's second
11598
+ * `balance()` pass (over a node's devices) weights on real per-pool session
11599
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
11600
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
11601
+ */
11602
+ devices: array(object({
11603
+ deviceKey: string(),
11604
+ backend: string(),
11605
+ attachedCameras: number(),
11606
+ queueDepthTotal: number()
11607
+ })).default([]),
11277
11608
  /** Hardware capability flags reported by this node. */
11278
11609
  hardware: object({
11279
11610
  hasGpu: boolean(),
@@ -12749,6 +13080,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
12749
13080
  kind: "mutation",
12750
13081
  auth: "admin"
12751
13082
  });
13083
+ 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;
13084
+ new Set(Object.values(DeviceType));
12752
13085
  /**
12753
13086
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
12754
13087
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -15843,17 +16176,30 @@ var EventKindCategorySchema = _enum([
15843
16176
  "audio",
15844
16177
  "detection",
15845
16178
  "sensor",
16179
+ "control",
15846
16180
  "custom",
15847
16181
  "package"
15848
16182
  ]);
16183
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16184
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15849
16185
  var EventKindDescriptorSchema = object({
15850
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16186
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15851
16187
  kind: string(),
16188
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16189
+ labelKey: string(),
16190
+ /** English fallback label (kept for clients that don't translate). */
15852
16191
  label: string(),
15853
16192
  /** Hex color for timeline/legend rendering. */
15854
16193
  color: string(),
16194
+ /** Dictionary id → lucide component on the UI side. */
16195
+ iconId: string(),
16196
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15855
16197
  icon: EventKindIconSchema,
15856
16198
  category: EventKindCategorySchema,
16199
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16200
+ parentKind: string().nullable(),
16201
+ /** Derived from `parentKind`, explicit for the client tree. */
16202
+ level: EventKindLevelSchema,
15857
16203
  /** Which cap + device contributes this kind. For built-ins the camera
15858
16204
  * itself; for sensor kinds the LINKED source device. */
15859
16205
  source: object({
@@ -15926,11 +16272,21 @@ var TrackAudioLabelSchema = object({
15926
16272
  firstAt: number(),
15927
16273
  lastAt: number()
15928
16274
  });
16275
+ /**
16276
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16277
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16278
+ * linked sensor/control state change (no positions; carries a snapshot). The
16279
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16280
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16281
+ */
16282
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15929
16283
  var TrackSchema = object({
15930
16284
  trackId: string(),
15931
16285
  deviceId: number(),
15932
16286
  className: string(),
15933
16287
  label: string().optional(),
16288
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16289
+ source: TrackSourceSchema.optional(),
15934
16290
  firstSeen: number(),
15935
16291
  lastSeen: number(),
15936
16292
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16307,6 +16663,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16307
16663
  eventId: string(),
16308
16664
  timestamp: number()
16309
16665
  });
16666
+ /**
16667
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16668
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16669
+ * caps into per-camera event-kind descriptors.
16670
+ *
16671
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16672
+ * is NOT duplicated here — every entry is derived from the single
16673
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16674
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16675
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16676
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16677
+ * eventful cap is missing.
16678
+ */
16679
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16680
+ var LEGACY_ICON = {
16681
+ motion: "motion",
16682
+ audio: "audio",
16683
+ person: "person",
16684
+ vehicle: "vehicle",
16685
+ animal: "animal",
16686
+ package: "package",
16687
+ door: "door",
16688
+ pir: "pir",
16689
+ smoke: "smoke",
16690
+ water: "water",
16691
+ button: "button",
16692
+ generic: "generic",
16693
+ gas: "smoke",
16694
+ vibration: "generic",
16695
+ tamper: "generic",
16696
+ presence: "person",
16697
+ lock: "generic",
16698
+ siren: "generic",
16699
+ switch: "generic",
16700
+ doorbell: "button"
16701
+ };
16702
+ function legacyIcon(iconId) {
16703
+ return LEGACY_ICON[iconId] ?? "generic";
16704
+ }
16705
+ /**
16706
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16707
+ * The anti-drift guard cross-checks this against the eventful caps declared
16708
+ * in `packages/types/src/capabilities/*.cap.ts`.
16709
+ */
16710
+ var CAP_TO_KIND = {
16711
+ contact: "contact",
16712
+ motion: "motion-sensor",
16713
+ smoke: "smoke",
16714
+ flood: "flood",
16715
+ gas: "gas",
16716
+ "carbon-monoxide": "carbon-monoxide",
16717
+ vibration: "vibration",
16718
+ tamper: "tamper",
16719
+ presence: "presence",
16720
+ "enum-sensor": "enum-sensor",
16721
+ "event-emitter": "device-event",
16722
+ "lock-control": "lock",
16723
+ switch: "switch",
16724
+ button: "button",
16725
+ doorbell: "doorbell"
16726
+ };
16727
+ function buildDescriptor(capName, kind) {
16728
+ const t = EVENT_TAXONOMY[kind];
16729
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16730
+ return {
16731
+ ...t,
16732
+ icon: legacyIcon(t.iconId)
16733
+ };
16734
+ }
16735
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16310
16736
  var CameraPipelineConfigSchema = object({
16311
16737
  engine: PipelineEngineChoiceSchema.optional(),
16312
16738
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16325,13 +16751,11 @@ var PipelineTemplateSchema = object({
16325
16751
  createdAt: string(),
16326
16752
  updatedAt: string()
16327
16753
  });
16328
- var AgentAddonConfigSchema = object({
16329
- enabled: boolean(),
16754
+ var DeviceStepConfigSchema = object({
16330
16755
  modelId: string().optional(),
16331
- settings: record(string(), unknown()).readonly()
16756
+ settings: record(string(), unknown()).optional()
16332
16757
  });
16333
16758
  var AgentPipelineSettingsSchema = object({
16334
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
16335
16759
  maxCameras: number().int().nonnegative().nullable().default(null),
16336
16760
  /** Per-node detection weight (relative share for the quota balancer). */
16337
16761
  detectWeight: number().positive().optional(),
@@ -16355,7 +16779,22 @@ var AgentPipelineSettingsSchema = object({
16355
16779
  * it already uses to reach the hub). Set this only when the auto-detected
16356
16780
  * address is wrong (multi-homed host, NAT, custom interface).
16357
16781
  */
16358
- reachableHost: string().optional()
16782
+ reachableHost: string().optional(),
16783
+ /**
16784
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
16785
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
16786
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
16787
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
16788
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
16789
+ * the default model/settings for every camera landing on that accelerator;
16790
+ * a stepId absent ⇒ the step uses that device's format default.
16791
+ */
16792
+ inferenceDevices: record(string(), object({
16793
+ enabled: boolean(),
16794
+ weight: number().positive().optional(),
16795
+ maxSessions: number().int().positive().optional(),
16796
+ steps: record(string(), DeviceStepConfigSchema).optional()
16797
+ })).optional()
16359
16798
  });
16360
16799
  var CameraPipelineForAgentSchema = object({
16361
16800
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16365,14 +16804,13 @@ var CameraPipelineForAgentSchema = object({
16365
16804
  }).nullable()
16366
16805
  });
16367
16806
  var CameraStepOverridePatchSchema = object({
16368
- enabled: boolean().optional(),
16369
16807
  modelId: string().optional(),
16370
16808
  settings: record(string(), unknown()).readonly().optional()
16371
16809
  });
16372
16810
  var CameraPipelineSettingsSchema = object({
16373
16811
  pinnedAgentNodeId: string().optional(),
16374
16812
  stepToggles: record(string(), boolean()).optional(),
16375
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
16813
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
16376
16814
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
16377
16815
  });
16378
16816
  /**
@@ -16586,6 +17024,44 @@ var CameraStatusSchema = object({
16586
17024
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
16587
17025
  fetchedAt: number()
16588
17026
  });
17027
+ var NodeInferenceDeviceSchema = object({
17028
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
17029
+ key: string(),
17030
+ backend: string(),
17031
+ device: string(),
17032
+ format: _enum(MODEL_FORMATS),
17033
+ /** Whether the node's live probe reports the device as usable right now. */
17034
+ available: boolean(),
17035
+ /**
17036
+ * Whether this device participates in dispatch. AUTO default (spec C2):
17037
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
17038
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
17039
+ * not a balanced target). An explicit stored value always wins; a stored-only
17040
+ * (unavailable) key keeps its stored value.
17041
+ */
17042
+ enabled: boolean(),
17043
+ /** Relative balancer weight for the enabled device (default 1). */
17044
+ weight: number(),
17045
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
17046
+ maxSessions: number().nullable(),
17047
+ /** Object-detection model the executor defaults to for this deviceKey. */
17048
+ defaultModelId: string(),
17049
+ /**
17050
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
17051
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
17052
+ * Absent/empty ⇒ no base (every step uses its device format default). The
17053
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
17054
+ * available per format; this is the stored selection that becomes the
17055
+ * default for EVERY camera landing on this accelerator.
17056
+ */
17057
+ steps: record(string(), DeviceStepConfigSchema).optional()
17058
+ });
17059
+ var NodeInferenceDevicesSchema = object({
17060
+ nodeId: string(),
17061
+ /** False when the node's platform-probe was unreachable (no live device set). */
17062
+ reachable: boolean(),
17063
+ devices: array(NodeInferenceDeviceSchema).readonly()
17064
+ });
16589
17065
  method(object({
16590
17066
  deviceId: number(),
16591
17067
  agentNodeId: string()
@@ -16595,7 +17071,13 @@ method(object({
16595
17071
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
16596
17072
  kind: "mutation",
16597
17073
  auth: "admin"
16598
- }), method(_void(), object({ migrated: number() }), {
17074
+ }), method(object({
17075
+ deviceId: number(),
17076
+ deviceKey: string()
17077
+ }), object({ success: literal(true) }), {
17078
+ kind: "mutation",
17079
+ auth: "admin"
17080
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
16599
17081
  kind: "mutation",
16600
17082
  auth: "admin"
16601
17083
  }), 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({
@@ -16629,13 +17111,7 @@ method(object({
16629
17111
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
16630
17112
  nodeId: string(),
16631
17113
  settings: AgentPipelineSettingsSchema
16632
- })).readonly()), method(object({
16633
- agentNodeId: string(),
16634
- defaults: record(string(), AgentAddonConfigSchema)
16635
- }), object({ success: literal(true) }), {
16636
- kind: "mutation",
16637
- auth: "admin"
16638
- }), method(object({ agentNodeId: string() }), object({
17114
+ })).readonly()), method(object({ agentNodeId: string() }), object({
16639
17115
  success: boolean(),
16640
17116
  removed: boolean()
16641
17117
  }), {
@@ -16667,7 +17143,18 @@ method(object({
16667
17143
  }), object({ success: literal(true) }), {
16668
17144
  kind: "mutation",
16669
17145
  auth: "admin"
16670
- }), method(object({ agentNodeId: string() }), object({
17146
+ }), method(object({
17147
+ agentNodeId: string(),
17148
+ inferenceDevices: record(string(), object({
17149
+ enabled: boolean(),
17150
+ weight: number().positive().optional(),
17151
+ maxSessions: number().int().positive().optional(),
17152
+ steps: record(string(), DeviceStepConfigSchema).optional()
17153
+ }))
17154
+ }), object({ success: literal(true) }), {
17155
+ kind: "mutation",
17156
+ auth: "admin"
17157
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
16671
17158
  success: literal(true),
16672
17159
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
16673
17160
  effectiveModelId: string().nullable(),
@@ -16683,9 +17170,10 @@ method(object({
16683
17170
  }), object({ success: literal(true) }), {
16684
17171
  kind: "mutation",
16685
17172
  auth: "admin"
16686
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
17173
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
16687
17174
  deviceId: number(),
16688
17175
  agentNodeId: string(),
17176
+ deviceKey: string(),
16689
17177
  addonId: string(),
16690
17178
  patch: CameraStepOverridePatchSchema.nullable()
16691
17179
  }), object({ success: literal(true) }), {
@@ -16722,14 +17210,13 @@ method(object({
16722
17210
  });
16723
17211
  /**
16724
17212
  * server-management — per-NODE singleton capability for a node's ROOT
16725
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
16726
- * agents).
17213
+ * package lifecycle (runtime-updatable node packages).
16727
17214
  *
16728
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
16729
- * on agents) carries the whole software stack in its npm dep tree, so ONE
16730
- * version describes the node. Updates install into
16731
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
16732
- * starter (probation boot + auto-rollback to N-1).
17215
+ * Every node role runs the SAME root package (`@camstack/server`), which
17216
+ * carries the whole software stack in its npm dep tree, so ONE version
17217
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
17218
+ * on restart via the baked starter (single-copy in-place swap — no probation,
17219
+ * no auto-rollback).
16733
17220
  *
16734
17221
  * Providers:
16735
17222
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -16837,7 +17324,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
16837
17324
  /** Explicit target version; omitted = latest from the registry. */
16838
17325
  version: string().optional() }), ServerUpdateActionResultSchema, {
16839
17326
  kind: "mutation",
16840
- auth: "admin"
17327
+ auth: "admin",
17328
+ timeoutMs: 16 * 6e4
16841
17329
  }), method(_void(), ServerUpdateActionResultSchema, {
16842
17330
  kind: "mutation",
16843
17331
  auth: "admin"
@@ -17926,22 +18414,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
17926
18414
  var RestartAddonResultSchema = unknown();
17927
18415
  var InstallPackageResultSchema = unknown();
17928
18416
  var ReloadPackagesResultSchema = unknown();
17929
- /**
17930
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
17931
- * server restarts so the admin UI can react to the `restartingAt`
17932
- * timestamp (shows reconnect overlay). The transition from
17933
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
17934
- * `system.restart-completed` event after the new process boots.
17935
- *
17936
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
17937
- */
17938
- var UpdateFrameworkPackageResultSchema = object({
17939
- packageName: string(),
17940
- fromVersion: string(),
17941
- toVersion: string(),
17942
- /** Ms-epoch the server scheduled its self-restart. */
17943
- restartingAt: number()
17944
- });
17945
18417
  var BulkUpdateItemStatusSchema = _enum([
17946
18418
  "queued",
17947
18419
  "updating",
@@ -18069,13 +18541,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
18069
18541
  }), object({ success: literal(true) }), {
18070
18542
  kind: "mutation",
18071
18543
  auth: "admin"
18072
- }), method(object({
18073
- packageName: string().min(1),
18074
- version: string().optional(),
18075
- deferRestart: boolean().optional()
18076
- }), UpdateFrameworkPackageResultSchema, {
18077
- kind: "mutation",
18078
- auth: "admin"
18079
18544
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
18080
18545
  kind: "mutation",
18081
18546
  auth: "admin"
@@ -18941,10 +19406,10 @@ var TopologyCategorySchema = object({
18941
19406
  addons: array(TopologyCategoryAddonSchema).readonly()
18942
19407
  });
18943
19408
  /**
18944
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
18945
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
18946
- * version visibility for the Server management surface. Nullable: offline
18947
- * rows and pre-phase-2 nodes report none.
19409
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
19410
+ * root package for every node role) as reported by its `registerNode`
19411
+ * manifest — version visibility for the Server management surface. Nullable:
19412
+ * offline rows and nodes that never reported one.
18948
19413
  */
18949
19414
  var TopologyRootPackageSchema = object({
18950
19415
  name: string(),
@@ -19332,17 +19797,28 @@ var PlatformScoreSchema = object({
19332
19797
  format: _enum([
19333
19798
  "onnx",
19334
19799
  "coreml",
19335
- "openvino"
19800
+ "openvino",
19801
+ "tflite"
19336
19802
  ]),
19337
19803
  score: number(),
19338
19804
  reason: string(),
19339
19805
  available: boolean()
19340
19806
  });
19807
+ var InferenceDeviceDescriptorSchema = object({
19808
+ key: string(),
19809
+ backend: string(),
19810
+ device: string(),
19811
+ format: ModelFormatSchema,
19812
+ runtime: literal("python"),
19813
+ score: number(),
19814
+ available: boolean()
19815
+ });
19341
19816
  var PlatformCapabilitiesSchema = object({
19342
19817
  hardware: HardwareInfoSchema,
19343
19818
  scores: array(PlatformScoreSchema).readonly(),
19344
19819
  bestScore: PlatformScoreSchema,
19345
- pythonPath: string().nullable()
19820
+ pythonPath: string().nullable(),
19821
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
19346
19822
  });
19347
19823
  var ModelRequirementSchema = object({
19348
19824
  modelId: string(),
@@ -20221,12 +20697,6 @@ Object.freeze({
20221
20697
  addonId: null,
20222
20698
  access: "delete"
20223
20699
  },
20224
- "addons.updateFrameworkPackage": {
20225
- capName: "addons",
20226
- capScope: "system",
20227
- addonId: null,
20228
- access: "create"
20229
- },
20230
20700
  "addons.updatePackage": {
20231
20701
  capName: "addons",
20232
20702
  capScope: "system",
@@ -22999,12 +23469,6 @@ Object.freeze({
22999
23469
  addonId: null,
23000
23470
  access: "view"
23001
23471
  },
23002
- "pipelineExecutor.reprobeEngine": {
23003
- capName: "pipeline-executor",
23004
- capScope: "system",
23005
- addonId: null,
23006
- access: "create"
23007
- },
23008
23472
  "pipelineExecutor.runAudioTest": {
23009
23473
  capName: "pipeline-executor",
23010
23474
  capScope: "system",
@@ -23155,6 +23619,12 @@ Object.freeze({
23155
23619
  addonId: null,
23156
23620
  access: "view"
23157
23621
  },
23622
+ "pipelineOrchestrator.getNodeInferenceDevices": {
23623
+ capName: "pipeline-orchestrator",
23624
+ capScope: "system",
23625
+ addonId: null,
23626
+ access: "view"
23627
+ },
23158
23628
  "pipelineOrchestrator.getPipelineAssignment": {
23159
23629
  capName: "pipeline-orchestrator",
23160
23630
  capScope: "system",
@@ -23167,6 +23637,12 @@ Object.freeze({
23167
23637
  addonId: null,
23168
23638
  access: "view"
23169
23639
  },
23640
+ "pipelineOrchestrator.getPipelineDevicePin": {
23641
+ capName: "pipeline-orchestrator",
23642
+ capScope: "system",
23643
+ addonId: null,
23644
+ access: "view"
23645
+ },
23170
23646
  "pipelineOrchestrator.listAgentSettings": {
23171
23647
  capName: "pipeline-orchestrator",
23172
23648
  capScope: "system",
@@ -23209,19 +23685,19 @@ Object.freeze({
23209
23685
  addonId: null,
23210
23686
  access: "create"
23211
23687
  },
23212
- "pipelineOrchestrator.setAgentAddonDefaults": {
23688
+ "pipelineOrchestrator.setAgentCapabilities": {
23213
23689
  capName: "pipeline-orchestrator",
23214
23690
  capScope: "system",
23215
23691
  addonId: null,
23216
23692
  access: "create"
23217
23693
  },
23218
- "pipelineOrchestrator.setAgentCapabilities": {
23694
+ "pipelineOrchestrator.setAgentDetectWeight": {
23219
23695
  capName: "pipeline-orchestrator",
23220
23696
  capScope: "system",
23221
23697
  addonId: null,
23222
23698
  access: "create"
23223
23699
  },
23224
- "pipelineOrchestrator.setAgentDetectWeight": {
23700
+ "pipelineOrchestrator.setAgentInferenceDevices": {
23225
23701
  capName: "pipeline-orchestrator",
23226
23702
  capScope: "system",
23227
23703
  addonId: null,
@@ -23263,6 +23739,12 @@ Object.freeze({
23263
23739
  addonId: null,
23264
23740
  access: "create"
23265
23741
  },
23742
+ "pipelineOrchestrator.setPipelineDevicePin": {
23743
+ capName: "pipeline-orchestrator",
23744
+ capScope: "system",
23745
+ addonId: null,
23746
+ access: "create"
23747
+ },
23266
23748
  "pipelineOrchestrator.unassignAudio": {
23267
23749
  capName: "pipeline-orchestrator",
23268
23750
  capScope: "system",
@@ -24815,32 +25297,6 @@ Object.freeze({
24815
25297
  "network-access": "ingress",
24816
25298
  "smtp-provider": "email"
24817
25299
  });
24818
- var frameworkSwapPackageSchema = object({
24819
- name: string(),
24820
- stagedPath: string(),
24821
- backupPath: string(),
24822
- toVersion: string(),
24823
- fromVersion: string().nullable()
24824
- });
24825
- object({
24826
- jobId: string(),
24827
- taskId: string(),
24828
- packages: array(frameworkSwapPackageSchema),
24829
- requestedAtMs: number(),
24830
- schemaVersion: literal(1)
24831
- });
24832
- object({
24833
- jobId: string(),
24834
- taskId: string(),
24835
- backups: array(object({
24836
- name: string(),
24837
- backupPath: string(),
24838
- livePath: string()
24839
- })),
24840
- appliedAtMs: number(),
24841
- bootAttempts: number(),
24842
- schemaVersion: literal(1)
24843
- });
24844
25300
  //#endregion
24845
25301
  //#region src/shared.ts
24846
25302
  /**