@camstack/addon-pipeline-orchestrator 1.2.289 → 1.2.290

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.
package/dist/index.js CHANGED
@@ -8076,7 +8076,7 @@ import { errMsg } from '@camstack/types'
8076
8076
  * Extract a human-readable message from an unknown error value.
8077
8077
  * Replaces the ubiquitous `errMsg(err)` pattern.
8078
8078
  */
8079
- function errMsg$1(err) {
8079
+ function errMsg$2(err) {
8080
8080
  if (err instanceof Error) return err.message;
8081
8081
  if (typeof err === "string") return err;
8082
8082
  return String(err);
@@ -11579,7 +11579,53 @@ var ModelFormatEntrySchema = object({
11579
11579
  /** Multi-file payload (directory members or sibling files). */
11580
11580
  files: array(string()).readonly().optional(),
11581
11581
  /** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
11582
- runtimes: array(_enum(["python"])).readonly().optional()
11582
+ runtimes: array(_enum(["python"])).readonly().optional(),
11583
+ /**
11584
+ * Content revision of the artefact, for one hosted OFF the CamStack mirror.
11585
+ * A format on the mirror leaves it unset: its revision (the sha256 of its
11586
+ * weight file) comes from the downloader's generated table, kept in step
11587
+ * with the mirror by `scripts/check-hosted-model-revisions.ts` (D744). A
11588
+ * node records the revision it downloaded next to the file
11589
+ * (`<file>.revision`); a copy with no record or a different one is stale
11590
+ * unless its weight file hashes to the revision, and a stale copy is
11591
+ * downloaded again, the old one serving until the new one is complete. No
11592
+ * revision at all ⇒ a file on disk is current, whatever it holds (D743).
11593
+ */
11594
+ revision: string().min(1).optional()
11595
+ });
11596
+ /**
11597
+ * What a node holds for one (model, format) (D746). `current` is a complete
11598
+ * copy at its catalog revision — exactly what `downloaded: true` means.
11599
+ * `stale` is bytes on disk that are not (yet) proven current under D743/D744:
11600
+ * no or a different revision record, or a missing sibling. `missing` is no
11601
+ * copy. `downloaded` alone folds `stale` into "not downloaded", which hid
11602
+ * reclaimable bytes and a copy about to be replaced.
11603
+ */
11604
+ var ModelOnDiskStateSchema = _enum([
11605
+ "current",
11606
+ "stale",
11607
+ "missing"
11608
+ ]);
11609
+ /**
11610
+ * The numeric precision of one format's BUILD (D746) — reported only when the
11611
+ * build states it (file-name token, an INT8 variant, an EdgeTPU compile, a
11612
+ * CamStack Core ML export). Absent means "not stated", never "fp32".
11613
+ */
11614
+ var ModelFormatPrecisionSchema = _enum([
11615
+ "fp32",
11616
+ "fp16",
11617
+ "int8"
11618
+ ]);
11619
+ /**
11620
+ * One format of one model as a node's `pipelineExecutor.getSchema` reports it.
11621
+ * `onDisk` / `precision` are optional for version skew: a runner that predates
11622
+ * them omits them, and a reader falls back to `downloaded`.
11623
+ */
11624
+ var PipelineModelFormatStatusSchema = object({
11625
+ downloaded: boolean(),
11626
+ sizeMB: number(),
11627
+ onDisk: ModelOnDiskStateSchema.optional(),
11628
+ precision: ModelFormatPrecisionSchema.optional()
11583
11629
  });
11584
11630
  /**
11585
11631
  * Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
@@ -24325,7 +24371,7 @@ var FrameRefSchema = object({
24325
24371
  timestamp: number(),
24326
24372
  capturedAt: number().optional()
24327
24373
  });
24328
- var ModelFormatSchema$1 = _enum([
24374
+ var ModelFormatSchema$2 = _enum([
24329
24375
  "onnx",
24330
24376
  "coreml",
24331
24377
  "openvino",
@@ -24343,7 +24389,7 @@ var PipelineSlotSchema = _enum([
24343
24389
  var PipelineEngineChoiceSchema = object({
24344
24390
  runtime: literal("python"),
24345
24391
  backend: string(),
24346
- format: ModelFormatSchema$1,
24392
+ format: ModelFormatSchema$2,
24347
24393
  device: string().optional()
24348
24394
  });
24349
24395
  /**
@@ -24400,10 +24446,7 @@ var PipelineTemplateSchema$1 = object({
24400
24446
  var PipelineModelOptionSchema = object({
24401
24447
  id: string(),
24402
24448
  name: string(),
24403
- formats: record(string(), object({
24404
- downloaded: boolean(),
24405
- sizeMB: number()
24406
- })),
24449
+ formats: record(string(), PipelineModelFormatStatusSchema),
24407
24450
  group: ModelVariantGroupSchema.optional(),
24408
24451
  legacy: boolean().optional(),
24409
24452
  provider: ModelProviderIdSchema.optional(),
@@ -24618,6 +24661,32 @@ var pipelineExecutorCapability = {
24618
24661
  auth: "admin"
24619
24662
  }),
24620
24663
  /**
24664
+ * Best settings (D745): the MODEL-BOUND thresholds (`bindsToModel` keys of
24665
+ * the object-detection root, and their binding keys) stored in THIS node's
24666
+ * per-camera detector settings — and, unless `dryRun`, remove them so the
24667
+ * running model's own defaults apply. Every other key of a camera's store
24668
+ * is kept. `nodeId` is the ROUTING key. `deviceIds` absent ⇒ every camera
24669
+ * with a store. A camera whose store could not be read is `refused`, never
24670
+ * reported as clean.
24671
+ */
24672
+ clearModelBoundDeviceOverrides: method(object({
24673
+ nodeId: string(),
24674
+ deviceIds: array(number()).readonly().optional(),
24675
+ dryRun: boolean()
24676
+ }), object({
24677
+ entries: array(object({
24678
+ deviceId: number(),
24679
+ keys: array(string()).readonly()
24680
+ })).readonly(),
24681
+ refused: array(object({
24682
+ deviceId: number(),
24683
+ reason: string()
24684
+ })).readonly()
24685
+ }), {
24686
+ kind: "mutation",
24687
+ auth: "admin"
24688
+ }),
24689
+ /**
24621
24690
  * Which of THIS node's inference devices the executor currently refuses,
24622
24691
  * and why. `nodeId` is the ROUTING key (stripped by the generated router).
24623
24692
  *
@@ -24712,12 +24781,12 @@ var pipelineExecutorCapability = {
24712
24781
  downloadModel: method(object({
24713
24782
  addonId: string(),
24714
24783
  modelId: string(),
24715
- format: ModelFormatSchema$1
24784
+ format: ModelFormatSchema$2
24716
24785
  }), DownloadModelResultSchema, { kind: "mutation" }),
24717
24786
  deleteModel: method(object({
24718
24787
  addonId: string(),
24719
24788
  modelId: string(),
24720
- format: ModelFormatSchema$1
24789
+ format: ModelFormatSchema$2
24721
24790
  }), object({ success: literal(true) }), { kind: "mutation" }),
24722
24791
  /**
24723
24792
  * Stateless single-frame execution. Callers (runner, benchmark) pass
@@ -24963,6 +25032,204 @@ var pipelineExecutorCapability = {
24963
25032
  getDetectionConfigSchema: method(_void(), ConfigUISchemaNullableBridge)
24964
25033
  }
24965
25034
  };
25035
+ /**
25036
+ * The wire shapes of the Best settings plan and apply (D745), used by the
25037
+ * `pipeline-orchestrator` cap. The recommender's own types
25038
+ * (`recommend.ts`, `demand.ts`) are the TS face of the same data; these
25039
+ * schemas are what crosses the cap boundary.
25040
+ */
25041
+ var ModelFormatSchema$1 = _enum(MODEL_FORMATS);
25042
+ var HardwareClassIdSchema = _enum([
25043
+ "intel-core-ultra",
25044
+ "intel-n-series",
25045
+ "apple-m-base",
25046
+ "apple-m-pro",
25047
+ "unknown"
25048
+ ]);
25049
+ var AcceleratorKindSchema = _enum([
25050
+ "intel-npu",
25051
+ "intel-igpu",
25052
+ "x86-cpu",
25053
+ "arm-cpu",
25054
+ "apple-ane",
25055
+ "coral"
25056
+ ]);
25057
+ var RecommendationReasonSchema = discriminatedUnion("kind", [
25058
+ object({
25059
+ kind: literal("fits"),
25060
+ utilisation: number()
25061
+ }),
25062
+ object({
25063
+ kind: literal("tie-faster"),
25064
+ over: string(),
25065
+ recallDelta: number()
25066
+ }),
25067
+ object({
25068
+ kind: literal("stepped-down"),
25069
+ from: string(),
25070
+ to: string(),
25071
+ limitedBy: string()
25072
+ }),
25073
+ object({
25074
+ kind: literal("no-fit-lightest"),
25075
+ utilisation: number()
25076
+ }),
25077
+ object({
25078
+ kind: literal("derived-latency"),
25079
+ modelId: string()
25080
+ }),
25081
+ object({
25082
+ kind: literal("estimated-load"),
25083
+ observedForMs: number().nullable()
25084
+ }),
25085
+ object({ kind: literal("cpu-device") }),
25086
+ object({ kind: literal("coral-untouched") }),
25087
+ object({ kind: literal("device-disabled") }),
25088
+ object({ kind: literal("device-unavailable") }),
25089
+ object({ kind: literal("node-offline") }),
25090
+ object({ kind: literal("no-hardware-facts") }),
25091
+ object({
25092
+ kind: literal("no-measured-data"),
25093
+ hardwareClass: HardwareClassIdSchema
25094
+ }),
25095
+ object({
25096
+ kind: literal("no-family-build"),
25097
+ format: ModelFormatSchema$1
25098
+ })
25099
+ ]);
25100
+ var DeviceDemandSchema = object({
25101
+ nodeId: string(),
25102
+ deviceKey: string(),
25103
+ demandFps: number(),
25104
+ maxCameraFps: number(),
25105
+ alwaysOnCameras: number(),
25106
+ onMotionSessions: number(),
25107
+ onMotionSource: _enum([
25108
+ "observed-24h",
25109
+ "estimate",
25110
+ "none"
25111
+ ]),
25112
+ observedForMs: number().nullable()
25113
+ });
25114
+ var DeviceModelChoiceSchema = object({
25115
+ modelId: string(),
25116
+ p50Ms: number(),
25117
+ fps: number(),
25118
+ measured: boolean(),
25119
+ utilisation: number(),
25120
+ personRecall: number()
25121
+ });
25122
+ var DeviceRecommendationSchema = object({
25123
+ deviceKey: string(),
25124
+ accelerator: AcceleratorKindSchema.nullable(),
25125
+ enabled: boolean(),
25126
+ available: boolean(),
25127
+ demand: DeviceDemandSchema.nullable(),
25128
+ chosen: DeviceModelChoiceSchema.nullable(),
25129
+ reasons: array(RecommendationReasonSchema).readonly()
25130
+ });
25131
+ var NodeRecommendationSchema = object({
25132
+ nodeId: string(),
25133
+ reachable: boolean(),
25134
+ hardwareClass: HardwareClassIdSchema.nullable(),
25135
+ cpuModel: string().nullable(),
25136
+ skipped: boolean(),
25137
+ acceleratorModelId: string().nullable(),
25138
+ devices: array(DeviceRecommendationSchema).readonly(),
25139
+ reasons: array(RecommendationReasonSchema).readonly()
25140
+ });
25141
+ var ModelPinChangeSchema = object({
25142
+ stepId: string(),
25143
+ before: string().nullable(),
25144
+ after: string().nullable()
25145
+ });
25146
+ var NodeDeviceChangeSchema = object({
25147
+ nodeId: string(),
25148
+ deviceKey: string(),
25149
+ madeExplicit: boolean(),
25150
+ modelPins: array(ModelPinChangeSchema).readonly(),
25151
+ thresholds: array(object({
25152
+ stepId: string(),
25153
+ keys: array(string()).readonly()
25154
+ })).readonly()
25155
+ });
25156
+ var CameraOverrideClearSchema = object({
25157
+ deviceId: number(),
25158
+ source: _enum(["step-override", "wholesale"]),
25159
+ nodeId: string(),
25160
+ deviceKey: string().nullable(),
25161
+ stepId: string(),
25162
+ modelIdBefore: string().nullable(),
25163
+ keys: array(string()).readonly()
25164
+ });
25165
+ var DetectorStoreClearSchema = object({
25166
+ deviceId: number(),
25167
+ keys: array(string()).readonly()
25168
+ });
25169
+ var BestSettingsWarningSchema = discriminatedUnion("kind", [
25170
+ object({
25171
+ kind: literal("detector-store-cluster-wide"),
25172
+ skippedNodes: array(string()).readonly()
25173
+ }),
25174
+ object({
25175
+ kind: literal("detector-store-unreadable"),
25176
+ error: string()
25177
+ }),
25178
+ object({
25179
+ kind: literal("coral-enabled"),
25180
+ nodeId: string(),
25181
+ deviceKey: string()
25182
+ })
25183
+ ]);
25184
+ var BestSettingsOptionsSchema = object({
25185
+ /** Share of each accelerator's throughput kept free (0.3–0.5). */
25186
+ headroom: number().min(.3).max(.5).default(.4) });
25187
+ var BestSettingsPlanSchema = object({
25188
+ /** Hash of everything the apply will write — the apply refuses a different one. */
25189
+ planHash: string(),
25190
+ family: literal("yolov9"),
25191
+ headroom: number(),
25192
+ generatedAt: number(),
25193
+ nodes: array(NodeRecommendationSchema).readonly(),
25194
+ nodeChanges: array(NodeDeviceChangeSchema).readonly(),
25195
+ cameraClears: array(CameraOverrideClearSchema).readonly(),
25196
+ detectorStoreClears: array(DetectorStoreClearSchema).readonly(),
25197
+ warnings: array(BestSettingsWarningSchema).readonly()
25198
+ });
25199
+ var BestSettingsApplyInputSchema = object({
25200
+ planHash: string(),
25201
+ headroom: number().min(.3).max(.5).default(.4)
25202
+ });
25203
+ var BestSettingsPhaseResultSchema = object({
25204
+ phase: _enum([
25205
+ "download",
25206
+ "camera-overrides",
25207
+ "detector-thresholds",
25208
+ "node-models",
25209
+ "redispatch"
25210
+ ]),
25211
+ ok: boolean(),
25212
+ detail: string()
25213
+ });
25214
+ var BestSettingsApplyResultSchema = discriminatedUnion("kind", [
25215
+ object({
25216
+ kind: literal("applied"),
25217
+ planHash: string(),
25218
+ phases: array(BestSettingsPhaseResultSchema).readonly()
25219
+ }),
25220
+ object({
25221
+ kind: literal("aborted"),
25222
+ phases: array(BestSettingsPhaseResultSchema).readonly()
25223
+ }),
25224
+ object({
25225
+ kind: literal("stale"),
25226
+ plan: BestSettingsPlanSchema
25227
+ }),
25228
+ object({
25229
+ kind: literal("refused"),
25230
+ reason: _enum(["apply-in-progress"])
25231
+ })
25232
+ ]);
24966
25233
  object({
24967
25234
  activeCameras: number(),
24968
25235
  throttledCameras: number(),
@@ -27149,6 +27416,27 @@ var pipelineOrchestratorCapability = {
27149
27416
  kind: "mutation",
27150
27417
  auth: "admin"
27151
27418
  }),
27419
+ /**
27420
+ * Best settings (D745) — DRY RUN. Recommends ONE detector family (YOLOv9)
27421
+ * for the whole cluster from each node's static hardware class, the
27422
+ * measured latency table and the detection demand, and lists every write
27423
+ * the apply would make: the node model pins set, the enrichment pins and
27424
+ * model-bound thresholds removed (node level and per camera), and the
27425
+ * per-camera detector thresholds cleared. Writes nothing. Step
27426
+ * enablement, device enablement, pins, recording, notifications, zones and
27427
+ * rules are never part of it. `planHash` names exactly this diff.
27428
+ */
27429
+ planBestSettings: method(BestSettingsOptionsSchema, BestSettingsPlanSchema, { auth: "admin" }),
27430
+ /**
27431
+ * Best settings (D745) — APPLY the plan the operator confirmed. Recomputes
27432
+ * the plan and refuses (`stale`, with the fresh plan) when its hash is not
27433
+ * `planHash`, so what is written is exactly the confirmed diff. Offline
27434
+ * nodes are skipped; there is no undo.
27435
+ */
27436
+ applyBestSettings: method(BestSettingsApplyInputSchema, BestSettingsApplyResultSchema, {
27437
+ kind: "mutation",
27438
+ auth: "admin"
27439
+ }),
27152
27440
  /** Read one camera's settings. Null when never touched (inherits agent defaults fully). */
27153
27441
  getCameraSettings: method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()),
27154
27442
  /** Set or clear the 3-state toggle for one (camera, addonId). Pass `enabled: null` to clear and revert to agent default. */
@@ -42950,6 +43238,12 @@ Object.freeze({
42950
43238
  addonId: null,
42951
43239
  access: "delete"
42952
43240
  },
43241
+ "pipelineExecutor.clearModelBoundDeviceOverrides": {
43242
+ capName: "pipeline-executor",
43243
+ capScope: "system",
43244
+ addonId: null,
43245
+ access: "delete"
43246
+ },
42953
43247
  "pipelineExecutor.deleteModel": {
42954
43248
  capName: "pipeline-executor",
42955
43249
  capScope: "system",
@@ -43166,6 +43460,12 @@ Object.freeze({
43166
43460
  addonId: null,
43167
43461
  access: "view"
43168
43462
  },
43463
+ "pipelineOrchestrator.applyBestSettings": {
43464
+ capName: "pipeline-orchestrator",
43465
+ capScope: "system",
43466
+ addonId: null,
43467
+ access: "create"
43468
+ },
43169
43469
  "pipelineOrchestrator.applyDeviceSettingsPatch": {
43170
43470
  capName: "pipeline-orchestrator",
43171
43471
  capScope: "system",
@@ -43340,6 +43640,12 @@ Object.freeze({
43340
43640
  addonId: null,
43341
43641
  access: "create"
43342
43642
  },
43643
+ "pipelineOrchestrator.planBestSettings": {
43644
+ capName: "pipeline-orchestrator",
43645
+ capScope: "system",
43646
+ addonId: null,
43647
+ access: "view"
43648
+ },
43343
43649
  "pipelineOrchestrator.rebalance": {
43344
43650
  capName: "pipeline-orchestrator",
43345
43651
  capScope: "system",
@@ -48790,6 +49096,11 @@ Object.freeze({
48790
49096
  form: "single",
48791
49097
  optional: false
48792
49098
  }],
49099
+ "pipelineExecutor.clearModelBoundDeviceOverrides": [{
49100
+ name: "deviceIds",
49101
+ form: "array",
49102
+ optional: true
49103
+ }],
48793
49104
  "pipelineExecutor.runPipeline": [{
48794
49105
  name: "deviceId",
48795
49106
  form: "single",
@@ -50684,6 +50995,830 @@ var REMOVED_MODEL_IDS = {
50684
50995
  function isRemovedModelId(stepId, modelId) {
50685
50996
  return REMOVED_MODEL_IDS[stepId]?.includes(modelId) ?? false;
50686
50997
  }
50998
+ /** Suffix of the sibling key that names the model a stored value was made for. */
50999
+ var MODEL_BINDING_SUFFIX = "ModelId";
51000
+ /** `minConfidencePerson` → `minConfidencePersonModelId`. */
51001
+ function modelBindingKey(key) {
51002
+ return `${key}${MODEL_BINDING_SUFFIX}`;
51003
+ }
51004
+ /**
51005
+ * The keys of every `bindsToModel` field of a step's config schema, groups
51006
+ * flattened (D738). One definition for the runner (which applies the binding)
51007
+ * and the orchestrator's Best settings (which clears those overrides, D745).
51008
+ */
51009
+ function collectModelBoundKeys(fields) {
51010
+ return fields.flatMap((field) => {
51011
+ if (field.type === "group") return collectModelBoundKeys(field.fields);
51012
+ return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
51013
+ });
51014
+ }
51015
+ var APPLE_CHIP = /^Apple M(\d+)(?:\s+(Pro|Max|Ultra))?\b/i;
51016
+ var INTEL_CORE_ULTRA = /Core\(TM\)\s+Ultra|Core Ultra/i;
51017
+ var INTEL_N_SERIES = /Intel\(R\)\s+N\d{2,3}\b|\bIntel\s+N\d{2,3}\b/i;
51018
+ function parseAppleChip(cpuModel) {
51019
+ const match = APPLE_CHIP.exec(cpuModel.trim());
51020
+ if (match === null) return null;
51021
+ const generation = Number(match[1]);
51022
+ const variantRaw = (match[2] ?? "base").toLowerCase();
51023
+ return {
51024
+ generation,
51025
+ variant: variantRaw === "pro" || variantRaw === "max" || variantRaw === "ultra" ? variantRaw : "base"
51026
+ };
51027
+ }
51028
+ /**
51029
+ * Classify a node. Apple base chips (M1–M4) map to the measured M2 column, the
51030
+ * Pro/Max/Ultra chips to the measured M3 Pro column — the M2 was measured at
51031
+ * 1.0–1.2× the M3 Pro on the Neural Engine, so the generation inside a variant
51032
+ * matters less than the column's own error.
51033
+ */
51034
+ function classifyInferenceHardware(hw) {
51035
+ const cpuModel = hw.cpuModel;
51036
+ const apple = parseAppleChip(cpuModel);
51037
+ if (apple !== null) return {
51038
+ hardwareClass: apple.variant === "base" ? "apple-m-base" : "apple-m-pro",
51039
+ cpuModel,
51040
+ apple
51041
+ };
51042
+ if (INTEL_CORE_ULTRA.test(cpuModel)) return {
51043
+ hardwareClass: "intel-core-ultra",
51044
+ cpuModel
51045
+ };
51046
+ if (INTEL_N_SERIES.test(cpuModel)) return {
51047
+ hardwareClass: "intel-n-series",
51048
+ cpuModel
51049
+ };
51050
+ return {
51051
+ hardwareClass: "unknown",
51052
+ cpuModel
51053
+ };
51054
+ }
51055
+ /**
51056
+ * The accelerator kind of one of a node's device keys (`openvino:npu`,
51057
+ * `openvino:gpu`, `coreml:all`, `edgetpu:usb`, `cpu`, `onnx:cpu`, …). `null`
51058
+ * for a key this table has no column for.
51059
+ */
51060
+ function acceleratorKindOf(deviceKey, hw) {
51061
+ const [backend = "", device = ""] = deviceKey.split(":");
51062
+ if (backend === "edgetpu") return "coral";
51063
+ if (backend === "coreml") return device === "cpu" ? "arm-cpu" : "apple-ane";
51064
+ if (backend === "openvino" && device === "npu") return "intel-npu";
51065
+ if (backend === "openvino" && device === "gpu") return "intel-igpu";
51066
+ if (backend === "cpu" || device === "cpu") return hw.arch === "arm64" ? "arm-cpu" : "x86-cpu";
51067
+ return null;
51068
+ }
51069
+ var DETECTOR_PERF = {
51070
+ "intel-core-ultra": {
51071
+ "intel-npu": {
51072
+ "yolov9t-320": {
51073
+ p50Ms: 2.9,
51074
+ fps: 383,
51075
+ measured: true
51076
+ },
51077
+ "yolov9t-320-int8": {
51078
+ p50Ms: 3.8,
51079
+ fps: 333,
51080
+ measured: true
51081
+ },
51082
+ "yolov9t-640": {
51083
+ p50Ms: 7.4,
51084
+ fps: 139,
51085
+ measured: true
51086
+ },
51087
+ "yolov9t-640-int8": {
51088
+ p50Ms: 7.5,
51089
+ fps: 144,
51090
+ measured: true
51091
+ },
51092
+ "yolov9s-320": {
51093
+ p50Ms: 4.4,
51094
+ fps: 241,
51095
+ measured: true
51096
+ },
51097
+ "yolov9s-320-int8": {
51098
+ p50Ms: 4.1,
51099
+ fps: 264,
51100
+ measured: true
51101
+ },
51102
+ "yolov9s-640": {
51103
+ p50Ms: 12.7,
51104
+ fps: 82,
51105
+ measured: true
51106
+ },
51107
+ "yolov9s-640-int8": {
51108
+ p50Ms: 10.7,
51109
+ fps: 99,
51110
+ measured: true
51111
+ },
51112
+ "yolov9m-320": {
51113
+ p50Ms: 8.4,
51114
+ fps: 123,
51115
+ measured: true
51116
+ },
51117
+ "yolov9m-320-int8": {
51118
+ p50Ms: 6.5,
51119
+ fps: 165,
51120
+ measured: true
51121
+ },
51122
+ "yolov9m-640": {
51123
+ p50Ms: 29.5,
51124
+ fps: 32,
51125
+ measured: true
51126
+ },
51127
+ "yolov9m-640-int8": {
51128
+ p50Ms: 19.5,
51129
+ fps: 52,
51130
+ measured: true
51131
+ }
51132
+ },
51133
+ "intel-igpu": {
51134
+ "yolov9t-320": {
51135
+ p50Ms: 2.6,
51136
+ fps: 405,
51137
+ measured: true
51138
+ },
51139
+ "yolov9t-320-int8": {
51140
+ p50Ms: 2.8,
51141
+ fps: 324,
51142
+ measured: true
51143
+ },
51144
+ "yolov9t-640": {
51145
+ p50Ms: 6.5,
51146
+ fps: 157,
51147
+ measured: true
51148
+ },
51149
+ "yolov9t-640-int8": {
51150
+ p50Ms: 5.6,
51151
+ fps: 185,
51152
+ measured: true
51153
+ },
51154
+ "yolov9s-320": {
51155
+ p50Ms: 4.6,
51156
+ fps: 200,
51157
+ measured: true
51158
+ },
51159
+ "yolov9s-320-int8": {
51160
+ p50Ms: 4.1,
51161
+ fps: 239,
51162
+ measured: true
51163
+ },
51164
+ "yolov9s-640": {
51165
+ p50Ms: 15.2,
51166
+ fps: 69,
51167
+ measured: true
51168
+ },
51169
+ "yolov9s-640-int8": {
51170
+ p50Ms: 10.5,
51171
+ fps: 99,
51172
+ measured: true
51173
+ },
51174
+ "yolov9m-320": {
51175
+ p50Ms: 11.4,
51176
+ fps: 88,
51177
+ measured: true
51178
+ },
51179
+ "yolov9m-320-int8": {
51180
+ p50Ms: 7,
51181
+ fps: 127,
51182
+ measured: true
51183
+ },
51184
+ "yolov9m-640": {
51185
+ p50Ms: 40,
51186
+ fps: 25,
51187
+ measured: true
51188
+ },
51189
+ "yolov9m-640-int8": {
51190
+ p50Ms: 23.5,
51191
+ fps: 44,
51192
+ measured: true
51193
+ }
51194
+ },
51195
+ "x86-cpu": {
51196
+ "yolov9t-320": {
51197
+ p50Ms: 6.9,
51198
+ fps: 145,
51199
+ measured: true
51200
+ },
51201
+ "yolov9t-320-int8": {
51202
+ p50Ms: 2.7,
51203
+ fps: 405,
51204
+ measured: true
51205
+ },
51206
+ "yolov9t-640": {
51207
+ p50Ms: 23,
51208
+ fps: 44,
51209
+ measured: true
51210
+ },
51211
+ "yolov9t-640-int8": {
51212
+ p50Ms: 8.8,
51213
+ fps: 124,
51214
+ measured: true
51215
+ },
51216
+ "yolov9s-320": {
51217
+ p50Ms: 15.8,
51218
+ fps: 63,
51219
+ measured: true
51220
+ },
51221
+ "yolov9s-320-int8": {
51222
+ p50Ms: 6.5,
51223
+ fps: 180,
51224
+ measured: true
51225
+ },
51226
+ "yolov9s-640": {
51227
+ p50Ms: 58.2,
51228
+ fps: 17,
51229
+ measured: true
51230
+ },
51231
+ "yolov9s-640-int8": {
51232
+ p50Ms: 20.1,
51233
+ fps: 52,
51234
+ measured: true
51235
+ },
51236
+ "yolov9m-320": {
51237
+ p50Ms: 40,
51238
+ fps: 25,
51239
+ measured: true
51240
+ },
51241
+ "yolov9m-320-int8": {
51242
+ p50Ms: 14.2,
51243
+ fps: 74,
51244
+ measured: true
51245
+ },
51246
+ "yolov9m-640": {
51247
+ p50Ms: 155.8,
51248
+ fps: 6,
51249
+ measured: true
51250
+ },
51251
+ "yolov9m-640-int8": {
51252
+ p50Ms: 52.4,
51253
+ fps: 19,
51254
+ measured: true
51255
+ }
51256
+ }
51257
+ },
51258
+ "intel-n-series": {
51259
+ "intel-igpu": {
51260
+ "yolov9t-320": {
51261
+ p50Ms: 15,
51262
+ fps: 48,
51263
+ measured: true
51264
+ },
51265
+ "yolov9t-320-int8": {
51266
+ p50Ms: 14.1,
51267
+ fps: 67,
51268
+ measured: true
51269
+ },
51270
+ "yolov9t-640": {
51271
+ p50Ms: 38.5,
51272
+ fps: 26,
51273
+ measured: true
51274
+ },
51275
+ "yolov9t-640-int8": {
51276
+ p50Ms: 33.1,
51277
+ fps: 33,
51278
+ measured: true
51279
+ },
51280
+ "yolov9s-320": {
51281
+ p50Ms: 41.4,
51282
+ fps: 24,
51283
+ measured: true
51284
+ },
51285
+ "yolov9s-320-int8": {
51286
+ p50Ms: 23.3,
51287
+ fps: 34,
51288
+ measured: true
51289
+ },
51290
+ "yolov9s-640": {
51291
+ p50Ms: 127.4,
51292
+ fps: 8,
51293
+ measured: true
51294
+ },
51295
+ "yolov9s-640-int8": {
51296
+ p50Ms: 92.9,
51297
+ fps: 11,
51298
+ measured: true
51299
+ },
51300
+ "yolov9m-320": {
51301
+ p50Ms: 63.5,
51302
+ fps: 16,
51303
+ measured: true
51304
+ },
51305
+ "yolov9m-320-int8": {
51306
+ p50Ms: 39.4,
51307
+ fps: 26,
51308
+ measured: true
51309
+ },
51310
+ "yolov9m-640": {
51311
+ p50Ms: 215.5,
51312
+ fps: 5,
51313
+ measured: true
51314
+ },
51315
+ "yolov9m-640-int8": {
51316
+ p50Ms: 139.1,
51317
+ fps: 7,
51318
+ measured: true
51319
+ }
51320
+ },
51321
+ "x86-cpu": {
51322
+ "yolov9t-320": {
51323
+ p50Ms: 86.2,
51324
+ fps: 12,
51325
+ measured: true
51326
+ },
51327
+ "yolov9t-320-int8": {
51328
+ p50Ms: 52.7,
51329
+ fps: 20,
51330
+ measured: true
51331
+ },
51332
+ "yolov9t-640": {
51333
+ p50Ms: 322.1,
51334
+ fps: 3,
51335
+ measured: true
51336
+ },
51337
+ "yolov9t-640-int8": {
51338
+ p50Ms: 205.6,
51339
+ fps: 5,
51340
+ measured: true
51341
+ },
51342
+ "yolov9s-320": {
51343
+ p50Ms: 164.4,
51344
+ fps: 6,
51345
+ measured: true
51346
+ },
51347
+ "yolov9s-320-int8": {
51348
+ p50Ms: 86.9,
51349
+ fps: 12,
51350
+ measured: true
51351
+ },
51352
+ "yolov9s-640": {
51353
+ p50Ms: 341.3,
51354
+ fps: 3,
51355
+ measured: true
51356
+ },
51357
+ "yolov9s-640-int8": {
51358
+ p50Ms: 185.3,
51359
+ fps: 5,
51360
+ measured: true
51361
+ },
51362
+ "yolov9m-320": {
51363
+ p50Ms: 166.8,
51364
+ fps: 6,
51365
+ measured: true
51366
+ },
51367
+ "yolov9m-320-int8": {
51368
+ p50Ms: 226.3,
51369
+ fps: 4,
51370
+ measured: true
51371
+ },
51372
+ "yolov9m-640": {
51373
+ p50Ms: 663.5,
51374
+ fps: 2,
51375
+ measured: true
51376
+ },
51377
+ "yolov9m-640-int8": {
51378
+ p50Ms: 324.4,
51379
+ fps: 3,
51380
+ measured: true
51381
+ }
51382
+ }
51383
+ },
51384
+ "apple-m-pro": { "apple-ane": {
51385
+ "yolov9t-320": {
51386
+ p50Ms: 1.1,
51387
+ fps: 952,
51388
+ measured: true
51389
+ },
51390
+ "yolov9t-640": {
51391
+ p50Ms: 3,
51392
+ fps: 333,
51393
+ measured: true
51394
+ },
51395
+ "yolov9s-320": {
51396
+ p50Ms: 1.5,
51397
+ fps: 667,
51398
+ measured: true
51399
+ },
51400
+ "yolov9s-640": {
51401
+ p50Ms: 5.1,
51402
+ fps: 196,
51403
+ measured: true
51404
+ },
51405
+ "yolov9m-320": {
51406
+ p50Ms: 3.2,
51407
+ fps: 312,
51408
+ measured: true
51409
+ },
51410
+ "yolov9m-640": {
51411
+ p50Ms: 10.2,
51412
+ fps: 98,
51413
+ measured: true
51414
+ }
51415
+ } },
51416
+ "apple-m-base": { "apple-ane": {
51417
+ "yolov9t-320": {
51418
+ p50Ms: 1.32,
51419
+ fps: 758,
51420
+ measured: false
51421
+ },
51422
+ "yolov9t-640": {
51423
+ p50Ms: 3.45,
51424
+ fps: 290,
51425
+ measured: true
51426
+ },
51427
+ "yolov9s-320": {
51428
+ p50Ms: 1.8,
51429
+ fps: 556,
51430
+ measured: false
51431
+ },
51432
+ "yolov9s-640": {
51433
+ p50Ms: 5.46,
51434
+ fps: 183,
51435
+ measured: true
51436
+ },
51437
+ "yolov9m-320": {
51438
+ p50Ms: 3.79,
51439
+ fps: 264,
51440
+ measured: true
51441
+ },
51442
+ "yolov9m-640": {
51443
+ p50Ms: 10.34,
51444
+ fps: 97,
51445
+ measured: true
51446
+ }
51447
+ } }
51448
+ };
51449
+ var DETECTOR_ACCURACY = {
51450
+ "yolov9m-320": {
51451
+ personPrecision: .979,
51452
+ personRecall: .649
51453
+ },
51454
+ "yolov9m-320-int8": {
51455
+ personPrecision: .974,
51456
+ personRecall: .684
51457
+ },
51458
+ "yolov9m-640": {
51459
+ personPrecision: .976,
51460
+ personRecall: .854
51461
+ },
51462
+ "yolov9m-640-int8": {
51463
+ personPrecision: .976,
51464
+ personRecall: .847
51465
+ },
51466
+ "yolov9s-320": {
51467
+ personPrecision: .958,
51468
+ personRecall: .479
51469
+ },
51470
+ "yolov9s-320-int8": {
51471
+ personPrecision: .969,
51472
+ personRecall: .438
51473
+ },
51474
+ "yolov9s-640": {
51475
+ personPrecision: .986,
51476
+ personRecall: .753
51477
+ },
51478
+ "yolov9s-640-int8": {
51479
+ personPrecision: .963,
51480
+ personRecall: .823
51481
+ },
51482
+ "yolov9t-320": {
51483
+ personPrecision: .946,
51484
+ personRecall: .368
51485
+ },
51486
+ "yolov9t-320-int8": {
51487
+ personPrecision: .943,
51488
+ personRecall: .347
51489
+ },
51490
+ "yolov9t-640": {
51491
+ personPrecision: .979,
51492
+ personRecall: .646
51493
+ },
51494
+ "yolov9t-640-int8": {
51495
+ personPrecision: .979,
51496
+ personRecall: .646
51497
+ }
51498
+ };
51499
+ var DEFAULT_DEMAND_OPTIONS = {
51500
+ onMotionConcurrency: .25,
51501
+ onMotionMinConcurrent: 2
51502
+ };
51503
+ function deviceId(nodeId, deviceKey) {
51504
+ return `${nodeId}\u0000${deviceKey}`;
51505
+ }
51506
+ function charges(load) {
51507
+ return load.mode !== "disabled" && load.runsPipelineRoot && load.fps > 0;
51508
+ }
51509
+ /** The highest `count` rates, summed. */
51510
+ function topRatesSum(rates, count) {
51511
+ return rates.toSorted((a, b) => b - a).slice(0, Math.max(0, count)).reduce((sum, r) => sum + r, 0);
51512
+ }
51513
+ /** Concurrent on-motion sessions assumed cluster-wide without an observation. */
51514
+ function estimatedConcurrentSessions(cameraCount, options) {
51515
+ if (cameraCount <= 0) return 0;
51516
+ const share = Math.ceil(options.onMotionConcurrency * cameraCount);
51517
+ return Math.min(cameraCount, Math.max(share, Math.min(cameraCount, options.onMotionMinConcurrent)));
51518
+ }
51519
+ /**
51520
+ * Demand on every eligible device of the cluster.
51521
+ */
51522
+ function estimateDeviceDemand(input) {
51523
+ const options = input.options ?? DEFAULT_DEMAND_OPTIONS;
51524
+ const eligibleById = new Map(input.eligible.map((d) => [deviceId(d.nodeId, d.deviceKey), d]));
51525
+ const peakById = new Map(input.peaks.map((p) => [deviceId(p.nodeId, p.deviceKey), p]));
51526
+ const weightByNode = /* @__PURE__ */ new Map();
51527
+ const eligibleCountByNode = /* @__PURE__ */ new Map();
51528
+ for (const d of input.eligible) {
51529
+ weightByNode.set(d.nodeId, (weightByNode.get(d.nodeId) ?? 0) + Math.max(d.weight, 0));
51530
+ eligibleCountByNode.set(d.nodeId, (eligibleCountByNode.get(d.nodeId) ?? 0) + 1);
51531
+ }
51532
+ const pinnedFps = /* @__PURE__ */ new Map();
51533
+ const alwaysOnFps = /* @__PURE__ */ new Map();
51534
+ const alwaysOnCount = /* @__PURE__ */ new Map();
51535
+ const maxFps = /* @__PURE__ */ new Map();
51536
+ const onMotionRates = [];
51537
+ const bump = (map, key, by) => {
51538
+ map.set(key, (map.get(key) ?? 0) + by);
51539
+ };
51540
+ const noteMax = (key, fps) => {
51541
+ maxFps.set(key, Math.max(maxFps.get(key) ?? 0, fps));
51542
+ };
51543
+ for (const cam of input.cameras) {
51544
+ if (!charges(cam)) continue;
51545
+ if (cam.devicePin !== null) {
51546
+ const key = deviceId(cam.devicePin.nodeId, cam.devicePin.deviceKey);
51547
+ if (!eligibleById.has(key)) continue;
51548
+ bump(pinnedFps, key, cam.fps);
51549
+ noteMax(key, cam.fps);
51550
+ continue;
51551
+ }
51552
+ if (cam.mode === "always-on" && cam.standingNodeId !== null) {
51553
+ const nodeWeight = weightByNode.get(cam.standingNodeId) ?? 0;
51554
+ for (const d of input.eligible) {
51555
+ if (d.nodeId !== cam.standingNodeId || nodeWeight <= 0) continue;
51556
+ const key = deviceId(d.nodeId, d.deviceKey);
51557
+ const share = Math.max(d.weight, 0) / nodeWeight;
51558
+ bump(alwaysOnFps, key, cam.fps * share);
51559
+ bump(alwaysOnCount, key, share);
51560
+ noteMax(key, cam.fps);
51561
+ }
51562
+ continue;
51563
+ }
51564
+ onMotionRates.push(cam.fps);
51565
+ }
51566
+ const fastestOnMotion = onMotionRates.reduce((m, r) => Math.max(m, r), 0);
51567
+ const nodeCount = weightByNode.size;
51568
+ const estimatedTotal = estimatedConcurrentSessions(onMotionRates.length, options);
51569
+ return input.eligible.map((d) => {
51570
+ const key = deviceId(d.nodeId, d.deviceKey);
51571
+ const peak = peakById.get(key) ?? (eligibleCountByNode.get(d.nodeId) === 1 ? peakById.get(deviceId(d.nodeId, "")) : void 0);
51572
+ const alwaysOn = alwaysOnCount.get(key) ?? 0;
51573
+ let onMotionSessions = 0;
51574
+ let onMotionSource = "none";
51575
+ if (onMotionRates.length > 0) if (peak !== void 0 && peak.observedForMs >= 864e5) {
51576
+ onMotionSessions = Math.max(0, Math.round(peak.peakSessions - alwaysOn));
51577
+ onMotionSource = "observed-24h";
51578
+ } else {
51579
+ const nodeWeight = weightByNode.get(d.nodeId) ?? 0;
51580
+ const share = nodeCount > 0 && nodeWeight > 0 ? Math.max(d.weight, 0) / nodeWeight / nodeCount : 0;
51581
+ onMotionSessions = Math.ceil(estimatedTotal * share);
51582
+ onMotionSource = "estimate";
51583
+ }
51584
+ const demandFps = (pinnedFps.get(key) ?? 0) + (alwaysOnFps.get(key) ?? 0) + topRatesSum(onMotionRates, onMotionSessions);
51585
+ return {
51586
+ nodeId: d.nodeId,
51587
+ deviceKey: d.deviceKey,
51588
+ demandFps: Math.round(demandFps * 100) / 100,
51589
+ maxCameraFps: Math.max(maxFps.get(key) ?? 0, fastestOnMotion),
51590
+ alwaysOnCameras: Math.round(alwaysOn * 100) / 100,
51591
+ onMotionSessions,
51592
+ onMotionSource,
51593
+ observedForMs: peak?.observedForMs ?? null
51594
+ };
51595
+ }).toSorted((a, b) => a.nodeId === b.nodeId ? a.deviceKey.localeCompare(b.deviceKey) : a.nodeId.localeCompare(b.nodeId));
51596
+ }
51597
+ /** The family the cluster runs. */
51598
+ var BEST_SETTINGS_FAMILY = "yolov9";
51599
+ function round(value, digits = 3) {
51600
+ const f = 10 ** digits;
51601
+ return Math.round(value * f) / f;
51602
+ }
51603
+ function utilisationOf(cell, demand) {
51604
+ return cell.fps > 0 ? round((demand?.demandFps ?? 0) / cell.fps) : Number.POSITIVE_INFINITY;
51605
+ }
51606
+ function fitsOn(cell, demand, headroom) {
51607
+ const budget = cell.fps * (1 - headroom);
51608
+ return budget >= (demand?.demandFps ?? 0) && budget >= (demand?.maxCameraFps ?? 0);
51609
+ }
51610
+ function candidatesFor(contexts, formatsByModel) {
51611
+ return Object.keys(DETECTOR_ACCURACY).filter((id) => id.startsWith(BEST_SETTINGS_FAMILY)).filter((id) => contexts.every((c) => c.column[id] !== void 0 && (formatsByModel[id] ?? []).includes(c.device.format))).map((id) => ({
51612
+ modelId: id,
51613
+ personRecall: DETECTOR_ACCURACY[id]?.personRecall ?? 0
51614
+ })).toSorted((a, b) => b.personRecall - a.personRecall || a.modelId.localeCompare(b.modelId));
51615
+ }
51616
+ function totalLatency(contexts, modelId) {
51617
+ return contexts.reduce((sum, c) => sum + (c.column[modelId]?.p50Ms ?? Number.POSITIVE_INFINITY), 0);
51618
+ }
51619
+ function worstUtilisation(contexts, modelId) {
51620
+ return contexts.reduce((worst, c) => {
51621
+ const cell = c.column[modelId];
51622
+ return Math.max(worst, cell ? utilisationOf(cell, c.demand) : Number.POSITIVE_INFINITY);
51623
+ }, 0);
51624
+ }
51625
+ /** One model for a group of devices that must agree (step 2–4 of the header). */
51626
+ function pickForGroup(contexts, formatsByModel, headroom) {
51627
+ const candidates = candidatesFor(contexts, formatsByModel);
51628
+ if (candidates.length === 0) return null;
51629
+ const fitting = candidates.filter((c) => contexts.every((ctx) => {
51630
+ const cell = ctx.column[c.modelId];
51631
+ return cell !== void 0 && fitsOn(cell, ctx.demand, headroom);
51632
+ }));
51633
+ if (fitting.length === 0) {
51634
+ const lightest = candidates.toSorted((a, b) => worstUtilisation(contexts, a.modelId) - worstUtilisation(contexts, b.modelId))[0];
51635
+ if (lightest === void 0) return null;
51636
+ return {
51637
+ modelId: lightest.modelId,
51638
+ reasons: [{
51639
+ kind: "no-fit-lightest",
51640
+ utilisation: worstUtilisation(contexts, lightest.modelId)
51641
+ }]
51642
+ };
51643
+ }
51644
+ const best = fitting[0];
51645
+ if (best === void 0) return null;
51646
+ const chosen = fitting.filter((c) => best.personRecall - c.personRecall <= .02).toSorted((a, b) => totalLatency(contexts, a.modelId) - totalLatency(contexts, b.modelId) || b.personRecall - a.personRecall)[0] ?? best;
51647
+ const reasons = [];
51648
+ const mostAccurate = candidates[0];
51649
+ if (mostAccurate !== void 0 && mostAccurate.modelId !== best.modelId) {
51650
+ const limitedBy = contexts.find((ctx) => {
51651
+ const cell = ctx.column[mostAccurate.modelId];
51652
+ return cell === void 0 || !fitsOn(cell, ctx.demand, headroom);
51653
+ })?.device.key ?? "";
51654
+ reasons.push({
51655
+ kind: "stepped-down",
51656
+ from: mostAccurate.modelId,
51657
+ to: best.modelId,
51658
+ limitedBy
51659
+ });
51660
+ }
51661
+ if (chosen.modelId !== best.modelId) reasons.push({
51662
+ kind: "tie-faster",
51663
+ over: best.modelId,
51664
+ recallDelta: round(best.personRecall - chosen.personRecall)
51665
+ });
51666
+ return {
51667
+ modelId: chosen.modelId,
51668
+ reasons
51669
+ };
51670
+ }
51671
+ function choiceOn(ctx, modelId) {
51672
+ const cell = ctx.column[modelId];
51673
+ if (cell === void 0) return null;
51674
+ return {
51675
+ modelId,
51676
+ p50Ms: cell.p50Ms,
51677
+ fps: cell.fps,
51678
+ measured: cell.measured,
51679
+ utilisation: utilisationOf(cell, ctx.demand),
51680
+ personRecall: DETECTOR_ACCURACY[modelId]?.personRecall ?? 0
51681
+ };
51682
+ }
51683
+ function deviceReasonsFor(ctx, choice) {
51684
+ const out = [{
51685
+ kind: "fits",
51686
+ utilisation: choice.utilisation
51687
+ }];
51688
+ if (!choice.measured) out.push({
51689
+ kind: "derived-latency",
51690
+ modelId: choice.modelId
51691
+ });
51692
+ if (ctx.demand?.onMotionSource === "estimate") out.push({
51693
+ kind: "estimated-load",
51694
+ observedForMs: ctx.demand.observedForMs
51695
+ });
51696
+ return out;
51697
+ }
51698
+ function skippedNode(node, reason, hardwareClass) {
51699
+ return {
51700
+ nodeId: node.nodeId,
51701
+ reachable: node.reachable,
51702
+ hardwareClass,
51703
+ cpuModel: node.hardware?.cpuModel ?? null,
51704
+ skipped: true,
51705
+ acceleratorModelId: null,
51706
+ devices: node.devices.map((d) => ({
51707
+ deviceKey: d.key,
51708
+ accelerator: node.hardware ? acceleratorKindOf(d.key, node.hardware) : null,
51709
+ enabled: d.enabled,
51710
+ available: d.available,
51711
+ demand: null,
51712
+ chosen: null,
51713
+ reasons: []
51714
+ })),
51715
+ reasons: [reason]
51716
+ };
51717
+ }
51718
+ /** Recommend one node. See the module header. */
51719
+ function recommendNode(input) {
51720
+ const { node } = input;
51721
+ const headroom = input.headroom ?? .4;
51722
+ const perf = input.perf ?? DETECTOR_PERF;
51723
+ if (!node.reachable) return skippedNode(node, { kind: "node-offline" }, null);
51724
+ if (node.hardware === null) return skippedNode(node, { kind: "no-hardware-facts" }, null);
51725
+ const hardware = node.hardware;
51726
+ const { hardwareClass, cpuModel } = classifyInferenceHardware(hardware);
51727
+ const classTable = perf[hardwareClass];
51728
+ if (hardwareClass === "unknown" || classTable === void 0) return skippedNode(node, {
51729
+ kind: "no-measured-data",
51730
+ hardwareClass
51731
+ }, hardwareClass);
51732
+ const demandByKey = new Map(input.demand.filter((d) => d.nodeId === node.nodeId).map((d) => [d.deviceKey, d]));
51733
+ const devices = node.devices.toSorted((a, b) => a.key.localeCompare(b.key));
51734
+ const contexts = /* @__PURE__ */ new Map();
51735
+ const early = /* @__PURE__ */ new Map();
51736
+ for (const device of devices) {
51737
+ const accelerator = acceleratorKindOf(device.key, hardware);
51738
+ if (accelerator === "coral") {
51739
+ early.set(device.key, device.enabled ? [{ kind: "coral-untouched" }] : []);
51740
+ continue;
51741
+ }
51742
+ if (!device.enabled) {
51743
+ early.set(device.key, [{ kind: "device-disabled" }]);
51744
+ continue;
51745
+ }
51746
+ if (!device.available) {
51747
+ early.set(device.key, [{ kind: "device-unavailable" }]);
51748
+ continue;
51749
+ }
51750
+ const column = accelerator === null ? void 0 : classTable[accelerator];
51751
+ if (accelerator === null || column === void 0) {
51752
+ early.set(device.key, [{
51753
+ kind: "no-family-build",
51754
+ format: device.format
51755
+ }]);
51756
+ continue;
51757
+ }
51758
+ contexts.set(device.key, {
51759
+ device,
51760
+ accelerator,
51761
+ column,
51762
+ demand: demandByKey.get(device.key) ?? null
51763
+ });
51764
+ }
51765
+ const isCpu = (a) => a === "x86-cpu" || a === "arm-cpu";
51766
+ const accelerators = [...contexts.values()].filter((c) => !isCpu(c.accelerator));
51767
+ const cpus = [...contexts.values()].filter((c) => isCpu(c.accelerator));
51768
+ const chosenByKey = /* @__PURE__ */ new Map();
51769
+ const reasonsByKey = /* @__PURE__ */ new Map();
51770
+ const nodeReasons = [];
51771
+ let acceleratorModelId = null;
51772
+ const groupPick = accelerators.length > 0 ? pickForGroup(accelerators, node.formatsByModel, headroom) : null;
51773
+ if (groupPick !== null) {
51774
+ acceleratorModelId = groupPick.modelId;
51775
+ nodeReasons.push(...groupPick.reasons);
51776
+ for (const ctx of accelerators) {
51777
+ const choice = choiceOn(ctx, groupPick.modelId);
51778
+ if (choice === null) continue;
51779
+ chosenByKey.set(ctx.device.key, choice);
51780
+ reasonsByKey.set(ctx.device.key, deviceReasonsFor(ctx, choice));
51781
+ }
51782
+ } else for (const ctx of accelerators) reasonsByKey.set(ctx.device.key, [{
51783
+ kind: "no-family-build",
51784
+ format: ctx.device.format
51785
+ }]);
51786
+ for (const ctx of cpus) {
51787
+ const pick = pickForGroup([ctx], node.formatsByModel, headroom);
51788
+ const choice = pick === null ? null : choiceOn(ctx, pick.modelId);
51789
+ if (pick === null || choice === null) {
51790
+ reasonsByKey.set(ctx.device.key, [{
51791
+ kind: "no-family-build",
51792
+ format: ctx.device.format
51793
+ }]);
51794
+ continue;
51795
+ }
51796
+ chosenByKey.set(ctx.device.key, choice);
51797
+ reasonsByKey.set(ctx.device.key, [
51798
+ { kind: "cpu-device" },
51799
+ ...pick.reasons,
51800
+ ...deviceReasonsFor(ctx, choice)
51801
+ ]);
51802
+ }
51803
+ return {
51804
+ nodeId: node.nodeId,
51805
+ reachable: true,
51806
+ hardwareClass,
51807
+ cpuModel,
51808
+ skipped: chosenByKey.size === 0,
51809
+ acceleratorModelId,
51810
+ devices: devices.map((device) => ({
51811
+ deviceKey: device.key,
51812
+ accelerator: acceleratorKindOf(device.key, hardware),
51813
+ enabled: device.enabled,
51814
+ available: device.available,
51815
+ demand: demandByKey.get(device.key) ?? null,
51816
+ chosen: chosenByKey.get(device.key) ?? null,
51817
+ reasons: early.get(device.key) ?? reasonsByKey.get(device.key) ?? []
51818
+ })),
51819
+ reasons: nodeReasons
51820
+ };
51821
+ }
50687
51822
  /**
50688
51823
  * THE detail-crop convention — the single derivation of the rectangle a
50689
51824
  * detail/enrichment step (clip-embedding, face-detection, plate-detection…)
@@ -51458,7 +52593,7 @@ var AudioSubscriptionController = class {
51458
52593
  } catch (err) {
51459
52594
  this.deps.logger.error("audio re-attach failed", {
51460
52595
  tags: { deviceId },
51461
- meta: { error: errMsg$1(err) }
52596
+ meta: { error: errMsg$2(err) }
51462
52597
  });
51463
52598
  }
51464
52599
  }
@@ -51569,7 +52704,7 @@ var AudioSubscriptionController = class {
51569
52704
  this.closeMotionAudioWindow(deviceId, "motion-cooldown-elapsed").catch((err) => {
51570
52705
  this.deps.logger.warn("lazy audio: cooldown teardown failed", {
51571
52706
  tags: { deviceId },
51572
- meta: { error: errMsg$1(err) }
52707
+ meta: { error: errMsg$2(err) }
51573
52708
  });
51574
52709
  });
51575
52710
  }, cooldownMs);
@@ -51588,7 +52723,7 @@ var AudioSubscriptionController = class {
51588
52723
  this.closeMotionAudioWindow(deviceId, "quiet-window-elapsed").catch((err) => {
51589
52724
  this.deps.logger.warn("lazy audio: quiet-window teardown failed", {
51590
52725
  tags: { deviceId },
51591
- meta: { error: errMsg$1(err) }
52726
+ meta: { error: errMsg$2(err) }
51592
52727
  });
51593
52728
  });
51594
52729
  }, windowMs);
@@ -51790,7 +52925,7 @@ var AudioSubscriptionController = class {
51790
52925
  meta: {
51791
52926
  audioNodeId,
51792
52927
  brokerId: audioBrokerId,
51793
- error: errMsg$1(err)
52928
+ error: errMsg$2(err)
51794
52929
  }
51795
52930
  });
51796
52931
  });
@@ -51865,7 +53000,7 @@ async function readOrRefuse(deps, deviceId) {
51865
53000
  try {
51866
53001
  device = await deps.readDevice(deviceId);
51867
53002
  } catch (err) {
51868
- return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$1(err)}`);
53003
+ return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$2(err)}`);
51869
53004
  }
51870
53005
  if (device === null) return refuse(deps, deviceId, `no device ${deviceId}`);
51871
53006
  return device;
@@ -53195,7 +54330,7 @@ var LoadShedController = class LoadShedController {
53195
54330
  if (await this.tryClusterRelocate(deviceId).catch((err) => {
53196
54331
  this.deps.logger.warn("Load management: cluster relocate failed", {
53197
54332
  tags: { deviceId },
53198
- meta: { error: errMsg$1(err) }
54333
+ meta: { error: errMsg$2(err) }
53199
54334
  });
53200
54335
  return false;
53201
54336
  })) {
@@ -53209,7 +54344,7 @@ var LoadShedController = class LoadShedController {
53209
54344
  this.deps.api()?.pipelineRunner.detachCamera.mutate({ deviceId }).catch((err) => {
53210
54345
  this.deps.logger.warn("Load management: detachCamera failed", {
53211
54346
  tags: { deviceId },
53212
- meta: { error: errMsg$1(err) }
54347
+ meta: { error: errMsg$2(err) }
53213
54348
  });
53214
54349
  });
53215
54350
  this.ensureLoadShedResumeTimer();
@@ -53241,7 +54376,7 @@ var LoadShedController = class LoadShedController {
53241
54376
  tags: { deviceId },
53242
54377
  meta: {
53243
54378
  from: currentNode,
53244
- error: errMsg$1(err)
54379
+ error: errMsg$2(err)
53245
54380
  }
53246
54381
  });
53247
54382
  });
@@ -53284,7 +54419,7 @@ var LoadShedController = class LoadShedController {
53284
54419
  this.deps.redispatchSingleCamera(deviceId).catch((err) => {
53285
54420
  this.deps.logger.warn("Load management: auto-resume dispatch failed", {
53286
54421
  tags: { deviceId },
53287
- meta: { error: errMsg$1(err) }
54422
+ meta: { error: errMsg$2(err) }
53288
54423
  });
53289
54424
  });
53290
54425
  }
@@ -53449,7 +54584,7 @@ var AgentLoadService = class AgentLoadService {
53449
54584
  ms,
53450
54585
  outcome: "failed",
53451
54586
  load: null,
53452
- error: errMsg$1(outcome.error)
54587
+ error: errMsg$2(outcome.error)
53453
54588
  };
53454
54589
  return {
53455
54590
  nodeId,
@@ -53916,7 +55051,7 @@ function buildCameraSensorsPorts(api, logger) {
53916
55051
  } catch (err) {
53917
55052
  return {
53918
55053
  ok: false,
53919
- error: errMsg$1(err)
55054
+ error: errMsg$2(err)
53920
55055
  };
53921
55056
  }
53922
55057
  };
@@ -54180,7 +55315,7 @@ var CameraSensorsController = class {
54180
55315
  async #readFleet() {
54181
55316
  const read = await this.#deps.listCameraIds().catch((err) => ({
54182
55317
  kind: "unreadable",
54183
- error: errMsg$1(err)
55318
+ error: errMsg$2(err)
54184
55319
  }));
54185
55320
  if (read.kind === "unreadable") {
54186
55321
  this.#deps.logger.warn("camera-sensors: camera list unreadable — mappings stay unloaded", { meta: { error: read.error } });
@@ -54207,7 +55342,7 @@ var CameraSensorsController = class {
54207
55342
  } catch (err) {
54208
55343
  this.#deps.logger.error("camera-sensors: refresh failed — nothing applied", {
54209
55344
  tags: { deviceId: cameraId },
54210
- meta: { error: errMsg$1(err) }
55345
+ meta: { error: errMsg$2(err) }
54211
55346
  });
54212
55347
  }
54213
55348
  }
@@ -54300,7 +55435,7 @@ async function readCameraSensorsSetting(read) {
54300
55435
  } catch (err) {
54301
55436
  return {
54302
55437
  state: "unreadable",
54303
- reason: `settings read failed — ${errMsg$1(err)}`
55438
+ reason: `settings read failed — ${errMsg$2(err)}`
54304
55439
  };
54305
55440
  }
54306
55441
  }
@@ -54610,7 +55745,7 @@ var CameraMotionSlice = class {
54610
55745
  st.chain = st.chain.then(job).catch((err) => {
54611
55746
  this.#deps.logger.error("camera motion slice: write job failed", {
54612
55747
  tags: { deviceId },
54613
- meta: { error: errMsg$1(err) }
55748
+ meta: { error: errMsg$2(err) }
54614
55749
  });
54615
55750
  });
54616
55751
  }
@@ -54674,7 +55809,7 @@ var CameraMotionSlice = class {
54674
55809
  meta: {
54675
55810
  detected: slice.detected,
54676
55811
  reason,
54677
- error: errMsg$1(err)
55812
+ error: errMsg$2(err)
54678
55813
  }
54679
55814
  });
54680
55815
  return;
@@ -54704,7 +55839,7 @@ var CameraMotionSlice = class {
54704
55839
  async #republishAll() {
54705
55840
  const listed = await this.#deps.listCameraIds().catch((err) => ({
54706
55841
  kind: "unreadable",
54707
- error: errMsg$1(err)
55842
+ error: errMsg$2(err)
54708
55843
  }));
54709
55844
  if (!this.#open || this.#stopped) return;
54710
55845
  if (listed.kind === "unreadable") this.#deps.logger.warn("camera motion slice: camera list unreadable — only cameras with motion since boot are re-published", { meta: { error: listed.error } });
@@ -54826,7 +55961,7 @@ function hubMotionSliceStore(api) {
54826
55961
  } catch (err) {
54827
55962
  return {
54828
55963
  kind: "unreadable",
54829
- error: errMsg$1(err)
55964
+ error: errMsg$2(err)
54830
55965
  };
54831
55966
  }
54832
55967
  }
@@ -56698,7 +57833,7 @@ function warnSampled(deps, deviceId, source, message, meta) {
56698
57833
  function warnUnreachable(deps, deviceId, source, err) {
56699
57834
  warnSampled(deps, deviceId, source, UNREACHABLE_MESSAGE, {
56700
57835
  source,
56701
- error: errMsg$1(err)
57836
+ error: errMsg$2(err)
56702
57837
  });
56703
57838
  }
56704
57839
  /**
@@ -57972,7 +59107,7 @@ var ONBOARD_DETECTION_SOURCE = "onboard";
57972
59107
  * configs, and it reads them the same way the runner does.
57973
59108
  */
57974
59109
  var DETECTION_SOURCES_WHEN_ABSENT = ["pipeline"];
57975
- function errMsg(err) {
59110
+ function errMsg$1(err) {
57976
59111
  return err instanceof Error ? err.message : String(err);
57977
59112
  }
57978
59113
  /**
@@ -57991,7 +59126,7 @@ async function reconcileOnboardForwarding(input, deps) {
57991
59126
  log.warn("onboard forwarding not reconciled — could not tell whether the camera has the cap", { meta: {
57992
59127
  wantsOnboard,
57993
59128
  detectionSources: [...detectionSources],
57994
- error: errMsg(err)
59129
+ error: errMsg$1(err)
57995
59130
  } });
57996
59131
  return {
57997
59132
  kind: "read-failed",
@@ -58009,7 +59144,7 @@ async function reconcileOnboardForwarding(input, deps) {
58009
59144
  log.warn("onboard forwarding not reconciled — the vendor flag could not be read", { meta: {
58010
59145
  wantsOnboard,
58011
59146
  detectionSources: [...detectionSources],
58012
- error: errMsg(err)
59147
+ error: errMsg$1(err)
58013
59148
  } });
58014
59149
  return {
58015
59150
  kind: "read-failed",
@@ -58027,7 +59162,7 @@ async function reconcileOnboardForwarding(input, deps) {
58027
59162
  was: current,
58028
59163
  wanted: wantsOnboard,
58029
59164
  detectionSources: [...detectionSources],
58030
- error: errMsg(err)
59165
+ error: errMsg$1(err)
58031
59166
  } });
58032
59167
  return {
58033
59168
  kind: "write-failed",
@@ -58223,7 +59358,7 @@ var DetectionWiringController = class {
58223
59358
  try {
58224
59359
  raw = await this.deps.ctx().settings.readDeviceStore(deviceId);
58225
59360
  } catch (err) {
58226
- const msg = errMsg$1(err);
59361
+ const msg = errMsg$2(err);
58227
59362
  this.deps.logger.error("resolveDeviceDetectionSettings failed", {
58228
59363
  tags: { deviceId },
58229
59364
  meta: { error: msg }
@@ -58254,7 +59389,7 @@ var DetectionWiringController = class {
58254
59389
  motionDetectionEnabled
58255
59390
  });
58256
59391
  } catch (err) {
58257
- const msg = errMsg$1(err);
59392
+ const msg = errMsg$2(err);
58258
59393
  this.deps.logger.error("resolveDeviceDetectionSettings narrowing failed", {
58259
59394
  tags: { deviceId },
58260
59395
  meta: { error: msg }
@@ -58289,7 +59424,7 @@ var DetectionWiringController = class {
58289
59424
  } catch (err) {
58290
59425
  this.deps.logger.debug("fetchAssignedProfiles: slot read failed (transient) — treating as unknown", {
58291
59426
  tags: { deviceId },
58292
- meta: { error: errMsg$1(err) }
59427
+ meta: { error: errMsg$2(err) }
58293
59428
  });
58294
59429
  return null;
58295
59430
  }
@@ -58365,7 +59500,7 @@ var DetectionWiringController = class {
58365
59500
  if (config.motionSources.includes("analyzer")) try {
58366
59501
  await this.deps.session.ensureMotionWatch(deviceId, config);
58367
59502
  } catch (err) {
58368
- log.error("ensureMotionWatch failed", { meta: { error: errMsg$1(err) } });
59503
+ log.error("ensureMotionWatch failed", { meta: { error: errMsg$2(err) } });
58369
59504
  }
58370
59505
  log.info("detection armed (session-time) — watching for motion", { meta: { motion: config.motionSources.join("+") } });
58371
59506
  } else {
@@ -58386,14 +59521,14 @@ var DetectionWiringController = class {
58386
59521
  try {
58387
59522
  await this.deps.placement.dispatchCamera(runnerConfig);
58388
59523
  } catch (err) {
58389
- const msg = errMsg$1(err);
59524
+ const msg = errMsg$2(err);
58390
59525
  log.error("dispatchCamera failed", { meta: { error: msg } });
58391
59526
  }
58392
59527
  }
58393
59528
  try {
58394
59529
  await this.deps.audio.resubscribe(deviceId, config);
58395
59530
  } catch (err) {
58396
- const msg = errMsg$1(err);
59531
+ const msg = errMsg$2(err);
58397
59532
  log.error("subscribeAudioStream failed", { meta: { error: msg } });
58398
59533
  }
58399
59534
  if (config.detectionMode !== "on-motion") log.info("Detection dispatched", { meta: {
@@ -58409,7 +59544,7 @@ var DetectionWiringController = class {
58409
59544
  try {
58410
59545
  await this.deps.placement.releaseCamera({ deviceId });
58411
59546
  } catch (err) {
58412
- const msg = errMsg$1(err);
59547
+ const msg = errMsg$2(err);
58413
59548
  this.deps.logger.error("releaseCamera failed", {
58414
59549
  tags: { deviceId },
58415
59550
  meta: { error: msg }
@@ -58435,7 +59570,7 @@ var DetectionWiringController = class {
58435
59570
  } catch (err) {
58436
59571
  this.deps.logger.warn("failed to clear per-device orchestrator settings on removal", {
58437
59572
  tags: { deviceId },
58438
- meta: { error: errMsg$1(err) }
59573
+ meta: { error: errMsg$2(err) }
58439
59574
  });
58440
59575
  }
58441
59576
  this.deps.logger.info("Purged pipeline config for removed device", { tags: { deviceId } });
@@ -58459,7 +59594,7 @@ var DetectionWiringController = class {
58459
59594
  } catch (err) {
58460
59595
  this.deps.logger.debug("device existence check failed — proceeding as before", {
58461
59596
  tags: { deviceId },
58462
- meta: { error: errMsg$1(err) }
59597
+ meta: { error: errMsg$2(err) }
58463
59598
  });
58464
59599
  return false;
58465
59600
  }
@@ -58576,7 +59711,7 @@ var DetectionWiringController = class {
58576
59711
  await this.deps.attachOn(assignment.agentNodeId, nextConfig);
58577
59712
  log.info("pipeline.camera-updated: hot-reloaded", { tags: { nodeId: assignment.agentNodeId } });
58578
59713
  } catch (err) {
58579
- const msg = errMsg$1(err);
59714
+ const msg = errMsg$2(err);
58580
59715
  log.error("pipeline.camera-updated: attachOn failed", {
58581
59716
  tags: { nodeId: assignment.agentNodeId },
58582
59717
  meta: { error: msg }
@@ -58652,7 +59787,7 @@ var DetectionWiringController = class {
58652
59787
  this.deps.audio.resubscribe(deviceId, audioCfg).catch((err) => {
58653
59788
  this.deps.logger.warn("watchdog audio re-subscribe failed", {
58654
59789
  tags: { deviceId },
58655
- meta: { error: errMsg$1(err) }
59790
+ meta: { error: errMsg$2(err) }
58656
59791
  });
58657
59792
  });
58658
59793
  }
@@ -58851,7 +59986,7 @@ var DeviceConfigContributions = class {
58851
59986
  issue: configIssue
58852
59987
  } });
58853
59988
  this.deps.topology.refreshNodeCapabilities().then(() => this.deps.redispatchAllActiveCameras("ingest-owner-changed")).catch((err) => {
58854
- this.deps.ctx().logger.warn("ingest-owner-changed re-dispatch failed", { meta: { error: errMsg$1(err) } });
59989
+ this.deps.ctx().logger.warn("ingest-owner-changed re-dispatch failed", { meta: { error: errMsg$2(err) } });
58855
59990
  });
58856
59991
  }
58857
59992
  this.deps.clearOnSettingsChange();
@@ -59305,10 +60440,25 @@ function rootRefusal(input, droppedRoots) {
59305
60440
  droppedRoots
59306
60441
  };
59307
60442
  }
60443
+ /**
60444
+ * Whose provisioning an attach applies. The elected device when there is one.
60445
+ * With NO elected device the runner's default pool serves the camera; when the
60446
+ * node has exactly one eligible device the balancer returns
60447
+ * `single-eligible-device` and that default pool IS that device (a Mac's
60448
+ * `coreml:all`, an N100 with only its iGPU on), so its model pin and the
60449
+ * camera's override stored under its key apply (D745 — before, the empty key
60450
+ * was read, which no store writes, and the pin was silently never run).
60451
+ * Otherwise `''`: the node's folded settings with no hardware fields.
60452
+ */
60453
+ function provisioningKeyFor(input) {
60454
+ if (input.deviceKey !== void 0) return input.deviceKey;
60455
+ const eligibleKeys = Object.keys(input.eligible);
60456
+ return eligibleKeys.length === 1 ? eligibleKeys[0] ?? "" : "";
60457
+ }
59308
60458
  /** Steps 3 of the module header: provision the resolved structure. */
59309
60459
  async function provisionAttachPipeline(input, roster, resolved, store) {
59310
60460
  const { deviceId, nodeId, deviceKey } = input;
59311
- const electedKey = deviceKey ?? "";
60461
+ const electedKey = provisioningKeyFor(input);
59312
60462
  const rosterKeys = (roster ?? []).map((d) => d.deviceKey);
59313
60463
  const sources = await store.readDeviceProvisioning(deviceId, nodeId, [...new Set([electedKey, ...rosterKeys])]);
59314
60464
  const elected = sources.get(electedKey);
@@ -59916,7 +61066,7 @@ var PlacementService = class {
59916
61066
  async releaseCamera(input) {
59917
61067
  const current = this.deps.ledger.getAssignment(input.deviceId);
59918
61068
  if (current) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
59919
- const msg = errMsg$1(err);
61069
+ const msg = errMsg$2(err);
59920
61070
  this.deps.ctx().logger.debug("releaseCamera detach failed", {
59921
61071
  tags: { deviceId: input.deviceId },
59922
61072
  meta: { error: msg }
@@ -59944,7 +61094,7 @@ var PlacementService = class {
59944
61094
  pipelineNodeId: input.agentNodeId,
59945
61095
  [PREFERRED_AGENT_SETTING]: null
59946
61096
  }).catch((err) => {
59947
- const msg = errMsg$1(err);
61097
+ const msg = errMsg$2(err);
59948
61098
  ctx.logger.warn("assignPipeline: failed to persist pin", {
59949
61099
  tags: { deviceId: input.deviceId },
59950
61100
  meta: { error: msg }
@@ -59976,7 +61126,7 @@ var PlacementService = class {
59976
61126
  }
59977
61127
  const current = this.deps.ledger.getAssignment(input.deviceId);
59978
61128
  if (current && current.agentNodeId !== input.agentNodeId) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
59979
- const msg = errMsg$1(err);
61129
+ const msg = errMsg$2(err);
59980
61130
  ctx.logger.debug("assignPipeline detach-old failed", {
59981
61131
  tags: { deviceId: input.deviceId },
59982
61132
  meta: { error: msg }
@@ -60001,7 +61151,7 @@ var PlacementService = class {
60001
61151
  await ctx.settings?.writeDeviceStore(input.deviceId, { pipelineDeviceKey: clearing ? "auto" : input.deviceKey }).catch((err) => {
60002
61152
  ctx.logger.warn("setPipelineDevicePin: failed to persist device pin", {
60003
61153
  tags: { deviceId: input.deviceId },
60004
- meta: { error: errMsg$1(err) }
61154
+ meta: { error: errMsg$2(err) }
60005
61155
  });
60006
61156
  });
60007
61157
  const sessionNode = this.deps.activeSessionNode(input.deviceId);
@@ -60024,7 +61174,7 @@ var PlacementService = class {
60024
61174
  [PREFERRED_AGENT_SETTING]: null,
60025
61175
  pipelineNodeId: "auto"
60026
61176
  }).catch((err) => {
60027
- const msg = errMsg$1(err);
61177
+ const msg = errMsg$2(err);
60028
61178
  ctx.logger.warn("unassignPipeline: failed to clear pin", {
60029
61179
  tags: { deviceId: input.deviceId },
60030
61180
  meta: { error: msg }
@@ -60045,7 +61195,7 @@ var PlacementService = class {
60045
61195
  else {
60046
61196
  const targetNodeId = decision.agentNodeId;
60047
61197
  if (current && current.agentNodeId !== targetNodeId) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
60048
- const msg = errMsg$1(err);
61198
+ const msg = errMsg$2(err);
60049
61199
  ctx.logger.debug("unassignPipeline detach-old failed", {
60050
61200
  tags: { deviceId: input.deviceId },
60051
61201
  meta: { error: msg }
@@ -60065,7 +61215,7 @@ var PlacementService = class {
60065
61215
  }
60066
61216
  }
60067
61217
  if (current) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
60068
- const msg = errMsg$1(err);
61218
+ const msg = errMsg$2(err);
60069
61219
  ctx.logger.debug("unassignPipeline detach failed", {
60070
61220
  tags: { deviceId: input.deviceId },
60071
61221
  meta: { error: msg }
@@ -60084,7 +61234,7 @@ var PlacementService = class {
60084
61234
  async runAutoRebalance() {
60085
61235
  if (!this.deps.ctxIfReady()) return;
60086
61236
  await this.deps.reconcilePlacementFromRunners().catch((err) => {
60087
- this.deps.ctx().logger.debug("auto-rebalance: attachment reconcile failed", { meta: { error: errMsg$1(err) } });
61237
+ this.deps.ctx().logger.debug("auto-rebalance: attachment reconcile failed", { meta: { error: errMsg$2(err) } });
60088
61238
  });
60089
61239
  if (this.autoRebalanceInFlight) return;
60090
61240
  if (this.deps.topology.enabledNodes.length < 2) return;
@@ -60093,7 +61243,7 @@ var PlacementService = class {
60093
61243
  const { migrated } = await this.rebalance({ minImprovement: AUTO_REBALANCE_MIN_IMPROVEMENT });
60094
61244
  if (migrated > 0) this.deps.ctx().logger.info("auto-rebalance: corrected placement drift", { meta: { migrated } });
60095
61245
  } catch (err) {
60096
- this.deps.ctx().logger.debug("auto-rebalance pass failed", { meta: { error: errMsg$1(err) } });
61246
+ this.deps.ctx().logger.debug("auto-rebalance pass failed", { meta: { error: errMsg$2(err) } });
60097
61247
  } finally {
60098
61248
  this.autoRebalanceInFlight = false;
60099
61249
  }
@@ -60165,7 +61315,7 @@ var PlacementService = class {
60165
61315
  const targetConfig = withAttachPipeline(config, placement.pipeline);
60166
61316
  if (current) {
60167
61317
  await this.detachOn(current.agentNodeId, deviceId).catch((err) => {
60168
- const msg = errMsg$1(err);
61318
+ const msg = errMsg$2(err);
60169
61319
  ctx.logger.debug("rebalance detach-old failed", {
60170
61320
  tags: { deviceId },
60171
61321
  meta: { error: msg }
@@ -60570,7 +61720,7 @@ var NodeLifecycleHandler = class {
60570
61720
  nodeId: decision.agentNodeId
60571
61721
  } });
60572
61722
  } catch (err) {
60573
- const msg = errMsg$1(err);
61723
+ const msg = errMsg$2(err);
60574
61724
  this.deps.logger.error("Failover: attach failed", {
60575
61725
  tags: {
60576
61726
  deviceId,
@@ -60600,7 +61750,7 @@ var NodeLifecycleHandler = class {
60600
61750
  this.deps.schedulePendingRetry();
60601
61751
  if (failoverPolicy.onReconnect === "rebalance") {
60602
61752
  await this.deps.rebalance().catch((err) => {
60603
- const msg = errMsg$1(err);
61753
+ const msg = errMsg$2(err);
60604
61754
  this.deps.logger.error("rebalance after reconnect failed", { meta: { error: msg } });
60605
61755
  });
60606
61756
  return;
@@ -60626,7 +61776,7 @@ var NodeLifecycleHandler = class {
60626
61776
  this.deps.ledger.recordAssignment(deviceId, nodeId, "failover", false);
60627
61777
  restored++;
60628
61778
  } catch (err) {
60629
- const msg = errMsg$1(err);
61779
+ const msg = errMsg$2(err);
60630
61780
  this.deps.logger.error("restore attach failed", {
60631
61781
  tags: {
60632
61782
  deviceId,
@@ -60720,7 +61870,7 @@ var NodeLifecycleHandler = class {
60720
61870
  deviceId,
60721
61871
  nodeId
60722
61872
  },
60723
- meta: { error: errMsg$1(err) }
61873
+ meta: { error: errMsg$2(err) }
60724
61874
  });
60725
61875
  }
60726
61876
  this.deps.logger.info("readiness: detection-pipeline reseeded agent defaults + redispatched cameras", {
@@ -60734,7 +61884,7 @@ var NodeLifecycleHandler = class {
60734
61884
  this.reattachStaleEmpties(nodeId).catch((err) => {
60735
61885
  this.deps.logger.debug("readiness deferred sweep failed", {
60736
61886
  tags: { nodeId },
60737
- meta: { error: errMsg$1(err) }
61887
+ meta: { error: errMsg$2(err) }
60738
61888
  });
60739
61889
  });
60740
61890
  }, 3e3);
@@ -60745,7 +61895,7 @@ var NodeLifecycleHandler = class {
60745
61895
  tags: { nodeId },
60746
61896
  meta: {
60747
61897
  epoch: t.epoch,
60748
- error: errMsg$1(err)
61898
+ error: errMsg$2(err)
60749
61899
  }
60750
61900
  });
60751
61901
  }
@@ -60824,7 +61974,7 @@ var NodeLifecycleHandler = class {
60824
61974
  deviceId,
60825
61975
  nodeId
60826
61976
  },
60827
- meta: { error: errMsg$1(err) }
61977
+ meta: { error: errMsg$2(err) }
60828
61978
  });
60829
61979
  }
60830
61980
  }
@@ -61146,7 +62296,7 @@ var NodeStressController = class NodeStressController {
61146
62296
  if (this.timer) return;
61147
62297
  this.timer = setInterval(() => {
61148
62298
  this.sweep().catch((err) => {
61149
- this.deps.logger.warn("node-stress sweep failed", { meta: { error: errMsg$1(err) } });
62299
+ this.deps.logger.warn("node-stress sweep failed", { meta: { error: errMsg$2(err) } });
61150
62300
  });
61151
62301
  }, NodeStressController.SWEEP_INTERVAL_MS);
61152
62302
  }
@@ -61319,7 +62469,7 @@ var NodeStressController = class NodeStressController {
61319
62469
  meta: {
61320
62470
  from: plan.fromNodeId,
61321
62471
  to: plan.toNodeId,
61322
- error: errMsg$1(err)
62472
+ error: errMsg$2(err)
61323
62473
  }
61324
62474
  });
61325
62475
  } finally {
@@ -62016,7 +63166,7 @@ var NodeTopologyService = class {
62016
63166
  try {
62017
63167
  blob = await this.deps.readAgentSettingsMap();
62018
63168
  } catch (err) {
62019
- this.deps.logger.warn("refreshNodeCapabilities: agent settings read failed", { meta: { error: errMsg$1(err) } });
63169
+ this.deps.logger.warn("refreshNodeCapabilities: agent settings read failed", { meta: { error: errMsg$2(err) } });
62020
63170
  }
62021
63171
  const nodeIds = new Set(["hub"]);
62022
63172
  for (const nodeId of Object.keys(blob)) if (typeof nodeId === "string" && nodeId.length > 0 && !nodeId.includes("/")) nodeIds.add(nodeId);
@@ -62190,7 +63340,7 @@ function wireOrchestratorSubscriptions(deps) {
62190
63340
  const unsubInferenceResult = deps.eventBus.subscribe({ category: EventCategory.PipelineInferenceResult }, (event) => {
62191
63341
  if (!isEvent(event, EventCategory.PipelineInferenceResult)) return;
62192
63342
  deps.handleInferenceResult(event.data).catch((err) => {
62193
- const msg = errMsg$1(err);
63343
+ const msg = errMsg$2(err);
62194
63344
  deps.logger.error("PipelineInferenceResult post-processing failed", {
62195
63345
  tags: { deviceId: event.data.deviceId },
62196
63346
  meta: { error: msg }
@@ -62209,7 +63359,7 @@ function wireOrchestratorSubscriptions(deps) {
62209
63359
  deps.logger.info("received onProfileSlotsChanged", { tags: { deviceId } });
62210
63360
  deps.handleDeviceRegistered(deviceId).catch((err) => {
62211
63361
  if (isTornDown) return;
62212
- const msg = errMsg$1(err);
63362
+ const msg = errMsg$2(err);
62213
63363
  deps.logger.error("handleDeviceRegistered failed", {
62214
63364
  tags: { deviceId },
62215
63365
  meta: { error: msg }
@@ -62221,7 +63371,7 @@ function wireOrchestratorSubscriptions(deps) {
62221
63371
  const unsubDeviceUnregistered = deps.eventBus.subscribe({ category: EventCategory.DeviceUnregistered }, (event) => {
62222
63372
  const { deviceId } = event.data;
62223
63373
  deps.handleDeviceUnregistered(deviceId).catch((err) => {
62224
- const msg = errMsg$1(err);
63374
+ const msg = errMsg$2(err);
62225
63375
  deps.logger.error("handleDeviceUnregistered failed", {
62226
63376
  tags: { deviceId },
62227
63377
  meta: { error: msg }
@@ -62234,7 +63384,7 @@ function wireOrchestratorSubscriptions(deps) {
62234
63384
  deps.cameraSensors.requestCamera(deviceId, "settings-updated");
62235
63385
  deps.handleSettingsChanged(deviceId).catch((err) => {
62236
63386
  if (isTornDown) return;
62237
- const msg = errMsg$1(err);
63387
+ const msg = errMsg$2(err);
62238
63388
  deps.logger.error("handleSettingsChanged failed", {
62239
63389
  tags: { deviceId },
62240
63390
  meta: { error: msg }
@@ -62244,7 +63394,7 @@ function wireOrchestratorSubscriptions(deps) {
62244
63394
  const unsubCameraUpdated = deps.eventBus.subscribe({ category: EventCategory.PipelineCameraUpdated }, (event) => {
62245
63395
  if (!isEvent(event, EventCategory.PipelineCameraUpdated)) return;
62246
63396
  deps.handlePipelineCameraUpdated(event.data.deviceId, event.data.config).catch((err) => {
62247
- const msg = errMsg$1(err);
63397
+ const msg = errMsg$2(err);
62248
63398
  deps.logger.error("handlePipelineCameraUpdated failed", {
62249
63399
  tags: { deviceId: event.data.deviceId },
62250
63400
  meta: { error: msg }
@@ -62257,7 +63407,7 @@ function wireOrchestratorSubscriptions(deps) {
62257
63407
  if (!(reason === "wrapper-activated" || reason === "wrapper-deactivated") || !(capName === "detection-pipeline" || capName === "audio-analysis" || capName === "motion-detection")) return;
62258
63408
  deps.handleSettingsChanged(deviceId).catch((err) => {
62259
63409
  if (isTornDown) return;
62260
- const msg = errMsg$1(err);
63410
+ const msg = errMsg$2(err);
62261
63411
  deps.logger.error("binding-changed re-dispatch failed", {
62262
63412
  tags: { deviceId },
62263
63413
  meta: {
@@ -62285,7 +63435,7 @@ function wireOrchestratorSubscriptions(deps) {
62285
63435
  tags: { deviceId },
62286
63436
  meta: {
62287
63437
  detected,
62288
- error: errMsg$1(err)
63438
+ error: errMsg$2(err)
62289
63439
  }
62290
63440
  });
62291
63441
  });
@@ -62332,7 +63482,7 @@ function wireOrchestratorSubscriptions(deps) {
62332
63482
  tags: { deviceId },
62333
63483
  meta: {
62334
63484
  detected,
62335
- error: errMsg$1(err)
63485
+ error: errMsg$2(err)
62336
63486
  }
62337
63487
  });
62338
63488
  });
@@ -62598,7 +63748,7 @@ async function reattachAfterPackageZoneChange(deps, deviceId) {
62598
63748
  deviceId,
62599
63749
  nodeId
62600
63750
  },
62601
- meta: { error: errMsg$1(err) }
63751
+ meta: { error: errMsg$2(err) }
62602
63752
  });
62603
63753
  }
62604
63754
  }
@@ -62879,7 +64029,7 @@ async function resetRemovedModelPins(deps, trigger) {
62879
64029
  } catch (err) {
62880
64030
  deps.logger.warn("removed-model pin reset skipped — custom model registry unreadable", { meta: {
62881
64031
  trigger,
62882
- error: errMsg$1(err)
64032
+ error: errMsg$2(err)
62883
64033
  } });
62884
64034
  return [];
62885
64035
  }
@@ -62898,7 +64048,7 @@ async function resetRemovedModelPins(deps, trigger) {
62898
64048
  } catch (err) {
62899
64049
  deps.logger.warn("removed-model pin reset failed", { meta: {
62900
64050
  trigger,
62901
- error: errMsg$1(err)
64051
+ error: errMsg$2(err)
62902
64052
  } });
62903
64053
  return [];
62904
64054
  }
@@ -63257,6 +64407,21 @@ var PipelineSettingsStore = class PipelineSettingsStore {
63257
64407
  });
63258
64408
  }
63259
64409
  /**
64410
+ * Rewrite the whole agent-settings map in ONE read-modify-write under
64411
+ * {@link blobWriteLock} — atomic across every node of the blob (Best
64412
+ * settings, D745, writes several nodes' devices at once). `transform` must
64413
+ * be pure: it may run on a fresh read.
64414
+ */
64415
+ async rewriteAgentSettingsMap(transform) {
64416
+ await this.blobWriteLock.run(PipelineSettingsStore.AGENT_SETTINGS_LOCK_KEY, async () => {
64417
+ await this.agentSettingsState.set(transform(await this.readAgentSettingsMap()));
64418
+ });
64419
+ }
64420
+ /** Rewrite the whole camera-settings map in one durable `update()` (D745). */
64421
+ async rewriteCameraSettingsMap(transform) {
64422
+ await this.cameraSettingsState.update(transform);
64423
+ }
64424
+ /**
63260
64425
  * Drop one agent's persisted settings entry. Returns whether an entry
63261
64426
  * existed (`false` also when the store is already disposed — the locked
63262
64427
  * section becomes a no-op then). Serialized via {@link blobWriteLock} for
@@ -63513,6 +64678,10 @@ var PipelineSettingsStore = class PipelineSettingsStore {
63513
64678
  * epochs and operator resets — a bound, not a budget.
63514
64679
  */
63515
64680
  static CATALOG_FENCE_REREADS = 3;
64681
+ /** The node's catalog, read through the cache (awaits the node when cold). */
64682
+ async readCatalogForNode(nodeId) {
64683
+ return this.getCatalogForAgent(nodeId);
64684
+ }
63516
64685
  async getCatalogForAgent(nodeId) {
63517
64686
  const api = this.deps.api();
63518
64687
  if (!api) return null;
@@ -63530,7 +64699,7 @@ var PipelineSettingsStore = class PipelineSettingsStore {
63530
64699
  this.catalogReadFailing.add(nodeId);
63531
64700
  this.deps.logger.warn("node catalog read failed — placements on it wait or stay pending", {
63532
64701
  tags: { nodeId },
63533
- meta: { error: errMsg$1(err) }
64702
+ meta: { error: errMsg$2(err) }
63534
64703
  });
63535
64704
  }
63536
64705
  return null;
@@ -63617,7 +64786,7 @@ var PipelineSettingsStore = class PipelineSettingsStore {
63617
64786
  tags: { deviceId },
63618
64787
  meta: {
63619
64788
  capName: "detection-pipeline",
63620
- error: errMsg$1(err)
64789
+ error: errMsg$2(err)
63621
64790
  }
63622
64791
  });
63623
64792
  return true;
@@ -64450,7 +65619,7 @@ var ReconcileController = class ReconcileController {
64450
65619
  try {
64451
65620
  slots = await api.streamBroker.listAllProfileSlots.query();
64452
65621
  } catch (err) {
64453
- this.deps.logger.debug("dispatch reconcile skipped — stream-broker not ready", { meta: { error: errMsg$1(err) } });
65622
+ this.deps.logger.debug("dispatch reconcile skipped — stream-broker not ready", { meta: { error: errMsg$2(err) } });
64454
65623
  return;
64455
65624
  }
64456
65625
  const desired = desiredDeviceIdsFromSlots(slots);
@@ -64477,7 +65646,7 @@ var ReconcileController = class ReconcileController {
64477
65646
  } catch (err) {
64478
65647
  this.deps.logger.warn("dispatch reconcile: handleDeviceRegistered failed", {
64479
65648
  tags: { deviceId },
64480
- meta: { error: errMsg$1(err) }
65649
+ meta: { error: errMsg$2(err) }
64481
65650
  });
64482
65651
  }
64483
65652
  await this.reconcilePlacementFromRunners();
@@ -64505,7 +65674,7 @@ var ReconcileController = class ReconcileController {
64505
65674
  const devices = await api.deviceManager.listAll.query({ projection: "slim" });
64506
65675
  return new Set(devices.map((device) => device.id));
64507
65676
  } catch (err) {
64508
- this.deps.logger.debug("dispatch reconcile: deviceManager.listAll failed", { meta: { error: errMsg$1(err) } });
65677
+ this.deps.logger.debug("dispatch reconcile: deviceManager.listAll failed", { meta: { error: errMsg$2(err) } });
64509
65678
  return null;
64510
65679
  }
64511
65680
  }
@@ -64531,7 +65700,7 @@ var ReconcileController = class ReconcileController {
64531
65700
  } catch (err) {
64532
65701
  this.deps.logger.warn("dispatch reconcile: teardown of vanished device failed", {
64533
65702
  tags: { deviceId },
64534
- meta: { error: errMsg$1(err) }
65703
+ meta: { error: errMsg$2(err) }
64535
65704
  });
64536
65705
  }
64537
65706
  }
@@ -64588,7 +65757,7 @@ var ReconcileController = class ReconcileController {
64588
65757
  this.deps.logger.debug("placement reconcile skipped node — getLocalCameras failed", {
64589
65758
  tags: { nodeId },
64590
65759
  meta: {
64591
- error: outcome.kind === "rejected" ? errMsg$1(outcome.error) : "budget exceeded",
65760
+ error: outcome.kind === "rejected" ? errMsg$2(outcome.error) : "budget exceeded",
64592
65761
  ms
64593
65762
  }
64594
65763
  });
@@ -64618,7 +65787,7 @@ var ReconcileController = class ReconcileController {
64618
65787
  nodeId,
64619
65788
  deviceId
64620
65789
  },
64621
- meta: { error: errMsg$1(err) }
65790
+ meta: { error: errMsg$2(err) }
64622
65791
  });
64623
65792
  })));
64624
65793
  }
@@ -64660,7 +65829,7 @@ var ReconcileController = class ReconcileController {
64660
65829
  this.deps.handleDeviceRegistered(deviceId).catch((err) => {
64661
65830
  this.deps.logger.debug("slot-read retry: handleDeviceRegistered failed", {
64662
65831
  tags: { deviceId },
64663
- meta: { error: errMsg$1(err) }
65832
+ meta: { error: errMsg$2(err) }
64664
65833
  });
64665
65834
  });
64666
65835
  }, delay);
@@ -64738,7 +65907,7 @@ var ReconcileController = class ReconcileController {
64738
65907
  } catch (err) {
64739
65908
  this.deps.logger.warn("pending retry: dispatchCamera failed", {
64740
65909
  tags: { deviceId },
64741
- meta: { error: errMsg$1(err) }
65910
+ meta: { error: errMsg$2(err) }
64742
65911
  });
64743
65912
  stillPending++;
64744
65913
  }
@@ -64816,7 +65985,7 @@ var ReconcileController = class ReconcileController {
64816
65985
  await this.deps.detachOn(assignment.agentNodeId, deviceId).catch((err) => {
64817
65986
  this.deps.logger.debug("remote-health exhausted-detach failed", {
64818
65987
  tags: { deviceId },
64819
- meta: { error: errMsg$1(err) }
65988
+ meta: { error: errMsg$2(err) }
64820
65989
  });
64821
65990
  });
64822
65991
  return;
@@ -64839,14 +66008,14 @@ var ReconcileController = class ReconcileController {
64839
66008
  await this.deps.detachOn(assignment.agentNodeId, deviceId).catch((err) => {
64840
66009
  this.deps.logger.warn("remote-health detach failed", {
64841
66010
  tags: { deviceId },
64842
- meta: { error: errMsg$1(err) }
66011
+ meta: { error: errMsg$2(err) }
64843
66012
  });
64844
66013
  });
64845
66014
  this.deps.ledger.dropAssignment(deviceId, "silent");
64846
66015
  await this.deps.redispatchCamera(cfg).catch((err) => {
64847
66016
  this.deps.logger.warn("remote-health re-dispatch failed", {
64848
66017
  tags: { deviceId },
64849
- meta: { error: errMsg$1(err) }
66018
+ meta: { error: errMsg$2(err) }
64850
66019
  });
64851
66020
  });
64852
66021
  }
@@ -65021,7 +66190,7 @@ var OccupancyRecheckController = class {
65021
66190
  tags: { deviceId },
65022
66191
  meta: {
65023
66192
  deviceId,
65024
- error: errMsg$1(err)
66193
+ error: errMsg$2(err)
65025
66194
  }
65026
66195
  });
65027
66196
  });
@@ -65155,7 +66324,7 @@ var OccupancyRecheckController = class {
65155
66324
  tags: { deviceId },
65156
66325
  meta: {
65157
66326
  deviceId,
65158
- error: errMsg$1(err)
66327
+ error: errMsg$2(err)
65159
66328
  }
65160
66329
  });
65161
66330
  });
@@ -65210,7 +66379,7 @@ var OccupancyRecheckController = class {
65210
66379
  meta: {
65211
66380
  deviceId,
65212
66381
  nodeId,
65213
- error: errMsg$1(err),
66382
+ error: errMsg$2(err),
65214
66383
  kind
65215
66384
  }
65216
66385
  });
@@ -65219,7 +66388,7 @@ var OccupancyRecheckController = class {
65219
66388
  }
65220
66389
  const answer = await this.deps.requestBurstOn(nodeId, deviceId, frames, reasonTag).catch((err) => ({
65221
66390
  started: false,
65222
- error: errMsg$1(err)
66391
+ error: errMsg$2(err)
65223
66392
  }));
65224
66393
  if (!answer.started) {
65225
66394
  const refusedBecause = "refusedBecause" in answer ? answer.refusedBecause : void 0;
@@ -65267,7 +66436,7 @@ var OccupancyRecheckController = class {
65267
66436
  meta: {
65268
66437
  deviceId,
65269
66438
  nodeId,
65270
- error: errMsg$1(err)
66439
+ error: errMsg$2(err)
65271
66440
  }
65272
66441
  });
65273
66442
  });
@@ -65324,7 +66493,7 @@ var OccupancyRecheckController = class {
65324
66493
  meta: {
65325
66494
  deviceId,
65326
66495
  nodeId: burst.nodeId,
65327
- error: errMsg$1(err)
66496
+ error: errMsg$2(err)
65328
66497
  }
65329
66498
  });
65330
66499
  });
@@ -65981,7 +67150,7 @@ var SessionDispatchController = class {
65981
67150
  deviceId,
65982
67151
  nodeId: session.nodeId
65983
67152
  },
65984
- meta: { error: errMsg$1(err) }
67153
+ meta: { error: errMsg$2(err) }
65985
67154
  });
65986
67155
  });
65987
67156
  this.deps.dropAssignment(deviceId, "silent");
@@ -66198,7 +67367,7 @@ var SessionDispatchController = class {
66198
67367
  };
66199
67368
  if (reconcileBudgetExceeded) log.warn("detection session: placement census overran its budget — placing without it", { meta: { budgetMs: 500 } });
66200
67369
  else if (censusOutcome.kind === "rejected") log.warn("detection session: placement census failed — placing on the ledger as it is", { meta: {
66201
- error: errMsg$1(censusOutcome.error),
67370
+ error: errMsg$2(censusOutcome.error),
66202
67371
  reconcileMs
66203
67372
  } });
66204
67373
  const balanceStartedAt = Date.now();
@@ -66392,7 +67561,7 @@ var SessionDispatchController = class {
66392
67561
  await this.resolveSessionLifecycleLocked(deviceId, reason);
66393
67562
  }).catch((err) => {
66394
67563
  log.warn("detection session: lifecycle re-check failed", { meta: {
66395
- error: errMsg$1(err),
67564
+ error: errMsg$2(err),
66396
67565
  reason
66397
67566
  } });
66398
67567
  });
@@ -66497,7 +67666,7 @@ var SessionDispatchController = class {
66497
67666
  log.warn("detection session: teardown detach failed", {
66498
67667
  tags: { nodeId: session.nodeId },
66499
67668
  meta: {
66500
- error: errMsg$1(err),
67669
+ error: errMsg$2(err),
66501
67670
  reason
66502
67671
  }
66503
67672
  });
@@ -66604,7 +67773,7 @@ var SessionDispatchController = class {
66604
67773
  deviceId,
66605
67774
  nodeId: session.nodeId
66606
67775
  },
66607
- meta: { error: errMsg$1(err) }
67776
+ meta: { error: errMsg$2(err) }
66608
67777
  });
66609
67778
  });
66610
67779
  this.sessionRegistry.deregister(deviceId);
@@ -66625,7 +67794,7 @@ var SessionDispatchController = class {
66625
67794
  deviceId,
66626
67795
  nodeId: watchNodeId
66627
67796
  },
66628
- meta: { error: errMsg$1(err) }
67797
+ meta: { error: errMsg$2(err) }
66629
67798
  });
66630
67799
  });
66631
67800
  this.motionWatchByDevice.delete(deviceId);
@@ -66808,7 +67977,7 @@ async function sweepMirror(deps, hydrator, cameraIds) {
66808
67977
  tags: { deviceId },
66809
67978
  meta: {
66810
67979
  mirror: hydrator.mirror,
66811
- error: errMsg$1(err)
67980
+ error: errMsg$2(err)
66812
67981
  }
66813
67982
  });
66814
67983
  return "threw";
@@ -66848,7 +68017,7 @@ async function readCameraIds(deps) {
66848
68017
  lastError = err;
66849
68018
  }
66850
68019
  if (Date.now() >= deadline) {
66851
- deps.logger.warn("zones mirror boot hydration SKIPPED — camera list never became readable; mirror-only consumers keep whatever the last write left", { meta: { error: lastError === null ? "ctx.api unavailable" : errMsg$1(lastError) } });
68020
+ deps.logger.warn("zones mirror boot hydration SKIPPED — camera list never became readable; mirror-only consumers keep whatever the last write left", { meta: { error: lastError === null ? "ctx.api unavailable" : errMsg$2(lastError) } });
66852
68021
  return null;
66853
68022
  }
66854
68023
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
@@ -67469,6 +68638,7 @@ async function buildOrchestratorControllers(deps) {
67469
68638
  readGlobalSettings: () => globalSettings,
67470
68639
  onCensus: (entries) => {
67471
68640
  motionSliceGate?.noteCensus(entries);
68641
+ deps.onRunnerCensus(entries.flatMap((e) => e.load === null ? [] : [e.load]));
67472
68642
  providerSliceGate?.refreshIfStale("runner-census");
67473
68643
  }
67474
68644
  });
@@ -67495,7 +68665,7 @@ async function buildOrchestratorControllers(deps) {
67495
68665
  } catch (err) {
67496
68666
  return {
67497
68667
  kind: "unknown",
67498
- reason: errMsg$1(err)
68668
+ reason: errMsg$2(err)
67499
68669
  };
67500
68670
  }
67501
68671
  };
@@ -67580,14 +68750,14 @@ async function buildOrchestratorControllers(deps) {
67580
68750
  readGlobalSettings: () => globalSettings
67581
68751
  });
67582
68752
  await NodeStressMoveLedger.declare(deps.ctx().api.settingsStore).then(() => nodeStress.hydrate()).catch((err) => {
67583
- deps.ctx().logger.warn("node-stress move history unavailable — this boot starts cold", { meta: { error: errMsg$1(err) } });
68753
+ deps.ctx().logger.warn("node-stress move history unavailable — this boot starts cold", { meta: { error: errMsg$2(err) } });
67584
68754
  });
67585
68755
  await NodeStressLts.declare(deps.ctx().api.settingsStore).catch((err) => {
67586
- deps.ctx().logger.warn("node-stress statistics unavailable — no baseline this boot", { meta: { error: errMsg$1(err) } });
68756
+ deps.ctx().logger.warn("node-stress statistics unavailable — no baseline this boot", { meta: { error: errMsg$2(err) } });
67587
68757
  });
67588
68758
  const nodeStressLtsTimer = setInterval(() => {
67589
68759
  nodeStressLts.flushDue().catch((err) => {
67590
- deps.ctx().logger.warn("node-stress statistics flush failed", { meta: { error: errMsg$1(err) } });
68760
+ deps.ctx().logger.warn("node-stress statistics flush failed", { meta: { error: errMsg$2(err) } });
67591
68761
  });
67592
68762
  }, 6e4);
67593
68763
  nodeStress.start();
@@ -67737,7 +68907,7 @@ async function buildOrchestratorControllers(deps) {
67737
68907
  } catch (err) {
67738
68908
  return {
67739
68909
  kind: "unreadable",
67740
- error: errMsg$1(err)
68910
+ error: errMsg$2(err)
67741
68911
  };
67742
68912
  }
67743
68913
  };
@@ -67771,7 +68941,7 @@ async function buildOrchestratorControllers(deps) {
67771
68941
  } catch (err) {
67772
68942
  return {
67773
68943
  kind: "unreadable",
67774
- error: errMsg$1(err)
68944
+ error: errMsg$2(err)
67775
68945
  };
67776
68946
  }
67777
68947
  },
@@ -67802,7 +68972,7 @@ async function buildOrchestratorControllers(deps) {
67802
68972
  } catch (err) {
67803
68973
  return {
67804
68974
  kind: "unreadable",
67805
- error: errMsg$1(err)
68975
+ error: errMsg$2(err)
67806
68976
  };
67807
68977
  }
67808
68978
  },
@@ -67815,7 +68985,7 @@ async function buildOrchestratorControllers(deps) {
67815
68985
  } catch (err) {
67816
68986
  return {
67817
68987
  kind: "unreadable",
67818
- error: errMsg$1(err)
68988
+ error: errMsg$2(err)
67819
68989
  };
67820
68990
  }
67821
68991
  },
@@ -67886,7 +69056,7 @@ async function buildOrchestratorControllers(deps) {
67886
69056
  deps.applyRuntimeSettings(globalSettings);
67887
69057
  await topology.refreshNodeCapabilities();
67888
69058
  } catch (err) {
67889
- const msg = errMsg$1(err);
69059
+ const msg = errMsg$2(err);
67890
69060
  deps.ctx().logger.warn("orchestrator settings load failed — using defaults", { meta: { error: msg } });
67891
69061
  }
67892
69062
  const rawNodeId = deps.ctx().kernel.localNodeId ?? "hub";
@@ -67927,7 +69097,7 @@ async function buildOrchestratorControllers(deps) {
67927
69097
  audio.markNodeOffline(nodeId);
67928
69098
  deps.forgetInferenceDeviceProbe(nodeId);
67929
69099
  nodeLifecycle.handleNodeDisconnect(nodeId).catch((err) => {
67930
- const msg = errMsg$1(err);
69100
+ const msg = errMsg$2(err);
67931
69101
  deps.ctx().logger.error("failover after disconnect failed", {
67932
69102
  tags: { nodeId },
67933
69103
  meta: { error: msg }
@@ -67962,7 +69132,7 @@ async function buildOrchestratorControllers(deps) {
67962
69132
  deps.subscribeToAudioAnalyzerReadiness(nodeId);
67963
69133
  }
67964
69134
  nodeLifecycle.handleNodeConnect(nodeId).catch((err) => {
67965
- const msg = errMsg$1(err);
69135
+ const msg = errMsg$2(err);
67966
69136
  deps.ctx().logger.error("reconnect handling failed", {
67967
69137
  tags: { nodeId },
67968
69138
  meta: { error: msg }
@@ -67981,7 +69151,7 @@ async function buildOrchestratorControllers(deps) {
67981
69151
  meta: { capName }
67982
69152
  });
67983
69153
  nodeLifecycle.handleNodeDisconnect(nodeId).catch((err) => {
67984
- const msg = errMsg$1(err);
69154
+ const msg = errMsg$2(err);
67985
69155
  deps.ctx().logger.error("failover after cap-down failed", {
67986
69156
  tags: { nodeId },
67987
69157
  meta: {
@@ -68044,7 +69214,7 @@ async function buildOrchestratorControllers(deps) {
68044
69214
  doneFlag: settingsStore.bindingsMigrationDoneState,
68045
69215
  logger: deps.ctx().logger
68046
69216
  }).catch((err) => {
68047
- deps.ctxIfReady()?.logger.warn("bindings migration failed — will retry on next boot", { meta: { error: errMsg$1(err) } });
69217
+ deps.ctxIfReady()?.logger.warn("bindings migration failed — will retry on next boot", { meta: { error: errMsg$2(err) } });
68048
69218
  });
68049
69219
  migrateStepGating({
68050
69220
  readCameraSettings: () => settingsStore.readCameraSettingsMap(),
@@ -68057,7 +69227,7 @@ async function buildOrchestratorControllers(deps) {
68057
69227
  for (let i = 0; deps.ctxIfReady()?.api == null && i < 30; i++) await new Promise((r) => setTimeout(r, 200));
68058
69228
  if (deps.ctxIfReady() !== null) await deps.resetRemovedModelPins("boot");
68059
69229
  }).catch((err) => {
68060
- deps.ctxIfReady()?.logger.warn("step-gating / per-node-device migration failed — retry next boot", { meta: { error: errMsg$1(err) } });
69230
+ deps.ctxIfReady()?.logger.warn("step-gating / per-node-device migration failed — retry next boot", { meta: { error: errMsg$2(err) } });
68061
69231
  });
68062
69232
  const zoneRulesProvider = new ZoneRulesProvider({
68063
69233
  logger: deps.ctx().logger.child("zone-rules"),
@@ -68099,7 +69269,7 @@ async function buildOrchestratorControllers(deps) {
68099
69269
  deviceId,
68100
69270
  nodeId: assignment.agentNodeId
68101
69271
  },
68102
- meta: { error: errMsg$1(err) }
69272
+ meta: { error: errMsg$2(err) }
68103
69273
  });
68104
69274
  });
68105
69275
  }
@@ -68115,7 +69285,7 @@ async function buildOrchestratorControllers(deps) {
68115
69285
  }],
68116
69286
  logger: deps.ctx().logger.child("zones")
68117
69287
  }).catch((err) => {
68118
- deps.ctxIfReady()?.logger.warn("zones mirror boot hydration failed", { meta: { error: errMsg$1(err) } });
69288
+ deps.ctxIfReady()?.logger.warn("zones mirror boot hydration failed", { meta: { error: errMsg$2(err) } });
68119
69289
  });
68120
69290
  const unsubOrchestratorSubscriptions = wireOrchestratorSubscriptions({
68121
69291
  eventBus: deps.ctx().eventBus,
@@ -68176,10 +69346,10 @@ async function buildOrchestratorControllers(deps) {
68176
69346
  deviceSettingsSchema: () => deps.deviceSettingsSchema()
68177
69347
  });
68178
69348
  await detectionWiring.hydrateFeaturesMirror().catch((err) => {
68179
- deps.ctx().logger.warn("device-features mirror not restored — this boot starts cold", { meta: { error: errMsg$1(err) } });
69349
+ deps.ctx().logger.warn("device-features mirror not restored — this boot starts cold", { meta: { error: errMsg$2(err) } });
68180
69350
  });
68181
69351
  cameraSensors.start().catch((err) => {
68182
- deps.ctx().logger.warn("camera sensors: boot refresh failed — waiting for the next lifecycle event", { meta: { error: errMsg$1(err) } });
69352
+ deps.ctx().logger.warn("camera sensors: boot refresh failed — waiting for the next lifecycle event", { meta: { error: errMsg$2(err) } });
68183
69353
  });
68184
69354
  return {
68185
69355
  ledger,
@@ -68811,6 +69981,633 @@ var pipelineOrchestratorWidgets = [{
68811
69981
  defaultRows: 4
68812
69982
  }];
68813
69983
  //#endregion
69984
+ //#region src/best-settings/plan-diff.ts
69985
+ /**
69986
+ * The Best settings writes, as pure transforms of the orchestrator's two
69987
+ * settings blobs (D745). The plan runs them on the current blobs to SHOW the
69988
+ * diff; the apply runs the same functions inside each blob's read-modify-write.
69989
+ *
69990
+ * What changes — and nothing else:
69991
+ * - node level (`agentSettings[node].inferenceDevices[deviceKey].steps`): the
69992
+ * object-detection `modelId` of every recommended device is set; the
69993
+ * `modelId` pin of every other in-scope step is removed (each runs its
69994
+ * step default again); every model-bound threshold (and its binding key) is
69995
+ * removed from every device's step `settings`, so the running model's own
69996
+ * defaults apply (D738–D741). `enabled`, `weight`, `maxSessions`,
69997
+ * `jumpDeviceKey` and every other setting are kept.
69998
+ * - camera level, scoped to the touched nodes: the same `modelId` pins and
69999
+ * model-bound thresholds are removed from `stepOverridesByDevice[node]` and
70000
+ * from the wholesale `pipelineByAgent[node]` snapshot (recursively). A
70001
+ * wholesale step keeps `enabled`, `children`, `jumpDeviceKey` and its other
70002
+ * settings: the attach provisions the node pin over the snapshot
70003
+ * (`applyDeviceProvisioning`: override ?? base), so once the snapshot pin
70004
+ * is gone the node's recommended model runs.
70005
+ * - never: `stepToggles`, `pinnedAgentNodeId`, devices' enablement, pins of
70006
+ * embedding steps (cluster-scoped, changing one forces a re-index).
70007
+ */
70008
+ /** The root detector step. */
70009
+ var DETECTOR_STEP_ID = "object-detection";
70010
+ /** Steps whose `modelId` pin Best settings never touches. */
70011
+ function isModelPinKept(stepId) {
70012
+ return stepId.endsWith("-embedding");
70013
+ }
70014
+ function boundKeysFor(input, stepId) {
70015
+ return input.boundKeysByStep[stepId] ?? [];
70016
+ }
70017
+ /** The model-bound keys present in `settings` (values or bindings), and the rest. */
70018
+ function stripBound(settings, boundKeys) {
70019
+ if (settings === void 0) return {
70020
+ kept: void 0,
70021
+ removed: []
70022
+ };
70023
+ const kept = { ...settings };
70024
+ const removed = [];
70025
+ for (const key of boundKeys) for (const k of [key, modelBindingKey(key)]) if (k in kept) {
70026
+ delete kept[k];
70027
+ removed.push(k);
70028
+ }
70029
+ return {
70030
+ kept,
70031
+ removed: removed.toSorted()
70032
+ };
70033
+ }
70034
+ function pinInScope(stepId) {
70035
+ return !isModelPinKept(stepId);
70036
+ }
70037
+ /** Rebuild one stored step without the in-scope pin and bound keys. */
70038
+ function scrubStep(stepId, step, boundKeys, targetModel) {
70039
+ const before = step.modelId ?? null;
70040
+ let modelId = step.modelId;
70041
+ if (stepId === "object-detection") {
70042
+ if (targetModel !== void 0) modelId = targetModel;
70043
+ } else if (pinInScope(stepId)) modelId = void 0;
70044
+ const { kept, removed } = stripBound(step.settings, boundKeys);
70045
+ const after = modelId ?? null;
70046
+ const next = {
70047
+ ...modelId !== void 0 ? { modelId } : {},
70048
+ ...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {},
70049
+ ...step.jumpDeviceKey !== void 0 ? { jumpDeviceKey: step.jumpDeviceKey } : {}
70050
+ };
70051
+ return {
70052
+ next: Object.keys(next).length > 0 ? next : null,
70053
+ pin: before !== after ? {
70054
+ stepId,
70055
+ before,
70056
+ after
70057
+ } : null,
70058
+ cleared: removed
70059
+ };
70060
+ }
70061
+ /** The node-level transform. Returns the next blob and every change it made. */
70062
+ function applyNodeBestSettings(blob, input) {
70063
+ const next = { ...blob };
70064
+ const changes = [];
70065
+ for (const nodeId of [...input.touchedNodes].toSorted()) {
70066
+ const entry = blob[nodeId];
70067
+ const targets = new Map((input.targets.get(nodeId) ?? []).map((t) => [t.deviceKey, t]));
70068
+ const stored = entry?.inferenceDevices ?? {};
70069
+ const deviceKeys = [...new Set([...Object.keys(stored), ...targets.keys()])].toSorted();
70070
+ const nextDevices = {};
70071
+ for (const deviceKey of deviceKeys) {
70072
+ const device = stored[deviceKey];
70073
+ const target = targets.get(deviceKey);
70074
+ const steps = { ...device?.steps };
70075
+ if (target !== void 0 && steps["object-detection"] === void 0) steps[DETECTOR_STEP_ID] = {};
70076
+ const nextSteps = {};
70077
+ const pins = [];
70078
+ const thresholds = [];
70079
+ for (const stepId of Object.keys(steps).toSorted()) {
70080
+ const step = steps[stepId];
70081
+ if (step === void 0) continue;
70082
+ const scrubbed = scrubStep(stepId, step, boundKeysFor(input, stepId), target?.modelId);
70083
+ if (scrubbed.next !== null) nextSteps[stepId] = scrubbed.next;
70084
+ if (scrubbed.pin !== null) pins.push(scrubbed.pin);
70085
+ if (scrubbed.cleared.length > 0) thresholds.push({
70086
+ stepId,
70087
+ keys: scrubbed.cleared
70088
+ });
70089
+ }
70090
+ const madeExplicit = device === void 0;
70091
+ nextDevices[deviceKey] = {
70092
+ ...device !== void 0 ? (({ steps: _steps, ...rest }) => rest)(device) : { enabled: target?.effectiveEnabled ?? false },
70093
+ ...Object.keys(nextSteps).length > 0 ? { steps: nextSteps } : {}
70094
+ };
70095
+ if (pins.length > 0 || thresholds.length > 0) changes.push({
70096
+ nodeId,
70097
+ deviceKey,
70098
+ madeExplicit,
70099
+ modelPins: pins,
70100
+ thresholds
70101
+ });
70102
+ }
70103
+ if (entry === void 0 && deviceKeys.length === 0) continue;
70104
+ next[nodeId] = {
70105
+ ...entry ?? { maxCameras: null },
70106
+ ...deviceKeys.length > 0 ? { inferenceDevices: nextDevices } : {}
70107
+ };
70108
+ }
70109
+ return {
70110
+ next,
70111
+ changes
70112
+ };
70113
+ }
70114
+ /** Scrub a wholesale snapshot tree; collects one clear per changed step. */
70115
+ function scrubTree(steps, input, onClear) {
70116
+ return steps.map((step) => {
70117
+ const children = step.children ? scrubTree(step.children, input, onClear) : void 0;
70118
+ const inScope = step.addonId === "object-detection" || pinInScope(step.addonId);
70119
+ const { kept, removed } = stripBound(step.settings, boundKeysFor(input, step.addonId));
70120
+ const dropPin = inScope && step.modelId !== void 0;
70121
+ if (dropPin || removed.length > 0) onClear(step.addonId, dropPin ? step.modelId ?? null : null, removed);
70122
+ const { modelId: _modelId, settings: _settings, children: _children, ...rest } = step;
70123
+ return {
70124
+ ...rest,
70125
+ ...!dropPin && step.modelId !== void 0 ? { modelId: step.modelId } : {},
70126
+ ...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {},
70127
+ ...children !== void 0 ? { children } : {}
70128
+ };
70129
+ });
70130
+ }
70131
+ /** The camera-level transform. Returns the next blob and every override it removed. */
70132
+ function applyCameraBestSettings(blob, input) {
70133
+ const next = {};
70134
+ const clears = [];
70135
+ for (const key of Object.keys(blob).toSorted((a, b) => Number(a) - Number(b))) {
70136
+ const camera = blob[key];
70137
+ if (camera === void 0) continue;
70138
+ const deviceId = Number(key);
70139
+ let nextCamera = camera;
70140
+ const byNode = camera.stepOverridesByDevice;
70141
+ if (byNode !== void 0) {
70142
+ const nextByNode = {};
70143
+ for (const [nodeId, byDevice] of Object.entries(byNode)) {
70144
+ if (!input.touchedNodes.has(nodeId)) {
70145
+ nextByNode[nodeId] = byDevice;
70146
+ continue;
70147
+ }
70148
+ const nextByDevice = {};
70149
+ for (const [deviceKey, byStep] of Object.entries(byDevice)) {
70150
+ const nextByStep = {};
70151
+ for (const [stepId, patch] of Object.entries(byStep)) {
70152
+ const inScope = stepId === "object-detection" || pinInScope(stepId);
70153
+ const { kept, removed } = stripBound(patch.settings, boundKeysFor(input, stepId));
70154
+ const dropPin = inScope && patch.modelId !== void 0;
70155
+ if (dropPin || removed.length > 0) clears.push({
70156
+ deviceId,
70157
+ source: "step-override",
70158
+ nodeId,
70159
+ deviceKey,
70160
+ stepId,
70161
+ modelIdBefore: dropPin ? patch.modelId ?? null : null,
70162
+ keys: removed
70163
+ });
70164
+ const rebuilt = {
70165
+ ...!dropPin && patch.modelId !== void 0 ? { modelId: patch.modelId } : {},
70166
+ ...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {}
70167
+ };
70168
+ if (Object.keys(rebuilt).length > 0) nextByStep[stepId] = rebuilt;
70169
+ }
70170
+ if (Object.keys(nextByStep).length > 0) nextByDevice[deviceKey] = nextByStep;
70171
+ }
70172
+ if (Object.keys(nextByDevice).length > 0) nextByNode[nodeId] = nextByDevice;
70173
+ }
70174
+ const { stepOverridesByDevice: _drop, ...rest } = nextCamera;
70175
+ nextCamera = {
70176
+ ...rest,
70177
+ ...Object.keys(nextByNode).length > 0 ? { stepOverridesByDevice: nextByNode } : {}
70178
+ };
70179
+ }
70180
+ const wholesale = camera.pipelineByAgent;
70181
+ if (wholesale !== void 0) {
70182
+ const nextWholesale = {};
70183
+ for (const [nodeId, snapshot] of Object.entries(wholesale)) {
70184
+ if (!input.touchedNodes.has(nodeId)) {
70185
+ nextWholesale[nodeId] = snapshot;
70186
+ continue;
70187
+ }
70188
+ nextWholesale[nodeId] = {
70189
+ ...snapshot,
70190
+ steps: scrubTree(snapshot.steps, input, (stepId, modelIdBefore, keys) => {
70191
+ clears.push({
70192
+ deviceId,
70193
+ source: "wholesale",
70194
+ nodeId,
70195
+ deviceKey: null,
70196
+ stepId,
70197
+ modelIdBefore,
70198
+ keys
70199
+ });
70200
+ })
70201
+ };
70202
+ }
70203
+ nextCamera = {
70204
+ ...nextCamera,
70205
+ pipelineByAgent: nextWholesale
70206
+ };
70207
+ }
70208
+ next[key] = nextCamera;
70209
+ }
70210
+ return {
70211
+ next,
70212
+ clears
70213
+ };
70214
+ }
70215
+ //#endregion
70216
+ //#region src/best-settings/best-settings-service.ts
70217
+ /**
70218
+ * Best settings (D745): gather the inputs, compute the plan, and apply exactly
70219
+ * the plan the operator confirmed.
70220
+ *
70221
+ * The plan is the recommender (`@camstack/types` best-settings) over every
70222
+ * runner node, plus the diff of the writes (`plan-diff.ts`) and the
70223
+ * per-camera detector thresholds the hub's executor would clear. Its
70224
+ * `planHash` names everything the apply writes; the apply recomputes the plan
70225
+ * and refuses a different hash, so a configuration change between the two is
70226
+ * shown to the operator again instead of being written over.
70227
+ *
70228
+ * Apply order, each phase reported:
70229
+ * 1. download every newly picked detector on its node FIRST — a node never
70230
+ * switches to a model it does not have (an N100 compile takes 5–25 s, a
70231
+ * Core ML package 40 MB); any failure stops the apply before a write;
70232
+ * 2. camera overrides (one camera-blob read-modify-write);
70233
+ * 3. per-camera detector thresholds (the hub executor's store);
70234
+ * 4. node model pins (one agent-blob read-modify-write);
70235
+ * 5. re-dispatch the cameras of every touched node.
70236
+ * A failure after phase 2 leaves "overrides cleared, previous node model",
70237
+ * which is a valid configuration (the defaults run); re-running converges.
70238
+ */
70239
+ function errMsg(err) {
70240
+ return err instanceof Error ? err.message : String(err);
70241
+ }
70242
+ /** Canonical JSON (sorted keys) — the plan hash must not depend on key order. */
70243
+ function canonical(value) {
70244
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
70245
+ if (typeof value === "object" && value !== null) return `{${Object.entries(value).filter(([, v]) => v !== void 0).toSorted(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
70246
+ return JSON.stringify(value);
70247
+ }
70248
+ function formatsByModelOf(catalog) {
70249
+ const out = {};
70250
+ for (const slot of catalog?.slots ?? []) for (const addon of slot.addons) {
70251
+ if (addon.id !== "object-detection") continue;
70252
+ for (const model of addon.models) out[model.id] = MODEL_FORMATS.filter((f) => f in model.formats);
70253
+ }
70254
+ return out;
70255
+ }
70256
+ function boundKeysOf(catalogs) {
70257
+ const byStep = /* @__PURE__ */ new Map();
70258
+ for (const catalog of catalogs) for (const slot of catalog?.slots ?? []) for (const addon of slot.addons) {
70259
+ const keys = collectModelBoundKeys(addon.configSchema ?? []);
70260
+ if (keys.length === 0) continue;
70261
+ const set = byStep.get(addon.id) ?? /* @__PURE__ */ new Set();
70262
+ for (const k of keys) set.add(k);
70263
+ byStep.set(addon.id, set);
70264
+ }
70265
+ return Object.fromEntries([...byStep].map(([step, set]) => [step, [...set].toSorted()]));
70266
+ }
70267
+ var BestSettingsService = class {
70268
+ deps;
70269
+ applying = false;
70270
+ constructor(deps) {
70271
+ this.deps = deps;
70272
+ }
70273
+ async plan(input) {
70274
+ return (await this.computePlan(input.headroom)).plan;
70275
+ }
70276
+ async computePlan(headroom) {
70277
+ const nodeIds = [...new Set(["hub", ...this.deps.runnerNodeIds()])].toSorted();
70278
+ const gathered = await Promise.all(nodeIds.map(async (nodeId) => {
70279
+ const [devices, hardware, catalog] = await Promise.all([
70280
+ this.deps.readNodeDevices(nodeId),
70281
+ this.deps.readHardware(nodeId).catch(() => null),
70282
+ this.deps.readCatalog(nodeId).catch(() => null)
70283
+ ]);
70284
+ return {
70285
+ nodeId,
70286
+ devices,
70287
+ hardware,
70288
+ catalog
70289
+ };
70290
+ }));
70291
+ const nodes = gathered.map((g) => ({
70292
+ nodeId: g.nodeId,
70293
+ reachable: g.devices.reachable && g.catalog !== null,
70294
+ hardware: g.hardware,
70295
+ devices: g.devices.devices.map((d) => ({
70296
+ key: d.key,
70297
+ format: d.format,
70298
+ enabled: d.enabled,
70299
+ available: d.available
70300
+ })),
70301
+ formatsByModel: formatsByModelOf(g.catalog)
70302
+ }));
70303
+ const eligible = gathered.flatMap((g) => g.devices.reachable ? g.devices.devices.filter((d) => d.enabled && d.available).map((d) => ({
70304
+ nodeId: g.nodeId,
70305
+ deviceKey: d.key,
70306
+ weight: d.weight
70307
+ })) : []);
70308
+ const demand = estimateDeviceDemand({
70309
+ cameras: await this.deps.cameraLoads(),
70310
+ eligible,
70311
+ peaks: this.deps.peaks()
70312
+ });
70313
+ const recommendations = nodes.map((node) => recommendNode({
70314
+ node,
70315
+ demand,
70316
+ headroom
70317
+ }));
70318
+ const touchedNodes = new Set(recommendations.filter((r) => !r.skipped).map((r) => r.nodeId));
70319
+ const targets = /* @__PURE__ */ new Map();
70320
+ const formats = /* @__PURE__ */ new Map();
70321
+ for (const rec of recommendations) {
70322
+ if (rec.skipped) continue;
70323
+ const node = nodes.find((n) => n.nodeId === rec.nodeId);
70324
+ const list = [];
70325
+ const byKey = /* @__PURE__ */ new Map();
70326
+ for (const d of rec.devices) {
70327
+ if (d.chosen === null) continue;
70328
+ list.push({
70329
+ deviceKey: d.deviceKey,
70330
+ modelId: d.chosen.modelId,
70331
+ effectiveEnabled: d.enabled
70332
+ });
70333
+ const format = node?.devices.find((x) => x.key === d.deviceKey)?.format;
70334
+ if (format !== void 0) byKey.set(d.deviceKey, format);
70335
+ }
70336
+ targets.set(rec.nodeId, list);
70337
+ formats.set(rec.nodeId, byKey);
70338
+ }
70339
+ const diffInput = {
70340
+ touchedNodes,
70341
+ targets,
70342
+ boundKeysByStep: boundKeysOf(gathered.map((g) => g.catalog))
70343
+ };
70344
+ const nodeChanges = applyNodeBestSettings(await this.deps.readAgentSettings(), diffInput).changes;
70345
+ const cameraClears = applyCameraBestSettings(await this.deps.readCameraSettings(), diffInput).clears;
70346
+ const warnings = [];
70347
+ let detectorStoreClears = [];
70348
+ if (touchedNodes.size > 0) try {
70349
+ const answer = await this.deps.detectorStore({ dryRun: true });
70350
+ detectorStoreClears = answer.entries.map((e) => ({
70351
+ deviceId: e.deviceId,
70352
+ keys: [...e.keys]
70353
+ }));
70354
+ if (answer.refused.length > 0) warnings.push({
70355
+ kind: "detector-store-unreadable",
70356
+ error: `${answer.refused.length} camera store(s) unreadable`
70357
+ });
70358
+ } catch (err) {
70359
+ warnings.push({
70360
+ kind: "detector-store-unreadable",
70361
+ error: errMsg(err)
70362
+ });
70363
+ }
70364
+ const skippedNodes = recommendations.filter((r) => r.skipped).map((r) => r.nodeId);
70365
+ if (detectorStoreClears.length > 0 && skippedNodes.length > 0) warnings.push({
70366
+ kind: "detector-store-cluster-wide",
70367
+ skippedNodes
70368
+ });
70369
+ for (const rec of recommendations) for (const d of rec.devices) if (d.accelerator === "coral" && d.enabled) warnings.push({
70370
+ kind: "coral-enabled",
70371
+ nodeId: rec.nodeId,
70372
+ deviceKey: d.deviceKey
70373
+ });
70374
+ return {
70375
+ plan: {
70376
+ planHash: (0, node_crypto.createHash)("sha256").update(canonical({
70377
+ version: 1,
70378
+ headroom,
70379
+ picks: recommendations.map((r) => ({
70380
+ nodeId: r.nodeId,
70381
+ skipped: r.skipped,
70382
+ devices: r.devices.map((d) => [d.deviceKey, d.chosen?.modelId ?? null])
70383
+ })),
70384
+ nodeChanges,
70385
+ cameraClears,
70386
+ detectorStoreClears
70387
+ })).digest("hex"),
70388
+ family: "yolov9",
70389
+ headroom,
70390
+ generatedAt: this.deps.now(),
70391
+ nodes: recommendations,
70392
+ nodeChanges: [...nodeChanges],
70393
+ cameraClears: [...cameraClears],
70394
+ detectorStoreClears,
70395
+ warnings
70396
+ },
70397
+ diffInput,
70398
+ formats
70399
+ };
70400
+ }
70401
+ async apply(input) {
70402
+ if (this.applying) return {
70403
+ kind: "refused",
70404
+ reason: "apply-in-progress"
70405
+ };
70406
+ this.applying = true;
70407
+ try {
70408
+ return await this.applyLocked(input);
70409
+ } finally {
70410
+ this.applying = false;
70411
+ }
70412
+ }
70413
+ async applyLocked(input) {
70414
+ const { plan, diffInput, formats } = await this.computePlan(input.headroom);
70415
+ if (plan.planHash !== input.planHash) {
70416
+ this.deps.logger.warn("best settings: stale plan refused", { meta: {
70417
+ confirmed: input.planHash,
70418
+ current: plan.planHash
70419
+ } });
70420
+ return {
70421
+ kind: "stale",
70422
+ plan
70423
+ };
70424
+ }
70425
+ const phases = [];
70426
+ const downloads = plan.nodeChanges.flatMap((c) => c.modelPins.filter((p) => p.stepId === "object-detection" && p.after !== null && p.after !== p.before).map((p) => ({
70427
+ nodeId: c.nodeId,
70428
+ modelId: p.after ?? "",
70429
+ format: formats.get(c.nodeId)?.get(c.deviceKey)
70430
+ })));
70431
+ const unique = new Map(downloads.map((d) => [`${d.nodeId}|${d.modelId}|${d.format}`, d]));
70432
+ try {
70433
+ for (const d of unique.values()) {
70434
+ if (d.format === void 0) continue;
70435
+ await this.deps.downloadModel(d.nodeId, d.modelId, d.format);
70436
+ }
70437
+ phases.push({
70438
+ phase: "download",
70439
+ ok: true,
70440
+ detail: `${unique.size} model(s) present on their nodes`
70441
+ });
70442
+ } catch (err) {
70443
+ phases.push({
70444
+ phase: "download",
70445
+ ok: false,
70446
+ detail: errMsg(err)
70447
+ });
70448
+ this.deps.logger.warn("best settings: a model download failed — nothing was written", { meta: { error: errMsg(err) } });
70449
+ return {
70450
+ kind: "aborted",
70451
+ phases
70452
+ };
70453
+ }
70454
+ try {
70455
+ await this.deps.rewriteCameraSettings((prev) => applyCameraBestSettings(prev, diffInput).next);
70456
+ for (const c of plan.cameraClears) this.deps.logger.info("best settings: camera override cleared", {
70457
+ tags: {
70458
+ deviceId: c.deviceId,
70459
+ nodeId: c.nodeId
70460
+ },
70461
+ meta: {
70462
+ deviceId: c.deviceId,
70463
+ source: c.source,
70464
+ deviceKey: c.deviceKey,
70465
+ stepId: c.stepId,
70466
+ modelIdBefore: c.modelIdBefore,
70467
+ keys: c.keys
70468
+ }
70469
+ });
70470
+ phases.push({
70471
+ phase: "camera-overrides",
70472
+ ok: true,
70473
+ detail: `${plan.cameraClears.length} cleared`
70474
+ });
70475
+ } catch (err) {
70476
+ phases.push({
70477
+ phase: "camera-overrides",
70478
+ ok: false,
70479
+ detail: errMsg(err)
70480
+ });
70481
+ }
70482
+ if (plan.detectorStoreClears.length > 0) try {
70483
+ const answer = await this.deps.detectorStore({
70484
+ dryRun: false,
70485
+ deviceIds: plan.detectorStoreClears.map((c) => c.deviceId)
70486
+ });
70487
+ phases.push({
70488
+ phase: "detector-thresholds",
70489
+ ok: answer.refused.length === 0,
70490
+ detail: `${answer.entries.length} cleared, ${answer.refused.length} refused`
70491
+ });
70492
+ } catch (err) {
70493
+ phases.push({
70494
+ phase: "detector-thresholds",
70495
+ ok: false,
70496
+ detail: errMsg(err)
70497
+ });
70498
+ }
70499
+ try {
70500
+ await this.deps.rewriteAgentSettings((prev) => applyNodeBestSettings(prev, diffInput).next);
70501
+ for (const c of plan.nodeChanges) for (const pin of c.modelPins) this.deps.logger.info("best settings: node model set", {
70502
+ tags: { nodeId: c.nodeId },
70503
+ meta: {
70504
+ nodeId: c.nodeId,
70505
+ deviceKey: c.deviceKey,
70506
+ stepId: pin.stepId,
70507
+ from: pin.before,
70508
+ to: pin.after
70509
+ }
70510
+ });
70511
+ phases.push({
70512
+ phase: "node-models",
70513
+ ok: true,
70514
+ detail: `${plan.nodeChanges.length} device(s) updated`
70515
+ });
70516
+ } catch (err) {
70517
+ phases.push({
70518
+ phase: "node-models",
70519
+ ok: false,
70520
+ detail: errMsg(err)
70521
+ });
70522
+ }
70523
+ this.deps.invalidateCatalog();
70524
+ const failed = [];
70525
+ for (const nodeId of [...diffInput.touchedNodes].toSorted()) try {
70526
+ await this.deps.redispatch(nodeId);
70527
+ } catch (err) {
70528
+ failed.push(`${nodeId}: ${errMsg(err)}`);
70529
+ }
70530
+ phases.push({
70531
+ phase: "redispatch",
70532
+ ok: failed.length === 0,
70533
+ detail: failed.length === 0 ? `${diffInput.touchedNodes.size} node(s)` : failed.join("; ")
70534
+ });
70535
+ for (const rec of plan.nodes.filter((r) => r.skipped)) this.deps.logger.info("best settings: node skipped", {
70536
+ tags: { nodeId: rec.nodeId },
70537
+ meta: {
70538
+ nodeId: rec.nodeId,
70539
+ reasons: rec.reasons
70540
+ }
70541
+ });
70542
+ this.deps.logger.info("best settings: applied", { meta: {
70543
+ planHash: plan.planHash,
70544
+ nodeChanges: plan.nodeChanges.length,
70545
+ cameraClears: plan.cameraClears.length,
70546
+ detectorStoreClears: plan.detectorStoreClears.length,
70547
+ phases
70548
+ } });
70549
+ return {
70550
+ kind: "applied",
70551
+ planHash: plan.planHash,
70552
+ phases
70553
+ };
70554
+ }
70555
+ };
70556
+ //#endregion
70557
+ //#region src/best-settings/device-peak-tracker.ts
70558
+ var BUCKET_MS = 3600 * 1e3;
70559
+ var DevicePeakTracker = class DevicePeakTracker {
70560
+ now;
70561
+ series = /* @__PURE__ */ new Map();
70562
+ constructor(now = () => Date.now()) {
70563
+ this.now = now;
70564
+ }
70565
+ static key(nodeId, deviceKey) {
70566
+ return `${nodeId}\u0000${deviceKey}`;
70567
+ }
70568
+ /** Fold one runner census in. A runner that did not answer is simply absent. */
70569
+ noteCensus(loads) {
70570
+ const now = this.now();
70571
+ const bucket = now - now % BUCKET_MS;
70572
+ for (const load of loads) {
70573
+ const rows = load.devices.length > 0 ? load.devices : [{
70574
+ deviceKey: "",
70575
+ attachedCameras: load.attachedCameras
70576
+ }];
70577
+ for (const device of rows) {
70578
+ const key = DevicePeakTracker.key(load.nodeId, device.deviceKey);
70579
+ let s = this.series.get(key);
70580
+ if (s === void 0) {
70581
+ s = {
70582
+ firstSeenAt: now,
70583
+ buckets: /* @__PURE__ */ new Map()
70584
+ };
70585
+ this.series.set(key, s);
70586
+ }
70587
+ s.buckets.set(bucket, Math.max(s.buckets.get(bucket) ?? 0, device.attachedCameras));
70588
+ for (const start of s.buckets.keys()) if (start + BUCKET_MS <= now - 864e5) s.buckets.delete(start);
70589
+ }
70590
+ }
70591
+ }
70592
+ /** Every observed device's peak over the window and how long it has been watched. */
70593
+ peaks() {
70594
+ const now = this.now();
70595
+ const out = [];
70596
+ for (const [key, s] of this.series) {
70597
+ const [nodeId = "", deviceKey = ""] = key.split("\0");
70598
+ let peak = 0;
70599
+ for (const [start, value] of s.buckets) if (start + BUCKET_MS > now - 864e5) peak = Math.max(peak, value);
70600
+ out.push({
70601
+ nodeId,
70602
+ deviceKey,
70603
+ peakSessions: peak,
70604
+ observedForMs: now - s.firstSeenAt
70605
+ });
70606
+ }
70607
+ return out.toSorted((a, b) => a.nodeId === b.nodeId ? a.deviceKey.localeCompare(b.deviceKey) : a.nodeId.localeCompare(b.nodeId));
70608
+ }
70609
+ };
70610
+ //#endregion
68814
70611
  //#region src/index.ts
68815
70612
  /**
68816
70613
  * addon-pipeline-orchestrator — hub-side camera-to-agent load balancer.
@@ -68878,6 +70675,9 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
68878
70675
  * not a claim this flag makes.
68879
70676
  */
68880
70677
  clusterModelChoiceReady = false;
70678
+ /** The per-device 24 h peak of concurrent sessions, fed by every runner census (D745). */
70679
+ devicePeaks = new DevicePeakTracker();
70680
+ bestSettingsService = null;
68881
70681
  /**
68882
70682
  * Single owner of the shared placement state (`assignments`/`cameraConfigs`/
68883
70683
  * `pendingReasons`/`dispatchingDevices`) — see `placement-ledger.ts` (S6
@@ -69154,6 +70954,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
69154
70954
  rebalance: (opts) => this.rebalance(opts),
69155
70955
  setCameraPipelineForAgent: (input) => this.setCameraPipelineForAgent(input),
69156
70956
  maintenancePaused: () => this.storageMigrationLeaseId !== null,
70957
+ onRunnerCensus: (loads) => this.devicePeaks.noteCensus(loads),
69157
70958
  setTopology: (topology) => {
69158
70959
  this.topology = topology;
69159
70960
  },
@@ -69295,7 +71096,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
69295
71096
  for (const deviceId of this.detectionWiring.activeDetectionDeviceIds()) try {
69296
71097
  await this.releaseCamera({ deviceId });
69297
71098
  } catch (err) {
69298
- const msg = errMsg$1(err);
71099
+ const msg = errMsg$2(err);
69299
71100
  this.ctx.logger.debug("releaseCamera failed during shutdown", {
69300
71101
  tags: { deviceId },
69301
71102
  meta: { error: msg }
@@ -70090,7 +71891,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70090
71891
  tags: { nodeId },
70091
71892
  meta: {
70092
71893
  nodeId,
70093
- error: errMsg$1(err)
71894
+ error: errMsg$2(err)
70094
71895
  }
70095
71896
  });
70096
71897
  return null;
@@ -70196,7 +71997,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70196
71997
  tags: { nodeId },
70197
71998
  meta: {
70198
71999
  nodeId,
70199
- error: errMsg$1(err)
72000
+ error: errMsg$2(err)
70200
72001
  }
70201
72002
  });
70202
72003
  return {
@@ -70243,7 +72044,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70243
72044
  } catch (err) {
70244
72045
  this.ctx.logger.warn("resetNodePipelineDefaults: executor clearDeviceOverrides failed (node offline?)", {
70245
72046
  tags: { nodeId: node },
70246
- meta: { error: errMsg$1(err) }
72047
+ meta: { error: errMsg$2(err) }
70247
72048
  });
70248
72049
  }
70249
72050
  this.settingsStore.invalidateCatalog();
@@ -70254,7 +72055,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70254
72055
  } catch (err) {
70255
72056
  this.ctx.logger.warn("resetNodePipelineDefaults: effective-model probe failed (node offline?)", {
70256
72057
  tags: { nodeId: node },
70257
- meta: { error: errMsg$1(err) }
72058
+ meta: { error: errMsg$2(err) }
70258
72059
  });
70259
72060
  }
70260
72061
  this.ctx.logger.info("node pipeline reset to hardware-aware defaults", {
@@ -70270,6 +72071,77 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70270
72071
  clearedCameraOverrides
70271
72072
  };
70272
72073
  }
72074
+ bestSettings() {
72075
+ if (this.bestSettingsService !== null) return this.bestSettingsService;
72076
+ const store = this.settingsStore;
72077
+ this.bestSettingsService = new BestSettingsService({
72078
+ logger: this.ctx.logger,
72079
+ runnerNodeIds: () => this.topology.collectRunnerNodeIds(),
72080
+ readNodeDevices: (nodeId) => this.getNodeInferenceDevices({ nodeId }),
72081
+ readHardware: async (nodeId) => {
72082
+ const api = this.ctx.api;
72083
+ if (!api) return null;
72084
+ return nodeId === this.localNodeId ? api.platformProbe.getHardware.query() : api.platformProbe.getHardware.query({ nodeId });
72085
+ },
72086
+ readCatalog: (nodeId) => store.readCatalogForNode(nodeId),
72087
+ readAgentSettings: () => store.readAgentSettingsMap(),
72088
+ readCameraSettings: () => store.readCameraSettingsMap(),
72089
+ rewriteAgentSettings: (transform) => store.rewriteAgentSettingsMap(transform),
72090
+ rewriteCameraSettings: (transform) => store.rewriteCameraSettingsMap(transform),
72091
+ cameraLoads: () => this.bestSettingsCameraLoads(),
72092
+ peaks: () => this.devicePeaks.peaks(),
72093
+ detectorStore: async (input) => {
72094
+ const api = this.ctx.api;
72095
+ if (!api) throw new Error("ctx.api unavailable");
72096
+ return api.pipelineExecutor.clearModelBoundDeviceOverrides.mutate({
72097
+ nodeId: this.localNodeId,
72098
+ dryRun: input.dryRun,
72099
+ ...input.deviceIds !== void 0 ? { deviceIds: input.deviceIds } : {}
72100
+ });
72101
+ },
72102
+ downloadModel: async (nodeId, modelId, format) => {
72103
+ const api = this.ctx.api;
72104
+ if (!api) throw new Error("ctx.api unavailable");
72105
+ await api.pipelineExecutor.downloadModel.mutate({
72106
+ addonId: "object-detection",
72107
+ modelId,
72108
+ format
72109
+ }, nodePin(nodeId));
72110
+ },
72111
+ invalidateCatalog: () => store.invalidateCatalog(),
72112
+ redispatch: (nodeId) => this.placement.redispatchCamerasOnAgent(nodeId),
72113
+ now: () => Date.now()
72114
+ });
72115
+ return this.bestSettingsService;
72116
+ }
72117
+ /** Every camera with detection started, as the recommender charges it (D745). */
72118
+ async bestSettingsCameraLoads() {
72119
+ const standing = new Map(this.getPipelineAssignments().map((a) => [a.deviceId, a.agentNodeId]));
72120
+ const cameraMap = await this.settingsStore.readCameraSettingsMap();
72121
+ const loads = [];
72122
+ for (const [deviceId, config] of this.detectionWiring.activeDetections) {
72123
+ const pinnedNode = cameraMap[String(deviceId)]?.pinnedAgentNodeId ?? standing.get(deviceId);
72124
+ const pinnedKey = await this.readPipelineDevicePin(deviceId);
72125
+ loads.push({
72126
+ deviceId,
72127
+ mode: config.detectionMode,
72128
+ fps: config.detectionFps,
72129
+ runsPipelineRoot: (config.detectionSources ?? ["pipeline"]).includes("pipeline"),
72130
+ standingNodeId: config.detectionMode === "always-on" ? standing.get(deviceId) ?? null : null,
72131
+ devicePin: pinnedKey !== null && pinnedNode !== void 0 ? {
72132
+ nodeId: pinnedNode,
72133
+ deviceKey: pinnedKey
72134
+ } : null
72135
+ });
72136
+ }
72137
+ return loads;
72138
+ }
72139
+ async planBestSettings(input) {
72140
+ return this.bestSettings().plan(input);
72141
+ }
72142
+ async applyBestSettings(input) {
72143
+ return this.bestSettings().apply(input);
72144
+ }
70273
72145
  async getCameraSettings(input) {
70274
72146
  return (await this.settingsStore.readCameraSettingsMap())[String(input.deviceId)] ?? null;
70275
72147
  }
@@ -70526,7 +72398,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
70526
72398
  tags: { deviceId },
70527
72399
  meta: {
70528
72400
  capName,
70529
- error: errMsg$1(err)
72401
+ error: errMsg$2(err)
70530
72402
  }
70531
72403
  });
70532
72404
  return true;