@camstack/addon-provider-unifi 0.1.14 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +213 -106
  2. package/dist/addon.mjs +213 -106
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -7098,6 +7098,17 @@ var ModelCatalogEntrySchema = object({
7098
7098
  "imagenet",
7099
7099
  "none"
7100
7100
  ]).optional(),
7101
+ /**
7102
+ * The model already applies softmax IN-GRAPH — its raw output is a
7103
+ * probability distribution, not logits. When set, the `softmax`
7104
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7105
+ * probability vector collapses it toward uniform (top-1 score craters far
7106
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7107
+ * the output is raw logits and the postprocessor applies softmax (the normal
7108
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7109
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7110
+ */
7111
+ outputProbabilities: boolean().optional(),
7101
7112
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7102
7113
  /**
7103
7114
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -12388,10 +12399,7 @@ var ConfigUISchemaNullableBridge = custom();
12388
12399
  var InferenceCapabilitiesBridge = custom();
12389
12400
  var ModelAvailabilityListBridge = custom();
12390
12401
  var PipelineRunResultBridge = custom();
12391
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
12392
- kind: "mutation",
12393
- auth: "admin"
12394
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
12402
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
12395
12403
  modelId: string(),
12396
12404
  settings: record(string(), unknown()).readonly()
12397
12405
  }))), method(object({ steps: record(string(), object({
@@ -12451,13 +12459,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
12451
12459
  * (inputClasses ≠ null) are skipped and served per-track via
12452
12460
  * pipelineRunner.runDetailSubtree (two-plane design).
12453
12461
  */
12454
- plane: _enum(["full", "frame"]).optional()
12462
+ plane: _enum(["full", "frame"]).optional(),
12463
+ /**
12464
+ * Inference-device selector (Phase 2 multi-device). Format
12465
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
12466
+ * Omitted ⇒ the runner's default device (current single-engine
12467
+ * behaviour). Selects WHICH device pool of the node runs the call.
12468
+ */
12469
+ deviceKey: string().optional()
12455
12470
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
12456
12471
  engine: PipelineEngineChoiceSchema.optional(),
12457
12472
  steps: array(PipelineStepInputSchema).min(1),
12458
12473
  frames: array(FrameInputSchema).min(1).max(255),
12459
12474
  deviceId: number().optional(),
12460
- sessionId: string().optional()
12475
+ sessionId: string().optional(),
12476
+ /**
12477
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
12478
+ * the batch to the Python pool's bench preprocess cache
12479
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
12480
+ * preprocessed ONCE and every later inference is a pure-inference cache
12481
+ * hit — the sustained-throughput run measures inference, not
12482
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
12483
+ * full preprocess every call, correct). Fresh per sustained run;
12484
+ * released via `uncacheFrame`.
12485
+ */
12486
+ frameId: number().int().nonnegative().optional(),
12487
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
12488
+ deviceKey: string().optional()
12461
12489
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
12462
12490
  data: _instanceof(Uint8Array),
12463
12491
  width: number().int().positive(),
@@ -12489,8 +12517,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
12489
12517
  * - `runtime` — main camera-serving engine (no idle TTL).
12490
12518
  * - `warm-override` — benchmark/test override held in the warm
12491
12519
  * cache; auto-disposed after the idle TTL.
12520
+ * - `device-pool` — a concurrent per-device pool (Phase 2
12521
+ * multi-device, keyed by `deviceKey`) resolved
12522
+ * via `resolveDeviceFactory`. Runs alongside the
12523
+ * `runtime` engine on a DIFFERENT accelerator
12524
+ * (NPU / iGPU / Coral) — this is how the
12525
+ * Engines tab shows all pools running at once.
12492
12526
  */
12493
- kind: _enum(["runtime", "warm-override"]),
12527
+ kind: _enum([
12528
+ "runtime",
12529
+ "warm-override",
12530
+ "device-pool"
12531
+ ]),
12494
12532
  /** Native pid of the underlying Python pool (null when no pool). */
12495
12533
  poolPid: number().nullable(),
12496
12534
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -12625,7 +12663,21 @@ var NativeCropResultSchema = object({
12625
12663
  /** Packed rgb (24-bit) pixels of the crop. */
12626
12664
  bytes: _instanceof(Uint8Array),
12627
12665
  width: number().int().positive(),
12628
- height: number().int().positive()
12666
+ height: number().int().positive(),
12667
+ /**
12668
+ * Which source served this crop, so a quality-sensitive consumer (the native
12669
+ * `keyFrame`) can reject a degraded fallback:
12670
+ * - `native` — cut from the decode worker's retained NATIVE surface (the
12671
+ * quality path).
12672
+ * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
12673
+ * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
12674
+ * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
12675
+ *
12676
+ * OPTIONAL: a pre-tier runner (version skew) omits it — an ABSENT `tier` MUST be
12677
+ * treated as `native` (accepted) by every consumer so mixed-version clusters
12678
+ * keep the pre-tier behaviour. An ROI miss returns `null` (no tier at all).
12679
+ */
12680
+ tier: _enum(["native", "ram-fullframe"]).optional()
12629
12681
  });
12630
12682
  /** Parent detection context passed to `runDetailSubtree` — the crop's
12631
12683
  * originating detection, in FRAME-space coordinates. Reuses
@@ -12865,7 +12917,14 @@ var RunnerCameraConfigSchema = object({
12865
12917
  * camera's detect node differs from its source-owner (P2d, gated by the
12866
12918
  * `remoteSourcingNodes` rollout setting).
12867
12919
  */
12868
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
12920
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
12921
+ /**
12922
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
12923
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
12924
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
12925
+ * this only selects WHICH device pool of that node runs the session.
12926
+ */
12927
+ deviceKey: string().optional()
12869
12928
  });
12870
12929
  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;
12871
12930
  /**
@@ -12886,6 +12945,19 @@ var RunnerLocalLoadSchema = object({
12886
12945
  avgInferenceTimeMs: number(),
12887
12946
  /** Total queue depth across motion + detection queues. */
12888
12947
  queueDepthTotal: number(),
12948
+ /**
12949
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
12950
+ * this runner currently has attached cameras on, so the orchestrator's second
12951
+ * `balance()` pass (over a node's devices) weights on real per-pool session
12952
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
12953
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
12954
+ */
12955
+ devices: array(object({
12956
+ deviceKey: string(),
12957
+ backend: string(),
12958
+ attachedCameras: number(),
12959
+ queueDepthTotal: number()
12960
+ })).default([]),
12889
12961
  /** Hardware capability flags reported by this node. */
12890
12962
  hardware: object({
12891
12963
  hasGpu: boolean(),
@@ -16034,6 +16106,8 @@ var BaseDeviceProvider = class extends BaseAddon {
16034
16106
  return toDeviceSummary(device, this.addonId);
16035
16107
  }
16036
16108
  };
16109
+ 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;
16110
+ new Set(Object.values(DeviceType));
16037
16111
  /**
16038
16112
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
16039
16113
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -17607,7 +17681,8 @@ var LinkedDeviceSchema = object({
17607
17681
  deviceId: number(),
17608
17682
  name: string(),
17609
17683
  location: string().nullable(),
17610
- features: array(string())
17684
+ features: array(string()),
17685
+ producesTrackedEvents: boolean().optional()
17611
17686
  });
17612
17687
  var SavedDeviceRowSchema = object({
17613
17688
  /** Numeric id reserved at allocateDeviceId time. */
@@ -19237,6 +19312,7 @@ var TrackSchema = object({
19237
19312
  deviceId: number(),
19238
19313
  className: string(),
19239
19314
  label: string().optional(),
19315
+ producingDeviceName: string().optional(),
19240
19316
  /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19241
19317
  source: TrackSourceSchema.optional(),
19242
19318
  firstSeen: number(),
@@ -19374,7 +19450,8 @@ var MediaFileKindEnum = _enum([
19374
19450
  "fullFrameBoxed",
19375
19451
  "faceCrop",
19376
19452
  "plateCrop",
19377
- "keyFrame"
19453
+ "keyFrame",
19454
+ "keyFrameSmall"
19378
19455
  ]);
19379
19456
  var MediaFileSchema = object({
19380
19457
  key: string(),
@@ -19703,13 +19780,11 @@ var PipelineTemplateSchema = object({
19703
19780
  createdAt: string(),
19704
19781
  updatedAt: string()
19705
19782
  });
19706
- var AgentAddonConfigSchema = object({
19707
- enabled: boolean(),
19783
+ var DeviceStepConfigSchema = object({
19708
19784
  modelId: string().optional(),
19709
- settings: record(string(), unknown()).readonly()
19785
+ settings: record(string(), unknown()).optional()
19710
19786
  });
19711
19787
  var AgentPipelineSettingsSchema = object({
19712
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
19713
19788
  maxCameras: number().int().nonnegative().nullable().default(null),
19714
19789
  /** Per-node detection weight (relative share for the quota balancer). */
19715
19790
  detectWeight: number().positive().optional(),
@@ -19733,7 +19808,22 @@ var AgentPipelineSettingsSchema = object({
19733
19808
  * it already uses to reach the hub). Set this only when the auto-detected
19734
19809
  * address is wrong (multi-homed host, NAT, custom interface).
19735
19810
  */
19736
- reachableHost: string().optional()
19811
+ reachableHost: string().optional(),
19812
+ /**
19813
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
19814
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
19815
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
19816
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
19817
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
19818
+ * the default model/settings for every camera landing on that accelerator;
19819
+ * a stepId absent ⇒ the step uses that device's format default.
19820
+ */
19821
+ inferenceDevices: record(string(), object({
19822
+ enabled: boolean(),
19823
+ weight: number().positive().optional(),
19824
+ maxSessions: number().int().positive().optional(),
19825
+ steps: record(string(), DeviceStepConfigSchema).optional()
19826
+ })).optional()
19737
19827
  });
19738
19828
  var CameraPipelineForAgentSchema = object({
19739
19829
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19743,14 +19833,13 @@ var CameraPipelineForAgentSchema = object({
19743
19833
  }).nullable()
19744
19834
  });
19745
19835
  var CameraStepOverridePatchSchema = object({
19746
- enabled: boolean().optional(),
19747
19836
  modelId: string().optional(),
19748
19837
  settings: record(string(), unknown()).readonly().optional()
19749
19838
  });
19750
19839
  var CameraPipelineSettingsSchema = object({
19751
19840
  pinnedAgentNodeId: string().optional(),
19752
19841
  stepToggles: record(string(), boolean()).optional(),
19753
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
19842
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
19754
19843
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
19755
19844
  });
19756
19845
  /**
@@ -19964,6 +20053,44 @@ var CameraStatusSchema = object({
19964
20053
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
19965
20054
  fetchedAt: number()
19966
20055
  });
20056
+ var NodeInferenceDeviceSchema = object({
20057
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
20058
+ key: string(),
20059
+ backend: string(),
20060
+ device: string(),
20061
+ format: _enum(MODEL_FORMATS),
20062
+ /** Whether the node's live probe reports the device as usable right now. */
20063
+ available: boolean(),
20064
+ /**
20065
+ * Whether this device participates in dispatch. AUTO default (spec C2):
20066
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
20067
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
20068
+ * not a balanced target). An explicit stored value always wins; a stored-only
20069
+ * (unavailable) key keeps its stored value.
20070
+ */
20071
+ enabled: boolean(),
20072
+ /** Relative balancer weight for the enabled device (default 1). */
20073
+ weight: number(),
20074
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
20075
+ maxSessions: number().nullable(),
20076
+ /** Object-detection model the executor defaults to for this deviceKey. */
20077
+ defaultModelId: string(),
20078
+ /**
20079
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
20080
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
20081
+ * Absent/empty ⇒ no base (every step uses its device format default). The
20082
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
20083
+ * available per format; this is the stored selection that becomes the
20084
+ * default for EVERY camera landing on this accelerator.
20085
+ */
20086
+ steps: record(string(), DeviceStepConfigSchema).optional()
20087
+ });
20088
+ var NodeInferenceDevicesSchema = object({
20089
+ nodeId: string(),
20090
+ /** False when the node's platform-probe was unreachable (no live device set). */
20091
+ reachable: boolean(),
20092
+ devices: array(NodeInferenceDeviceSchema).readonly()
20093
+ });
19967
20094
  method(object({
19968
20095
  deviceId: number(),
19969
20096
  agentNodeId: string()
@@ -19973,7 +20100,13 @@ method(object({
19973
20100
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
19974
20101
  kind: "mutation",
19975
20102
  auth: "admin"
19976
- }), method(_void(), object({ migrated: number() }), {
20103
+ }), method(object({
20104
+ deviceId: number(),
20105
+ deviceKey: string()
20106
+ }), object({ success: literal(true) }), {
20107
+ kind: "mutation",
20108
+ auth: "admin"
20109
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
19977
20110
  kind: "mutation",
19978
20111
  auth: "admin"
19979
20112
  }), 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({
@@ -20007,13 +20140,7 @@ method(object({
20007
20140
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
20008
20141
  nodeId: string(),
20009
20142
  settings: AgentPipelineSettingsSchema
20010
- })).readonly()), method(object({
20011
- agentNodeId: string(),
20012
- defaults: record(string(), AgentAddonConfigSchema)
20013
- }), object({ success: literal(true) }), {
20014
- kind: "mutation",
20015
- auth: "admin"
20016
- }), method(object({ agentNodeId: string() }), object({
20143
+ })).readonly()), method(object({ agentNodeId: string() }), object({
20017
20144
  success: boolean(),
20018
20145
  removed: boolean()
20019
20146
  }), {
@@ -20045,7 +20172,18 @@ method(object({
20045
20172
  }), object({ success: literal(true) }), {
20046
20173
  kind: "mutation",
20047
20174
  auth: "admin"
20048
- }), method(object({ agentNodeId: string() }), object({
20175
+ }), method(object({
20176
+ agentNodeId: string(),
20177
+ inferenceDevices: record(string(), object({
20178
+ enabled: boolean(),
20179
+ weight: number().positive().optional(),
20180
+ maxSessions: number().int().positive().optional(),
20181
+ steps: record(string(), DeviceStepConfigSchema).optional()
20182
+ }))
20183
+ }), object({ success: literal(true) }), {
20184
+ kind: "mutation",
20185
+ auth: "admin"
20186
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
20049
20187
  success: literal(true),
20050
20188
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
20051
20189
  effectiveModelId: string().nullable(),
@@ -20061,9 +20199,10 @@ method(object({
20061
20199
  }), object({ success: literal(true) }), {
20062
20200
  kind: "mutation",
20063
20201
  auth: "admin"
20064
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
20202
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
20065
20203
  deviceId: number(),
20066
20204
  agentNodeId: string(),
20205
+ deviceKey: string(),
20067
20206
  addonId: string(),
20068
20207
  patch: CameraStepOverridePatchSchema.nullable()
20069
20208
  }), object({ success: literal(true) }), {
@@ -20100,14 +20239,13 @@ method(object({
20100
20239
  });
20101
20240
  /**
20102
20241
  * server-management — per-NODE singleton capability for a node's ROOT
20103
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
20104
- * agents).
20242
+ * package lifecycle (runtime-updatable node packages).
20105
20243
  *
20106
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
20107
- * on agents) carries the whole software stack in its npm dep tree, so ONE
20108
- * version describes the node. Updates install into
20109
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
20110
- * starter (probation boot + auto-rollback to N-1).
20244
+ * Every node role runs the SAME root package (`@camstack/server`), which
20245
+ * carries the whole software stack in its npm dep tree, so ONE version
20246
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
20247
+ * on restart via the baked starter (single-copy in-place swap — no probation,
20248
+ * no auto-rollback).
20111
20249
  *
20112
20250
  * Providers:
20113
20251
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -20215,7 +20353,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
20215
20353
  /** Explicit target version; omitted = latest from the registry. */
20216
20354
  version: string().optional() }), ServerUpdateActionResultSchema, {
20217
20355
  kind: "mutation",
20218
- auth: "admin"
20356
+ auth: "admin",
20357
+ timeoutMs: 16 * 6e4
20219
20358
  }), method(_void(), ServerUpdateActionResultSchema, {
20220
20359
  kind: "mutation",
20221
20360
  auth: "admin"
@@ -21259,22 +21398,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
21259
21398
  var RestartAddonResultSchema = unknown();
21260
21399
  var InstallPackageResultSchema = unknown();
21261
21400
  var ReloadPackagesResultSchema = unknown();
21262
- /**
21263
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
21264
- * server restarts so the admin UI can react to the `restartingAt`
21265
- * timestamp (shows reconnect overlay). The transition from
21266
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
21267
- * `system.restart-completed` event after the new process boots.
21268
- *
21269
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
21270
- */
21271
- var UpdateFrameworkPackageResultSchema = object({
21272
- packageName: string(),
21273
- fromVersion: string(),
21274
- toVersion: string(),
21275
- /** Ms-epoch the server scheduled its self-restart. */
21276
- restartingAt: number()
21277
- });
21278
21401
  var BulkUpdateItemStatusSchema = _enum([
21279
21402
  "queued",
21280
21403
  "updating",
@@ -21402,13 +21525,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
21402
21525
  }), object({ success: literal(true) }), {
21403
21526
  kind: "mutation",
21404
21527
  auth: "admin"
21405
- }), method(object({
21406
- packageName: string().min(1),
21407
- version: string().optional(),
21408
- deferRestart: boolean().optional()
21409
- }), UpdateFrameworkPackageResultSchema, {
21410
- kind: "mutation",
21411
- auth: "admin"
21412
21528
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
21413
21529
  kind: "mutation",
21414
21530
  auth: "admin"
@@ -22291,10 +22407,10 @@ var TopologyCategorySchema = object({
22291
22407
  addons: array(TopologyCategoryAddonSchema).readonly()
22292
22408
  });
22293
22409
  /**
22294
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
22295
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
22296
- * version visibility for the Server management surface. Nullable: offline
22297
- * rows and pre-phase-2 nodes report none.
22410
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
22411
+ * root package for every node role) as reported by its `registerNode`
22412
+ * manifest — version visibility for the Server management surface. Nullable:
22413
+ * offline rows and nodes that never reported one.
22298
22414
  */
22299
22415
  var TopologyRootPackageSchema = object({
22300
22416
  name: string(),
@@ -22682,17 +22798,28 @@ var PlatformScoreSchema = object({
22682
22798
  format: _enum([
22683
22799
  "onnx",
22684
22800
  "coreml",
22685
- "openvino"
22801
+ "openvino",
22802
+ "tflite"
22686
22803
  ]),
22687
22804
  score: number(),
22688
22805
  reason: string(),
22689
22806
  available: boolean()
22690
22807
  });
22808
+ var InferenceDeviceDescriptorSchema = object({
22809
+ key: string(),
22810
+ backend: string(),
22811
+ device: string(),
22812
+ format: ModelFormatSchema,
22813
+ runtime: literal("python"),
22814
+ score: number(),
22815
+ available: boolean()
22816
+ });
22691
22817
  var PlatformCapabilitiesSchema = object({
22692
22818
  hardware: HardwareInfoSchema,
22693
22819
  scores: array(PlatformScoreSchema).readonly(),
22694
22820
  bestScore: PlatformScoreSchema,
22695
- pythonPath: string().nullable()
22821
+ pythonPath: string().nullable(),
22822
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
22696
22823
  });
22697
22824
  var ModelRequirementSchema = object({
22698
22825
  modelId: string(),
@@ -23571,12 +23698,6 @@ Object.freeze({
23571
23698
  addonId: null,
23572
23699
  access: "delete"
23573
23700
  },
23574
- "addons.updateFrameworkPackage": {
23575
- capName: "addons",
23576
- capScope: "system",
23577
- addonId: null,
23578
- access: "create"
23579
- },
23580
23701
  "addons.updatePackage": {
23581
23702
  capName: "addons",
23582
23703
  capScope: "system",
@@ -26349,12 +26470,6 @@ Object.freeze({
26349
26470
  addonId: null,
26350
26471
  access: "view"
26351
26472
  },
26352
- "pipelineExecutor.reprobeEngine": {
26353
- capName: "pipeline-executor",
26354
- capScope: "system",
26355
- addonId: null,
26356
- access: "create"
26357
- },
26358
26473
  "pipelineExecutor.runAudioTest": {
26359
26474
  capName: "pipeline-executor",
26360
26475
  capScope: "system",
@@ -26505,6 +26620,12 @@ Object.freeze({
26505
26620
  addonId: null,
26506
26621
  access: "view"
26507
26622
  },
26623
+ "pipelineOrchestrator.getNodeInferenceDevices": {
26624
+ capName: "pipeline-orchestrator",
26625
+ capScope: "system",
26626
+ addonId: null,
26627
+ access: "view"
26628
+ },
26508
26629
  "pipelineOrchestrator.getPipelineAssignment": {
26509
26630
  capName: "pipeline-orchestrator",
26510
26631
  capScope: "system",
@@ -26517,6 +26638,12 @@ Object.freeze({
26517
26638
  addonId: null,
26518
26639
  access: "view"
26519
26640
  },
26641
+ "pipelineOrchestrator.getPipelineDevicePin": {
26642
+ capName: "pipeline-orchestrator",
26643
+ capScope: "system",
26644
+ addonId: null,
26645
+ access: "view"
26646
+ },
26520
26647
  "pipelineOrchestrator.listAgentSettings": {
26521
26648
  capName: "pipeline-orchestrator",
26522
26649
  capScope: "system",
@@ -26559,19 +26686,19 @@ Object.freeze({
26559
26686
  addonId: null,
26560
26687
  access: "create"
26561
26688
  },
26562
- "pipelineOrchestrator.setAgentAddonDefaults": {
26689
+ "pipelineOrchestrator.setAgentCapabilities": {
26563
26690
  capName: "pipeline-orchestrator",
26564
26691
  capScope: "system",
26565
26692
  addonId: null,
26566
26693
  access: "create"
26567
26694
  },
26568
- "pipelineOrchestrator.setAgentCapabilities": {
26695
+ "pipelineOrchestrator.setAgentDetectWeight": {
26569
26696
  capName: "pipeline-orchestrator",
26570
26697
  capScope: "system",
26571
26698
  addonId: null,
26572
26699
  access: "create"
26573
26700
  },
26574
- "pipelineOrchestrator.setAgentDetectWeight": {
26701
+ "pipelineOrchestrator.setAgentInferenceDevices": {
26575
26702
  capName: "pipeline-orchestrator",
26576
26703
  capScope: "system",
26577
26704
  addonId: null,
@@ -26613,6 +26740,12 @@ Object.freeze({
26613
26740
  addonId: null,
26614
26741
  access: "create"
26615
26742
  },
26743
+ "pipelineOrchestrator.setPipelineDevicePin": {
26744
+ capName: "pipeline-orchestrator",
26745
+ capScope: "system",
26746
+ addonId: null,
26747
+ access: "create"
26748
+ },
26616
26749
  "pipelineOrchestrator.unassignAudio": {
26617
26750
  capName: "pipeline-orchestrator",
26618
26751
  capScope: "system",
@@ -28165,32 +28298,6 @@ Object.freeze({
28165
28298
  "network-access": "ingress",
28166
28299
  "smtp-provider": "email"
28167
28300
  });
28168
- var frameworkSwapPackageSchema = object({
28169
- name: string(),
28170
- stagedPath: string(),
28171
- backupPath: string(),
28172
- toVersion: string(),
28173
- fromVersion: string().nullable()
28174
- });
28175
- object({
28176
- jobId: string(),
28177
- taskId: string(),
28178
- packages: array(frameworkSwapPackageSchema),
28179
- requestedAtMs: number(),
28180
- schemaVersion: literal(1)
28181
- });
28182
- object({
28183
- jobId: string(),
28184
- taskId: string(),
28185
- backups: array(object({
28186
- name: string(),
28187
- backupPath: string(),
28188
- livePath: string()
28189
- })),
28190
- appliedAtMs: number(),
28191
- bootAttempts: number(),
28192
- schemaVersion: literal(1)
28193
- });
28194
28301
  //#endregion
28195
28302
  //#region src/config.ts
28196
28303
  /**
package/dist/addon.mjs CHANGED
@@ -7097,6 +7097,17 @@ var ModelCatalogEntrySchema = object({
7097
7097
  "imagenet",
7098
7098
  "none"
7099
7099
  ]).optional(),
7100
+ /**
7101
+ * The model already applies softmax IN-GRAPH — its raw output is a
7102
+ * probability distribution, not logits. When set, the `softmax`
7103
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7104
+ * probability vector collapses it toward uniform (top-1 score craters far
7105
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7106
+ * the output is raw logits and the postprocessor applies softmax (the normal
7107
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7108
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7109
+ */
7110
+ outputProbabilities: boolean().optional(),
7100
7111
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7101
7112
  /**
7102
7113
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -12387,10 +12398,7 @@ var ConfigUISchemaNullableBridge = custom();
12387
12398
  var InferenceCapabilitiesBridge = custom();
12388
12399
  var ModelAvailabilityListBridge = custom();
12389
12400
  var PipelineRunResultBridge = custom();
12390
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
12391
- kind: "mutation",
12392
- auth: "admin"
12393
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
12401
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
12394
12402
  modelId: string(),
12395
12403
  settings: record(string(), unknown()).readonly()
12396
12404
  }))), method(object({ steps: record(string(), object({
@@ -12450,13 +12458,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
12450
12458
  * (inputClasses ≠ null) are skipped and served per-track via
12451
12459
  * pipelineRunner.runDetailSubtree (two-plane design).
12452
12460
  */
12453
- plane: _enum(["full", "frame"]).optional()
12461
+ plane: _enum(["full", "frame"]).optional(),
12462
+ /**
12463
+ * Inference-device selector (Phase 2 multi-device). Format
12464
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
12465
+ * Omitted ⇒ the runner's default device (current single-engine
12466
+ * behaviour). Selects WHICH device pool of the node runs the call.
12467
+ */
12468
+ deviceKey: string().optional()
12454
12469
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
12455
12470
  engine: PipelineEngineChoiceSchema.optional(),
12456
12471
  steps: array(PipelineStepInputSchema).min(1),
12457
12472
  frames: array(FrameInputSchema).min(1).max(255),
12458
12473
  deviceId: number().optional(),
12459
- sessionId: string().optional()
12474
+ sessionId: string().optional(),
12475
+ /**
12476
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
12477
+ * the batch to the Python pool's bench preprocess cache
12478
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
12479
+ * preprocessed ONCE and every later inference is a pure-inference cache
12480
+ * hit — the sustained-throughput run measures inference, not
12481
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
12482
+ * full preprocess every call, correct). Fresh per sustained run;
12483
+ * released via `uncacheFrame`.
12484
+ */
12485
+ frameId: number().int().nonnegative().optional(),
12486
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
12487
+ deviceKey: string().optional()
12460
12488
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
12461
12489
  data: _instanceof(Uint8Array),
12462
12490
  width: number().int().positive(),
@@ -12488,8 +12516,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
12488
12516
  * - `runtime` — main camera-serving engine (no idle TTL).
12489
12517
  * - `warm-override` — benchmark/test override held in the warm
12490
12518
  * cache; auto-disposed after the idle TTL.
12519
+ * - `device-pool` — a concurrent per-device pool (Phase 2
12520
+ * multi-device, keyed by `deviceKey`) resolved
12521
+ * via `resolveDeviceFactory`. Runs alongside the
12522
+ * `runtime` engine on a DIFFERENT accelerator
12523
+ * (NPU / iGPU / Coral) — this is how the
12524
+ * Engines tab shows all pools running at once.
12491
12525
  */
12492
- kind: _enum(["runtime", "warm-override"]),
12526
+ kind: _enum([
12527
+ "runtime",
12528
+ "warm-override",
12529
+ "device-pool"
12530
+ ]),
12493
12531
  /** Native pid of the underlying Python pool (null when no pool). */
12494
12532
  poolPid: number().nullable(),
12495
12533
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -12624,7 +12662,21 @@ var NativeCropResultSchema = object({
12624
12662
  /** Packed rgb (24-bit) pixels of the crop. */
12625
12663
  bytes: _instanceof(Uint8Array),
12626
12664
  width: number().int().positive(),
12627
- height: number().int().positive()
12665
+ height: number().int().positive(),
12666
+ /**
12667
+ * Which source served this crop, so a quality-sensitive consumer (the native
12668
+ * `keyFrame`) can reject a degraded fallback:
12669
+ * - `native` — cut from the decode worker's retained NATIVE surface (the
12670
+ * quality path).
12671
+ * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
12672
+ * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
12673
+ * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
12674
+ *
12675
+ * OPTIONAL: a pre-tier runner (version skew) omits it — an ABSENT `tier` MUST be
12676
+ * treated as `native` (accepted) by every consumer so mixed-version clusters
12677
+ * keep the pre-tier behaviour. An ROI miss returns `null` (no tier at all).
12678
+ */
12679
+ tier: _enum(["native", "ram-fullframe"]).optional()
12628
12680
  });
12629
12681
  /** Parent detection context passed to `runDetailSubtree` — the crop's
12630
12682
  * originating detection, in FRAME-space coordinates. Reuses
@@ -12864,7 +12916,14 @@ var RunnerCameraConfigSchema = object({
12864
12916
  * camera's detect node differs from its source-owner (P2d, gated by the
12865
12917
  * `remoteSourcingNodes` rollout setting).
12866
12918
  */
12867
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
12919
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
12920
+ /**
12921
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
12922
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
12923
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
12924
+ * this only selects WHICH device pool of that node runs the session.
12925
+ */
12926
+ deviceKey: string().optional()
12868
12927
  });
12869
12928
  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;
12870
12929
  /**
@@ -12885,6 +12944,19 @@ var RunnerLocalLoadSchema = object({
12885
12944
  avgInferenceTimeMs: number(),
12886
12945
  /** Total queue depth across motion + detection queues. */
12887
12946
  queueDepthTotal: number(),
12947
+ /**
12948
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
12949
+ * this runner currently has attached cameras on, so the orchestrator's second
12950
+ * `balance()` pass (over a node's devices) weights on real per-pool session
12951
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
12952
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
12953
+ */
12954
+ devices: array(object({
12955
+ deviceKey: string(),
12956
+ backend: string(),
12957
+ attachedCameras: number(),
12958
+ queueDepthTotal: number()
12959
+ })).default([]),
12888
12960
  /** Hardware capability flags reported by this node. */
12889
12961
  hardware: object({
12890
12962
  hasGpu: boolean(),
@@ -16033,6 +16105,8 @@ var BaseDeviceProvider = class extends BaseAddon {
16033
16105
  return toDeviceSummary(device, this.addonId);
16034
16106
  }
16035
16107
  };
16108
+ 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;
16109
+ new Set(Object.values(DeviceType));
16036
16110
  /**
16037
16111
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
16038
16112
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -17606,7 +17680,8 @@ var LinkedDeviceSchema = object({
17606
17680
  deviceId: number(),
17607
17681
  name: string(),
17608
17682
  location: string().nullable(),
17609
- features: array(string())
17683
+ features: array(string()),
17684
+ producesTrackedEvents: boolean().optional()
17610
17685
  });
17611
17686
  var SavedDeviceRowSchema = object({
17612
17687
  /** Numeric id reserved at allocateDeviceId time. */
@@ -19236,6 +19311,7 @@ var TrackSchema = object({
19236
19311
  deviceId: number(),
19237
19312
  className: string(),
19238
19313
  label: string().optional(),
19314
+ producingDeviceName: string().optional(),
19239
19315
  /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19240
19316
  source: TrackSourceSchema.optional(),
19241
19317
  firstSeen: number(),
@@ -19373,7 +19449,8 @@ var MediaFileKindEnum = _enum([
19373
19449
  "fullFrameBoxed",
19374
19450
  "faceCrop",
19375
19451
  "plateCrop",
19376
- "keyFrame"
19452
+ "keyFrame",
19453
+ "keyFrameSmall"
19377
19454
  ]);
19378
19455
  var MediaFileSchema = object({
19379
19456
  key: string(),
@@ -19702,13 +19779,11 @@ var PipelineTemplateSchema = object({
19702
19779
  createdAt: string(),
19703
19780
  updatedAt: string()
19704
19781
  });
19705
- var AgentAddonConfigSchema = object({
19706
- enabled: boolean(),
19782
+ var DeviceStepConfigSchema = object({
19707
19783
  modelId: string().optional(),
19708
- settings: record(string(), unknown()).readonly()
19784
+ settings: record(string(), unknown()).optional()
19709
19785
  });
19710
19786
  var AgentPipelineSettingsSchema = object({
19711
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
19712
19787
  maxCameras: number().int().nonnegative().nullable().default(null),
19713
19788
  /** Per-node detection weight (relative share for the quota balancer). */
19714
19789
  detectWeight: number().positive().optional(),
@@ -19732,7 +19807,22 @@ var AgentPipelineSettingsSchema = object({
19732
19807
  * it already uses to reach the hub). Set this only when the auto-detected
19733
19808
  * address is wrong (multi-homed host, NAT, custom interface).
19734
19809
  */
19735
- reachableHost: string().optional()
19810
+ reachableHost: string().optional(),
19811
+ /**
19812
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
19813
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
19814
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
19815
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
19816
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
19817
+ * the default model/settings for every camera landing on that accelerator;
19818
+ * a stepId absent ⇒ the step uses that device's format default.
19819
+ */
19820
+ inferenceDevices: record(string(), object({
19821
+ enabled: boolean(),
19822
+ weight: number().positive().optional(),
19823
+ maxSessions: number().int().positive().optional(),
19824
+ steps: record(string(), DeviceStepConfigSchema).optional()
19825
+ })).optional()
19736
19826
  });
19737
19827
  var CameraPipelineForAgentSchema = object({
19738
19828
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19742,14 +19832,13 @@ var CameraPipelineForAgentSchema = object({
19742
19832
  }).nullable()
19743
19833
  });
19744
19834
  var CameraStepOverridePatchSchema = object({
19745
- enabled: boolean().optional(),
19746
19835
  modelId: string().optional(),
19747
19836
  settings: record(string(), unknown()).readonly().optional()
19748
19837
  });
19749
19838
  var CameraPipelineSettingsSchema = object({
19750
19839
  pinnedAgentNodeId: string().optional(),
19751
19840
  stepToggles: record(string(), boolean()).optional(),
19752
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
19841
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
19753
19842
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
19754
19843
  });
19755
19844
  /**
@@ -19963,6 +20052,44 @@ var CameraStatusSchema = object({
19963
20052
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
19964
20053
  fetchedAt: number()
19965
20054
  });
20055
+ var NodeInferenceDeviceSchema = object({
20056
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
20057
+ key: string(),
20058
+ backend: string(),
20059
+ device: string(),
20060
+ format: _enum(MODEL_FORMATS),
20061
+ /** Whether the node's live probe reports the device as usable right now. */
20062
+ available: boolean(),
20063
+ /**
20064
+ * Whether this device participates in dispatch. AUTO default (spec C2):
20065
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
20066
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
20067
+ * not a balanced target). An explicit stored value always wins; a stored-only
20068
+ * (unavailable) key keeps its stored value.
20069
+ */
20070
+ enabled: boolean(),
20071
+ /** Relative balancer weight for the enabled device (default 1). */
20072
+ weight: number(),
20073
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
20074
+ maxSessions: number().nullable(),
20075
+ /** Object-detection model the executor defaults to for this deviceKey. */
20076
+ defaultModelId: string(),
20077
+ /**
20078
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
20079
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
20080
+ * Absent/empty ⇒ no base (every step uses its device format default). The
20081
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
20082
+ * available per format; this is the stored selection that becomes the
20083
+ * default for EVERY camera landing on this accelerator.
20084
+ */
20085
+ steps: record(string(), DeviceStepConfigSchema).optional()
20086
+ });
20087
+ var NodeInferenceDevicesSchema = object({
20088
+ nodeId: string(),
20089
+ /** False when the node's platform-probe was unreachable (no live device set). */
20090
+ reachable: boolean(),
20091
+ devices: array(NodeInferenceDeviceSchema).readonly()
20092
+ });
19966
20093
  method(object({
19967
20094
  deviceId: number(),
19968
20095
  agentNodeId: string()
@@ -19972,7 +20099,13 @@ method(object({
19972
20099
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
19973
20100
  kind: "mutation",
19974
20101
  auth: "admin"
19975
- }), method(_void(), object({ migrated: number() }), {
20102
+ }), method(object({
20103
+ deviceId: number(),
20104
+ deviceKey: string()
20105
+ }), object({ success: literal(true) }), {
20106
+ kind: "mutation",
20107
+ auth: "admin"
20108
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
19976
20109
  kind: "mutation",
19977
20110
  auth: "admin"
19978
20111
  }), 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({
@@ -20006,13 +20139,7 @@ method(object({
20006
20139
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
20007
20140
  nodeId: string(),
20008
20141
  settings: AgentPipelineSettingsSchema
20009
- })).readonly()), method(object({
20010
- agentNodeId: string(),
20011
- defaults: record(string(), AgentAddonConfigSchema)
20012
- }), object({ success: literal(true) }), {
20013
- kind: "mutation",
20014
- auth: "admin"
20015
- }), method(object({ agentNodeId: string() }), object({
20142
+ })).readonly()), method(object({ agentNodeId: string() }), object({
20016
20143
  success: boolean(),
20017
20144
  removed: boolean()
20018
20145
  }), {
@@ -20044,7 +20171,18 @@ method(object({
20044
20171
  }), object({ success: literal(true) }), {
20045
20172
  kind: "mutation",
20046
20173
  auth: "admin"
20047
- }), method(object({ agentNodeId: string() }), object({
20174
+ }), method(object({
20175
+ agentNodeId: string(),
20176
+ inferenceDevices: record(string(), object({
20177
+ enabled: boolean(),
20178
+ weight: number().positive().optional(),
20179
+ maxSessions: number().int().positive().optional(),
20180
+ steps: record(string(), DeviceStepConfigSchema).optional()
20181
+ }))
20182
+ }), object({ success: literal(true) }), {
20183
+ kind: "mutation",
20184
+ auth: "admin"
20185
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
20048
20186
  success: literal(true),
20049
20187
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
20050
20188
  effectiveModelId: string().nullable(),
@@ -20060,9 +20198,10 @@ method(object({
20060
20198
  }), object({ success: literal(true) }), {
20061
20199
  kind: "mutation",
20062
20200
  auth: "admin"
20063
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
20201
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
20064
20202
  deviceId: number(),
20065
20203
  agentNodeId: string(),
20204
+ deviceKey: string(),
20066
20205
  addonId: string(),
20067
20206
  patch: CameraStepOverridePatchSchema.nullable()
20068
20207
  }), object({ success: literal(true) }), {
@@ -20099,14 +20238,13 @@ method(object({
20099
20238
  });
20100
20239
  /**
20101
20240
  * server-management — per-NODE singleton capability for a node's ROOT
20102
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
20103
- * agents).
20241
+ * package lifecycle (runtime-updatable node packages).
20104
20242
  *
20105
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
20106
- * on agents) carries the whole software stack in its npm dep tree, so ONE
20107
- * version describes the node. Updates install into
20108
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
20109
- * starter (probation boot + auto-rollback to N-1).
20243
+ * Every node role runs the SAME root package (`@camstack/server`), which
20244
+ * carries the whole software stack in its npm dep tree, so ONE version
20245
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
20246
+ * on restart via the baked starter (single-copy in-place swap — no probation,
20247
+ * no auto-rollback).
20110
20248
  *
20111
20249
  * Providers:
20112
20250
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -20214,7 +20352,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
20214
20352
  /** Explicit target version; omitted = latest from the registry. */
20215
20353
  version: string().optional() }), ServerUpdateActionResultSchema, {
20216
20354
  kind: "mutation",
20217
- auth: "admin"
20355
+ auth: "admin",
20356
+ timeoutMs: 16 * 6e4
20218
20357
  }), method(_void(), ServerUpdateActionResultSchema, {
20219
20358
  kind: "mutation",
20220
20359
  auth: "admin"
@@ -21258,22 +21397,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
21258
21397
  var RestartAddonResultSchema = unknown();
21259
21398
  var InstallPackageResultSchema = unknown();
21260
21399
  var ReloadPackagesResultSchema = unknown();
21261
- /**
21262
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
21263
- * server restarts so the admin UI can react to the `restartingAt`
21264
- * timestamp (shows reconnect overlay). The transition from
21265
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
21266
- * `system.restart-completed` event after the new process boots.
21267
- *
21268
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
21269
- */
21270
- var UpdateFrameworkPackageResultSchema = object({
21271
- packageName: string(),
21272
- fromVersion: string(),
21273
- toVersion: string(),
21274
- /** Ms-epoch the server scheduled its self-restart. */
21275
- restartingAt: number()
21276
- });
21277
21400
  var BulkUpdateItemStatusSchema = _enum([
21278
21401
  "queued",
21279
21402
  "updating",
@@ -21401,13 +21524,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
21401
21524
  }), object({ success: literal(true) }), {
21402
21525
  kind: "mutation",
21403
21526
  auth: "admin"
21404
- }), method(object({
21405
- packageName: string().min(1),
21406
- version: string().optional(),
21407
- deferRestart: boolean().optional()
21408
- }), UpdateFrameworkPackageResultSchema, {
21409
- kind: "mutation",
21410
- auth: "admin"
21411
21527
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
21412
21528
  kind: "mutation",
21413
21529
  auth: "admin"
@@ -22290,10 +22406,10 @@ var TopologyCategorySchema = object({
22290
22406
  addons: array(TopologyCategoryAddonSchema).readonly()
22291
22407
  });
22292
22408
  /**
22293
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
22294
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
22295
- * version visibility for the Server management surface. Nullable: offline
22296
- * rows and pre-phase-2 nodes report none.
22409
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
22410
+ * root package for every node role) as reported by its `registerNode`
22411
+ * manifest — version visibility for the Server management surface. Nullable:
22412
+ * offline rows and nodes that never reported one.
22297
22413
  */
22298
22414
  var TopologyRootPackageSchema = object({
22299
22415
  name: string(),
@@ -22681,17 +22797,28 @@ var PlatformScoreSchema = object({
22681
22797
  format: _enum([
22682
22798
  "onnx",
22683
22799
  "coreml",
22684
- "openvino"
22800
+ "openvino",
22801
+ "tflite"
22685
22802
  ]),
22686
22803
  score: number(),
22687
22804
  reason: string(),
22688
22805
  available: boolean()
22689
22806
  });
22807
+ var InferenceDeviceDescriptorSchema = object({
22808
+ key: string(),
22809
+ backend: string(),
22810
+ device: string(),
22811
+ format: ModelFormatSchema,
22812
+ runtime: literal("python"),
22813
+ score: number(),
22814
+ available: boolean()
22815
+ });
22690
22816
  var PlatformCapabilitiesSchema = object({
22691
22817
  hardware: HardwareInfoSchema,
22692
22818
  scores: array(PlatformScoreSchema).readonly(),
22693
22819
  bestScore: PlatformScoreSchema,
22694
- pythonPath: string().nullable()
22820
+ pythonPath: string().nullable(),
22821
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
22695
22822
  });
22696
22823
  var ModelRequirementSchema = object({
22697
22824
  modelId: string(),
@@ -23570,12 +23697,6 @@ Object.freeze({
23570
23697
  addonId: null,
23571
23698
  access: "delete"
23572
23699
  },
23573
- "addons.updateFrameworkPackage": {
23574
- capName: "addons",
23575
- capScope: "system",
23576
- addonId: null,
23577
- access: "create"
23578
- },
23579
23700
  "addons.updatePackage": {
23580
23701
  capName: "addons",
23581
23702
  capScope: "system",
@@ -26348,12 +26469,6 @@ Object.freeze({
26348
26469
  addonId: null,
26349
26470
  access: "view"
26350
26471
  },
26351
- "pipelineExecutor.reprobeEngine": {
26352
- capName: "pipeline-executor",
26353
- capScope: "system",
26354
- addonId: null,
26355
- access: "create"
26356
- },
26357
26472
  "pipelineExecutor.runAudioTest": {
26358
26473
  capName: "pipeline-executor",
26359
26474
  capScope: "system",
@@ -26504,6 +26619,12 @@ Object.freeze({
26504
26619
  addonId: null,
26505
26620
  access: "view"
26506
26621
  },
26622
+ "pipelineOrchestrator.getNodeInferenceDevices": {
26623
+ capName: "pipeline-orchestrator",
26624
+ capScope: "system",
26625
+ addonId: null,
26626
+ access: "view"
26627
+ },
26507
26628
  "pipelineOrchestrator.getPipelineAssignment": {
26508
26629
  capName: "pipeline-orchestrator",
26509
26630
  capScope: "system",
@@ -26516,6 +26637,12 @@ Object.freeze({
26516
26637
  addonId: null,
26517
26638
  access: "view"
26518
26639
  },
26640
+ "pipelineOrchestrator.getPipelineDevicePin": {
26641
+ capName: "pipeline-orchestrator",
26642
+ capScope: "system",
26643
+ addonId: null,
26644
+ access: "view"
26645
+ },
26519
26646
  "pipelineOrchestrator.listAgentSettings": {
26520
26647
  capName: "pipeline-orchestrator",
26521
26648
  capScope: "system",
@@ -26558,19 +26685,19 @@ Object.freeze({
26558
26685
  addonId: null,
26559
26686
  access: "create"
26560
26687
  },
26561
- "pipelineOrchestrator.setAgentAddonDefaults": {
26688
+ "pipelineOrchestrator.setAgentCapabilities": {
26562
26689
  capName: "pipeline-orchestrator",
26563
26690
  capScope: "system",
26564
26691
  addonId: null,
26565
26692
  access: "create"
26566
26693
  },
26567
- "pipelineOrchestrator.setAgentCapabilities": {
26694
+ "pipelineOrchestrator.setAgentDetectWeight": {
26568
26695
  capName: "pipeline-orchestrator",
26569
26696
  capScope: "system",
26570
26697
  addonId: null,
26571
26698
  access: "create"
26572
26699
  },
26573
- "pipelineOrchestrator.setAgentDetectWeight": {
26700
+ "pipelineOrchestrator.setAgentInferenceDevices": {
26574
26701
  capName: "pipeline-orchestrator",
26575
26702
  capScope: "system",
26576
26703
  addonId: null,
@@ -26612,6 +26739,12 @@ Object.freeze({
26612
26739
  addonId: null,
26613
26740
  access: "create"
26614
26741
  },
26742
+ "pipelineOrchestrator.setPipelineDevicePin": {
26743
+ capName: "pipeline-orchestrator",
26744
+ capScope: "system",
26745
+ addonId: null,
26746
+ access: "create"
26747
+ },
26615
26748
  "pipelineOrchestrator.unassignAudio": {
26616
26749
  capName: "pipeline-orchestrator",
26617
26750
  capScope: "system",
@@ -28164,32 +28297,6 @@ Object.freeze({
28164
28297
  "network-access": "ingress",
28165
28298
  "smtp-provider": "email"
28166
28299
  });
28167
- var frameworkSwapPackageSchema = object({
28168
- name: string(),
28169
- stagedPath: string(),
28170
- backupPath: string(),
28171
- toVersion: string(),
28172
- fromVersion: string().nullable()
28173
- });
28174
- object({
28175
- jobId: string(),
28176
- taskId: string(),
28177
- packages: array(frameworkSwapPackageSchema),
28178
- requestedAtMs: number(),
28179
- schemaVersion: literal(1)
28180
- });
28181
- object({
28182
- jobId: string(),
28183
- taskId: string(),
28184
- backups: array(object({
28185
- name: string(),
28186
- backupPath: string(),
28187
- livePath: string()
28188
- })),
28189
- appliedAtMs: number(),
28190
- bootAttempts: number(),
28191
- schemaVersion: literal(1)
28192
- });
28193
28300
  //#endregion
28194
28301
  //#region src/config.ts
28195
28302
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-unifi",
3
- "version": "0.1.14",
3
+ "version": "0.2.1",
4
4
  "description": "UniFi Network controller device-provider addon for CamStack — local-controller infra switches/APs (as containers) + network-client presence. NO cameras/Protect.",
5
5
  "keywords": [
6
6
  "camstack",