@camstack/addon-ai 0.1.9 → 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
@@ -7112,6 +7112,17 @@ var ModelCatalogEntrySchema = object({
7112
7112
  "imagenet",
7113
7113
  "none"
7114
7114
  ]).optional(),
7115
+ /**
7116
+ * The model already applies softmax IN-GRAPH — its raw output is a
7117
+ * probability distribution, not logits. When set, the `softmax`
7118
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7119
+ * probability vector collapses it toward uniform (top-1 score craters far
7120
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7121
+ * the output is raw logits and the postprocessor applies softmax (the normal
7122
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7123
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7124
+ */
7125
+ outputProbabilities: boolean().optional(),
7115
7126
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7116
7127
  /**
7117
7128
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -11155,10 +11166,7 @@ var ConfigUISchemaNullableBridge = custom();
11155
11166
  var InferenceCapabilitiesBridge = custom();
11156
11167
  var ModelAvailabilityListBridge = custom();
11157
11168
  var PipelineRunResultBridge = custom();
11158
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
11159
- kind: "mutation",
11160
- auth: "admin"
11161
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
11169
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
11162
11170
  modelId: string(),
11163
11171
  settings: record(string(), unknown()).readonly()
11164
11172
  }))), method(object({ steps: record(string(), object({
@@ -11218,13 +11226,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
11218
11226
  * (inputClasses ≠ null) are skipped and served per-track via
11219
11227
  * pipelineRunner.runDetailSubtree (two-plane design).
11220
11228
  */
11221
- plane: _enum(["full", "frame"]).optional()
11229
+ plane: _enum(["full", "frame"]).optional(),
11230
+ /**
11231
+ * Inference-device selector (Phase 2 multi-device). Format
11232
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
11233
+ * Omitted ⇒ the runner's default device (current single-engine
11234
+ * behaviour). Selects WHICH device pool of the node runs the call.
11235
+ */
11236
+ deviceKey: string().optional()
11222
11237
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
11223
11238
  engine: PipelineEngineChoiceSchema.optional(),
11224
11239
  steps: array(PipelineStepInputSchema).min(1),
11225
11240
  frames: array(FrameInputSchema).min(1).max(255),
11226
11241
  deviceId: number().optional(),
11227
- sessionId: string().optional()
11242
+ sessionId: string().optional(),
11243
+ /**
11244
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
11245
+ * the batch to the Python pool's bench preprocess cache
11246
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
11247
+ * preprocessed ONCE and every later inference is a pure-inference cache
11248
+ * hit — the sustained-throughput run measures inference, not
11249
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
11250
+ * full preprocess every call, correct). Fresh per sustained run;
11251
+ * released via `uncacheFrame`.
11252
+ */
11253
+ frameId: number().int().nonnegative().optional(),
11254
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
11255
+ deviceKey: string().optional()
11228
11256
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
11229
11257
  data: _instanceof(Uint8Array),
11230
11258
  width: number().int().positive(),
@@ -11256,8 +11284,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
11256
11284
  * - `runtime` — main camera-serving engine (no idle TTL).
11257
11285
  * - `warm-override` — benchmark/test override held in the warm
11258
11286
  * cache; auto-disposed after the idle TTL.
11287
+ * - `device-pool` — a concurrent per-device pool (Phase 2
11288
+ * multi-device, keyed by `deviceKey`) resolved
11289
+ * via `resolveDeviceFactory`. Runs alongside the
11290
+ * `runtime` engine on a DIFFERENT accelerator
11291
+ * (NPU / iGPU / Coral) — this is how the
11292
+ * Engines tab shows all pools running at once.
11259
11293
  */
11260
- kind: _enum(["runtime", "warm-override"]),
11294
+ kind: _enum([
11295
+ "runtime",
11296
+ "warm-override",
11297
+ "device-pool"
11298
+ ]),
11261
11299
  /** Native pid of the underlying Python pool (null when no pool). */
11262
11300
  poolPid: number().nullable(),
11263
11301
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -11359,7 +11397,21 @@ var NativeCropResultSchema = object({
11359
11397
  /** Packed rgb (24-bit) pixels of the crop. */
11360
11398
  bytes: _instanceof(Uint8Array),
11361
11399
  width: number().int().positive(),
11362
- height: number().int().positive()
11400
+ height: number().int().positive(),
11401
+ /**
11402
+ * Which source served this crop, so a quality-sensitive consumer (the native
11403
+ * `keyFrame`) can reject a degraded fallback:
11404
+ * - `native` — cut from the decode worker's retained NATIVE surface (the
11405
+ * quality path).
11406
+ * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
11407
+ * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
11408
+ * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
11409
+ *
11410
+ * OPTIONAL: a pre-tier runner (version skew) omits it — an ABSENT `tier` MUST be
11411
+ * treated as `native` (accepted) by every consumer so mixed-version clusters
11412
+ * keep the pre-tier behaviour. An ROI miss returns `null` (no tier at all).
11413
+ */
11414
+ tier: _enum(["native", "ram-fullframe"]).optional()
11363
11415
  });
11364
11416
  /** Parent detection context passed to `runDetailSubtree` — the crop's
11365
11417
  * originating detection, in FRAME-space coordinates. Reuses
@@ -11599,7 +11651,14 @@ var RunnerCameraConfigSchema = object({
11599
11651
  * camera's detect node differs from its source-owner (P2d, gated by the
11600
11652
  * `remoteSourcingNodes` rollout setting).
11601
11653
  */
11602
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
11654
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
11655
+ /**
11656
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
11657
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
11658
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
11659
+ * this only selects WHICH device pool of that node runs the session.
11660
+ */
11661
+ deviceKey: string().optional()
11603
11662
  });
11604
11663
  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;
11605
11664
  /**
@@ -11620,6 +11679,19 @@ var RunnerLocalLoadSchema = object({
11620
11679
  avgInferenceTimeMs: number(),
11621
11680
  /** Total queue depth across motion + detection queues. */
11622
11681
  queueDepthTotal: number(),
11682
+ /**
11683
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
11684
+ * this runner currently has attached cameras on, so the orchestrator's second
11685
+ * `balance()` pass (over a node's devices) weights on real per-pool session
11686
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
11687
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
11688
+ */
11689
+ devices: array(object({
11690
+ deviceKey: string(),
11691
+ backend: string(),
11692
+ attachedCameras: number(),
11693
+ queueDepthTotal: number()
11694
+ })).default([]),
11623
11695
  /** Hardware capability flags reported by this node. */
11624
11696
  hardware: object({
11625
11697
  hasGpu: boolean(),
@@ -13095,6 +13167,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
13095
13167
  kind: "mutation",
13096
13168
  auth: "admin"
13097
13169
  });
13170
+ 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;
13171
+ new Set(Object.values(DeviceType));
13098
13172
  /**
13099
13173
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
13100
13174
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -14668,7 +14742,8 @@ var LinkedDeviceSchema = object({
14668
14742
  deviceId: number(),
14669
14743
  name: string(),
14670
14744
  location: string().nullable(),
14671
- features: array(string())
14745
+ features: array(string()),
14746
+ producesTrackedEvents: boolean().optional()
14672
14747
  });
14673
14748
  var SavedDeviceRowSchema = object({
14674
14749
  /** Numeric id reserved at allocateDeviceId time. */
@@ -16343,6 +16418,7 @@ var TrackSchema = object({
16343
16418
  deviceId: number(),
16344
16419
  className: string(),
16345
16420
  label: string().optional(),
16421
+ producingDeviceName: string().optional(),
16346
16422
  /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16347
16423
  source: TrackSourceSchema.optional(),
16348
16424
  firstSeen: number(),
@@ -16480,7 +16556,8 @@ var MediaFileKindEnum = _enum([
16480
16556
  "fullFrameBoxed",
16481
16557
  "faceCrop",
16482
16558
  "plateCrop",
16483
- "keyFrame"
16559
+ "keyFrame",
16560
+ "keyFrameSmall"
16484
16561
  ]);
16485
16562
  var MediaFileSchema = object({
16486
16563
  key: string(),
@@ -16809,13 +16886,11 @@ var PipelineTemplateSchema = object({
16809
16886
  createdAt: string(),
16810
16887
  updatedAt: string()
16811
16888
  });
16812
- var AgentAddonConfigSchema = object({
16813
- enabled: boolean(),
16889
+ var DeviceStepConfigSchema = object({
16814
16890
  modelId: string().optional(),
16815
- settings: record(string(), unknown()).readonly()
16891
+ settings: record(string(), unknown()).optional()
16816
16892
  });
16817
16893
  var AgentPipelineSettingsSchema = object({
16818
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
16819
16894
  maxCameras: number().int().nonnegative().nullable().default(null),
16820
16895
  /** Per-node detection weight (relative share for the quota balancer). */
16821
16896
  detectWeight: number().positive().optional(),
@@ -16839,7 +16914,22 @@ var AgentPipelineSettingsSchema = object({
16839
16914
  * it already uses to reach the hub). Set this only when the auto-detected
16840
16915
  * address is wrong (multi-homed host, NAT, custom interface).
16841
16916
  */
16842
- reachableHost: string().optional()
16917
+ reachableHost: string().optional(),
16918
+ /**
16919
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
16920
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
16921
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
16922
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
16923
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
16924
+ * the default model/settings for every camera landing on that accelerator;
16925
+ * a stepId absent ⇒ the step uses that device's format default.
16926
+ */
16927
+ inferenceDevices: record(string(), object({
16928
+ enabled: boolean(),
16929
+ weight: number().positive().optional(),
16930
+ maxSessions: number().int().positive().optional(),
16931
+ steps: record(string(), DeviceStepConfigSchema).optional()
16932
+ })).optional()
16843
16933
  });
16844
16934
  var CameraPipelineForAgentSchema = object({
16845
16935
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16849,14 +16939,13 @@ var CameraPipelineForAgentSchema = object({
16849
16939
  }).nullable()
16850
16940
  });
16851
16941
  var CameraStepOverridePatchSchema = object({
16852
- enabled: boolean().optional(),
16853
16942
  modelId: string().optional(),
16854
16943
  settings: record(string(), unknown()).readonly().optional()
16855
16944
  });
16856
16945
  var CameraPipelineSettingsSchema = object({
16857
16946
  pinnedAgentNodeId: string().optional(),
16858
16947
  stepToggles: record(string(), boolean()).optional(),
16859
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
16948
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
16860
16949
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
16861
16950
  });
16862
16951
  /**
@@ -17070,6 +17159,44 @@ var CameraStatusSchema = object({
17070
17159
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
17071
17160
  fetchedAt: number()
17072
17161
  });
17162
+ var NodeInferenceDeviceSchema = object({
17163
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
17164
+ key: string(),
17165
+ backend: string(),
17166
+ device: string(),
17167
+ format: _enum(MODEL_FORMATS),
17168
+ /** Whether the node's live probe reports the device as usable right now. */
17169
+ available: boolean(),
17170
+ /**
17171
+ * Whether this device participates in dispatch. AUTO default (spec C2):
17172
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
17173
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
17174
+ * not a balanced target). An explicit stored value always wins; a stored-only
17175
+ * (unavailable) key keeps its stored value.
17176
+ */
17177
+ enabled: boolean(),
17178
+ /** Relative balancer weight for the enabled device (default 1). */
17179
+ weight: number(),
17180
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
17181
+ maxSessions: number().nullable(),
17182
+ /** Object-detection model the executor defaults to for this deviceKey. */
17183
+ defaultModelId: string(),
17184
+ /**
17185
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
17186
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
17187
+ * Absent/empty ⇒ no base (every step uses its device format default). The
17188
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
17189
+ * available per format; this is the stored selection that becomes the
17190
+ * default for EVERY camera landing on this accelerator.
17191
+ */
17192
+ steps: record(string(), DeviceStepConfigSchema).optional()
17193
+ });
17194
+ var NodeInferenceDevicesSchema = object({
17195
+ nodeId: string(),
17196
+ /** False when the node's platform-probe was unreachable (no live device set). */
17197
+ reachable: boolean(),
17198
+ devices: array(NodeInferenceDeviceSchema).readonly()
17199
+ });
17073
17200
  method(object({
17074
17201
  deviceId: number(),
17075
17202
  agentNodeId: string()
@@ -17079,7 +17206,13 @@ method(object({
17079
17206
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
17080
17207
  kind: "mutation",
17081
17208
  auth: "admin"
17082
- }), method(_void(), object({ migrated: number() }), {
17209
+ }), method(object({
17210
+ deviceId: number(),
17211
+ deviceKey: string()
17212
+ }), object({ success: literal(true) }), {
17213
+ kind: "mutation",
17214
+ auth: "admin"
17215
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
17083
17216
  kind: "mutation",
17084
17217
  auth: "admin"
17085
17218
  }), 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({
@@ -17113,13 +17246,7 @@ method(object({
17113
17246
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
17114
17247
  nodeId: string(),
17115
17248
  settings: AgentPipelineSettingsSchema
17116
- })).readonly()), method(object({
17117
- agentNodeId: string(),
17118
- defaults: record(string(), AgentAddonConfigSchema)
17119
- }), object({ success: literal(true) }), {
17120
- kind: "mutation",
17121
- auth: "admin"
17122
- }), method(object({ agentNodeId: string() }), object({
17249
+ })).readonly()), method(object({ agentNodeId: string() }), object({
17123
17250
  success: boolean(),
17124
17251
  removed: boolean()
17125
17252
  }), {
@@ -17151,7 +17278,18 @@ method(object({
17151
17278
  }), object({ success: literal(true) }), {
17152
17279
  kind: "mutation",
17153
17280
  auth: "admin"
17154
- }), method(object({ agentNodeId: string() }), object({
17281
+ }), method(object({
17282
+ agentNodeId: string(),
17283
+ inferenceDevices: record(string(), object({
17284
+ enabled: boolean(),
17285
+ weight: number().positive().optional(),
17286
+ maxSessions: number().int().positive().optional(),
17287
+ steps: record(string(), DeviceStepConfigSchema).optional()
17288
+ }))
17289
+ }), object({ success: literal(true) }), {
17290
+ kind: "mutation",
17291
+ auth: "admin"
17292
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
17155
17293
  success: literal(true),
17156
17294
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
17157
17295
  effectiveModelId: string().nullable(),
@@ -17167,9 +17305,10 @@ method(object({
17167
17305
  }), object({ success: literal(true) }), {
17168
17306
  kind: "mutation",
17169
17307
  auth: "admin"
17170
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
17308
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
17171
17309
  deviceId: number(),
17172
17310
  agentNodeId: string(),
17311
+ deviceKey: string(),
17173
17312
  addonId: string(),
17174
17313
  patch: CameraStepOverridePatchSchema.nullable()
17175
17314
  }), object({ success: literal(true) }), {
@@ -17206,14 +17345,13 @@ method(object({
17206
17345
  });
17207
17346
  /**
17208
17347
  * server-management — per-NODE singleton capability for a node's ROOT
17209
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
17210
- * agents).
17348
+ * package lifecycle (runtime-updatable node packages).
17211
17349
  *
17212
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
17213
- * on agents) carries the whole software stack in its npm dep tree, so ONE
17214
- * version describes the node. Updates install into
17215
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
17216
- * starter (probation boot + auto-rollback to N-1).
17350
+ * Every node role runs the SAME root package (`@camstack/server`), which
17351
+ * carries the whole software stack in its npm dep tree, so ONE version
17352
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
17353
+ * on restart via the baked starter (single-copy in-place swap — no probation,
17354
+ * no auto-rollback).
17217
17355
  *
17218
17356
  * Providers:
17219
17357
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -17321,7 +17459,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
17321
17459
  /** Explicit target version; omitted = latest from the registry. */
17322
17460
  version: string().optional() }), ServerUpdateActionResultSchema, {
17323
17461
  kind: "mutation",
17324
- auth: "admin"
17462
+ auth: "admin",
17463
+ timeoutMs: 16 * 6e4
17325
17464
  }), method(_void(), ServerUpdateActionResultSchema, {
17326
17465
  kind: "mutation",
17327
17466
  auth: "admin"
@@ -18365,22 +18504,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
18365
18504
  var RestartAddonResultSchema = unknown();
18366
18505
  var InstallPackageResultSchema = unknown();
18367
18506
  var ReloadPackagesResultSchema = unknown();
18368
- /**
18369
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
18370
- * server restarts so the admin UI can react to the `restartingAt`
18371
- * timestamp (shows reconnect overlay). The transition from
18372
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
18373
- * `system.restart-completed` event after the new process boots.
18374
- *
18375
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
18376
- */
18377
- var UpdateFrameworkPackageResultSchema = object({
18378
- packageName: string(),
18379
- fromVersion: string(),
18380
- toVersion: string(),
18381
- /** Ms-epoch the server scheduled its self-restart. */
18382
- restartingAt: number()
18383
- });
18384
18507
  var BulkUpdateItemStatusSchema = _enum([
18385
18508
  "queued",
18386
18509
  "updating",
@@ -18508,13 +18631,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
18508
18631
  }), object({ success: literal(true) }), {
18509
18632
  kind: "mutation",
18510
18633
  auth: "admin"
18511
- }), method(object({
18512
- packageName: string().min(1),
18513
- version: string().optional(),
18514
- deferRestart: boolean().optional()
18515
- }), UpdateFrameworkPackageResultSchema, {
18516
- kind: "mutation",
18517
- auth: "admin"
18518
18634
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
18519
18635
  kind: "mutation",
18520
18636
  auth: "admin"
@@ -19380,10 +19496,10 @@ var TopologyCategorySchema = object({
19380
19496
  addons: array(TopologyCategoryAddonSchema).readonly()
19381
19497
  });
19382
19498
  /**
19383
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
19384
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
19385
- * version visibility for the Server management surface. Nullable: offline
19386
- * rows and pre-phase-2 nodes report none.
19499
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
19500
+ * root package for every node role) as reported by its `registerNode`
19501
+ * manifest — version visibility for the Server management surface. Nullable:
19502
+ * offline rows and nodes that never reported one.
19387
19503
  */
19388
19504
  var TopologyRootPackageSchema = object({
19389
19505
  name: string(),
@@ -19771,17 +19887,28 @@ var PlatformScoreSchema = object({
19771
19887
  format: _enum([
19772
19888
  "onnx",
19773
19889
  "coreml",
19774
- "openvino"
19890
+ "openvino",
19891
+ "tflite"
19775
19892
  ]),
19776
19893
  score: number(),
19777
19894
  reason: string(),
19778
19895
  available: boolean()
19779
19896
  });
19897
+ var InferenceDeviceDescriptorSchema = object({
19898
+ key: string(),
19899
+ backend: string(),
19900
+ device: string(),
19901
+ format: ModelFormatSchema,
19902
+ runtime: literal("python"),
19903
+ score: number(),
19904
+ available: boolean()
19905
+ });
19780
19906
  var PlatformCapabilitiesSchema = object({
19781
19907
  hardware: HardwareInfoSchema,
19782
19908
  scores: array(PlatformScoreSchema).readonly(),
19783
19909
  bestScore: PlatformScoreSchema,
19784
- pythonPath: string().nullable()
19910
+ pythonPath: string().nullable(),
19911
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
19785
19912
  });
19786
19913
  var ModelRequirementSchema = object({
19787
19914
  modelId: string(),
@@ -20660,12 +20787,6 @@ Object.freeze({
20660
20787
  addonId: null,
20661
20788
  access: "delete"
20662
20789
  },
20663
- "addons.updateFrameworkPackage": {
20664
- capName: "addons",
20665
- capScope: "system",
20666
- addonId: null,
20667
- access: "create"
20668
- },
20669
20790
  "addons.updatePackage": {
20670
20791
  capName: "addons",
20671
20792
  capScope: "system",
@@ -23438,12 +23559,6 @@ Object.freeze({
23438
23559
  addonId: null,
23439
23560
  access: "view"
23440
23561
  },
23441
- "pipelineExecutor.reprobeEngine": {
23442
- capName: "pipeline-executor",
23443
- capScope: "system",
23444
- addonId: null,
23445
- access: "create"
23446
- },
23447
23562
  "pipelineExecutor.runAudioTest": {
23448
23563
  capName: "pipeline-executor",
23449
23564
  capScope: "system",
@@ -23594,6 +23709,12 @@ Object.freeze({
23594
23709
  addonId: null,
23595
23710
  access: "view"
23596
23711
  },
23712
+ "pipelineOrchestrator.getNodeInferenceDevices": {
23713
+ capName: "pipeline-orchestrator",
23714
+ capScope: "system",
23715
+ addonId: null,
23716
+ access: "view"
23717
+ },
23597
23718
  "pipelineOrchestrator.getPipelineAssignment": {
23598
23719
  capName: "pipeline-orchestrator",
23599
23720
  capScope: "system",
@@ -23606,6 +23727,12 @@ Object.freeze({
23606
23727
  addonId: null,
23607
23728
  access: "view"
23608
23729
  },
23730
+ "pipelineOrchestrator.getPipelineDevicePin": {
23731
+ capName: "pipeline-orchestrator",
23732
+ capScope: "system",
23733
+ addonId: null,
23734
+ access: "view"
23735
+ },
23609
23736
  "pipelineOrchestrator.listAgentSettings": {
23610
23737
  capName: "pipeline-orchestrator",
23611
23738
  capScope: "system",
@@ -23648,19 +23775,19 @@ Object.freeze({
23648
23775
  addonId: null,
23649
23776
  access: "create"
23650
23777
  },
23651
- "pipelineOrchestrator.setAgentAddonDefaults": {
23778
+ "pipelineOrchestrator.setAgentCapabilities": {
23652
23779
  capName: "pipeline-orchestrator",
23653
23780
  capScope: "system",
23654
23781
  addonId: null,
23655
23782
  access: "create"
23656
23783
  },
23657
- "pipelineOrchestrator.setAgentCapabilities": {
23784
+ "pipelineOrchestrator.setAgentDetectWeight": {
23658
23785
  capName: "pipeline-orchestrator",
23659
23786
  capScope: "system",
23660
23787
  addonId: null,
23661
23788
  access: "create"
23662
23789
  },
23663
- "pipelineOrchestrator.setAgentDetectWeight": {
23790
+ "pipelineOrchestrator.setAgentInferenceDevices": {
23664
23791
  capName: "pipeline-orchestrator",
23665
23792
  capScope: "system",
23666
23793
  addonId: null,
@@ -23702,6 +23829,12 @@ Object.freeze({
23702
23829
  addonId: null,
23703
23830
  access: "create"
23704
23831
  },
23832
+ "pipelineOrchestrator.setPipelineDevicePin": {
23833
+ capName: "pipeline-orchestrator",
23834
+ capScope: "system",
23835
+ addonId: null,
23836
+ access: "create"
23837
+ },
23705
23838
  "pipelineOrchestrator.unassignAudio": {
23706
23839
  capName: "pipeline-orchestrator",
23707
23840
  capScope: "system",
@@ -25254,32 +25387,6 @@ Object.freeze({
25254
25387
  "network-access": "ingress",
25255
25388
  "smtp-provider": "email"
25256
25389
  });
25257
- var frameworkSwapPackageSchema = object({
25258
- name: string(),
25259
- stagedPath: string(),
25260
- backupPath: string(),
25261
- toVersion: string(),
25262
- fromVersion: string().nullable()
25263
- });
25264
- object({
25265
- jobId: string(),
25266
- taskId: string(),
25267
- packages: array(frameworkSwapPackageSchema),
25268
- requestedAtMs: number(),
25269
- schemaVersion: literal(1)
25270
- });
25271
- object({
25272
- jobId: string(),
25273
- taskId: string(),
25274
- backups: array(object({
25275
- name: string(),
25276
- backupPath: string(),
25277
- livePath: string()
25278
- })),
25279
- appliedAtMs: number(),
25280
- bootAttempts: number(),
25281
- schemaVersion: literal(1)
25282
- });
25283
25390
  //#endregion
25284
25391
  //#region src/adapters/adapter.ts
25285
25392
  function toBase64(bytes) {
package/dist/addon.mjs CHANGED
@@ -7074,6 +7074,17 @@ var ModelCatalogEntrySchema = object({
7074
7074
  "imagenet",
7075
7075
  "none"
7076
7076
  ]).optional(),
7077
+ /**
7078
+ * The model already applies softmax IN-GRAPH — its raw output is a
7079
+ * probability distribution, not logits. When set, the `softmax`
7080
+ * postprocessor must NOT re-apply softmax: re-softmaxing an already-normalised
7081
+ * probability vector collapses it toward uniform (top-1 score craters far
7082
+ * below its true value, making every confidence gate meaningless). Absent ⇒
7083
+ * the output is raw logits and the postprocessor applies softmax (the normal
7084
+ * case). Set on the Google AIY Birds `bird-classifier` (softmax baked into the
7085
+ * TF graph). Threaded to the Python pool via `PoolModelConfig.outputProbabilities`.
7086
+ */
7087
+ outputProbabilities: boolean().optional(),
7077
7088
  preprocessMode: _enum(["letterbox", "resize"]).optional(),
7078
7089
  /**
7079
7090
  * Per-MODEL postprocessor override. Absent ⇒ the step's own
@@ -11117,10 +11128,7 @@ var ConfigUISchemaNullableBridge = custom();
11117
11128
  var InferenceCapabilitiesBridge = custom();
11118
11129
  var ModelAvailabilityListBridge = custom();
11119
11130
  var PipelineRunResultBridge = custom();
11120
- method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(_void(), PipelineEngineChoiceSchema, {
11121
- kind: "mutation",
11122
- auth: "admin"
11123
- }), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
11131
+ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngineChoiceSchema), method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)), method(object({ nodeId: string() }), EngineProvisioningSchema), method(_void(), record(string(), object({
11124
11132
  modelId: string(),
11125
11133
  settings: record(string(), unknown()).readonly()
11126
11134
  }))), method(object({ steps: record(string(), object({
@@ -11180,13 +11188,33 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
11180
11188
  * (inputClasses ≠ null) are skipped and served per-track via
11181
11189
  * pipelineRunner.runDetailSubtree (two-plane design).
11182
11190
  */
11183
- plane: _enum(["full", "frame"]).optional()
11191
+ plane: _enum(["full", "frame"]).optional(),
11192
+ /**
11193
+ * Inference-device selector (Phase 2 multi-device). Format
11194
+ * `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
11195
+ * Omitted ⇒ the runner's default device (current single-engine
11196
+ * behaviour). Selects WHICH device pool of the node runs the call.
11197
+ */
11198
+ deviceKey: string().optional()
11184
11199
  }), PipelineRunResultBridge, { kind: "mutation" }), method(object({
11185
11200
  engine: PipelineEngineChoiceSchema.optional(),
11186
11201
  steps: array(PipelineStepInputSchema).min(1),
11187
11202
  frames: array(FrameInputSchema).min(1).max(255),
11188
11203
  deviceId: number().optional(),
11189
- sessionId: string().optional()
11204
+ sessionId: string().optional(),
11205
+ /**
11206
+ * Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
11207
+ * the batch to the Python pool's bench preprocess cache
11208
+ * (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
11209
+ * preprocessed ONCE and every later inference is a pure-inference cache
11210
+ * hit — the sustained-throughput run measures inference, not
11211
+ * decode+preprocess+infer. Omitted/0 for live frames (all different →
11212
+ * full preprocess every call, correct). Fresh per sustained run;
11213
+ * released via `uncacheFrame`.
11214
+ */
11215
+ frameId: number().int().nonnegative().optional(),
11216
+ /** Inference-device selector (Phase 2 multi-device); see runPipeline. */
11217
+ deviceKey: string().optional()
11190
11218
  }), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }), method(object({
11191
11219
  data: _instanceof(Uint8Array),
11192
11220
  width: number().int().positive(),
@@ -11218,8 +11246,18 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
11218
11246
  * - `runtime` — main camera-serving engine (no idle TTL).
11219
11247
  * - `warm-override` — benchmark/test override held in the warm
11220
11248
  * cache; auto-disposed after the idle TTL.
11249
+ * - `device-pool` — a concurrent per-device pool (Phase 2
11250
+ * multi-device, keyed by `deviceKey`) resolved
11251
+ * via `resolveDeviceFactory`. Runs alongside the
11252
+ * `runtime` engine on a DIFFERENT accelerator
11253
+ * (NPU / iGPU / Coral) — this is how the
11254
+ * Engines tab shows all pools running at once.
11221
11255
  */
11222
- kind: _enum(["runtime", "warm-override"]),
11256
+ kind: _enum([
11257
+ "runtime",
11258
+ "warm-override",
11259
+ "device-pool"
11260
+ ]),
11223
11261
  /** Native pid of the underlying Python pool (null when no pool). */
11224
11262
  poolPid: number().nullable(),
11225
11263
  /** ms since this factory was last used (null when not warm-tracked). */
@@ -11321,7 +11359,21 @@ var NativeCropResultSchema = object({
11321
11359
  /** Packed rgb (24-bit) pixels of the crop. */
11322
11360
  bytes: _instanceof(Uint8Array),
11323
11361
  width: number().int().positive(),
11324
- height: number().int().positive()
11362
+ height: number().int().positive(),
11363
+ /**
11364
+ * Which source served this crop, so a quality-sensitive consumer (the native
11365
+ * `keyFrame`) can reject a degraded fallback:
11366
+ * - `native` — cut from the decode worker's retained NATIVE surface (the
11367
+ * quality path).
11368
+ * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
11369
+ * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
11370
+ * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
11371
+ *
11372
+ * OPTIONAL: a pre-tier runner (version skew) omits it — an ABSENT `tier` MUST be
11373
+ * treated as `native` (accepted) by every consumer so mixed-version clusters
11374
+ * keep the pre-tier behaviour. An ROI miss returns `null` (no tier at all).
11375
+ */
11376
+ tier: _enum(["native", "ram-fullframe"]).optional()
11325
11377
  });
11326
11378
  /** Parent detection context passed to `runDetailSubtree` — the crop's
11327
11379
  * originating detection, in FRAME-space coordinates. Reuses
@@ -11561,7 +11613,14 @@ var RunnerCameraConfigSchema = object({
11561
11613
  * camera's detect node differs from its source-owner (P2d, gated by the
11562
11614
  * `remoteSourcingNodes` rollout setting).
11563
11615
  */
11564
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
11616
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
11617
+ /**
11618
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
11619
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
11620
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
11621
+ * this only selects WHICH device pool of that node runs the session.
11622
+ */
11623
+ deviceKey: string().optional()
11565
11624
  });
11566
11625
  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;
11567
11626
  /**
@@ -11582,6 +11641,19 @@ var RunnerLocalLoadSchema = object({
11582
11641
  avgInferenceTimeMs: number(),
11583
11642
  /** Total queue depth across motion + detection queues. */
11584
11643
  queueDepthTotal: number(),
11644
+ /**
11645
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
11646
+ * this runner currently has attached cameras on, so the orchestrator's second
11647
+ * `balance()` pass (over a node's devices) weights on real per-pool session
11648
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
11649
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
11650
+ */
11651
+ devices: array(object({
11652
+ deviceKey: string(),
11653
+ backend: string(),
11654
+ attachedCameras: number(),
11655
+ queueDepthTotal: number()
11656
+ })).default([]),
11585
11657
  /** Hardware capability flags reported by this node. */
11586
11658
  hardware: object({
11587
11659
  hasGpu: boolean(),
@@ -13057,6 +13129,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
13057
13129
  kind: "mutation",
13058
13130
  auth: "admin"
13059
13131
  });
13132
+ 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;
13133
+ new Set(Object.values(DeviceType));
13060
13134
  /**
13061
13135
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
13062
13136
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -14630,7 +14704,8 @@ var LinkedDeviceSchema = object({
14630
14704
  deviceId: number(),
14631
14705
  name: string(),
14632
14706
  location: string().nullable(),
14633
- features: array(string())
14707
+ features: array(string()),
14708
+ producesTrackedEvents: boolean().optional()
14634
14709
  });
14635
14710
  var SavedDeviceRowSchema = object({
14636
14711
  /** Numeric id reserved at allocateDeviceId time. */
@@ -16305,6 +16380,7 @@ var TrackSchema = object({
16305
16380
  deviceId: number(),
16306
16381
  className: string(),
16307
16382
  label: string().optional(),
16383
+ producingDeviceName: string().optional(),
16308
16384
  /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16309
16385
  source: TrackSourceSchema.optional(),
16310
16386
  firstSeen: number(),
@@ -16442,7 +16518,8 @@ var MediaFileKindEnum = _enum([
16442
16518
  "fullFrameBoxed",
16443
16519
  "faceCrop",
16444
16520
  "plateCrop",
16445
- "keyFrame"
16521
+ "keyFrame",
16522
+ "keyFrameSmall"
16446
16523
  ]);
16447
16524
  var MediaFileSchema = object({
16448
16525
  key: string(),
@@ -16771,13 +16848,11 @@ var PipelineTemplateSchema = object({
16771
16848
  createdAt: string(),
16772
16849
  updatedAt: string()
16773
16850
  });
16774
- var AgentAddonConfigSchema = object({
16775
- enabled: boolean(),
16851
+ var DeviceStepConfigSchema = object({
16776
16852
  modelId: string().optional(),
16777
- settings: record(string(), unknown()).readonly()
16853
+ settings: record(string(), unknown()).optional()
16778
16854
  });
16779
16855
  var AgentPipelineSettingsSchema = object({
16780
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
16781
16856
  maxCameras: number().int().nonnegative().nullable().default(null),
16782
16857
  /** Per-node detection weight (relative share for the quota balancer). */
16783
16858
  detectWeight: number().positive().optional(),
@@ -16801,7 +16876,22 @@ var AgentPipelineSettingsSchema = object({
16801
16876
  * it already uses to reach the hub). Set this only when the auto-detected
16802
16877
  * address is wrong (multi-homed host, NAT, custom interface).
16803
16878
  */
16804
- reachableHost: string().optional()
16879
+ reachableHost: string().optional(),
16880
+ /**
16881
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
16882
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
16883
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
16884
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
16885
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
16886
+ * the default model/settings for every camera landing on that accelerator;
16887
+ * a stepId absent ⇒ the step uses that device's format default.
16888
+ */
16889
+ inferenceDevices: record(string(), object({
16890
+ enabled: boolean(),
16891
+ weight: number().positive().optional(),
16892
+ maxSessions: number().int().positive().optional(),
16893
+ steps: record(string(), DeviceStepConfigSchema).optional()
16894
+ })).optional()
16805
16895
  });
16806
16896
  var CameraPipelineForAgentSchema = object({
16807
16897
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16811,14 +16901,13 @@ var CameraPipelineForAgentSchema = object({
16811
16901
  }).nullable()
16812
16902
  });
16813
16903
  var CameraStepOverridePatchSchema = object({
16814
- enabled: boolean().optional(),
16815
16904
  modelId: string().optional(),
16816
16905
  settings: record(string(), unknown()).readonly().optional()
16817
16906
  });
16818
16907
  var CameraPipelineSettingsSchema = object({
16819
16908
  pinnedAgentNodeId: string().optional(),
16820
16909
  stepToggles: record(string(), boolean()).optional(),
16821
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
16910
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
16822
16911
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
16823
16912
  });
16824
16913
  /**
@@ -17032,6 +17121,44 @@ var CameraStatusSchema = object({
17032
17121
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
17033
17122
  fetchedAt: number()
17034
17123
  });
17124
+ var NodeInferenceDeviceSchema = object({
17125
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
17126
+ key: string(),
17127
+ backend: string(),
17128
+ device: string(),
17129
+ format: _enum(MODEL_FORMATS),
17130
+ /** Whether the node's live probe reports the device as usable right now. */
17131
+ available: boolean(),
17132
+ /**
17133
+ * Whether this device participates in dispatch. AUTO default (spec C2):
17134
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
17135
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
17136
+ * not a balanced target). An explicit stored value always wins; a stored-only
17137
+ * (unavailable) key keeps its stored value.
17138
+ */
17139
+ enabled: boolean(),
17140
+ /** Relative balancer weight for the enabled device (default 1). */
17141
+ weight: number(),
17142
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
17143
+ maxSessions: number().nullable(),
17144
+ /** Object-detection model the executor defaults to for this deviceKey. */
17145
+ defaultModelId: string(),
17146
+ /**
17147
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
17148
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
17149
+ * Absent/empty ⇒ no base (every step uses its device format default). The
17150
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
17151
+ * available per format; this is the stored selection that becomes the
17152
+ * default for EVERY camera landing on this accelerator.
17153
+ */
17154
+ steps: record(string(), DeviceStepConfigSchema).optional()
17155
+ });
17156
+ var NodeInferenceDevicesSchema = object({
17157
+ nodeId: string(),
17158
+ /** False when the node's platform-probe was unreachable (no live device set). */
17159
+ reachable: boolean(),
17160
+ devices: array(NodeInferenceDeviceSchema).readonly()
17161
+ });
17035
17162
  method(object({
17036
17163
  deviceId: number(),
17037
17164
  agentNodeId: string()
@@ -17041,7 +17168,13 @@ method(object({
17041
17168
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
17042
17169
  kind: "mutation",
17043
17170
  auth: "admin"
17044
- }), method(_void(), object({ migrated: number() }), {
17171
+ }), method(object({
17172
+ deviceId: number(),
17173
+ deviceKey: string()
17174
+ }), object({ success: literal(true) }), {
17175
+ kind: "mutation",
17176
+ auth: "admin"
17177
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
17045
17178
  kind: "mutation",
17046
17179
  auth: "admin"
17047
17180
  }), 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({
@@ -17075,13 +17208,7 @@ method(object({
17075
17208
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
17076
17209
  nodeId: string(),
17077
17210
  settings: AgentPipelineSettingsSchema
17078
- })).readonly()), method(object({
17079
- agentNodeId: string(),
17080
- defaults: record(string(), AgentAddonConfigSchema)
17081
- }), object({ success: literal(true) }), {
17082
- kind: "mutation",
17083
- auth: "admin"
17084
- }), method(object({ agentNodeId: string() }), object({
17211
+ })).readonly()), method(object({ agentNodeId: string() }), object({
17085
17212
  success: boolean(),
17086
17213
  removed: boolean()
17087
17214
  }), {
@@ -17113,7 +17240,18 @@ method(object({
17113
17240
  }), object({ success: literal(true) }), {
17114
17241
  kind: "mutation",
17115
17242
  auth: "admin"
17116
- }), method(object({ agentNodeId: string() }), object({
17243
+ }), method(object({
17244
+ agentNodeId: string(),
17245
+ inferenceDevices: record(string(), object({
17246
+ enabled: boolean(),
17247
+ weight: number().positive().optional(),
17248
+ maxSessions: number().int().positive().optional(),
17249
+ steps: record(string(), DeviceStepConfigSchema).optional()
17250
+ }))
17251
+ }), object({ success: literal(true) }), {
17252
+ kind: "mutation",
17253
+ auth: "admin"
17254
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
17117
17255
  success: literal(true),
17118
17256
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
17119
17257
  effectiveModelId: string().nullable(),
@@ -17129,9 +17267,10 @@ method(object({
17129
17267
  }), object({ success: literal(true) }), {
17130
17268
  kind: "mutation",
17131
17269
  auth: "admin"
17132
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
17270
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
17133
17271
  deviceId: number(),
17134
17272
  agentNodeId: string(),
17273
+ deviceKey: string(),
17135
17274
  addonId: string(),
17136
17275
  patch: CameraStepOverridePatchSchema.nullable()
17137
17276
  }), object({ success: literal(true) }), {
@@ -17168,14 +17307,13 @@ method(object({
17168
17307
  });
17169
17308
  /**
17170
17309
  * server-management — per-NODE singleton capability for a node's ROOT
17171
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
17172
- * agents).
17310
+ * package lifecycle (runtime-updatable node packages).
17173
17311
  *
17174
- * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
17175
- * on agents) carries the whole software stack in its npm dep tree, so ONE
17176
- * version describes the node. Updates install into
17177
- * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
17178
- * starter (probation boot + auto-rollback to N-1).
17312
+ * Every node role runs the SAME root package (`@camstack/server`), which
17313
+ * carries the whole software stack in its npm dep tree, so ONE version
17314
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
17315
+ * on restart via the baked starter (single-copy in-place swap — no probation,
17316
+ * no auto-rollback).
17179
17317
  *
17180
17318
  * Providers:
17181
17319
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -17283,7 +17421,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
17283
17421
  /** Explicit target version; omitted = latest from the registry. */
17284
17422
  version: string().optional() }), ServerUpdateActionResultSchema, {
17285
17423
  kind: "mutation",
17286
- auth: "admin"
17424
+ auth: "admin",
17425
+ timeoutMs: 16 * 6e4
17287
17426
  }), method(_void(), ServerUpdateActionResultSchema, {
17288
17427
  kind: "mutation",
17289
17428
  auth: "admin"
@@ -18327,22 +18466,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
18327
18466
  var RestartAddonResultSchema = unknown();
18328
18467
  var InstallPackageResultSchema = unknown();
18329
18468
  var ReloadPackagesResultSchema = unknown();
18330
- /**
18331
- * Result of `updateFrameworkPackage`. The cap method returns BEFORE the
18332
- * server restarts so the admin UI can react to the `restartingAt`
18333
- * timestamp (shows reconnect overlay). The transition from
18334
- * `fromVersion` to `toVersion` will be confirmed by a subsequent
18335
- * `system.restart-completed` event after the new process boots.
18336
- *
18337
- * Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
18338
- */
18339
- var UpdateFrameworkPackageResultSchema = object({
18340
- packageName: string(),
18341
- fromVersion: string(),
18342
- toVersion: string(),
18343
- /** Ms-epoch the server scheduled its self-restart. */
18344
- restartingAt: number()
18345
- });
18346
18469
  var BulkUpdateItemStatusSchema = _enum([
18347
18470
  "queued",
18348
18471
  "updating",
@@ -18470,13 +18593,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
18470
18593
  }), object({ success: literal(true) }), {
18471
18594
  kind: "mutation",
18472
18595
  auth: "admin"
18473
- }), method(object({
18474
- packageName: string().min(1),
18475
- version: string().optional(),
18476
- deferRestart: boolean().optional()
18477
- }), UpdateFrameworkPackageResultSchema, {
18478
- kind: "mutation",
18479
- auth: "admin"
18480
18596
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
18481
18597
  kind: "mutation",
18482
18598
  auth: "admin"
@@ -19342,10 +19458,10 @@ var TopologyCategorySchema = object({
19342
19458
  addons: array(TopologyCategoryAddonSchema).readonly()
19343
19459
  });
19344
19460
  /**
19345
- * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
19346
- * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
19347
- * version visibility for the Server management surface. Nullable: offline
19348
- * rows and pre-phase-2 nodes report none.
19461
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
19462
+ * root package for every node role) as reported by its `registerNode`
19463
+ * manifest — version visibility for the Server management surface. Nullable:
19464
+ * offline rows and nodes that never reported one.
19349
19465
  */
19350
19466
  var TopologyRootPackageSchema = object({
19351
19467
  name: string(),
@@ -19733,17 +19849,28 @@ var PlatformScoreSchema = object({
19733
19849
  format: _enum([
19734
19850
  "onnx",
19735
19851
  "coreml",
19736
- "openvino"
19852
+ "openvino",
19853
+ "tflite"
19737
19854
  ]),
19738
19855
  score: number(),
19739
19856
  reason: string(),
19740
19857
  available: boolean()
19741
19858
  });
19859
+ var InferenceDeviceDescriptorSchema = object({
19860
+ key: string(),
19861
+ backend: string(),
19862
+ device: string(),
19863
+ format: ModelFormatSchema,
19864
+ runtime: literal("python"),
19865
+ score: number(),
19866
+ available: boolean()
19867
+ });
19742
19868
  var PlatformCapabilitiesSchema = object({
19743
19869
  hardware: HardwareInfoSchema,
19744
19870
  scores: array(PlatformScoreSchema).readonly(),
19745
19871
  bestScore: PlatformScoreSchema,
19746
- pythonPath: string().nullable()
19872
+ pythonPath: string().nullable(),
19873
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
19747
19874
  });
19748
19875
  var ModelRequirementSchema = object({
19749
19876
  modelId: string(),
@@ -20622,12 +20749,6 @@ Object.freeze({
20622
20749
  addonId: null,
20623
20750
  access: "delete"
20624
20751
  },
20625
- "addons.updateFrameworkPackage": {
20626
- capName: "addons",
20627
- capScope: "system",
20628
- addonId: null,
20629
- access: "create"
20630
- },
20631
20752
  "addons.updatePackage": {
20632
20753
  capName: "addons",
20633
20754
  capScope: "system",
@@ -23400,12 +23521,6 @@ Object.freeze({
23400
23521
  addonId: null,
23401
23522
  access: "view"
23402
23523
  },
23403
- "pipelineExecutor.reprobeEngine": {
23404
- capName: "pipeline-executor",
23405
- capScope: "system",
23406
- addonId: null,
23407
- access: "create"
23408
- },
23409
23524
  "pipelineExecutor.runAudioTest": {
23410
23525
  capName: "pipeline-executor",
23411
23526
  capScope: "system",
@@ -23556,6 +23671,12 @@ Object.freeze({
23556
23671
  addonId: null,
23557
23672
  access: "view"
23558
23673
  },
23674
+ "pipelineOrchestrator.getNodeInferenceDevices": {
23675
+ capName: "pipeline-orchestrator",
23676
+ capScope: "system",
23677
+ addonId: null,
23678
+ access: "view"
23679
+ },
23559
23680
  "pipelineOrchestrator.getPipelineAssignment": {
23560
23681
  capName: "pipeline-orchestrator",
23561
23682
  capScope: "system",
@@ -23568,6 +23689,12 @@ Object.freeze({
23568
23689
  addonId: null,
23569
23690
  access: "view"
23570
23691
  },
23692
+ "pipelineOrchestrator.getPipelineDevicePin": {
23693
+ capName: "pipeline-orchestrator",
23694
+ capScope: "system",
23695
+ addonId: null,
23696
+ access: "view"
23697
+ },
23571
23698
  "pipelineOrchestrator.listAgentSettings": {
23572
23699
  capName: "pipeline-orchestrator",
23573
23700
  capScope: "system",
@@ -23610,19 +23737,19 @@ Object.freeze({
23610
23737
  addonId: null,
23611
23738
  access: "create"
23612
23739
  },
23613
- "pipelineOrchestrator.setAgentAddonDefaults": {
23740
+ "pipelineOrchestrator.setAgentCapabilities": {
23614
23741
  capName: "pipeline-orchestrator",
23615
23742
  capScope: "system",
23616
23743
  addonId: null,
23617
23744
  access: "create"
23618
23745
  },
23619
- "pipelineOrchestrator.setAgentCapabilities": {
23746
+ "pipelineOrchestrator.setAgentDetectWeight": {
23620
23747
  capName: "pipeline-orchestrator",
23621
23748
  capScope: "system",
23622
23749
  addonId: null,
23623
23750
  access: "create"
23624
23751
  },
23625
- "pipelineOrchestrator.setAgentDetectWeight": {
23752
+ "pipelineOrchestrator.setAgentInferenceDevices": {
23626
23753
  capName: "pipeline-orchestrator",
23627
23754
  capScope: "system",
23628
23755
  addonId: null,
@@ -23664,6 +23791,12 @@ Object.freeze({
23664
23791
  addonId: null,
23665
23792
  access: "create"
23666
23793
  },
23794
+ "pipelineOrchestrator.setPipelineDevicePin": {
23795
+ capName: "pipeline-orchestrator",
23796
+ capScope: "system",
23797
+ addonId: null,
23798
+ access: "create"
23799
+ },
23667
23800
  "pipelineOrchestrator.unassignAudio": {
23668
23801
  capName: "pipeline-orchestrator",
23669
23802
  capScope: "system",
@@ -25216,32 +25349,6 @@ Object.freeze({
25216
25349
  "network-access": "ingress",
25217
25350
  "smtp-provider": "email"
25218
25351
  });
25219
- var frameworkSwapPackageSchema = object({
25220
- name: string(),
25221
- stagedPath: string(),
25222
- backupPath: string(),
25223
- toVersion: string(),
25224
- fromVersion: string().nullable()
25225
- });
25226
- object({
25227
- jobId: string(),
25228
- taskId: string(),
25229
- packages: array(frameworkSwapPackageSchema),
25230
- requestedAtMs: number(),
25231
- schemaVersion: literal(1)
25232
- });
25233
- object({
25234
- jobId: string(),
25235
- taskId: string(),
25236
- backups: array(object({
25237
- name: string(),
25238
- backupPath: string(),
25239
- livePath: string()
25240
- })),
25241
- appliedAtMs: number(),
25242
- bootAttempts: number(),
25243
- schemaVersion: literal(1)
25244
- });
25245
25352
  //#endregion
25246
25353
  //#region src/adapters/adapter.ts
25247
25354
  function toBase64(bytes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "AI addon for CamStack — the `llm` collection provider (cloud, LAN, and camstack-managed local llama.cpp profiles) plus the per-node `llm-runtime` managed executor.",
5
5
  "keywords": [
6
6
  "camstack",