@camstack/addon-pipeline 1.2.42 → 1.2.44

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 (39) hide show
  1. package/dist/audio-analyzer/index.js +4 -4
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/detection-pipeline/index.js +164 -88
  4. package/dist/detection-pipeline/index.mjs +158 -82
  5. package/dist/{dist-BdDd3ql_.js → dist-BuFE5rOK.js} +1608 -36
  6. package/dist/{dist-COK_1Ot8.mjs → dist-DOu93i_g.mjs} +1525 -37
  7. package/dist/{event-loop-stall-monitor-CWOJtTNc.mjs → event-loop-stall-monitor-CShgQE6l.mjs} +40 -2
  8. package/dist/{event-loop-stall-monitor-DNFRgNef.js → event-loop-stall-monitor-L08yG6bB.js} +40 -2
  9. package/dist/{model-download-service-Cp9f4dk6-fsdDExML.js → model-download-service-D8B-4ktF-7-YxE9Wx.js} +2 -2
  10. package/dist/{model-download-service-Cp9f4dk6-CwUb5v3Y.mjs → model-download-service-D8B-4ktF-BB7oZL3y.mjs} +2 -2
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/pipeline-runner/index.js +496 -67
  14. package/dist/pipeline-runner/index.mjs +496 -67
  15. package/dist/recorder/index.js +194 -11
  16. package/dist/recorder/index.mjs +191 -8
  17. package/dist/session-decode/decode-worker-child.js +197 -24
  18. package/dist/session-decode/decode-worker-child.mjs +197 -24
  19. package/dist/stream-broker/_stub.js +2 -2
  20. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C8ovZN0g.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DiXoZnou.mjs} +2 -2
  21. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BRc-m6W-.mjs +26 -0
  22. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DAjAxXHm.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B_hakWAb.mjs} +1 -1
  23. package/dist/stream-broker/{hostInit-mEtjQWcr.mjs → hostInit-khRXMnjl.mjs} +2 -2
  24. package/dist/stream-broker/index.js +17331 -14101
  25. package/dist/stream-broker/index.mjs +17330 -14100
  26. package/dist/stream-broker/remoteEntry.js +1 -1
  27. package/dist/worker-protocol-BOXlUhWO.mjs +244 -0
  28. package/dist/worker-protocol-VURr0nUh.js +279 -0
  29. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C0TIfr0G.js → MaskShapeCanvas-DI4BY7W2-DHZuYYjH.js} +1 -1
  30. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-x0SkfHwv.js → MotionZonesSettings-NcxxQN8r-jFDOzPCD.js} +1 -1
  31. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BoaAB4Ta.js → PrivacyMaskSettings-APgPLF7p-BPBXgIX8.js} +1 -1
  32. package/embed-dist/assets/{index-BgRdCCVy.js → index-4CtQAybX.js} +12 -12
  33. package/embed-dist/assets/index-DC63og2c.css +2 -0
  34. package/embed-dist/index.html +2 -2
  35. package/package.json +1 -1
  36. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-SU_6F3Y7.mjs +0 -26
  37. package/dist/worker-protocol-D7RzZIla.mjs +0 -77
  38. package/dist/worker-protocol-DextwlTX.js +0 -94
  39. package/embed-dist/assets/index-CGdwTcwE.css +0 -2
@@ -3,9 +3,9 @@ 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-BdDd3ql_.js");
6
+ const require_dist = require("../dist-BuFE5rOK.js");
7
7
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
8
- const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-fsdDExML.js");
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");
10
10
  node_fs = require_chunk.__toESM(node_fs);
11
11
  let node_path = require("node:path");
@@ -84,7 +84,7 @@ var YamnetPythonPipeline = class {
84
84
  url: YAMNET_MODEL_URL,
85
85
  dest: modelPath
86
86
  } });
87
- await require_model_download_service_Cp9f4dk6.downloadFile(YAMNET_MODEL_URL, modelPath);
87
+ await require_model_download_service_D8B_4ktF.downloadFile(YAMNET_MODEL_URL, modelPath);
88
88
  this.log.info("YAMNet ONNX model downloaded", { meta: { sizeBytes: node_fs.statSync(modelPath).size } });
89
89
  }
90
90
  if (!node_fs.existsSync(labelsPath)) {
@@ -92,7 +92,7 @@ var YamnetPythonPipeline = class {
92
92
  url: YAMNET_LABELS_URL,
93
93
  dest: labelsPath
94
94
  } });
95
- await require_model_download_service_Cp9f4dk6.downloadFile(YAMNET_LABELS_URL, labelsPath);
95
+ await require_model_download_service_D8B_4ktF.downloadFile(YAMNET_LABELS_URL, labelsPath);
96
96
  }
97
97
  const pythonDir = resolveAudioPythonDir();
98
98
  if (this.installPythonRequirements) await this.installPythonRequirements(node_path.join(pythonDir, "requirements-audio.txt"));
@@ -1,7 +1,7 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { A as mapAudioLabelToMacro, U as errMsg, W as BaseAddon, Y as hydrateSchema, f as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, v as audioAnalysisCapability, y as audioAnalyzerCapability } from "../dist-COK_1Ot8.mjs";
2
+ import { H as mapAudioLabelToMacro, T as audioAnalyzerCapability, it as BaseAddon, lt as hydrateSchema, m as HF_BASE_URL, n as AUDIO_BACKEND_CHOICES, rt as errMsg, s as DEFAULT_AUDIO_ANALYZER_CONFIG, w as audioAnalysisCapability } from "../dist-DOu93i_g.mjs";
3
3
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
4
- import { i as downloadFile } from "../model-download-service-Cp9f4dk6-CwUb5v3Y.mjs";
4
+ import { i as downloadFile } from "../model-download-service-D8B-4ktF-BB7oZL3y.mjs";
5
5
  import * as fs from "node:fs";
6
6
  import * as path$1 from "node:path";
7
7
  //#region src/audio-analyzer/audio-pipeline.ts
@@ -3,11 +3,11 @@ 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-BdDd3ql_.js");
7
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-DNFRgNef.js");
6
+ const require_dist = require("../dist-BuFE5rOK.js");
7
+ const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-L08yG6bB.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
- const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-fsdDExML.js");
10
+ const require_model_download_service_D8B_4ktF = require("../model-download-service-D8B-4ktF-7-YxE9Wx.js");
11
11
  let sharp = require("sharp");
12
12
  sharp = require_chunk.__toESM(sharp);
13
13
  let node_child_process = require("node:child_process");
@@ -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.
@@ -3490,6 +3508,21 @@ function isEnrichmentOutput(output) {
3490
3508
  return output.kind === "classifications" || output.kind === "embedding" || output.kind === "text";
3491
3509
  }
3492
3510
  /**
3511
+ * Stamp the camera onto an inference input so a failure inside the SHARED pool
3512
+ * can name it (`InferenceInput.deviceId` — diagnostic only, it selects
3513
+ * nothing). Immutable, and a no-op for a caller with no real camera: the
3514
+ * benchmark and the reference runs pass `deviceId: 0`, and a fabricated `0` in
3515
+ * a log tag is worse than no tag.
3516
+ */
3517
+ function withInferenceDeviceId(input, deviceId) {
3518
+ if (!Number.isFinite(deviceId) || deviceId <= 0) return input;
3519
+ if (input.deviceId === deviceId) return input;
3520
+ return {
3521
+ ...input,
3522
+ deviceId
3523
+ };
3524
+ }
3525
+ /**
3493
3526
  * Re-project a `crop-zone` root's detections from crop-relative coordinates
3494
3527
  * back to full-frame image space by the crop's origin — the root analogue of
3495
3528
  * the crop-roi child re-projection (`transformBboxToImageSpace`). Landmarks (if
@@ -3593,7 +3626,7 @@ var PipelineExecutor = class {
3593
3626
  if (debug) console.log(`[executor] rootStep=${rootStep.stepId} settings=${JSON.stringify(rootStep.settings ?? {})}`);
3594
3627
  const cropPrep = rootStep.definition.extractMode === "crop-zone" && cropZoneBbox !== void 0 ? await this.prepareCropZoneInput(cropZoneBbox, fullFrameJpegProvider, imageWidth, imageHeight, deviceId) : null;
3595
3628
  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);
3629
+ 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
3630
  const rootMs = Date.now() - rootStart;
3598
3631
  if (rawRootOutput === null) continue;
3599
3632
  const rootOutput = cropPrep && rawRootOutput.kind === "detections" ? reprojectCropZoneDetections(rawRootOutput, cropPrep.origin) : rawRootOutput;
@@ -3784,10 +3817,10 @@ var PipelineExecutor = class {
3784
3817
  const ox = Math.max(0, Math.round(cropZoneBbox[0]));
3785
3818
  const oy = Math.max(0, Math.round(cropZoneBbox[1]));
3786
3819
  return {
3787
- input: {
3820
+ input: withInferenceDeviceId({
3788
3821
  kind: "jpeg",
3789
3822
  data: crop.jpeg
3790
- },
3823
+ }, deviceId),
3791
3824
  width: crop.width,
3792
3825
  height: crop.height,
3793
3826
  origin: [
@@ -4013,10 +4046,10 @@ var PipelineExecutor = class {
4013
4046
  }
4014
4047
  } catch {}
4015
4048
  }
4016
- const childOutput = await this.executeStep(child, {
4049
+ const childOutput = await this.executeStep(child, withInferenceDeviceId({
4017
4050
  kind: "jpeg",
4018
4051
  data: cropJpegBuf
4019
- }, cropW, cropH, "crop-roi", parentDetection.bbox, parentDetection.macroClass, traceBuilder, stepTimings, poolAgg);
4052
+ }, deviceId), cropW, cropH, "crop-roi", parentDetection.bbox, parentDetection.macroClass, traceBuilder, stepTimings, poolAgg);
4020
4053
  const childMs = Date.now() - childStart;
4021
4054
  if (childOutput === null) continue;
4022
4055
  const detailsBefore = ctx.details.length;
@@ -4659,8 +4692,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4659
4692
  */
4660
4693
  deviceProxies = /* @__PURE__ */ new Map();
4661
4694
  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
4695
  /**
4665
4696
  * Last logged "step configured" signature — used to dedupe the
4666
4697
  * per-step debug trail so it only fires on actual config CHANGE
@@ -5079,7 +5110,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5079
5110
  if (!step.enabled) continue;
5080
5111
  const modelEntry = await this.resolveModelEntry(step.addonId, step.modelId);
5081
5112
  if (!modelEntry) continue;
5082
- if (require_model_download_service_Cp9f4dk6.isModelDownloaded(this.modelsDir, modelEntry, format)) continue;
5113
+ if (require_model_download_service_D8B_4ktF.isModelDownloaded(this.modelsDir, modelEntry, format)) continue;
5083
5114
  await this.downloadWithRetry(modelEntry, format, 3);
5084
5115
  }
5085
5116
  }
@@ -5456,7 +5487,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5456
5487
  const formats = {};
5457
5488
  for (const [formatKey, entry] of Object.entries(m.formats)) {
5458
5489
  if (!entry) continue;
5459
- const downloaded = require_model_download_service_Cp9f4dk6.isModelDownloaded(this.modelsDir, m, formatKey);
5490
+ const downloaded = require_model_download_service_D8B_4ktF.isModelDownloaded(this.modelsDir, m, formatKey);
5460
5491
  formats[formatKey] = {
5461
5492
  url: entry.url,
5462
5493
  sizeMB: entry.sizeMB,
@@ -5579,7 +5610,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5579
5610
  const { modelId, format, addonId } = input;
5580
5611
  const modelEntry = await this.resolveModelEntry(addonId, modelId);
5581
5612
  if (!modelEntry) throw new Error(`Model "${modelId}" not found in step "${addonId}" catalog`);
5582
- if (!require_model_download_service_Cp9f4dk6.deleteModelFromDisk(this.modelsDir, modelEntry, format)) throw new Error(`Model "${modelId}" (${format}) is not downloaded — nothing to delete`);
5613
+ if (!require_model_download_service_D8B_4ktF.deleteModelFromDisk(this.modelsDir, modelEntry, format)) throw new Error(`Model "${modelId}" (${format}) is not downloaded — nothing to delete`);
5583
5614
  this.log.info("Model deleted from disk", { meta: {
5584
5615
  modelId,
5585
5616
  format
@@ -6310,7 +6341,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6310
6341
  const work = (async () => {
6311
6342
  for (const step of needed) {
6312
6343
  const modelEntry = await this.resolveModelEntry(step.addonId, step.modelId);
6313
- if (modelEntry && !require_model_download_service_Cp9f4dk6.isModelDownloaded(this.modelsDir, modelEntry, format)) {
6344
+ if (modelEntry && !require_model_download_service_D8B_4ktF.isModelDownloaded(this.modelsDir, modelEntry, format)) {
6314
6345
  if (modelEntry.formats[format] === void 0) throw new Error(`Model "${modelEntry.id}" has no ${format} format build (available: ${Object.keys(modelEntry.formats).join(", ") || "none"}) — not retrying a permanent format mismatch`);
6315
6346
  this.log.info("Downloading model for step", { meta: {
6316
6347
  modelId: step.modelId,
@@ -6336,7 +6367,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6336
6367
  /** Download a model with retry + exponential backoff */
6337
6368
  async downloadWithRetry(entry, format, maxRetries, onProgress) {
6338
6369
  for (let attempt = 1; attempt <= maxRetries; attempt++) try {
6339
- await require_model_download_service_Cp9f4dk6.ensureModel(this.modelsDir, entry, format, onProgress);
6370
+ await require_model_download_service_D8B_4ktF.ensureModel(this.modelsDir, entry, format, onProgress);
6340
6371
  this.log.info("Model downloaded successfully", { meta: { modelId: entry.id } });
6341
6372
  return;
6342
6373
  } catch (err) {
@@ -6701,20 +6732,66 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6701
6732
  * If global steps exist, initializes with them. Otherwise, creates an empty pool.
6702
6733
  */
6703
6734
  async ensureEngineFactory() {
6704
- if (this.initPromise) await this.initPromise;
6705
6735
  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([]);
6736
+ const inflight = this.engineFactoryInflight;
6737
+ if (inflight) {
6738
+ await inflight;
6739
+ return;
6740
+ }
6741
+ const creation = (async () => {
6742
+ await this.assertEngineReady();
6743
+ await this.ensureBackendDeps(this.currentEngine);
6744
+ const factory = new EngineFactory({
6745
+ engine: this.currentEngine,
6746
+ modelsDir: this.modelsDir,
6747
+ logger: this.log.child("engine"),
6748
+ pythonPath: this.executorOptions.pythonPath ?? "",
6749
+ provisioning: this.executorOptions.provisioning
6750
+ });
6751
+ try {
6752
+ await factory.initialize([]);
6753
+ } catch (err) {
6754
+ await factory.dispose().catch(() => void 0);
6755
+ this.log.error("Node-default pool failed to initialize", { meta: {
6756
+ deviceKey: deviceKeyOf(this.currentEngine),
6757
+ backend: this.currentEngine.backend,
6758
+ device: this.currentEngine.device ?? null,
6759
+ error: require_dist.errMsg(err)
6760
+ } });
6761
+ throw err;
6762
+ }
6763
+ this.engineFactory = factory;
6764
+ return factory;
6765
+ })();
6766
+ this.engineFactoryInflight = creation;
6767
+ try {
6768
+ await creation;
6769
+ } finally {
6770
+ this.engineFactoryInflight = null;
6771
+ }
6716
6772
  }
6717
6773
  /**
6774
+ * In-flight node-default pool creation. Single-flights
6775
+ * {@link ensureEngineFactory} exactly as `deviceFactoryInflight` does for the
6776
+ * per-device pools.
6777
+ *
6778
+ * Its absence was the chronic `PoolWorker[w0]: not initialized` storm
6779
+ * (1263 lines / 24 h, 2026-08-07): `this.engineFactory` was assigned BEFORE
6780
+ * `initialize()` was awaited, so every caller arriving during the 1-3.5 s
6781
+ * Python spawn took the early-exit and dispatched into a worker whose ready
6782
+ * handshake had not landed. The frames were logged as ERRORs and lost.
6783
+ *
6784
+ * The same bug was found and fixed twice before in this file — once for the
6785
+ * benchmark override cache (whose comment describes it verbatim) and once for
6786
+ * `resolveDeviceFactory` — which is why no `not initialized` line ever
6787
+ * carried a NON-default deviceKey. This is the third and last copy.
6788
+ *
6789
+ * It replaces a `private initPromise` field that was read here and assigned
6790
+ * NOWHERE: a dead lock that read, for as long as it existed, as if this path
6791
+ * were already guarded.
6792
+ */
6793
+ engineFactoryInflight = null;
6794
+ /**
6718
6795
  * Get (or lazily build) the inference-pool factory for an explicit device
6719
6796
  * (`<backend>:<device>`, e.g. `openvino:npu`, `edgetpu:usb`) — Phase 2
6720
6797
  * multi-device. Each device runs its OWN pool concurrently. Never mutates
@@ -7132,7 +7209,7 @@ function buildSchemaSlots(formats, modelsDir, customByStep) {
7132
7209
  id: m.id,
7133
7210
  name: m.name,
7134
7211
  formats: Object.fromEntries(Object.entries(m.formats).map(([f, entry]) => [f, {
7135
- downloaded: require_model_download_service_Cp9f4dk6.isModelDownloaded(modelsDir, m, f),
7212
+ downloaded: require_model_download_service_D8B_4ktF.isModelDownloaded(modelsDir, m, f),
7136
7213
  sizeMB: entry.sizeMB
7137
7214
  }])),
7138
7215
  ...m.group ? { group: m.group } : {},
@@ -7724,13 +7801,12 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7724
7801
  }
7725
7802
  }
7726
7803
  /**
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.
7804
+ * Bare node id for this instance. MUST match the provider's
7805
+ * `localProbeNodeId()` (kernel.localNodeId, default 'hub') so the per-node
7806
+ * settings projection here and the provider's own node targeting agree. The
7807
+ * old `?? this.ctx.id` fallback resolved to the ADDON id
7808
+ * ('detection-pipeline') when kernel.localNodeId was absent, so UI saves
7809
+ * landed on a key the provider never read.
7734
7810
  */
7735
7811
  localNodeId() {
7736
7812
  return normalizeEngineNodeId(this.ctxIfReady?.kernel?.localNodeId ?? "hub");