@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
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-UHbKZiSa.js");
6
+ const require_dist = require("../dist-QyZ7QSRP.js");
7
7
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
8
8
  const require_model_download_service_D8B_4ktF = require("../model-download-service-D8B-4ktF-7-YxE9Wx.js");
9
9
  let node_fs = require("node:fs");
@@ -1,5 +1,5 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { K as errMsg, N as mapAudioLabelToMacro, Q as hydrateSchema, b as audioAnalyzerCapability, n as AUDIO_BACKEND_CHOICES, p as HF_BASE_URL, q as BaseAddon, s as DEFAULT_AUDIO_ANALYZER_CONFIG, y as audioAnalysisCapability } from "../dist-B-VVBzrL.mjs";
2
+ import { $ as errMsg, C as audioAnalysisCapability, R as mapAudioLabelToMacro, at as BaseAddon, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, p as HF_BASE_URL, ut as hydrateSchema, w as audioAnalyzerCapability } from "../dist-BDkEhx4Z.mjs";
3
3
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
4
4
  import { i as downloadFile } from "../model-download-service-D8B-4ktF-BB7oZL3y.mjs";
5
5
  import * as fs from "node:fs";
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-UHbKZiSa.js");
7
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-C7_L6vHO.js");
6
+ const require_dist = require("../dist-QyZ7QSRP.js");
7
+ const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-COEKVBQK.js");
8
8
  const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
9
9
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
10
10
  const require_model_download_service_D8B_4ktF = require("../model-download-service-D8B-4ktF-7-YxE9Wx.js");
@@ -254,7 +254,7 @@ function normalizeEngineNodeId(rawNodeId) {
254
254
  //#endregion
255
255
  //#region src/detection-pipeline/default-detection-model.ts
256
256
  /** The object-detection step id — the only slot this resolver applies to. */
257
- var OBJECT_DETECTION_STEP_ID$1 = "object-detection";
257
+ var OBJECT_DETECTION_STEP_ID = "object-detection";
258
258
  /**
259
259
  * Balanced default object-detection model per accelerator class. `'cpu'` maps to
260
260
  * `null` → the caller substitutes the step's own `defaultModelId` (`yolo26n`).
@@ -316,7 +316,7 @@ function classifyAccelerator(backend, device) {
316
316
  function resolveDefaultDetectionModel(backend, device, format, getStepDef = require_event_loop_stall_monitor.getStepDefinition) {
317
317
  let def;
318
318
  try {
319
- def = getStepDef(OBJECT_DETECTION_STEP_ID$1);
319
+ def = getStepDef(OBJECT_DETECTION_STEP_ID);
320
320
  } catch {
321
321
  return "yolo26n";
322
322
  }
@@ -442,7 +442,7 @@ var PoolHandle = class {
442
442
  }
443
443
  async infer(input) {
444
444
  const start = performance.now();
445
- 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);
445
+ 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);
446
446
  }
447
447
  /**
448
448
  * Inference on a frame previously cached in the Python pool via
@@ -646,12 +646,12 @@ var PoolWorker = class {
646
646
  });
647
647
  });
648
648
  }
649
- async infer(modelByte, jpeg) {
649
+ async infer(modelByte, jpeg, deviceId) {
650
650
  this.ensureReady();
651
651
  const payload = Buffer.concat([Buffer.from([modelByte]), jpeg]);
652
- return this.dispatch(MSG_INFER_JPEG, payload);
652
+ return this.dispatch(MSG_INFER_JPEG, payload, deviceId);
653
653
  }
654
- async inferRaw(modelByte, raw, width, height, format) {
654
+ async inferRaw(modelByte, raw, width, height, format, deviceId) {
655
655
  this.ensureReady();
656
656
  const fmtCode = RAW_FMT_CODE[format];
657
657
  const rawHeader = Buffer.allocUnsafe(10);
@@ -659,7 +659,7 @@ var PoolWorker = class {
659
659
  rawHeader.writeUInt32LE(width, 1);
660
660
  rawHeader.writeUInt32LE(height, 5);
661
661
  rawHeader[9] = fmtCode;
662
- return this.dispatchMulti(MSG_INFER_RAW, rawHeader.length + raw.length, [rawHeader, raw]);
662
+ return this.dispatchMulti(MSG_INFER_RAW, rawHeader.length + raw.length, [rawHeader, raw], deviceId);
663
663
  }
664
664
  async inferBatch(modelByte, items, frameId = 0) {
665
665
  this.ensureReady();
@@ -697,12 +697,12 @@ var PoolWorker = class {
697
697
  const result = await this.dispatchMulti(MSG_CACHE_FRAME, payloadLen, [header, raw]);
698
698
  if (result["error"]) throw new Error(String(result["error"]));
699
699
  }
700
- async inferCached(modelByte, frameId) {
700
+ async inferCached(modelByte, frameId, deviceId) {
701
701
  this.ensureReady();
702
702
  const payload = Buffer.allocUnsafe(5);
703
703
  payload[0] = modelByte;
704
704
  payload.writeUInt32LE(frameId, 1);
705
- return this.dispatch(MSG_INFER_CACHED, payload);
705
+ return this.dispatch(MSG_INFER_CACHED, payload, deviceId);
706
706
  }
707
707
  async sendCommand(cmd) {
708
708
  this.ensureReady();
@@ -726,7 +726,7 @@ var PoolWorker = class {
726
726
  * raise — but logging none is how a camera went dark for 52 minutes looking
727
727
  * like a quiet afternoon.
728
728
  */
729
- shedIfSaturated(msgType) {
729
+ shedIfSaturated(msgType, deviceId) {
730
730
  if (!SHEDDABLE_MSG_TYPES.has(msgType)) return null;
731
731
  if (this.pending.size < POOL_MAX_IN_FLIGHT) {
732
732
  this.shedding = false;
@@ -734,15 +734,18 @@ var PoolWorker = class {
734
734
  }
735
735
  if (!this.shedding) {
736
736
  this.shedding = true;
737
- this.log.warn("worker saturated — shedding inference until it catches up", { meta: {
738
- worker: this.opts.workerLabel,
739
- pid: this.getPid(),
740
- runtime: this.opts.poolRuntime,
741
- device: this.opts.device ?? "default",
742
- inFlight: this.pending.size,
743
- cap: POOL_MAX_IN_FLIGHT,
744
- deadlineMs: POOL_LIVE_INFER_TIMEOUT_MS
745
- } });
737
+ this.log.warn("worker saturated — shedding inference until it catches up", {
738
+ ...deviceId !== void 0 ? { tags: { deviceId } } : {},
739
+ meta: {
740
+ worker: this.opts.workerLabel,
741
+ pid: this.getPid(),
742
+ runtime: this.opts.poolRuntime,
743
+ device: this.opts.device ?? "default",
744
+ inFlight: this.pending.size,
745
+ cap: POOL_MAX_IN_FLIGHT,
746
+ deadlineMs: POOL_LIVE_INFER_TIMEOUT_MS
747
+ }
748
+ });
746
749
  }
747
750
  this.shedCount++;
748
751
  return {
@@ -755,12 +758,12 @@ var PoolWorker = class {
755
758
  deadlineFor(msgType) {
756
759
  return SHEDDABLE_MSG_TYPES.has(msgType) ? POOL_LIVE_INFER_TIMEOUT_MS : POOL_INFER_TIMEOUT_MS;
757
760
  }
758
- dispatch(msgType, payload) {
759
- const shed = this.shedIfSaturated(msgType);
761
+ dispatch(msgType, payload, deviceId) {
762
+ const shed = this.shedIfSaturated(msgType, deviceId);
760
763
  if (shed) return Promise.resolve(shed);
761
764
  const reqId = this.allocRequestId();
762
765
  return new Promise((resolve, reject) => {
763
- const timer = this.armRequestTimeout(reqId, reject, this.deadlineFor(msgType));
766
+ const timer = this.armRequestTimeout(reqId, reject, this.deadlineFor(msgType), deviceId);
764
767
  this.pending.set(reqId, {
765
768
  resolve,
766
769
  reject,
@@ -780,30 +783,33 @@ var PoolWorker = class {
780
783
  * `POOL_INFER_TIMEOUT_MS`. `unref` so it never keeps the event loop alive.
781
784
  * The normal response + `rejectAll` paths clear it via `PendingRequest.timer`.
782
785
  */
783
- armRequestTimeout(reqId, reject, timeoutMs) {
786
+ armRequestTimeout(reqId, reject, timeoutMs, deviceId) {
784
787
  const timer = setTimeout(() => {
785
788
  if (this.pending.delete(reqId)) {
786
- this.log.error("inference request timed out", { meta: {
787
- worker: this.opts.workerLabel,
788
- pid: this.getPid(),
789
- runtime: this.opts.poolRuntime,
790
- device: this.opts.device ?? "default",
791
- inFlight: this.pending.size,
792
- reqId,
793
- timeoutMs
794
- } });
789
+ this.log.error("inference request timed out", {
790
+ ...deviceId !== void 0 ? { tags: { deviceId } } : {},
791
+ meta: {
792
+ worker: this.opts.workerLabel,
793
+ pid: this.getPid(),
794
+ runtime: this.opts.poolRuntime,
795
+ device: this.opts.device ?? "default",
796
+ inFlight: this.pending.size,
797
+ reqId,
798
+ timeoutMs
799
+ }
800
+ });
795
801
  reject(/* @__PURE__ */ new Error(`PoolWorker[${this.opts.workerLabel}]: inference request ${reqId} timed out after ${timeoutMs}ms (worker alive, no reply)`));
796
802
  }
797
803
  }, timeoutMs);
798
804
  timer.unref?.();
799
805
  return timer;
800
806
  }
801
- dispatchMulti(msgType, payloadLen, parts) {
802
- const shed = this.shedIfSaturated(msgType);
807
+ dispatchMulti(msgType, payloadLen, parts, deviceId) {
808
+ const shed = this.shedIfSaturated(msgType, deviceId);
803
809
  if (shed) return Promise.resolve(shed);
804
810
  const reqId = this.allocRequestId();
805
811
  return new Promise((resolve, reject) => {
806
- const timer = this.armRequestTimeout(reqId, reject, this.deadlineFor(msgType));
812
+ const timer = this.armRequestTimeout(reqId, reject, this.deadlineFor(msgType), deviceId);
807
813
  this.pending.set(reqId, {
808
814
  resolve,
809
815
  reject,
@@ -999,21 +1005,21 @@ var SharedInferencePool = class {
999
1005
  workers: this.numWorkers
1000
1006
  };
1001
1007
  }
1002
- async infer(modelIndex, jpeg) {
1008
+ async infer(modelIndex, jpeg, deviceId) {
1003
1009
  const w = this.pickWorker();
1004
- return this.trackDroppedResponse(await w.infer(this.encodeModelByte(modelIndex), jpeg), modelIndex);
1010
+ return this.trackDroppedResponse(await w.infer(this.encodeModelByte(modelIndex), jpeg, deviceId), modelIndex);
1005
1011
  }
1006
- async inferRaw(modelIndex, raw, width, height, format) {
1012
+ async inferRaw(modelIndex, raw, width, height, format, deviceId) {
1007
1013
  const w = this.pickWorker();
1008
- return this.trackDroppedResponse(await w.inferRaw(this.encodeModelByte(modelIndex), raw, width, height, format), modelIndex);
1014
+ return this.trackDroppedResponse(await w.inferRaw(this.encodeModelByte(modelIndex), raw, width, height, format, deviceId), modelIndex);
1009
1015
  }
1010
1016
  async inferBatch(modelIndex, items, frameId = 0) {
1011
1017
  if (items.length > 255) throw new Error(`SharedInferencePool.inferBatch: max 255 items per call, got ${items.length}`);
1012
1018
  return this.pickWorker().inferBatch(this.encodeModelByte(modelIndex), items, frameId);
1013
1019
  }
1014
- async inferCached(modelIndex, frameId) {
1020
+ async inferCached(modelIndex, frameId, deviceId) {
1015
1021
  const w = this.pickWorker();
1016
- return this.trackDroppedResponse(await w.inferCached(this.encodeModelByte(modelIndex), frameId), modelIndex);
1022
+ return this.trackDroppedResponse(await w.inferCached(this.encodeModelByte(modelIndex), frameId, deviceId), modelIndex);
1017
1023
  }
1018
1024
  /**
1019
1025
  * Total `"dropped": true` responses seen from the Python pool — frames
@@ -1639,12 +1645,24 @@ var RUNTIME_TO_FORMAT = {
1639
1645
  onnxruntime: "onnx",
1640
1646
  edgetpu: "tflite"
1641
1647
  };
1642
- /** The step whose detections are re-filtered downstream by the executor's
1643
- * per-macro `minConfidence*` sliders (`macroFilterVerdict`). Its pool-side
1644
- * floor must stay BELOW those sliders or it silently pre-empts them. */
1645
- var OBJECT_DETECTION_STEP_ID = "object-detection";
1646
1648
  /**
1647
- * Pool-side confidence FLOOR for the object-detection step (#4 foot-gun fix).
1649
+ * Steps whose detections are re-filtered downstream by the executor's per-macro
1650
+ * `minConfidence*` sliders (`macroFilterVerdict` runs on EVERY root, keyed by
1651
+ * the detection's macro class). Their pool-side floor must stay BELOW those
1652
+ * sliders or it silently pre-empts them.
1653
+ *
1654
+ * `package-detection` joined on 2026-08-07, when it gained a
1655
+ * `minConfidencePackage` slider: while it kept `defaultConfidence` (0.5) as its
1656
+ * pool floor, every sub-0.5 position of that brand-new slider was dead on
1657
+ * arrival — the same foot-gun this constant was introduced to remove for
1658
+ * object-detection. A step with NO operator-facing threshold keeps its own
1659
+ * `def.defaultConfidence`; adding one without adding the id here ships a
1660
+ * control that lies below its default.
1661
+ */
1662
+ var PER_MACRO_REFILTERED_STEP_IDS = new Set(["object-detection", "package-detection"]);
1663
+ /**
1664
+ * Pool-side confidence FLOOR for a step the executor re-filters (#4 foot-gun
1665
+ * fix).
1648
1666
  *
1649
1667
  * The pool used to receive `def.defaultConfidence` (0.5) as its floor and drop
1650
1668
  * every box < 0.5 BEFORE the executor's per-macro `minConfidence*` sliders run —
@@ -1652,17 +1670,17 @@ var OBJECT_DETECTION_STEP_ID = "object-detection";
1652
1670
  * constant so the operator-visible per-macro sliders are the authoritative
1653
1671
  * filter across their full 0–1 range. The per-macro slider DEFAULTS stay at 0.5
1654
1672
  * (see `step-definitions.ts` getConfigSchema), so default UI behaviour is
1655
- * unchanged; only sub-0.5 sliders start having an effect. Non-object-detection
1656
- * steps keep their own `def.defaultConfidence` floor (no per-macro re-filter). */
1657
- var OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR = .05;
1673
+ * unchanged; only sub-0.5 sliders start having an effect. */
1674
+ var PER_MACRO_POOL_CONFIDENCE_FLOOR = .05;
1658
1675
  /**
1659
- * Resolve the pool-side confidence FLOOR for a step (#4). The object-detection
1660
- * step gets the small {@link OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR} so its
1661
- * per-macro `minConfidence*` sliders (applied downstream by the executor) are
1662
- * the authoritative filter; every other step keeps its own `defaultConfidence`.
1676
+ * Resolve the pool-side confidence FLOOR for a step (#4). A step in
1677
+ * {@link PER_MACRO_REFILTERED_STEP_IDS} gets the small
1678
+ * {@link PER_MACRO_POOL_CONFIDENCE_FLOOR} so its per-macro `minConfidence*`
1679
+ * slider (applied downstream by the executor) is the authoritative filter;
1680
+ * every other step keeps its own `defaultConfidence`.
1663
1681
  */
1664
1682
  function resolvePoolConfidenceFloor(stepId, defaultConfidence) {
1665
- return stepId === OBJECT_DETECTION_STEP_ID ? OBJECT_DETECTION_POOL_CONFIDENCE_FLOOR : defaultConfidence;
1683
+ return PER_MACRO_REFILTERED_STEP_IDS.has(stepId) ? PER_MACRO_POOL_CONFIDENCE_FLOOR : defaultConfidence;
1666
1684
  }
1667
1685
  /**
1668
1686
  * Load a model's runtime label list from its `-labels.json` extraFile, if any.
@@ -2196,6 +2214,7 @@ var VALID_KINDS = new Set([
2196
2214
  var KNOWN_POSTPROCESSORS = new Set([
2197
2215
  "yolo",
2198
2216
  "ssd",
2217
+ "rfdetr",
2199
2218
  "yolo-seg",
2200
2219
  "scrfd",
2201
2220
  "arcface",
@@ -3490,6 +3509,21 @@ function isEnrichmentOutput(output) {
3490
3509
  return output.kind === "classifications" || output.kind === "embedding" || output.kind === "text";
3491
3510
  }
3492
3511
  /**
3512
+ * Stamp the camera onto an inference input so a failure inside the SHARED pool
3513
+ * can name it (`InferenceInput.deviceId` — diagnostic only, it selects
3514
+ * nothing). Immutable, and a no-op for a caller with no real camera: the
3515
+ * benchmark and the reference runs pass `deviceId: 0`, and a fabricated `0` in
3516
+ * a log tag is worse than no tag.
3517
+ */
3518
+ function withInferenceDeviceId(input, deviceId) {
3519
+ if (!Number.isFinite(deviceId) || deviceId <= 0) return input;
3520
+ if (input.deviceId === deviceId) return input;
3521
+ return {
3522
+ ...input,
3523
+ deviceId
3524
+ };
3525
+ }
3526
+ /**
3493
3527
  * Re-project a `crop-zone` root's detections from crop-relative coordinates
3494
3528
  * back to full-frame image space by the crop's origin — the root analogue of
3495
3529
  * the crop-roi child re-projection (`transformBboxToImageSpace`). Landmarks (if
@@ -3593,7 +3627,7 @@ var PipelineExecutor = class {
3593
3627
  if (debug) console.log(`[executor] rootStep=${rootStep.stepId} settings=${JSON.stringify(rootStep.settings ?? {})}`);
3594
3628
  const cropPrep = rootStep.definition.extractMode === "crop-zone" && cropZoneBbox !== void 0 ? await this.prepareCropZoneInput(cropZoneBbox, fullFrameJpegProvider, imageWidth, imageHeight, deviceId) : null;
3595
3629
  const rootStart = Date.now();
3596
- 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);
3630
+ 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);
3597
3631
  const rootMs = Date.now() - rootStart;
3598
3632
  if (rawRootOutput === null) continue;
3599
3633
  const rootOutput = cropPrep && rawRootOutput.kind === "detections" ? reprojectCropZoneDetections(rawRootOutput, cropPrep.origin) : rawRootOutput;
@@ -3784,10 +3818,10 @@ var PipelineExecutor = class {
3784
3818
  const ox = Math.max(0, Math.round(cropZoneBbox[0]));
3785
3819
  const oy = Math.max(0, Math.round(cropZoneBbox[1]));
3786
3820
  return {
3787
- input: {
3821
+ input: withInferenceDeviceId({
3788
3822
  kind: "jpeg",
3789
3823
  data: crop.jpeg
3790
- },
3824
+ }, deviceId),
3791
3825
  width: crop.width,
3792
3826
  height: crop.height,
3793
3827
  origin: [
@@ -4013,10 +4047,10 @@ var PipelineExecutor = class {
4013
4047
  }
4014
4048
  } catch {}
4015
4049
  }
4016
- const childOutput = await this.executeStep(child, {
4050
+ const childOutput = await this.executeStep(child, withInferenceDeviceId({
4017
4051
  kind: "jpeg",
4018
4052
  data: cropJpegBuf
4019
- }, cropW, cropH, "crop-roi", parentDetection.bbox, parentDetection.macroClass, traceBuilder, stepTimings, poolAgg);
4053
+ }, deviceId), cropW, cropH, "crop-roi", parentDetection.bbox, parentDetection.macroClass, traceBuilder, stepTimings, poolAgg);
4020
4054
  const childMs = Date.now() - childStart;
4021
4055
  if (childOutput === null) continue;
4022
4056
  const detailsBefore = ctx.details.length;
@@ -4659,8 +4693,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4659
4693
  */
4660
4694
  deviceProxies = /* @__PURE__ */ new Map();
4661
4695
  proxyUnsubs = /* @__PURE__ */ new Map();
4662
- /** Initialization lock — ensures only one init runs at a time; concurrent callers await the same promise */
4663
- initPromise = null;
4664
4696
  /**
4665
4697
  * Last logged "step configured" signature — used to dedupe the
4666
4698
  * per-step debug trail so it only fires on actual config CHANGE
@@ -6701,20 +6733,66 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6701
6733
  * If global steps exist, initializes with them. Otherwise, creates an empty pool.
6702
6734
  */
6703
6735
  async ensureEngineFactory() {
6704
- if (this.initPromise) await this.initPromise;
6705
6736
  if (this.engineFactory) return;
6706
- await this.assertEngineReady();
6707
- await this.ensureBackendDeps(this.currentEngine);
6708
- this.engineFactory = new EngineFactory({
6709
- engine: this.currentEngine,
6710
- modelsDir: this.modelsDir,
6711
- logger: this.log.child("engine"),
6712
- pythonPath: this.executorOptions.pythonPath ?? "",
6713
- provisioning: this.executorOptions.provisioning
6714
- });
6715
- await this.engineFactory.initialize([]);
6737
+ const inflight = this.engineFactoryInflight;
6738
+ if (inflight) {
6739
+ await inflight;
6740
+ return;
6741
+ }
6742
+ const creation = (async () => {
6743
+ await this.assertEngineReady();
6744
+ await this.ensureBackendDeps(this.currentEngine);
6745
+ const factory = new EngineFactory({
6746
+ engine: this.currentEngine,
6747
+ modelsDir: this.modelsDir,
6748
+ logger: this.log.child("engine"),
6749
+ pythonPath: this.executorOptions.pythonPath ?? "",
6750
+ provisioning: this.executorOptions.provisioning
6751
+ });
6752
+ try {
6753
+ await factory.initialize([]);
6754
+ } catch (err) {
6755
+ await factory.dispose().catch(() => void 0);
6756
+ this.log.error("Node-default pool failed to initialize", { meta: {
6757
+ deviceKey: deviceKeyOf(this.currentEngine),
6758
+ backend: this.currentEngine.backend,
6759
+ device: this.currentEngine.device ?? null,
6760
+ error: require_dist.errMsg(err)
6761
+ } });
6762
+ throw err;
6763
+ }
6764
+ this.engineFactory = factory;
6765
+ return factory;
6766
+ })();
6767
+ this.engineFactoryInflight = creation;
6768
+ try {
6769
+ await creation;
6770
+ } finally {
6771
+ this.engineFactoryInflight = null;
6772
+ }
6716
6773
  }
6717
6774
  /**
6775
+ * In-flight node-default pool creation. Single-flights
6776
+ * {@link ensureEngineFactory} exactly as `deviceFactoryInflight` does for the
6777
+ * per-device pools.
6778
+ *
6779
+ * Its absence was the chronic `PoolWorker[w0]: not initialized` storm
6780
+ * (1263 lines / 24 h, 2026-08-07): `this.engineFactory` was assigned BEFORE
6781
+ * `initialize()` was awaited, so every caller arriving during the 1-3.5 s
6782
+ * Python spawn took the early-exit and dispatched into a worker whose ready
6783
+ * handshake had not landed. The frames were logged as ERRORs and lost.
6784
+ *
6785
+ * The same bug was found and fixed twice before in this file — once for the
6786
+ * benchmark override cache (whose comment describes it verbatim) and once for
6787
+ * `resolveDeviceFactory` — which is why no `not initialized` line ever
6788
+ * carried a NON-default deviceKey. This is the third and last copy.
6789
+ *
6790
+ * It replaces a `private initPromise` field that was read here and assigned
6791
+ * NOWHERE: a dead lock that read, for as long as it existed, as if this path
6792
+ * were already guarded.
6793
+ */
6794
+ engineFactoryInflight = null;
6795
+ /**
6718
6796
  * Get (or lazily build) the inference-pool factory for an explicit device
6719
6797
  * (`<backend>:<device>`, e.g. `openvino:npu`, `edgetpu:usb`) — Phase 2
6720
6798
  * multi-device. Each device runs its OWN pool concurrently. Never mutates
@@ -7724,13 +7802,12 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7724
7802
  }
7725
7803
  }
7726
7804
  /**
7727
- * Bare node id used to scope the engine cascade in the shared store. MUST
7728
- * match the provider's `localProbeNodeId()` (kernel.localNodeId, default
7729
- * 'hub') so the UI write path (this) and the provider read/write path
7730
- * (loadEngine / autoPick / reprobe) target the SAME `<key>@<nodeId>`. The old
7731
- * `?? this.ctx.id` fallback resolved to the ADDON id ('detection-pipeline')
7732
- * when kernel.localNodeId was absent, so UI saves landed on a key the
7733
- * provider never read — silently losing the per-node selection.
7805
+ * Bare node id for this instance. MUST match the provider's
7806
+ * `localProbeNodeId()` (kernel.localNodeId, default 'hub') so the per-node
7807
+ * settings projection here and the provider's own node targeting agree. The
7808
+ * old `?? this.ctx.id` fallback resolved to the ADDON id
7809
+ * ('detection-pipeline') when kernel.localNodeId was absent, so UI saves
7810
+ * landed on a key the provider never read.
7734
7811
  */
7735
7812
  localNodeId() {
7736
7813
  return normalizeEngineNodeId(this.ctxIfReady?.kernel?.localNodeId ?? "hub");