@camstack/addon-export-alexa 1.1.26 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7118,6 +7118,17 @@ var ModelCatalogEntrySchema = object({
7118
7118
  "imagenet",
7119
7119
  "none"
7120
7120
  ]).optional(),
7121
+ /**
7122
+ * The model already applies softmax IN-GRAPH — its raw output is a
7123
+ * probability distribution, not logits. When set, the `softmax`
7124
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7125
+ * probability vector collapses it toward uniform (top-1 score craters far
7126
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7127
+ * the output is raw logits and the postprocessor applies softmax (the normal
7128
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7129
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7130
+ */
7131
+ outputProbabilities: boolean().optional(),
7121
7132
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7122
7133
  /**
7123
7134
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -7848,6 +7859,160 @@ function pickClosestResolution(entries, target) {
7848
7859
  }
7849
7860
  return bestAbove?.entry ?? bestBelow?.entry;
7850
7861
  }
7862
+ var COCO_TO_MACRO = {
7863
+ mapping: {
7864
+ person: "person",
7865
+ bicycle: "vehicle",
7866
+ car: "vehicle",
7867
+ motorcycle: "vehicle",
7868
+ airplane: "vehicle",
7869
+ bus: "vehicle",
7870
+ train: "vehicle",
7871
+ truck: "vehicle",
7872
+ boat: "vehicle",
7873
+ bird: "animal",
7874
+ cat: "animal",
7875
+ dog: "animal",
7876
+ horse: "animal",
7877
+ sheep: "animal",
7878
+ cow: "animal",
7879
+ elephant: "animal",
7880
+ bear: "animal",
7881
+ zebra: "animal",
7882
+ giraffe: "animal",
7883
+ suitcase: "package",
7884
+ backpack: "package",
7885
+ handbag: "package"
7886
+ },
7887
+ preserveOriginal: false
7888
+ };
7889
+ var AUDIO_MACRO_LABELS = [
7890
+ {
7891
+ id: "speech",
7892
+ name: "Speech",
7893
+ icon: "🗣️"
7894
+ },
7895
+ {
7896
+ id: "scream",
7897
+ name: "Scream / Shout",
7898
+ icon: "😱"
7899
+ },
7900
+ {
7901
+ id: "crying",
7902
+ name: "Crying / Baby",
7903
+ icon: "😢"
7904
+ },
7905
+ {
7906
+ id: "laughter",
7907
+ name: "Laughter",
7908
+ icon: "😂"
7909
+ },
7910
+ {
7911
+ id: "music",
7912
+ name: "Music",
7913
+ icon: "🎵"
7914
+ },
7915
+ {
7916
+ id: "dog",
7917
+ name: "Dog",
7918
+ icon: "🐕"
7919
+ },
7920
+ {
7921
+ id: "cat",
7922
+ name: "Cat",
7923
+ icon: "🐈"
7924
+ },
7925
+ {
7926
+ id: "bird",
7927
+ name: "Bird",
7928
+ icon: "🐦"
7929
+ },
7930
+ {
7931
+ id: "animal",
7932
+ name: "Animal (other)",
7933
+ icon: "🐾"
7934
+ },
7935
+ {
7936
+ id: "alarm",
7937
+ name: "Alarm / Siren",
7938
+ icon: "🚨"
7939
+ },
7940
+ {
7941
+ id: "doorbell",
7942
+ name: "Doorbell / Knock",
7943
+ icon: "🔔"
7944
+ },
7945
+ {
7946
+ id: "glass_breaking",
7947
+ name: "Glass Breaking",
7948
+ icon: "💥"
7949
+ },
7950
+ {
7951
+ id: "gunshot",
7952
+ name: "Gunshot / Explosion",
7953
+ icon: "💣"
7954
+ },
7955
+ {
7956
+ id: "vehicle",
7957
+ name: "Vehicle",
7958
+ icon: "🚗"
7959
+ },
7960
+ {
7961
+ id: "siren",
7962
+ name: "Emergency Siren",
7963
+ icon: "🚑"
7964
+ },
7965
+ {
7966
+ id: "fire",
7967
+ name: "Fire / Smoke",
7968
+ icon: "🔥"
7969
+ },
7970
+ {
7971
+ id: "water",
7972
+ name: "Water",
7973
+ icon: "💧"
7974
+ },
7975
+ {
7976
+ id: "wind",
7977
+ name: "Wind / Weather",
7978
+ icon: "🌬️"
7979
+ },
7980
+ {
7981
+ id: "door",
7982
+ name: "Door",
7983
+ icon: "🚪"
7984
+ },
7985
+ {
7986
+ id: "footsteps",
7987
+ name: "Footsteps",
7988
+ icon: "👣"
7989
+ },
7990
+ {
7991
+ id: "crowd",
7992
+ name: "Crowd / Chatter",
7993
+ icon: "👥"
7994
+ },
7995
+ {
7996
+ id: "telephone",
7997
+ name: "Telephone",
7998
+ icon: "📞"
7999
+ },
8000
+ {
8001
+ id: "engine",
8002
+ name: "Engine / Motor",
8003
+ icon: "⚙️"
8004
+ },
8005
+ {
8006
+ id: "tools",
8007
+ name: "Tools / Construction",
8008
+ icon: "🔨"
8009
+ },
8010
+ {
8011
+ id: "silence",
8012
+ name: "Silence",
8013
+ icon: "🤫"
8014
+ }
8015
+ ];
7851
8016
  var YAMNET_TO_MACRO = {
7852
8017
  mapping: {
7853
8018
  Speech: "speech",
@@ -8114,6 +8279,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8114
8279
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8115
8280
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8116
8281
  /**
8282
+ * Unified event-kind taxonomy — THE single source of truth for
8283
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8284
+ * icon }`.
8285
+ *
8286
+ * This dictionary folds together what used to be scattered across four
8287
+ * copies:
8288
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8289
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8290
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8291
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8292
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8293
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8294
+ *
8295
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8296
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8297
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8298
+ * color or an icon: they read this dictionary (server descriptors carry the
8299
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8300
+ *
8301
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8302
+ */
8303
+ var TAXONOMY_COLORS = {
8304
+ motion: "#f59e0b",
8305
+ audio: "#06b6d4",
8306
+ person: "#22c55e",
8307
+ vehicle: "#3b82f6",
8308
+ animal: "#f97316",
8309
+ package: "#a855f7",
8310
+ sensor: "#8b5cf6",
8311
+ control: "#10b981",
8312
+ genericDetection: "#64748b"
8313
+ };
8314
+ var DETECTION_SUB_COLORS = {
8315
+ car: "#f59e0b",
8316
+ truck: "#d97706",
8317
+ bus: "#b45309",
8318
+ motorcycle: "#eab308",
8319
+ bicycle: "#ca8a04",
8320
+ airplane: "#60a5fa",
8321
+ boat: "#2563eb",
8322
+ train: "#1d4ed8",
8323
+ bird: "#14b8a6",
8324
+ dog: "#84cc16",
8325
+ cat: "#f97316",
8326
+ horse: "#a16207",
8327
+ sheep: "#a3a3a3",
8328
+ cow: "#78716c",
8329
+ elephant: "#6b7280",
8330
+ bear: "#7c2d12",
8331
+ zebra: "#404040",
8332
+ giraffe: "#d4a373"
8333
+ };
8334
+ function titleCase(id) {
8335
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8336
+ }
8337
+ var entries = /* @__PURE__ */ new Map();
8338
+ function macro(kind, category, color, iconId, label) {
8339
+ entries.set(kind, {
8340
+ kind,
8341
+ parentKind: null,
8342
+ level: "macro",
8343
+ category,
8344
+ color,
8345
+ iconId,
8346
+ labelKey: `eventKind.${kind}`,
8347
+ label
8348
+ });
8349
+ }
8350
+ function sub(kind, parentKind, category, color, iconId, label) {
8351
+ entries.set(kind, {
8352
+ kind,
8353
+ parentKind,
8354
+ level: "sub",
8355
+ category,
8356
+ color,
8357
+ iconId,
8358
+ labelKey: `eventKind.${kind}`,
8359
+ label
8360
+ });
8361
+ }
8362
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8363
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8364
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8365
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8366
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8367
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8368
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8369
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8370
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8371
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8372
+ if (entries.has(cocoClass)) continue;
8373
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8374
+ }
8375
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8376
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8377
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8378
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8379
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8380
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8381
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8382
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8383
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8384
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8385
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8386
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8387
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8388
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8389
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8390
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8391
+ sub("button", "control", "control", "#10b981", "button", "Button");
8392
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8393
+ for (const l of AUDIO_MACRO_LABELS) {
8394
+ const kind = `audio-${l.id}`;
8395
+ if (entries.has(kind)) continue;
8396
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8397
+ }
8398
+ /** The complete taxonomy dictionary, keyed by kind. */
8399
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8400
+ /**
8117
8401
  * Error types for the safe expression engine. Two distinct classes so callers
8118
8402
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8119
8403
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -11029,10 +11313,7 @@ var ConfigUISchemaNullableBridge = custom();
11029
11313
  var InferenceCapabilitiesBridge = custom();
11030
11314
  var ModelAvailabilityListBridge = custom();
11031
11315
  var PipelineRunResultBridge = custom();
11032
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
11033
- kind: "mutation",
11034
- auth: "admin"
11035
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
11316
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
11036
11317
  modelId: string(),
11037
11318
  settings: record(string(), unknown()).readonly()
11038
11319
  }))), method(object({ steps: record(string(), object({
@@ -11092,13 +11373,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
11092
11373
  * (inputClasses ≠ null) are skipped and served per-track via
11093
11374
  * pipelineRunner.runDetailSubtree (two-plane design).
11094
11375
  */
11095
- plane: _enum(["full", "frame"]).optional()
11376
+ plane: _enum(["full", "frame"]).optional(),
11377
+ /**
11378
+ * Inference-device selector (Phase 2 multi-device). Format
11379
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
11380
+ * Omitted ⇒ the runner's default device (current single-engine
11381
+ * behaviour). Selects WHICH device pool of the node runs the call.
11382
+ */
11383
+ deviceKey: string().optional()
11096
11384
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
11097
11385
  engine: PipelineEngineChoiceSchema.optional(),
11098
11386
  steps: array(PipelineStepInputSchema).min(1),
11099
11387
  frames: array(FrameInputSchema).min(1).max(255),
11100
11388
  deviceId: number().optional(),
11101
- sessionId: string().optional()
11389
+ sessionId: string().optional(),
11390
+ /**
11391
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
11392
+ * the batch to the Python pool's bench preprocess cache
11393
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
11394
+ * preprocessed ONCE and every later inference is a pure-inference cache
11395
+ * hit — the sustained-throughput run measures inference, not
11396
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
11397
+ * full preprocess every call, correct). Fresh per sustained run;
11398
+ * released via `uncacheFrame`.
11399
+ */
11400
+ frameId: number().int().nonnegative().optional(),
11401
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
11402
+ deviceKey: string().optional()
11102
11403
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
11103
11404
  data: _instanceof(Uint8Array),
11104
11405
  width: number().int().positive(),
@@ -11130,8 +11431,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
11130
11431
  * - `runtime` — main camera-serving engine (no idle TTL).
11131
11432
  * - `warm-override` — benchmark/test override held in the warm
11132
11433
  * cache; auto-disposed after the idle TTL.
11434
+ * - `device-pool` — a concurrent per-device pool (Phase 2
11435
+ * multi-device, keyed by `deviceKey`) resolved
11436
+ * via `resolveDeviceFactory`. Runs alongside the
11437
+ * `runtime` engine on a DIFFERENT accelerator
11438
+ * (NPU / iGPU / Coral) — this is how the
11439
+ * Engines tab shows all pools running at once.
11133
11440
  */
11134
- kind: _enum(["runtime", "warm-override"]),
11441
+ kind: _enum([
11442
+ "runtime",
11443
+ "warm-override",
11444
+ "device-pool"
11445
+ ]),
11135
11446
  /** Native pid of the underlying Python pool (null when no pool). */
11136
11447
  poolPid: number().nullable(),
11137
11448
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -11473,7 +11784,14 @@ var RunnerCameraConfigSchema = object({
11473
11784
  * camera's detect node differs from its source-owner (P2d, gated by the
11474
11785
  * `remoteSourcingNodes` rollout setting).
11475
11786
  */
11476
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
11787
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
11788
+ /**
11789
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
11790
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
11791
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
11792
+ * this only selects WHICH device pool of that node runs the session.
11793
+ */
11794
+ deviceKey: string().optional()
11477
11795
  });
11478
11796
  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;
11479
11797
  /**
@@ -11494,6 +11812,19 @@ var RunnerLocalLoadSchema = object({
11494
11812
  avgInferenceTimeMs: number(),
11495
11813
  /** Total queue depth across motion + detection queues. */
11496
11814
  queueDepthTotal: number(),
11815
+ /**
11816
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
11817
+ * this runner currently has attached cameras on, so the orchestrator's second
11818
+ * `balance()` pass (over a node's devices) weights on real per-pool session
11819
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
11820
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
11821
+ */
11822
+ devices: array(object({
11823
+ deviceKey: string(),
11824
+ backend: string(),
11825
+ attachedCameras: number(),
11826
+ queueDepthTotal: number()
11827
+ })).default([]),
11497
11828
  /** Hardware capability flags reported by this node. */
11498
11829
  hardware: object({
11499
11830
  hasGpu: boolean(),
@@ -12969,6 +13300,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
12969
13300
  kind: "mutation",
12970
13301
  auth: "admin"
12971
13302
  });
13303
+ 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;
13304
+ new Set(Object.values(DeviceType));
12972
13305
  /**
12973
13306
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
12974
13307
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -16134,17 +16467,30 @@ var EventKindCategorySchema = _enum([
16134
16467
  "audio",
16135
16468
  "detection",
16136
16469
  "sensor",
16470
+ "control",
16137
16471
  "custom",
16138
16472
  "package"
16139
16473
  ]);
16474
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16475
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
16140
16476
  var EventKindDescriptorSchema = object({
16141
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16477
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
16142
16478
  kind: string(),
16479
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16480
+ labelKey: string(),
16481
+ /** English fallback label (kept for clients that don't translate). */
16143
16482
  label: string(),
16144
16483
  /** Hex color for timeline/legend rendering. */
16145
16484
  color: string(),
16485
+ /** Dictionary id → lucide component on the UI side. */
16486
+ iconId: string(),
16487
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
16146
16488
  icon: EventKindIconSchema,
16147
16489
  category: EventKindCategorySchema,
16490
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16491
+ parentKind: string().nullable(),
16492
+ /** Derived from `parentKind`, explicit for the client tree. */
16493
+ level: EventKindLevelSchema,
16148
16494
  /** Which cap + device contributes this kind. For built-ins the camera
16149
16495
  * itself; for sensor kinds the LINKED source device. */
16150
16496
  source: object({
@@ -16217,11 +16563,21 @@ var TrackAudioLabelSchema = object({
16217
16563
  firstAt: number(),
16218
16564
  lastAt: number()
16219
16565
  });
16566
+ /**
16567
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16568
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16569
+ * linked sensor/control state change (no positions; carries a snapshot). The
16570
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16571
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16572
+ */
16573
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16220
16574
  var TrackSchema = object({
16221
16575
  trackId: string(),
16222
16576
  deviceId: number(),
16223
16577
  className: string(),
16224
16578
  label: string().optional(),
16579
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16580
+ source: TrackSourceSchema.optional(),
16225
16581
  firstSeen: number(),
16226
16582
  lastSeen: number(),
16227
16583
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16598,6 +16954,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16598
16954
  eventId: string(),
16599
16955
  timestamp: number()
16600
16956
  });
16957
+ /**
16958
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16959
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16960
+ * caps into per-camera event-kind descriptors.
16961
+ *
16962
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16963
+ * is NOT duplicated here — every entry is derived from the single
16964
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16965
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16966
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16967
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16968
+ * eventful cap is missing.
16969
+ */
16970
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16971
+ var LEGACY_ICON = {
16972
+ motion: "motion",
16973
+ audio: "audio",
16974
+ person: "person",
16975
+ vehicle: "vehicle",
16976
+ animal: "animal",
16977
+ package: "package",
16978
+ door: "door",
16979
+ pir: "pir",
16980
+ smoke: "smoke",
16981
+ water: "water",
16982
+ button: "button",
16983
+ generic: "generic",
16984
+ gas: "smoke",
16985
+ vibration: "generic",
16986
+ tamper: "generic",
16987
+ presence: "person",
16988
+ lock: "generic",
16989
+ siren: "generic",
16990
+ switch: "generic",
16991
+ doorbell: "button"
16992
+ };
16993
+ function legacyIcon(iconId) {
16994
+ return LEGACY_ICON[iconId] ?? "generic";
16995
+ }
16996
+ /**
16997
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16998
+ * The anti-drift guard cross-checks this against the eventful caps declared
16999
+ * in `packages/types/src/capabilities/*.cap.ts`.
17000
+ */
17001
+ var CAP_TO_KIND = {
17002
+ contact: "contact",
17003
+ motion: "motion-sensor",
17004
+ smoke: "smoke",
17005
+ flood: "flood",
17006
+ gas: "gas",
17007
+ "carbon-monoxide": "carbon-monoxide",
17008
+ vibration: "vibration",
17009
+ tamper: "tamper",
17010
+ presence: "presence",
17011
+ "enum-sensor": "enum-sensor",
17012
+ "event-emitter": "device-event",
17013
+ "lock-control": "lock",
17014
+ switch: "switch",
17015
+ button: "button",
17016
+ doorbell: "doorbell"
17017
+ };
17018
+ function buildDescriptor(capName, kind) {
17019
+ const t = EVENT_TAXONOMY[kind];
17020
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
17021
+ return {
17022
+ ...t,
17023
+ icon: legacyIcon(t.iconId)
17024
+ };
17025
+ }
17026
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16601
17027
  var CameraPipelineConfigSchema = object({
16602
17028
  engine: PipelineEngineChoiceSchema.optional(),
16603
17029
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16616,13 +17042,11 @@ var PipelineTemplateSchema = object({
16616
17042
  createdAt: string(),
16617
17043
  updatedAt: string()
16618
17044
  });
16619
- var AgentAddonConfigSchema = object({
16620
- enabled: boolean(),
17045
+ var DeviceStepConfigSchema = object({
16621
17046
  modelId: string().optional(),
16622
- settings: record(string(), unknown()).readonly()
17047
+ settings: record(string(), unknown()).optional()
16623
17048
  });
16624
17049
  var AgentPipelineSettingsSchema = object({
16625
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
16626
17050
  maxCameras: number().int().nonnegative().nullable().default(null),
16627
17051
  /** Per-node detection weight (relative share for the quota balancer). */
16628
17052
  detectWeight: number().positive().optional(),
@@ -16646,7 +17070,22 @@ var AgentPipelineSettingsSchema = object({
16646
17070
  * it already uses to reach the hub). Set this only when the auto-detected
16647
17071
  * address is wrong (multi-homed host, NAT, custom interface).
16648
17072
  */
16649
- reachableHost: string().optional()
17073
+ reachableHost: string().optional(),
17074
+ /**
17075
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
17076
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
17077
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
17078
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
17079
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
17080
+ * the default model/settings for every camera landing on that accelerator;
17081
+ * a stepId absent ⇒ the step uses that device's format default.
17082
+ */
17083
+ inferenceDevices: record(string(), object({
17084
+ enabled: boolean(),
17085
+ weight: number().positive().optional(),
17086
+ maxSessions: number().int().positive().optional(),
17087
+ steps: record(string(), DeviceStepConfigSchema).optional()
17088
+ })).optional()
16650
17089
  });
16651
17090
  var CameraPipelineForAgentSchema = object({
16652
17091
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16656,14 +17095,13 @@ var CameraPipelineForAgentSchema = object({
16656
17095
  }).nullable()
16657
17096
  });
16658
17097
  var CameraStepOverridePatchSchema = object({
16659
- enabled: boolean().optional(),
16660
17098
  modelId: string().optional(),
16661
17099
  settings: record(string(), unknown()).readonly().optional()
16662
17100
  });
16663
17101
  var CameraPipelineSettingsSchema = object({
16664
17102
  pinnedAgentNodeId: string().optional(),
16665
17103
  stepToggles: record(string(), boolean()).optional(),
16666
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
17104
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
16667
17105
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
16668
17106
  });
16669
17107
  /**
@@ -16877,6 +17315,44 @@ var CameraStatusSchema = object({
16877
17315
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
16878
17316
  fetchedAt: number()
16879
17317
  });
17318
+ var NodeInferenceDeviceSchema = object({
17319
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
17320
+ key: string(),
17321
+ backend: string(),
17322
+ device: string(),
17323
+ format: _enum(MODEL_FORMATS),
17324
+ /** Whether the node's live probe reports the device as usable right now. */
17325
+ available: boolean(),
17326
+ /**
17327
+ * Whether this device participates in dispatch. AUTO default (spec C2):
17328
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
17329
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
17330
+ * not a balanced target). An explicit stored value always wins; a stored-only
17331
+ * (unavailable) key keeps its stored value.
17332
+ */
17333
+ enabled: boolean(),
17334
+ /** Relative balancer weight for the enabled device (default 1). */
17335
+ weight: number(),
17336
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
17337
+ maxSessions: number().nullable(),
17338
+ /** Object-detection model the executor defaults to for this deviceKey. */
17339
+ defaultModelId: string(),
17340
+ /**
17341
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
17342
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
17343
+ * Absent/empty ⇒ no base (every step uses its device format default). The
17344
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
17345
+ * available per format; this is the stored selection that becomes the
17346
+ * default for EVERY camera landing on this accelerator.
17347
+ */
17348
+ steps: record(string(), DeviceStepConfigSchema).optional()
17349
+ });
17350
+ var NodeInferenceDevicesSchema = object({
17351
+ nodeId: string(),
17352
+ /** False when the node's platform-probe was unreachable (no live device set). */
17353
+ reachable: boolean(),
17354
+ devices: array(NodeInferenceDeviceSchema).readonly()
17355
+ });
16880
17356
  method(object({
16881
17357
  deviceId: number(),
16882
17358
  agentNodeId: string()
@@ -16886,7 +17362,13 @@ method(object({
16886
17362
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
16887
17363
  kind: "mutation",
16888
17364
  auth: "admin"
16889
- }), method(_void(), object({ migrated: number() }), {
17365
+ }), method(object({
17366
+ deviceId: number(),
17367
+ deviceKey: string()
17368
+ }), object({ success: literal(true) }), {
17369
+ kind: "mutation",
17370
+ auth: "admin"
17371
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
16890
17372
  kind: "mutation",
16891
17373
  auth: "admin"
16892
17374
  }), 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({
@@ -16920,13 +17402,7 @@ method(object({
16920
17402
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
16921
17403
  nodeId: string(),
16922
17404
  settings: AgentPipelineSettingsSchema
16923
- })).readonly()), method(object({
16924
- agentNodeId: string(),
16925
- defaults: record(string(), AgentAddonConfigSchema)
16926
- }), object({ success: literal(true) }), {
16927
- kind: "mutation",
16928
- auth: "admin"
16929
- }), method(object({ agentNodeId: string() }), object({
17405
+ })).readonly()), method(object({ agentNodeId: string() }), object({
16930
17406
  success: boolean(),
16931
17407
  removed: boolean()
16932
17408
  }), {
@@ -16958,7 +17434,18 @@ method(object({
16958
17434
  }), object({ success: literal(true) }), {
16959
17435
  kind: "mutation",
16960
17436
  auth: "admin"
16961
- }), method(object({ agentNodeId: string() }), object({
17437
+ }), method(object({
17438
+ agentNodeId: string(),
17439
+ inferenceDevices: record(string(), object({
17440
+ enabled: boolean(),
17441
+ weight: number().positive().optional(),
17442
+ maxSessions: number().int().positive().optional(),
17443
+ steps: record(string(), DeviceStepConfigSchema).optional()
17444
+ }))
17445
+ }), object({ success: literal(true) }), {
17446
+ kind: "mutation",
17447
+ auth: "admin"
17448
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
16962
17449
  success: literal(true),
16963
17450
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
16964
17451
  effectiveModelId: string().nullable(),
@@ -16974,9 +17461,10 @@ method(object({
16974
17461
  }), object({ success: literal(true) }), {
16975
17462
  kind: "mutation",
16976
17463
  auth: "admin"
16977
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
17464
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
16978
17465
  deviceId: number(),
16979
17466
  agentNodeId: string(),
17467
+ deviceKey: string(),
16980
17468
  addonId: string(),
16981
17469
  patch: CameraStepOverridePatchSchema.nullable()
16982
17470
  }), object({ success: literal(true) }), {
@@ -17013,14 +17501,13 @@ method(object({
17013
17501
  });
17014
17502
  /**
17015
17503
  * server-management — per-NODE singleton capability for a node's ROOT
17016
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
17017
- * agents).
17504
+ * package lifecycle (runtime-updatable node packages).
17018
17505
  *
17019
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
17020
- * on agents) carries the whole software stack in its npm dep tree, so ONE
17021
- * version describes the node. Updates install into
17022
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
17023
- * starter (probation boot + auto-rollback to N-1).
17506
+ * Every node role runs the SAME root package (`@camstack/server`), which
17507
+ * carries the whole software stack in its npm dep tree, so ONE version
17508
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
17509
+ * on restart via the baked starter (single-copy in-place swap — no probation,
17510
+ * no auto-rollback).
17024
17511
  *
17025
17512
  * Providers:
17026
17513
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -17128,7 +17615,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
17128
17615
  /** Explicit target version; omitted = latest from the registry. */
17129
17616
  version: string().optional() }), ServerUpdateActionResultSchema, {
17130
17617
  kind: "mutation",
17131
- auth: "admin"
17618
+ auth: "admin",
17619
+ timeoutMs: 16 * 6e4
17132
17620
  }), method(_void(), ServerUpdateActionResultSchema, {
17133
17621
  kind: "mutation",
17134
17622
  auth: "admin"
@@ -18172,22 +18660,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
18172
18660
  var RestartAddonResultSchema = unknown();
18173
18661
  var InstallPackageResultSchema = unknown();
18174
18662
  var ReloadPackagesResultSchema = unknown();
18175
- /**
18176
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
18177
- * server restarts so the admin UI can react to the `restartingAt`
18178
- * timestamp (shows reconnect overlay). The transition from
18179
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
18180
- * `system.restart-completed` event after the new process boots.
18181
- *
18182
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
18183
- */
18184
- var UpdateFrameworkPackageResultSchema = object({
18185
- packageName: string(),
18186
- fromVersion: string(),
18187
- toVersion: string(),
18188
- /** Ms-epoch the server scheduled its self-restart. */
18189
- restartingAt: number()
18190
- });
18191
18663
  var BulkUpdateItemStatusSchema = _enum([
18192
18664
  "queued",
18193
18665
  "updating",
@@ -18315,13 +18787,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
18315
18787
  }), object({ success: literal(true) }), {
18316
18788
  kind: "mutation",
18317
18789
  auth: "admin"
18318
- }), method(object({
18319
- packageName: string().min(1),
18320
- version: string().optional(),
18321
- deferRestart: boolean().optional()
18322
- }), UpdateFrameworkPackageResultSchema, {
18323
- kind: "mutation",
18324
- auth: "admin"
18325
18790
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
18326
18791
  kind: "mutation",
18327
18792
  auth: "admin"
@@ -19187,10 +19652,10 @@ var TopologyCategorySchema = object({
19187
19652
  addons: array(TopologyCategoryAddonSchema).readonly()
19188
19653
  });
19189
19654
  /**
19190
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
19191
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
19192
- * version visibility for the Server management surface. Nullable: offline
19193
- * rows and pre-phase-2 nodes report none.
19655
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
19656
+ * root package for every node role) as reported by its `registerNode`
19657
+ * manifest — version visibility for the Server management surface. Nullable:
19658
+ * offline rows and nodes that never reported one.
19194
19659
  */
19195
19660
  var TopologyRootPackageSchema = object({
19196
19661
  name: string(),
@@ -19578,17 +20043,28 @@ var PlatformScoreSchema = object({
19578
20043
  format: _enum([
19579
20044
  "onnx",
19580
20045
  "coreml",
19581
- "openvino"
20046
+ "openvino",
20047
+ "tflite"
19582
20048
  ]),
19583
20049
  score: number(),
19584
20050
  reason: string(),
19585
20051
  available: boolean()
19586
20052
  });
20053
+ var InferenceDeviceDescriptorSchema = object({
20054
+ key: string(),
20055
+ backend: string(),
20056
+ device: string(),
20057
+ format: ModelFormatSchema,
20058
+ runtime: literal("python"),
20059
+ score: number(),
20060
+ available: boolean()
20061
+ });
19587
20062
  var PlatformCapabilitiesSchema = object({
19588
20063
  hardware: HardwareInfoSchema,
19589
20064
  scores: array(PlatformScoreSchema).readonly(),
19590
20065
  bestScore: PlatformScoreSchema,
19591
- pythonPath: string().nullable()
20066
+ pythonPath: string().nullable(),
20067
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
19592
20068
  });
19593
20069
  var ModelRequirementSchema = object({
19594
20070
  modelId: string(),
@@ -20467,12 +20943,6 @@ Object.freeze({
20467
20943
  addonId: null,
20468
20944
  access: "delete"
20469
20945
  },
20470
- "addons.updateFrameworkPackage": {
20471
- capName: "addons",
20472
- capScope: "system",
20473
- addonId: null,
20474
- access: "create"
20475
- },
20476
20946
  "addons.updatePackage": {
20477
20947
  capName: "addons",
20478
20948
  capScope: "system",
@@ -23245,12 +23715,6 @@ Object.freeze({
23245
23715
  addonId: null,
23246
23716
  access: "view"
23247
23717
  },
23248
- "pipelineExecutor.reprobeEngine": {
23249
- capName: "pipeline-executor",
23250
- capScope: "system",
23251
- addonId: null,
23252
- access: "create"
23253
- },
23254
23718
  "pipelineExecutor.runAudioTest": {
23255
23719
  capName: "pipeline-executor",
23256
23720
  capScope: "system",
@@ -23401,6 +23865,12 @@ Object.freeze({
23401
23865
  addonId: null,
23402
23866
  access: "view"
23403
23867
  },
23868
+ "pipelineOrchestrator.getNodeInferenceDevices": {
23869
+ capName: "pipeline-orchestrator",
23870
+ capScope: "system",
23871
+ addonId: null,
23872
+ access: "view"
23873
+ },
23404
23874
  "pipelineOrchestrator.getPipelineAssignment": {
23405
23875
  capName: "pipeline-orchestrator",
23406
23876
  capScope: "system",
@@ -23413,6 +23883,12 @@ Object.freeze({
23413
23883
  addonId: null,
23414
23884
  access: "view"
23415
23885
  },
23886
+ "pipelineOrchestrator.getPipelineDevicePin": {
23887
+ capName: "pipeline-orchestrator",
23888
+ capScope: "system",
23889
+ addonId: null,
23890
+ access: "view"
23891
+ },
23416
23892
  "pipelineOrchestrator.listAgentSettings": {
23417
23893
  capName: "pipeline-orchestrator",
23418
23894
  capScope: "system",
@@ -23455,19 +23931,19 @@ Object.freeze({
23455
23931
  addonId: null,
23456
23932
  access: "create"
23457
23933
  },
23458
- "pipelineOrchestrator.setAgentAddonDefaults": {
23934
+ "pipelineOrchestrator.setAgentCapabilities": {
23459
23935
  capName: "pipeline-orchestrator",
23460
23936
  capScope: "system",
23461
23937
  addonId: null,
23462
23938
  access: "create"
23463
23939
  },
23464
- "pipelineOrchestrator.setAgentCapabilities": {
23940
+ "pipelineOrchestrator.setAgentDetectWeight": {
23465
23941
  capName: "pipeline-orchestrator",
23466
23942
  capScope: "system",
23467
23943
  addonId: null,
23468
23944
  access: "create"
23469
23945
  },
23470
- "pipelineOrchestrator.setAgentDetectWeight": {
23946
+ "pipelineOrchestrator.setAgentInferenceDevices": {
23471
23947
  capName: "pipeline-orchestrator",
23472
23948
  capScope: "system",
23473
23949
  addonId: null,
@@ -23509,6 +23985,12 @@ Object.freeze({
23509
23985
  addonId: null,
23510
23986
  access: "create"
23511
23987
  },
23988
+ "pipelineOrchestrator.setPipelineDevicePin": {
23989
+ capName: "pipeline-orchestrator",
23990
+ capScope: "system",
23991
+ addonId: null,
23992
+ access: "create"
23993
+ },
23512
23994
  "pipelineOrchestrator.unassignAudio": {
23513
23995
  capName: "pipeline-orchestrator",
23514
23996
  capScope: "system",
@@ -25061,32 +25543,6 @@ Object.freeze({
25061
25543
  "network-access": "ingress",
25062
25544
  "smtp-provider": "email"
25063
25545
  });
25064
- var frameworkSwapPackageSchema = object({
25065
- name: string(),
25066
- stagedPath: string(),
25067
- backupPath: string(),
25068
- toVersion: string(),
25069
- fromVersion: string().nullable()
25070
- });
25071
- object({
25072
- jobId: string(),
25073
- taskId: string(),
25074
- packages: array(frameworkSwapPackageSchema),
25075
- requestedAtMs: number(),
25076
- schemaVersion: literal(1)
25077
- });
25078
- object({
25079
- jobId: string(),
25080
- taskId: string(),
25081
- backups: array(object({
25082
- name: string(),
25083
- backupPath: string(),
25084
- livePath: string()
25085
- })),
25086
- appliedAtMs: number(),
25087
- bootAttempts: number(),
25088
- schemaVersion: literal(1)
25089
- });
25090
25546
  //#endregion
25091
25547
  //#region src/custom-actions.ts
25092
25548
  /**