@camstack/addon-ai 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +193 -103
  2. package/dist/addon.mjs +193 -103
  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). */
@@ -11599,7 +11637,14 @@ var RunnerCameraConfigSchema = object({
11599
11637
  * camera's detect node differs from its source-owner (P2d, gated by the
11600
11638
  * `remoteSourcingNodes` rollout setting).
11601
11639
  */
11602
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
11640
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
11641
+ /**
11642
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
11643
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
11644
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
11645
+ * this only selects WHICH device pool of that node runs the session.
11646
+ */
11647
+ deviceKey: string().optional()
11603
11648
  });
11604
11649
  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
11650
  /**
@@ -11620,6 +11665,19 @@ var RunnerLocalLoadSchema = object({
11620
11665
  avgInferenceTimeMs: number(),
11621
11666
  /** Total queue depth across motion + detection queues. */
11622
11667
  queueDepthTotal: number(),
11668
+ /**
11669
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
11670
+ * this runner currently has attached cameras on, so the orchestrator's second
11671
+ * `balance()` pass (over a node's devices) weights on real per-pool session
11672
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
11673
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
11674
+ */
11675
+ devices: array(object({
11676
+ deviceKey: string(),
11677
+ backend: string(),
11678
+ attachedCameras: number(),
11679
+ queueDepthTotal: number()
11680
+ })).default([]),
11623
11681
  /** Hardware capability flags reported by this node. */
11624
11682
  hardware: object({
11625
11683
  hasGpu: boolean(),
@@ -13095,6 +13153,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
13095
13153
  kind: "mutation",
13096
13154
  auth: "admin"
13097
13155
  });
13156
+ 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;
13157
+ new Set(Object.values(DeviceType));
13098
13158
  /**
13099
13159
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
13100
13160
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -16809,13 +16869,11 @@ var PipelineTemplateSchema = object({
16809
16869
  createdAt: string(),
16810
16870
  updatedAt: string()
16811
16871
  });
16812
- var AgentAddonConfigSchema = object({
16813
- enabled: boolean(),
16872
+ var DeviceStepConfigSchema = object({
16814
16873
  modelId: string().optional(),
16815
- settings: record(string(), unknown()).readonly()
16874
+ settings: record(string(), unknown()).optional()
16816
16875
  });
16817
16876
  var AgentPipelineSettingsSchema = object({
16818
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
16819
16877
  maxCameras: number().int().nonnegative().nullable().default(null),
16820
16878
  /** Per-node detection weight (relative share for the quota balancer). */
16821
16879
  detectWeight: number().positive().optional(),
@@ -16839,7 +16897,22 @@ var AgentPipelineSettingsSchema = object({
16839
16897
  * it already uses to reach the hub). Set this only when the auto-detected
16840
16898
  * address is wrong (multi-homed host, NAT, custom interface).
16841
16899
  */
16842
- reachableHost: string().optional()
16900
+ reachableHost: string().optional(),
16901
+ /**
16902
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
16903
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
16904
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
16905
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
16906
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
16907
+ * the default model/settings for every camera landing on that accelerator;
16908
+ * a stepId absent ⇒ the step uses that device's format default.
16909
+ */
16910
+ inferenceDevices: record(string(), object({
16911
+ enabled: boolean(),
16912
+ weight: number().positive().optional(),
16913
+ maxSessions: number().int().positive().optional(),
16914
+ steps: record(string(), DeviceStepConfigSchema).optional()
16915
+ })).optional()
16843
16916
  });
16844
16917
  var CameraPipelineForAgentSchema = object({
16845
16918
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16849,14 +16922,13 @@ var CameraPipelineForAgentSchema = object({
16849
16922
  }).nullable()
16850
16923
  });
16851
16924
  var CameraStepOverridePatchSchema = object({
16852
- enabled: boolean().optional(),
16853
16925
  modelId: string().optional(),
16854
16926
  settings: record(string(), unknown()).readonly().optional()
16855
16927
  });
16856
16928
  var CameraPipelineSettingsSchema = object({
16857
16929
  pinnedAgentNodeId: string().optional(),
16858
16930
  stepToggles: record(string(), boolean()).optional(),
16859
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
16931
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
16860
16932
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
16861
16933
  });
16862
16934
  /**
@@ -17070,6 +17142,44 @@ var CameraStatusSchema = object({
17070
17142
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
17071
17143
  fetchedAt: number()
17072
17144
  });
17145
+ var NodeInferenceDeviceSchema = object({
17146
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
17147
+ key: string(),
17148
+ backend: string(),
17149
+ device: string(),
17150
+ format: _enum(MODEL_FORMATS),
17151
+ /** Whether the node's live probe reports the device as usable right now. */
17152
+ available: boolean(),
17153
+ /**
17154
+ * Whether this device participates in dispatch. AUTO default (spec C2):
17155
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
17156
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
17157
+ * not a balanced target). An explicit stored value always wins; a stored-only
17158
+ * (unavailable) key keeps its stored value.
17159
+ */
17160
+ enabled: boolean(),
17161
+ /** Relative balancer weight for the enabled device (default 1). */
17162
+ weight: number(),
17163
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
17164
+ maxSessions: number().nullable(),
17165
+ /** Object-detection model the executor defaults to for this deviceKey. */
17166
+ defaultModelId: string(),
17167
+ /**
17168
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
17169
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
17170
+ * Absent/empty ⇒ no base (every step uses its device format default). The
17171
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
17172
+ * available per format; this is the stored selection that becomes the
17173
+ * default for EVERY camera landing on this accelerator.
17174
+ */
17175
+ steps: record(string(), DeviceStepConfigSchema).optional()
17176
+ });
17177
+ var NodeInferenceDevicesSchema = object({
17178
+ nodeId: string(),
17179
+ /** False when the node's platform-probe was unreachable (no live device set). */
17180
+ reachable: boolean(),
17181
+ devices: array(NodeInferenceDeviceSchema).readonly()
17182
+ });
17073
17183
  method(object({
17074
17184
  deviceId: number(),
17075
17185
  agentNodeId: string()
@@ -17079,7 +17189,13 @@ method(object({
17079
17189
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
17080
17190
  kind: "mutation",
17081
17191
  auth: "admin"
17082
- }), method(_void(), object({ migrated: number() }), {
17192
+ }), method(object({
17193
+ deviceId: number(),
17194
+ deviceKey: string()
17195
+ }), object({ success: literal(true) }), {
17196
+ kind: "mutation",
17197
+ auth: "admin"
17198
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
17083
17199
  kind: "mutation",
17084
17200
  auth: "admin"
17085
17201
  }), 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 +17229,7 @@ method(object({
17113
17229
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
17114
17230
  nodeId: string(),
17115
17231
  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({
17232
+ })).readonly()), method(object({ agentNodeId: string() }), object({
17123
17233
  success: boolean(),
17124
17234
  removed: boolean()
17125
17235
  }), {
@@ -17151,7 +17261,18 @@ method(object({
17151
17261
  }), object({ success: literal(true) }), {
17152
17262
  kind: "mutation",
17153
17263
  auth: "admin"
17154
- }), method(object({ agentNodeId: string() }), object({
17264
+ }), method(object({
17265
+ agentNodeId: string(),
17266
+ inferenceDevices: record(string(), object({
17267
+ enabled: boolean(),
17268
+ weight: number().positive().optional(),
17269
+ maxSessions: number().int().positive().optional(),
17270
+ steps: record(string(), DeviceStepConfigSchema).optional()
17271
+ }))
17272
+ }), object({ success: literal(true) }), {
17273
+ kind: "mutation",
17274
+ auth: "admin"
17275
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
17155
17276
  success: literal(true),
17156
17277
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
17157
17278
  effectiveModelId: string().nullable(),
@@ -17167,9 +17288,10 @@ method(object({
17167
17288
  }), object({ success: literal(true) }), {
17168
17289
  kind: "mutation",
17169
17290
  auth: "admin"
17170
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
17291
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
17171
17292
  deviceId: number(),
17172
17293
  agentNodeId: string(),
17294
+ deviceKey: string(),
17173
17295
  addonId: string(),
17174
17296
  patch: CameraStepOverridePatchSchema.nullable()
17175
17297
  }), object({ success: literal(true) }), {
@@ -17206,14 +17328,13 @@ method(object({
17206
17328
  });
17207
17329
  /**
17208
17330
  * server-management — per-NODE singleton capability for a node's ROOT
17209
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
17210
- * agents).
17331
+ * package lifecycle (runtime-updatable node packages).
17211
17332
  *
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).
17333
+ * Every node role runs the SAME root package (`@camstack/server`), which
17334
+ * carries the whole software stack in its npm dep tree, so ONE version
17335
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
17336
+ * on restart via the baked starter (single-copy in-place swap — no probation,
17337
+ * no auto-rollback).
17217
17338
  *
17218
17339
  * Providers:
17219
17340
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -17321,7 +17442,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
17321
17442
  /** Explicit target version; omitted = latest from the registry. */
17322
17443
  version: string().optional() }), ServerUpdateActionResultSchema, {
17323
17444
  kind: "mutation",
17324
- auth: "admin"
17445
+ auth: "admin",
17446
+ timeoutMs: 16 * 6e4
17325
17447
  }), method(_void(), ServerUpdateActionResultSchema, {
17326
17448
  kind: "mutation",
17327
17449
  auth: "admin"
@@ -18365,22 +18487,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
18365
18487
  var RestartAddonResultSchema = unknown();
18366
18488
  var InstallPackageResultSchema = unknown();
18367
18489
  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
18490
  var BulkUpdateItemStatusSchema = _enum([
18385
18491
  "queued",
18386
18492
  "updating",
@@ -18508,13 +18614,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
18508
18614
  }), object({ success: literal(true) }), {
18509
18615
  kind: "mutation",
18510
18616
  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
18617
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
18519
18618
  kind: "mutation",
18520
18619
  auth: "admin"
@@ -19380,10 +19479,10 @@ var TopologyCategorySchema = object({
19380
19479
  addons: array(TopologyCategoryAddonSchema).readonly()
19381
19480
  });
19382
19481
  /**
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.
19482
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
19483
+ * root package for every node role) as reported by its `registerNode`
19484
+ * manifest — version visibility for the Server management surface. Nullable:
19485
+ * offline rows and nodes that never reported one.
19387
19486
  */
19388
19487
  var TopologyRootPackageSchema = object({
19389
19488
  name: string(),
@@ -19771,17 +19870,28 @@ var PlatformScoreSchema = object({
19771
19870
  format: _enum([
19772
19871
  "onnx",
19773
19872
  "coreml",
19774
- "openvino"
19873
+ "openvino",
19874
+ "tflite"
19775
19875
  ]),
19776
19876
  score: number(),
19777
19877
  reason: string(),
19778
19878
  available: boolean()
19779
19879
  });
19880
+ var InferenceDeviceDescriptorSchema = object({
19881
+ key: string(),
19882
+ backend: string(),
19883
+ device: string(),
19884
+ format: ModelFormatSchema,
19885
+ runtime: literal("python"),
19886
+ score: number(),
19887
+ available: boolean()
19888
+ });
19780
19889
  var PlatformCapabilitiesSchema = object({
19781
19890
  hardware: HardwareInfoSchema,
19782
19891
  scores: array(PlatformScoreSchema).readonly(),
19783
19892
  bestScore: PlatformScoreSchema,
19784
- pythonPath: string().nullable()
19893
+ pythonPath: string().nullable(),
19894
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
19785
19895
  });
19786
19896
  var ModelRequirementSchema = object({
19787
19897
  modelId: string(),
@@ -20660,12 +20770,6 @@ Object.freeze({
20660
20770
  addonId: null,
20661
20771
  access: "delete"
20662
20772
  },
20663
- "addons.updateFrameworkPackage": {
20664
- capName: "addons",
20665
- capScope: "system",
20666
- addonId: null,
20667
- access: "create"
20668
- },
20669
20773
  "addons.updatePackage": {
20670
20774
  capName: "addons",
20671
20775
  capScope: "system",
@@ -23438,12 +23542,6 @@ Object.freeze({
23438
23542
  addonId: null,
23439
23543
  access: "view"
23440
23544
  },
23441
- "pipelineExecutor.reprobeEngine": {
23442
- capName: "pipeline-executor",
23443
- capScope: "system",
23444
- addonId: null,
23445
- access: "create"
23446
- },
23447
23545
  "pipelineExecutor.runAudioTest": {
23448
23546
  capName: "pipeline-executor",
23449
23547
  capScope: "system",
@@ -23594,6 +23692,12 @@ Object.freeze({
23594
23692
  addonId: null,
23595
23693
  access: "view"
23596
23694
  },
23695
+ "pipelineOrchestrator.getNodeInferenceDevices": {
23696
+ capName: "pipeline-orchestrator",
23697
+ capScope: "system",
23698
+ addonId: null,
23699
+ access: "view"
23700
+ },
23597
23701
  "pipelineOrchestrator.getPipelineAssignment": {
23598
23702
  capName: "pipeline-orchestrator",
23599
23703
  capScope: "system",
@@ -23606,6 +23710,12 @@ Object.freeze({
23606
23710
  addonId: null,
23607
23711
  access: "view"
23608
23712
  },
23713
+ "pipelineOrchestrator.getPipelineDevicePin": {
23714
+ capName: "pipeline-orchestrator",
23715
+ capScope: "system",
23716
+ addonId: null,
23717
+ access: "view"
23718
+ },
23609
23719
  "pipelineOrchestrator.listAgentSettings": {
23610
23720
  capName: "pipeline-orchestrator",
23611
23721
  capScope: "system",
@@ -23648,19 +23758,19 @@ Object.freeze({
23648
23758
  addonId: null,
23649
23759
  access: "create"
23650
23760
  },
23651
- "pipelineOrchestrator.setAgentAddonDefaults": {
23761
+ "pipelineOrchestrator.setAgentCapabilities": {
23652
23762
  capName: "pipeline-orchestrator",
23653
23763
  capScope: "system",
23654
23764
  addonId: null,
23655
23765
  access: "create"
23656
23766
  },
23657
- "pipelineOrchestrator.setAgentCapabilities": {
23767
+ "pipelineOrchestrator.setAgentDetectWeight": {
23658
23768
  capName: "pipeline-orchestrator",
23659
23769
  capScope: "system",
23660
23770
  addonId: null,
23661
23771
  access: "create"
23662
23772
  },
23663
- "pipelineOrchestrator.setAgentDetectWeight": {
23773
+ "pipelineOrchestrator.setAgentInferenceDevices": {
23664
23774
  capName: "pipeline-orchestrator",
23665
23775
  capScope: "system",
23666
23776
  addonId: null,
@@ -23702,6 +23812,12 @@ Object.freeze({
23702
23812
  addonId: null,
23703
23813
  access: "create"
23704
23814
  },
23815
+ "pipelineOrchestrator.setPipelineDevicePin": {
23816
+ capName: "pipeline-orchestrator",
23817
+ capScope: "system",
23818
+ addonId: null,
23819
+ access: "create"
23820
+ },
23705
23821
  "pipelineOrchestrator.unassignAudio": {
23706
23822
  capName: "pipeline-orchestrator",
23707
23823
  capScope: "system",
@@ -25254,32 +25370,6 @@ Object.freeze({
25254
25370
  "network-access": "ingress",
25255
25371
  "smtp-provider": "email"
25256
25372
  });
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
25373
  //#endregion
25284
25374
  //#region src/adapters/adapter.ts
25285
25375
  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). */
@@ -11561,7 +11599,14 @@ var RunnerCameraConfigSchema = object({
11561
11599
  * camera's detect node differs from its source-owner (P2d, gated by the
11562
11600
  * `remoteSourcingNodes` rollout setting).
11563
11601
  */
11564
- frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" })
11602
+ frameSource: RunnerFrameSourceSchema.default({ kind: "local-broker" }),
11603
+ /**
11604
+ * Inference-device selector for this camera's sessions (Phase 2 multi-device).
11605
+ * Format `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`);
11606
+ * omitted ⇒ the runner's default device. The engine itself stays node-local —
11607
+ * this only selects WHICH device pool of that node runs the session.
11608
+ */
11609
+ deviceKey: string().optional()
11565
11610
  });
11566
11611
  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
11612
  /**
@@ -11582,6 +11627,19 @@ var RunnerLocalLoadSchema = object({
11582
11627
  avgInferenceTimeMs: number(),
11583
11628
  /** Total queue depth across motion + detection queues. */
11584
11629
  queueDepthTotal: number(),
11630
+ /**
11631
+ * Per-inference-device live load (multi-device C4). One entry per deviceKey
11632
+ * this runner currently has attached cameras on, so the orchestrator's second
11633
+ * `balance()` pass (over a node's devices) weights on real per-pool session
11634
+ * counts. Empty on single-device / pre-multi-device runners. `queueDepthTotal`
11635
+ * per device is 0 until the pool backlog gets a public accessor (follow-up).
11636
+ */
11637
+ devices: array(object({
11638
+ deviceKey: string(),
11639
+ backend: string(),
11640
+ attachedCameras: number(),
11641
+ queueDepthTotal: number()
11642
+ })).default([]),
11585
11643
  /** Hardware capability flags reported by this node. */
11586
11644
  hardware: object({
11587
11645
  hasGpu: boolean(),
@@ -13057,6 +13115,8 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
13057
13115
  kind: "mutation",
13058
13116
  auth: "admin"
13059
13117
  });
13118
+ 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;
13119
+ new Set(Object.values(DeviceType));
13060
13120
  /**
13061
13121
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
13062
13122
  * surface that admin-ui consumes through `useAddonPagesListPages()`.
@@ -16771,13 +16831,11 @@ var PipelineTemplateSchema = object({
16771
16831
  createdAt: string(),
16772
16832
  updatedAt: string()
16773
16833
  });
16774
- var AgentAddonConfigSchema = object({
16775
- enabled: boolean(),
16834
+ var DeviceStepConfigSchema = object({
16776
16835
  modelId: string().optional(),
16777
- settings: record(string(), unknown()).readonly()
16836
+ settings: record(string(), unknown()).optional()
16778
16837
  });
16779
16838
  var AgentPipelineSettingsSchema = object({
16780
- addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
16781
16839
  maxCameras: number().int().nonnegative().nullable().default(null),
16782
16840
  /** Per-node detection weight (relative share for the quota balancer). */
16783
16841
  detectWeight: number().positive().optional(),
@@ -16801,7 +16859,22 @@ var AgentPipelineSettingsSchema = object({
16801
16859
  * it already uses to reach the hub). Set this only when the auto-detected
16802
16860
  * address is wrong (multi-homed host, NAT, custom interface).
16803
16861
  */
16804
- reachableHost: string().optional()
16862
+ reachableHost: string().optional(),
16863
+ /**
16864
+ * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
16865
+ * weight, steps}. Absent / all-disabled ⇒ the node's single default
16866
+ * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
16867
+ * detection sessions across them so they run CONCURRENTLY. `steps` is the
16868
+ * per-(node,device) BASE provisioning (`stepId → {modelId?, settings?}`) —
16869
+ * the default model/settings for every camera landing on that accelerator;
16870
+ * a stepId absent ⇒ the step uses that device's format default.
16871
+ */
16872
+ inferenceDevices: record(string(), object({
16873
+ enabled: boolean(),
16874
+ weight: number().positive().optional(),
16875
+ maxSessions: number().int().positive().optional(),
16876
+ steps: record(string(), DeviceStepConfigSchema).optional()
16877
+ })).optional()
16805
16878
  });
16806
16879
  var CameraPipelineForAgentSchema = object({
16807
16880
  steps: array(PipelineStepInputSchema).readonly(),
@@ -16811,14 +16884,13 @@ var CameraPipelineForAgentSchema = object({
16811
16884
  }).nullable()
16812
16885
  });
16813
16886
  var CameraStepOverridePatchSchema = object({
16814
- enabled: boolean().optional(),
16815
16887
  modelId: string().optional(),
16816
16888
  settings: record(string(), unknown()).readonly().optional()
16817
16889
  });
16818
16890
  var CameraPipelineSettingsSchema = object({
16819
16891
  pinnedAgentNodeId: string().optional(),
16820
16892
  stepToggles: record(string(), boolean()).optional(),
16821
- stepOverridesByAgent: record(string(), record(string(), CameraStepOverridePatchSchema)).optional(),
16893
+ stepOverridesByDevice: record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).optional(),
16822
16894
  pipelineByAgent: record(string(), CameraPipelineForAgentSchema).optional()
16823
16895
  });
16824
16896
  /**
@@ -17032,6 +17104,44 @@ var CameraStatusSchema = object({
17032
17104
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
17033
17105
  fetchedAt: number()
17034
17106
  });
17107
+ var NodeInferenceDeviceSchema = object({
17108
+ /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
17109
+ key: string(),
17110
+ backend: string(),
17111
+ device: string(),
17112
+ format: _enum(MODEL_FORMATS),
17113
+ /** Whether the node's live probe reports the device as usable right now. */
17114
+ available: boolean(),
17115
+ /**
17116
+ * Whether this device participates in dispatch. AUTO default (spec C2):
17117
+ * accelerators are opt-OUT (a discovered NPU/iGPU/Coral/ANE with no stored
17118
+ * entry is `true`); CPU is opt-IN (`false` by default — the fallback pool,
17119
+ * not a balanced target). An explicit stored value always wins; a stored-only
17120
+ * (unavailable) key keeps its stored value.
17121
+ */
17122
+ enabled: boolean(),
17123
+ /** Relative balancer weight for the enabled device (default 1). */
17124
+ weight: number(),
17125
+ /** Per-device concurrent-session cap; null = unlimited (multi-device C4). */
17126
+ maxSessions: number().nullable(),
17127
+ /** Object-detection model the executor defaults to for this deviceKey. */
17128
+ defaultModelId: string(),
17129
+ /**
17130
+ * Per-(node,device) BASE provisioning (C7.2/C7.4) — the RAW stored
17131
+ * `stepId → {modelId?, settings?}` map from `inferenceDevices[key].steps`.
17132
+ * Absent/empty ⇒ no base (every step uses its device format default). The
17133
+ * UI cross-references `pipelineExecutor.getSchema` for the models actually
17134
+ * available per format; this is the stored selection that becomes the
17135
+ * default for EVERY camera landing on this accelerator.
17136
+ */
17137
+ steps: record(string(), DeviceStepConfigSchema).optional()
17138
+ });
17139
+ var NodeInferenceDevicesSchema = object({
17140
+ nodeId: string(),
17141
+ /** False when the node's platform-probe was unreachable (no live device set). */
17142
+ reachable: boolean(),
17143
+ devices: array(NodeInferenceDeviceSchema).readonly()
17144
+ });
17035
17145
  method(object({
17036
17146
  deviceId: number(),
17037
17147
  agentNodeId: string()
@@ -17041,7 +17151,13 @@ method(object({
17041
17151
  }), method(object({ deviceId: number() }), object({ success: literal(true) }), {
17042
17152
  kind: "mutation",
17043
17153
  auth: "admin"
17044
- }), method(_void(), object({ migrated: number() }), {
17154
+ }), method(object({
17155
+ deviceId: number(),
17156
+ deviceKey: string()
17157
+ }), object({ success: literal(true) }), {
17158
+ kind: "mutation",
17159
+ auth: "admin"
17160
+ }), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({ migrated: number() }), {
17045
17161
  kind: "mutation",
17046
17162
  auth: "admin"
17047
17163
  }), 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 +17191,7 @@ method(object({
17075
17191
  }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
17076
17192
  nodeId: string(),
17077
17193
  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({
17194
+ })).readonly()), method(object({ agentNodeId: string() }), object({
17085
17195
  success: boolean(),
17086
17196
  removed: boolean()
17087
17197
  }), {
@@ -17113,7 +17223,18 @@ method(object({
17113
17223
  }), object({ success: literal(true) }), {
17114
17224
  kind: "mutation",
17115
17225
  auth: "admin"
17116
- }), method(object({ agentNodeId: string() }), object({
17226
+ }), method(object({
17227
+ agentNodeId: string(),
17228
+ inferenceDevices: record(string(), object({
17229
+ enabled: boolean(),
17230
+ weight: number().positive().optional(),
17231
+ maxSessions: number().int().positive().optional(),
17232
+ steps: record(string(), DeviceStepConfigSchema).optional()
17233
+ }))
17234
+ }), object({ success: literal(true) }), {
17235
+ kind: "mutation",
17236
+ auth: "admin"
17237
+ }), method(object({ nodeId: string() }), NodeInferenceDevicesSchema), method(object({ agentNodeId: string() }), object({
17117
17238
  success: literal(true),
17118
17239
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
17119
17240
  effectiveModelId: string().nullable(),
@@ -17129,9 +17250,10 @@ method(object({
17129
17250
  }), object({ success: literal(true) }), {
17130
17251
  kind: "mutation",
17131
17252
  auth: "admin"
17132
- }), method(object({ deviceId: number() }), record(string(), record(string(), CameraStepOverridePatchSchema)).nullable()), method(object({
17253
+ }), method(object({ deviceId: number() }), record(string(), record(string(), record(string(), CameraStepOverridePatchSchema))).nullable()), method(object({
17133
17254
  deviceId: number(),
17134
17255
  agentNodeId: string(),
17256
+ deviceKey: string(),
17135
17257
  addonId: string(),
17136
17258
  patch: CameraStepOverridePatchSchema.nullable()
17137
17259
  }), object({ success: literal(true) }), {
@@ -17168,14 +17290,13 @@ method(object({
17168
17290
  });
17169
17291
  /**
17170
17292
  * server-management — per-NODE singleton capability for a node's ROOT
17171
- * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
17172
- * agents).
17293
+ * package lifecycle (runtime-updatable node packages).
17173
17294
  *
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).
17295
+ * Every node role runs the SAME root package (`@camstack/server`), which
17296
+ * carries the whole software stack in its npm dep tree, so ONE version
17297
+ * describes the node. Updates stage into `<dataDir>/server-root/` and apply
17298
+ * on restart via the baked starter (single-copy in-place swap — no probation,
17299
+ * no auto-rollback).
17179
17300
  *
17180
17301
  * Providers:
17181
17302
  * - HUB: `ServerUpdateService` behind the `server-provided` mount
@@ -17283,7 +17404,8 @@ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), S
17283
17404
  /** Explicit target version; omitted = latest from the registry. */
17284
17405
  version: string().optional() }), ServerUpdateActionResultSchema, {
17285
17406
  kind: "mutation",
17286
- auth: "admin"
17407
+ auth: "admin",
17408
+ timeoutMs: 16 * 6e4
17287
17409
  }), method(_void(), ServerUpdateActionResultSchema, {
17288
17410
  kind: "mutation",
17289
17411
  auth: "admin"
@@ -18327,22 +18449,6 @@ var AddonAutoUpdateSchema = ChannelWithInheritSchema;
18327
18449
  var RestartAddonResultSchema = unknown();
18328
18450
  var InstallPackageResultSchema = unknown();
18329
18451
  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
18452
  var BulkUpdateItemStatusSchema = _enum([
18347
18453
  "queued",
18348
18454
  "updating",
@@ -18470,13 +18576,6 @@ method(_void(), array(AddonListItemSchema).readonly()), method(object({
18470
18576
  }), object({ success: literal(true) }), {
18471
18577
  kind: "mutation",
18472
18578
  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
18579
  }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
18481
18580
  kind: "mutation",
18482
18581
  auth: "admin"
@@ -19342,10 +19441,10 @@ var TopologyCategorySchema = object({
19342
19441
  addons: array(TopologyCategoryAddonSchema).readonly()
19343
19442
  });
19344
19443
  /**
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.
19444
+ * The node's runtime-updatable ROOT package (`@camstack/server` the single
19445
+ * root package for every node role) as reported by its `registerNode`
19446
+ * manifest — version visibility for the Server management surface. Nullable:
19447
+ * offline rows and nodes that never reported one.
19349
19448
  */
19350
19449
  var TopologyRootPackageSchema = object({
19351
19450
  name: string(),
@@ -19733,17 +19832,28 @@ var PlatformScoreSchema = object({
19733
19832
  format: _enum([
19734
19833
  "onnx",
19735
19834
  "coreml",
19736
- "openvino"
19835
+ "openvino",
19836
+ "tflite"
19737
19837
  ]),
19738
19838
  score: number(),
19739
19839
  reason: string(),
19740
19840
  available: boolean()
19741
19841
  });
19842
+ var InferenceDeviceDescriptorSchema = object({
19843
+ key: string(),
19844
+ backend: string(),
19845
+ device: string(),
19846
+ format: ModelFormatSchema,
19847
+ runtime: literal("python"),
19848
+ score: number(),
19849
+ available: boolean()
19850
+ });
19742
19851
  var PlatformCapabilitiesSchema = object({
19743
19852
  hardware: HardwareInfoSchema,
19744
19853
  scores: array(PlatformScoreSchema).readonly(),
19745
19854
  bestScore: PlatformScoreSchema,
19746
- pythonPath: string().nullable()
19855
+ pythonPath: string().nullable(),
19856
+ devices: array(InferenceDeviceDescriptorSchema).readonly()
19747
19857
  });
19748
19858
  var ModelRequirementSchema = object({
19749
19859
  modelId: string(),
@@ -20622,12 +20732,6 @@ Object.freeze({
20622
20732
  addonId: null,
20623
20733
  access: "delete"
20624
20734
  },
20625
- "addons.updateFrameworkPackage": {
20626
- capName: "addons",
20627
- capScope: "system",
20628
- addonId: null,
20629
- access: "create"
20630
- },
20631
20735
  "addons.updatePackage": {
20632
20736
  capName: "addons",
20633
20737
  capScope: "system",
@@ -23400,12 +23504,6 @@ Object.freeze({
23400
23504
  addonId: null,
23401
23505
  access: "view"
23402
23506
  },
23403
- "pipelineExecutor.reprobeEngine": {
23404
- capName: "pipeline-executor",
23405
- capScope: "system",
23406
- addonId: null,
23407
- access: "create"
23408
- },
23409
23507
  "pipelineExecutor.runAudioTest": {
23410
23508
  capName: "pipeline-executor",
23411
23509
  capScope: "system",
@@ -23556,6 +23654,12 @@ Object.freeze({
23556
23654
  addonId: null,
23557
23655
  access: "view"
23558
23656
  },
23657
+ "pipelineOrchestrator.getNodeInferenceDevices": {
23658
+ capName: "pipeline-orchestrator",
23659
+ capScope: "system",
23660
+ addonId: null,
23661
+ access: "view"
23662
+ },
23559
23663
  "pipelineOrchestrator.getPipelineAssignment": {
23560
23664
  capName: "pipeline-orchestrator",
23561
23665
  capScope: "system",
@@ -23568,6 +23672,12 @@ Object.freeze({
23568
23672
  addonId: null,
23569
23673
  access: "view"
23570
23674
  },
23675
+ "pipelineOrchestrator.getPipelineDevicePin": {
23676
+ capName: "pipeline-orchestrator",
23677
+ capScope: "system",
23678
+ addonId: null,
23679
+ access: "view"
23680
+ },
23571
23681
  "pipelineOrchestrator.listAgentSettings": {
23572
23682
  capName: "pipeline-orchestrator",
23573
23683
  capScope: "system",
@@ -23610,19 +23720,19 @@ Object.freeze({
23610
23720
  addonId: null,
23611
23721
  access: "create"
23612
23722
  },
23613
- "pipelineOrchestrator.setAgentAddonDefaults": {
23723
+ "pipelineOrchestrator.setAgentCapabilities": {
23614
23724
  capName: "pipeline-orchestrator",
23615
23725
  capScope: "system",
23616
23726
  addonId: null,
23617
23727
  access: "create"
23618
23728
  },
23619
- "pipelineOrchestrator.setAgentCapabilities": {
23729
+ "pipelineOrchestrator.setAgentDetectWeight": {
23620
23730
  capName: "pipeline-orchestrator",
23621
23731
  capScope: "system",
23622
23732
  addonId: null,
23623
23733
  access: "create"
23624
23734
  },
23625
- "pipelineOrchestrator.setAgentDetectWeight": {
23735
+ "pipelineOrchestrator.setAgentInferenceDevices": {
23626
23736
  capName: "pipeline-orchestrator",
23627
23737
  capScope: "system",
23628
23738
  addonId: null,
@@ -23664,6 +23774,12 @@ Object.freeze({
23664
23774
  addonId: null,
23665
23775
  access: "create"
23666
23776
  },
23777
+ "pipelineOrchestrator.setPipelineDevicePin": {
23778
+ capName: "pipeline-orchestrator",
23779
+ capScope: "system",
23780
+ addonId: null,
23781
+ access: "create"
23782
+ },
23667
23783
  "pipelineOrchestrator.unassignAudio": {
23668
23784
  capName: "pipeline-orchestrator",
23669
23785
  capScope: "system",
@@ -25216,32 +25332,6 @@ Object.freeze({
25216
25332
  "network-access": "ingress",
25217
25333
  "smtp-provider": "email"
25218
25334
  });
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
25335
  //#endregion
25246
25336
  //#region src/adapters/adapter.ts
25247
25337
  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.0",
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",