@camstack/addon-pipeline 1.2.43 → 1.2.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/audio-analyzer/index.js +1 -1
  2. package/dist/audio-analyzer/index.mjs +1 -1
  3. package/dist/detection-pipeline/index.js +158 -81
  4. package/dist/detection-pipeline/index.mjs +158 -81
  5. package/dist/{dist-B-VVBzrL.mjs → dist-BDkEhx4Z.mjs} +1315 -127
  6. package/dist/{dist-UHbKZiSa.js → dist-QyZ7QSRP.js} +1386 -126
  7. package/dist/{event-loop-stall-monitor-C7_L6vHO.js → event-loop-stall-monitor-COEKVBQK.js} +53 -8
  8. package/dist/{event-loop-stall-monitor-DfKdC7G4.mjs → event-loop-stall-monitor-IAuNEdmG.mjs} +53 -8
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/pipeline-runner/index.js +342 -69
  12. package/dist/pipeline-runner/index.mjs +342 -69
  13. package/dist/recorder/index.js +189 -6
  14. package/dist/recorder/index.mjs +189 -6
  15. package/dist/session-decode/decode-worker-child.js +197 -24
  16. package/dist/session-decode/decode-worker-child.mjs +197 -24
  17. package/dist/stream-broker/_stub.js +2 -2
  18. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D7iQbuKY.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C68YfSUH.mjs} +2 -2
  19. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CT2bGWqM.mjs +26 -0
  20. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-54IklEep.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B3GxBnXM.mjs} +1 -1
  21. package/dist/stream-broker/{hostInit-CKYS9yZO.mjs → hostInit-BMCrULpc.mjs} +2 -2
  22. package/dist/stream-broker/index.js +4602 -1442
  23. package/dist/stream-broker/index.mjs +4601 -1442
  24. package/dist/stream-broker/remoteEntry.js +1 -1
  25. package/dist/worker-protocol-DFH_z2-q.mjs +244 -0
  26. package/dist/worker-protocol-le27Mw_8.js +279 -0
  27. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-DxZ75vB8.js → MaskShapeCanvas-DI4BY7W2-C7bsz4cP.js} +1 -1
  28. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BBY2Ztz5.js → MotionZonesSettings-NcxxQN8r-BmhLinUd.js} +1 -1
  29. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BC-3QmHa.js → PrivacyMaskSettings-APgPLF7p-BJjyGWFy.js} +1 -1
  30. package/embed-dist/assets/index-DC63og2c.css +2 -0
  31. package/embed-dist/assets/{index-on1wyOnd.js → index-a4CMt-Uc.js} +12 -12
  32. package/embed-dist/index.html +2 -2
  33. package/package.json +1 -1
  34. package/python/postprocessors/__init__.py +2 -0
  35. package/python/postprocessors/rfdetr.py +161 -0
  36. package/python/postprocessors/test_rfdetr.py +193 -0
  37. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCC9fqHH.mjs +0 -26
  38. package/dist/worker-protocol-D7RzZIla.mjs +0 -77
  39. package/dist/worker-protocol-DextwlTX.js +0 -94
  40. package/embed-dist/assets/index-CGdwTcwE.css +0 -2
@@ -1,6 +1,6 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { A as evaluateZoneRules, K as errMsg, L as pipelineExecutorCapability, O as detectionPipelineCapability, Q as hydrateSchema, V as runtimeDevices$1, W as supportedRuntimes$1, Z as createEvent, _ as YAMNET_TO_MACRO, _t as union, ct as array, gt as string, k as enumerateInferenceDevices, l as DEVICE_BACKEND_TO_FORMAT, mt as object, nt as nodePin, ot as sleep, q as BaseAddon, rt as parseJsonUnknown, t as APPLE_SA_TO_MACRO, vt as EventCategory, w as defaultDeviceFor$1 } from "../dist-B-VVBzrL.mjs";
3
- import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-DfKdC7G4.mjs";
2
+ import { $ as errMsg, At as EventCategory, Et as object, F as enumerateInferenceDevices, I as evaluateZoneRules, J as runtimeDevices$1, M as detectionPipelineCapability, O as defaultDeviceFor$1, Ot as string, U as pipelineExecutorCapability, Z as supportedRuntimes$1, at as BaseAddon, bt as array, ht as parseJsonUnknown, kt as union, l as DEVICE_BACKEND_TO_FORMAT, lt as createEvent, mt as nodePin, t as APPLE_SA_TO_MACRO, ut as hydrateSchema, vt as sleep, x as YAMNET_TO_MACRO } from "../dist-BDkEhx4Z.mjs";
3
+ import { a as getDefaultModelForFormat, c as getStepDefinition, i as ALL_STEPS, l as resolveModelForFormat, n as startEventLoopStallMonitor, o as getDefaultModelForFormatFromDef, r as ALL_PIPELINE_STEPS, s as getStep } from "../event-loop-stall-monitor-IAuNEdmG.mjs";
4
4
  import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
5
5
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
6
6
  import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../model-download-service-D8B-4ktF-BB7oZL3y.mjs";
@@ -246,7 +246,7 @@ function normalizeEngineNodeId(rawNodeId) {
246
246
  //#endregion
247
247
  //#region src/detection-pipeline/default-detection-model.ts
248
248
  /** The object-detection step id — the only slot this resolver applies to. */
249
- var OBJECT_DETECTION_STEP_ID$1 = "object-detection";
249
+ var OBJECT_DETECTION_STEP_ID = "object-detection";
250
250
  /**
251
251
  * Balanced default object-detection model per accelerator class. `'cpu'` maps to
252
252
  * `null` → the caller substitutes the step's own `defaultModelId` (`yolo26n`).
@@ -308,7 +308,7 @@ function classifyAccelerator(backend, device) {
308
308
  function resolveDefaultDetectionModel(backend, device, format, getStepDef = getStepDefinition) {
309
309
  let def;
310
310
  try {
311
- def = getStepDef(OBJECT_DETECTION_STEP_ID$1);
311
+ def = getStepDef(OBJECT_DETECTION_STEP_ID);
312
312
  } catch {
313
313
  return "yolo26n";
314
314
  }
@@ -434,7 +434,7 @@ var PoolHandle = class {
434
434
  }
435
435
  async infer(input) {
436
436
  const start = performance.now();
437
- return poolResultToEngineOutput(input.kind === "raw" ? await this.pool.inferRaw(this.modelIndex, input.data, input.width, input.height, input.format) : await this.pool.infer(this.modelIndex, input.data), performance.now() - start);
437
+ return poolResultToEngineOutput(input.kind === "raw" ? await this.pool.inferRaw(this.modelIndex, input.data, input.width, input.height, input.format, input.deviceId) : await this.pool.infer(this.modelIndex, input.data, input.deviceId), performance.now() - start);
438
438
  }
439
439
  /**
440
440
  * Inference on a frame previously cached in the Python pool via
@@ -638,12 +638,12 @@ var PoolWorker = class {
638
638
  });
639
639
  });
640
640
  }
641
- async infer(modelByte, jpeg) {
641
+ async infer(modelByte, jpeg, deviceId) {
642
642
  this.ensureReady();
643
643
  const payload = Buffer.concat([Buffer.from([modelByte]), jpeg]);
644
- return this.dispatch(MSG_INFER_JPEG, payload);
644
+ return this.dispatch(MSG_INFER_JPEG, payload, deviceId);
645
645
  }
646
- async inferRaw(modelByte, raw, width, height, format) {
646
+ async inferRaw(modelByte, raw, width, height, format, deviceId) {
647
647
  this.ensureReady();
648
648
  const fmtCode = RAW_FMT_CODE[format];
649
649
  const rawHeader = Buffer.allocUnsafe(10);
@@ -651,7 +651,7 @@ var PoolWorker = class {
651
651
  rawHeader.writeUInt32LE(width, 1);
652
652
  rawHeader.writeUInt32LE(height, 5);
653
653
  rawHeader[9] = fmtCode;
654
- return this.dispatchMulti(MSG_INFER_RAW, rawHeader.length + raw.length, [rawHeader, raw]);
654
+ return this.dispatchMulti(MSG_INFER_RAW, rawHeader.length + raw.length, [rawHeader, raw], deviceId);
655
655
  }
656
656
  async inferBatch(modelByte, items, frameId = 0) {
657
657
  this.ensureReady();
@@ -689,12 +689,12 @@ var PoolWorker = class {
689
689
  const result = await this.dispatchMulti(MSG_CACHE_FRAME, payloadLen, [header, raw]);
690
690
  if (result["error"]) throw new Error(String(result["error"]));
691
691
  }
692
- async inferCached(modelByte, frameId) {
692
+ async inferCached(modelByte, frameId, deviceId) {
693
693
  this.ensureReady();
694
694
  const payload = Buffer.allocUnsafe(5);
695
695
  payload[0] = modelByte;
696
696
  payload.writeUInt32LE(frameId, 1);
697
- return this.dispatch(MSG_INFER_CACHED, payload);
697
+ return this.dispatch(MSG_INFER_CACHED, payload, deviceId);
698
698
  }
699
699
  async sendCommand(cmd) {
700
700
  this.ensureReady();
@@ -718,7 +718,7 @@ var PoolWorker = class {
718
718
  * raise — but logging none is how a camera went dark for 52 minutes looking
719
719
  * like a quiet afternoon.
720
720
  */
721
- shedIfSaturated(msgType) {
721
+ shedIfSaturated(msgType, deviceId) {
722
722
  if (!SHEDDABLE_MSG_TYPES.has(msgType)) return null;
723
723
  if (this.pending.size < POOL_MAX_IN_FLIGHT) {
724
724
  this.shedding = false;
@@ -726,15 +726,18 @@ var PoolWorker = class {
726
726
  }
727
727
  if (!this.shedding) {
728
728
  this.shedding = true;
729
- this.log.warn("worker saturated — shedding inference until it catches up", { meta: {
730
- worker: this.opts.workerLabel,
731
- pid: this.getPid(),
732
- runtime: this.opts.poolRuntime,
733
- device: this.opts.device ?? "default",
734
- inFlight: this.pending.size,
735
- cap: POOL_MAX_IN_FLIGHT,
736
- deadlineMs: POOL_LIVE_INFER_TIMEOUT_MS
737
- } });
729
+ this.log.warn("worker saturated — shedding inference until it catches up", {
730
+ ...deviceId !== void 0 ? { tags: { deviceId } } : {},
731
+ meta: {
732
+ worker: this.opts.workerLabel,
733
+ pid: this.getPid(),
734
+ runtime: this.opts.poolRuntime,
735
+ device: this.opts.device ?? "default",
736
+ inFlight: this.pending.size,
737
+ cap: POOL_MAX_IN_FLIGHT,
738
+ deadlineMs: POOL_LIVE_INFER_TIMEOUT_MS
739
+ }
740
+ });
738
741
  }
739
742
  this.shedCount++;
740
743
  return {
@@ -747,12 +750,12 @@ var PoolWorker = class {
747
750
  deadlineFor(msgType) {
748
751
  return SHEDDABLE_MSG_TYPES.has(msgType) ? POOL_LIVE_INFER_TIMEOUT_MS : POOL_INFER_TIMEOUT_MS;
749
752
  }
750
- dispatch(msgType, payload) {
751
- const shed = this.shedIfSaturated(msgType);
753
+ dispatch(msgType, payload, deviceId) {
754
+ const shed = this.shedIfSaturated(msgType, deviceId);
752
755
  if (shed) return Promise.resolve(shed);
753
756
  const reqId = this.allocRequestId();
754
757
  return new Promise((resolve, reject) => {
755
- const timer = this.armRequestTimeout(reqId, reject, this.deadlineFor(msgType));
758
+ const timer = this.armRequestTimeout(reqId, reject, this.deadlineFor(msgType), deviceId);
756
759
  this.pending.set(reqId, {
757
760
  resolve,
758
761
  reject,
@@ -772,30 +775,33 @@ var PoolWorker = class {
772
775
  * `POOL_INFER_TIMEOUT_MS`. `unref` so it never keeps the event loop alive.
773
776
  * The normal response + `rejectAll` paths clear it via `PendingRequest.timer`.
774
777
  */
775
- armRequestTimeout(reqId, reject, timeoutMs) {
778
+ armRequestTimeout(reqId, reject, timeoutMs, deviceId) {
776
779
  const timer = setTimeout(() => {
777
780
  if (this.pending.delete(reqId)) {
778
- this.log.error("inference request timed out", { meta: {
779
- worker: this.opts.workerLabel,
780
- pid: this.getPid(),
781
- runtime: this.opts.poolRuntime,
782
- device: this.opts.device ?? "default",
783
- inFlight: this.pending.size,
784
- reqId,
785
- timeoutMs
786
- } });
781
+ this.log.error("inference request timed out", {
782
+ ...deviceId !== void 0 ? { tags: { deviceId } } : {},
783
+ meta: {
784
+ worker: this.opts.workerLabel,
785
+ pid: this.getPid(),
786
+ runtime: this.opts.poolRuntime,
787
+ device: this.opts.device ?? "default",
788
+ inFlight: this.pending.size,
789
+ reqId,
790
+ timeoutMs
791
+ }
792
+ });
787
793
  reject(/* @__PURE__ */ new Error(`PoolWorker[${this.opts.workerLabel}]: inference request ${reqId} timed out after ${timeoutMs}ms (worker alive, no reply)`));
788
794
  }
789
795
  }, timeoutMs);
790
796
  timer.unref?.();
791
797
  return timer;
792
798
  }
793
- dispatchMulti(msgType, payloadLen, parts) {
794
- const shed = this.shedIfSaturated(msgType);
799
+ dispatchMulti(msgType, payloadLen, parts, deviceId) {
800
+ const shed = this.shedIfSaturated(msgType, deviceId);
795
801
  if (shed) return Promise.resolve(shed);
796
802
  const reqId = this.allocRequestId();
797
803
  return new Promise((resolve, reject) => {
798
- const timer = this.armRequestTimeout(reqId, reject, this.deadlineFor(msgType));
804
+ const timer = this.armRequestTimeout(reqId, reject, this.deadlineFor(msgType), deviceId);
799
805
  this.pending.set(reqId, {
800
806
  resolve,
801
807
  reject,
@@ -991,21 +997,21 @@ var SharedInferencePool = class {
991
997
  workers: this.numWorkers
992
998
  };
993
999
  }
994
- async infer(modelIndex, jpeg) {
1000
+ async infer(modelIndex, jpeg, deviceId) {
995
1001
  const w = this.pickWorker();
996
- return this.trackDroppedResponse(await w.infer(this.encodeModelByte(modelIndex), jpeg), modelIndex);
1002
+ return this.trackDroppedResponse(await w.infer(this.encodeModelByte(modelIndex), jpeg, deviceId), modelIndex);
997
1003
  }
998
- async inferRaw(modelIndex, raw, width, height, format) {
1004
+ async inferRaw(modelIndex, raw, width, height, format, deviceId) {
999
1005
  const w = this.pickWorker();
1000
- return this.trackDroppedResponse(await w.inferRaw(this.encodeModelByte(modelIndex), raw, width, height, format), modelIndex);
1006
+ return this.trackDroppedResponse(await w.inferRaw(this.encodeModelByte(modelIndex), raw, width, height, format, deviceId), modelIndex);
1001
1007
  }
1002
1008
  async inferBatch(modelIndex, items, frameId = 0) {
1003
1009
  if (items.length > 255) throw new Error(`SharedInferencePool.inferBatch: max 255 items per call, got ${items.length}`);
1004
1010
  return this.pickWorker().inferBatch(this.encodeModelByte(modelIndex), items, frameId);
1005
1011
  }
1006
- async inferCached(modelIndex, frameId) {
1012
+ async inferCached(modelIndex, frameId, deviceId) {
1007
1013
  const w = this.pickWorker();
1008
- return this.trackDroppedResponse(await w.inferCached(this.encodeModelByte(modelIndex), frameId), modelIndex);
1014
+ return this.trackDroppedResponse(await w.inferCached(this.encodeModelByte(modelIndex), frameId, deviceId), modelIndex);
1009
1015
  }
1010
1016
  /**
1011
1017
  * Total `"dropped": true` responses seen from the Python pool — frames
@@ -1631,12 +1637,24 @@ var RUNTIME_TO_FORMAT = {
1631
1637
  onnxruntime: "onnx",
1632
1638
  edgetpu: "tflite"
1633
1639
  };
1634
- /** The step whose detections are re-filtered downstream by the executor's
1635
- * per-macro `minConfidence*` sliders (`macroFilterVerdict`). Its pool-side
1636
- * floor must stay BELOW those sliders or it silently pre-empts them. */
1637
- var OBJECT_DETECTION_STEP_ID = "object-detection";
1638
1640
  /**
1639
- * Pool-side confidence FLOOR for the object-detection step (#4 foot-gun fix).
1641
+ * Steps whose detections are re-filtered downstream by the executor's per-macro
1642
+ * `minConfidence*` sliders (`macroFilterVerdict` runs on EVERY root, keyed by
1643
+ * the detection's macro class). Their pool-side floor must stay BELOW those
1644
+ * sliders or it silently pre-empts them.
1645
+ *
1646
+ * `package-detection` joined on 2026-08-07, when it gained a
1647
+ * `minConfidencePackage` slider: while it kept `defaultConfidence` (0.5) as its
1648
+ * pool floor, every sub-0.5 position of that brand-new slider was dead on
1649
+ * arrival — the same foot-gun this constant was introduced to remove for
1650
+ * object-detection. A step with NO operator-facing threshold keeps its own
1651
+ * `def.defaultConfidence`; adding one without adding the id here ships a
1652
+ * control that lies below its default.
1653
+ */
1654
+ var PER_MACRO_REFILTERED_STEP_IDS = new Set(["object-detection", "package-detection"]);
1655
+ /**
1656
+ * Pool-side confidence FLOOR for a step the executor re-filters (#4 foot-gun
1657
+ * fix).
1640
1658
  *
1641
1659
  * The pool used to receive `def.defaultConfidence` (0.5) as its floor and drop
1642
1660
  * every box < 0.5 BEFORE the executor's per-macro `minConfidence*` sliders run —
@@ -1644,17 +1662,17 @@ var OBJECT_DETECTION_STEP_ID = "object-detection";
1644
1662
  * constant so the operator-visible per-macro sliders are the authoritative
1645
1663
  * filter across their full 0–1 range. The per-macro slider DEFAULTS stay at 0.5
1646
1664
  * (see `step-definitions.ts` getConfigSchema), so default UI behaviour is
1647
- * unchanged; only sub-0.5 sliders start having an effect. Non-object-detection
1648
- * steps keep their own `def.defaultConfidence` floor (no per-macro re-filter). */
1649
- var OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR = .05;
1665
+ * unchanged; only sub-0.5 sliders start having an effect. */
1666
+ var PER_MACRO_POOL_CONFIDENCE_FLOOR = .05;
1650
1667
  /**
1651
- * Resolve the pool-side confidence FLOOR for a step (#4). The object-detection
1652
- * step gets the small {@link OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR} so its
1653
- * per-macro `minConfidence*` sliders (applied downstream by the executor) are
1654
- * the authoritative filter; every other step keeps its own `defaultConfidence`.
1668
+ * Resolve the pool-side confidence FLOOR for a step (#4). A step in
1669
+ * {@link PER_MACRO_REFILTERED_STEP_IDS} gets the small
1670
+ * {@link PER_MACRO_POOL_CONFIDENCE_FLOOR} so its per-macro `minConfidence*`
1671
+ * slider (applied downstream by the executor) is the authoritative filter;
1672
+ * every other step keeps its own `defaultConfidence`.
1655
1673
  */
1656
1674
  function resolvePoolConfidenceFloor(stepId, defaultConfidence) {
1657
- return stepId === OBJECT_DETECTION_STEP_ID ? OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR : defaultConfidence;
1675
+ return PER_MACRO_REFILTERED_STEP_IDS.has(stepId) ? PER_MACRO_POOL_CONFIDENCE_FLOOR : defaultConfidence;
1658
1676
  }
1659
1677
  /**
1660
1678
  * Load a model's runtime label list from its `-labels.json` extraFile, if any.
@@ -2188,6 +2206,7 @@ var VALID_KINDS = new Set([
2188
2206
  var KNOWN_POSTPROCESSORS = new Set([
2189
2207
  "yolo",
2190
2208
  "ssd",
2209
+ "rfdetr",
2191
2210
  "yolo-seg",
2192
2211
  "scrfd",
2193
2212
  "arcface",
@@ -3482,6 +3501,21 @@ function isEnrichmentOutput(output) {
3482
3501
  return output.kind === "classifications" || output.kind === "embedding" || output.kind === "text";
3483
3502
  }
3484
3503
  /**
3504
+ * Stamp the camera onto an inference input so a failure inside the SHARED pool
3505
+ * can name it (`InferenceInput.deviceId` — diagnostic only, it selects
3506
+ * nothing). Immutable, and a no-op for a caller with no real camera: the
3507
+ * benchmark and the reference runs pass `deviceId: 0`, and a fabricated `0` in
3508
+ * a log tag is worse than no tag.
3509
+ */
3510
+ function withInferenceDeviceId(input, deviceId) {
3511
+ if (!Number.isFinite(deviceId) || deviceId <= 0) return input;
3512
+ if (input.deviceId === deviceId) return input;
3513
+ return {
3514
+ ...input,
3515
+ deviceId
3516
+ };
3517
+ }
3518
+ /**
3485
3519
  * Re-project a `crop-zone` root's detections from crop-relative coordinates
3486
3520
  * back to full-frame image space by the crop's origin — the root analogue of
3487
3521
  * the crop-roi child re-projection (`transformBboxToImageSpace`). Landmarks (if
@@ -3585,7 +3619,7 @@ var PipelineExecutor = class {
3585
3619
  if (debug) console.log(`[executor] rootStep=${rootStep.stepId} settings=${JSON.stringify(rootStep.settings ?? {})}`);
3586
3620
  const cropPrep = rootStep.definition.extractMode === "crop-zone" && cropZoneBbox !== void 0 ? await this.prepareCropZoneInput(cropZoneBbox, fullFrameJpegProvider, imageWidth, imageHeight, deviceId) : null;
3587
3621
  const rootStart = Date.now();
3588
- const rawRootOutput = await this.executeStep(rootStep, cropPrep?.input ?? rootInput, cropPrep?.width ?? imageWidth, cropPrep?.height ?? imageHeight, "full-frame", void 0, void 0, traceBuilder, stepTimings, poolAgg, rootShed);
3622
+ const rawRootOutput = await this.executeStep(rootStep, cropPrep?.input ?? withInferenceDeviceId(rootInput, deviceId), cropPrep?.width ?? imageWidth, cropPrep?.height ?? imageHeight, "full-frame", void 0, void 0, traceBuilder, stepTimings, poolAgg, rootShed);
3589
3623
  const rootMs = Date.now() - rootStart;
3590
3624
  if (rawRootOutput === null) continue;
3591
3625
  const rootOutput = cropPrep && rawRootOutput.kind === "detections" ? reprojectCropZoneDetections(rawRootOutput, cropPrep.origin) : rawRootOutput;
@@ -3776,10 +3810,10 @@ var PipelineExecutor = class {
3776
3810
  const ox = Math.max(0, Math.round(cropZoneBbox[0]));
3777
3811
  const oy = Math.max(0, Math.round(cropZoneBbox[1]));
3778
3812
  return {
3779
- input: {
3813
+ input: withInferenceDeviceId({
3780
3814
  kind: "jpeg",
3781
3815
  data: crop.jpeg
3782
- },
3816
+ }, deviceId),
3783
3817
  width: crop.width,
3784
3818
  height: crop.height,
3785
3819
  origin: [
@@ -4005,10 +4039,10 @@ var PipelineExecutor = class {
4005
4039
  }
4006
4040
  } catch {}
4007
4041
  }
4008
- const childOutput = await this.executeStep(child, {
4042
+ const childOutput = await this.executeStep(child, withInferenceDeviceId({
4009
4043
  kind: "jpeg",
4010
4044
  data: cropJpegBuf
4011
- }, cropW, cropH, "crop-roi", parentDetection.bbox, parentDetection.macroClass, traceBuilder, stepTimings, poolAgg);
4045
+ }, deviceId), cropW, cropH, "crop-roi", parentDetection.bbox, parentDetection.macroClass, traceBuilder, stepTimings, poolAgg);
4012
4046
  const childMs = Date.now() - childStart;
4013
4047
  if (childOutput === null) continue;
4014
4048
  const detailsBefore = ctx.details.length;
@@ -4651,8 +4685,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4651
4685
  */
4652
4686
  deviceProxies = /* @__PURE__ */ new Map();
4653
4687
  proxyUnsubs = /* @__PURE__ */ new Map();
4654
- /** Initialization lock — ensures only one init runs at a time; concurrent callers await the same promise */
4655
- initPromise = null;
4656
4688
  /**
4657
4689
  * Last logged "step configured" signature — used to dedupe the
4658
4690
  * per-step debug trail so it only fires on actual config CHANGE
@@ -6693,20 +6725,66 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6693
6725
  * If global steps exist, initializes with them. Otherwise, creates an empty pool.
6694
6726
  */
6695
6727
  async ensureEngineFactory() {
6696
- if (this.initPromise) await this.initPromise;
6697
6728
  if (this.engineFactory) return;
6698
- await this.assertEngineReady();
6699
- await this.ensureBackendDeps(this.currentEngine);
6700
- this.engineFactory = new EngineFactory({
6701
- engine: this.currentEngine,
6702
- modelsDir: this.modelsDir,
6703
- logger: this.log.child("engine"),
6704
- pythonPath: this.executorOptions.pythonPath ?? "",
6705
- provisioning: this.executorOptions.provisioning
6706
- });
6707
- await this.engineFactory.initialize([]);
6729
+ const inflight = this.engineFactoryInflight;
6730
+ if (inflight) {
6731
+ await inflight;
6732
+ return;
6733
+ }
6734
+ const creation = (async () => {
6735
+ await this.assertEngineReady();
6736
+ await this.ensureBackendDeps(this.currentEngine);
6737
+ const factory = new EngineFactory({
6738
+ engine: this.currentEngine,
6739
+ modelsDir: this.modelsDir,
6740
+ logger: this.log.child("engine"),
6741
+ pythonPath: this.executorOptions.pythonPath ?? "",
6742
+ provisioning: this.executorOptions.provisioning
6743
+ });
6744
+ try {
6745
+ await factory.initialize([]);
6746
+ } catch (err) {
6747
+ await factory.dispose().catch(() => void 0);
6748
+ this.log.error("Node-default pool failed to initialize", { meta: {
6749
+ deviceKey: deviceKeyOf(this.currentEngine),
6750
+ backend: this.currentEngine.backend,
6751
+ device: this.currentEngine.device ?? null,
6752
+ error: errMsg(err)
6753
+ } });
6754
+ throw err;
6755
+ }
6756
+ this.engineFactory = factory;
6757
+ return factory;
6758
+ })();
6759
+ this.engineFactoryInflight = creation;
6760
+ try {
6761
+ await creation;
6762
+ } finally {
6763
+ this.engineFactoryInflight = null;
6764
+ }
6708
6765
  }
6709
6766
  /**
6767
+ * In-flight node-default pool creation. Single-flights
6768
+ * {@link ensureEngineFactory} exactly as `deviceFactoryInflight` does for the
6769
+ * per-device pools.
6770
+ *
6771
+ * Its absence was the chronic `PoolWorker[w0]: not initialized` storm
6772
+ * (1263 lines / 24 h, 2026-08-07): `this.engineFactory` was assigned BEFORE
6773
+ * `initialize()` was awaited, so every caller arriving during the 1-3.5 s
6774
+ * Python spawn took the early-exit and dispatched into a worker whose ready
6775
+ * handshake had not landed. The frames were logged as ERRORs and lost.
6776
+ *
6777
+ * The same bug was found and fixed twice before in this file — once for the
6778
+ * benchmark override cache (whose comment describes it verbatim) and once for
6779
+ * `resolveDeviceFactory` — which is why no `not initialized` line ever
6780
+ * carried a NON-default deviceKey. This is the third and last copy.
6781
+ *
6782
+ * It replaces a `private initPromise` field that was read here and assigned
6783
+ * NOWHERE: a dead lock that read, for as long as it existed, as if this path
6784
+ * were already guarded.
6785
+ */
6786
+ engineFactoryInflight = null;
6787
+ /**
6710
6788
  * Get (or lazily build) the inference-pool factory for an explicit device
6711
6789
  * (`<backend>:<device>`, e.g. `openvino:npu`, `edgetpu:usb`) — Phase 2
6712
6790
  * multi-device. Each device runs its OWN pool concurrently. Never mutates
@@ -7716,13 +7794,12 @@ var DetectionPipelineAddon = class extends BaseAddon {
7716
7794
  }
7717
7795
  }
7718
7796
  /**
7719
- * Bare node id used to scope the engine cascade in the shared store. MUST
7720
- * match the provider's `localProbeNodeId()` (kernel.localNodeId, default
7721
- * 'hub') so the UI write path (this) and the provider read/write path
7722
- * (loadEngine / autoPick / reprobe) target the SAME `<key>@<nodeId>`. The old
7723
- * `?? this.ctx.id` fallback resolved to the ADDON id ('detection-pipeline')
7724
- * when kernel.localNodeId was absent, so UI saves landed on a key the
7725
- * provider never read — silently losing the per-node selection.
7797
+ * Bare node id for this instance. MUST match the provider's
7798
+ * `localProbeNodeId()` (kernel.localNodeId, default 'hub') so the per-node
7799
+ * settings projection here and the provider's own node targeting agree. The
7800
+ * old `?? this.ctx.id` fallback resolved to the ADDON id
7801
+ * ('detection-pipeline') when kernel.localNodeId was absent, so UI saves
7802
+ * landed on a key the provider never read.
7726
7803
  */
7727
7804
  localNodeId() {
7728
7805
  return normalizeEngineNodeId(this.ctxIfReady?.kernel?.localNodeId ?? "hub");