@camstack/addon-pipeline 1.1.28 → 1.1.29

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 +1 -1
  2. package/dist/audio-analyzer/index.mjs +1 -1
  3. package/dist/audio-codec-ffmpeg/index.js +1 -5
  4. package/dist/audio-codec-ffmpeg/index.mjs +1 -5
  5. package/dist/decoder-ffmpeg/index.js +102 -352
  6. package/dist/decoder-ffmpeg/index.mjs +82 -332
  7. package/dist/decoder-nodeav/index.js +6 -4
  8. package/dist/decoder-nodeav/index.mjs +6 -4
  9. package/dist/detection-pipeline/index.js +204 -180
  10. package/dist/detection-pipeline/index.mjs +204 -180
  11. package/dist/{dist-Biq62zt4.js → dist-Cwc0TUQr.js} +7 -42
  12. package/dist/{dist-C6_wgXqF.mjs → dist-DjuGmyG9.mjs} +7 -42
  13. package/dist/ffmpeg-args-C5GPp8Cw.mjs +323 -0
  14. package/dist/ffmpeg-args-D6h1edXK.js +418 -0
  15. package/dist/frame-dropper-AjheBGMG.mjs +22 -0
  16. package/dist/frame-dropper-DKLM6pMz.js +27 -0
  17. package/dist/{frame-handle-plane-BIoY6nRV.mjs → frame-handle-plane-Bkxz-TTD.mjs} +1 -1
  18. package/dist/{frame-handle-plane-D6BzyEgy.js → frame-handle-plane-DQNCTrpC.js} +1 -1
  19. package/dist/{frame-ring-sink-9J0wCdLF.js → frame-ring-sink-8LLV-cvH.js} +1 -1
  20. package/dist/{frame-ring-sink-Cs9vby6v.mjs → frame-ring-sink-ClEWjiRU.mjs} +1 -1
  21. package/dist/motion-wasm/index.js +1 -1
  22. package/dist/motion-wasm/index.mjs +1 -1
  23. package/dist/pipeline-runner/index.js +2 -2
  24. package/dist/pipeline-runner/index.mjs +2 -2
  25. package/dist/recorder/index.js +1 -1
  26. package/dist/recorder/index.mjs +1 -1
  27. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DoReAb4y.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D567z31g.mjs} +3 -3
  28. package/dist/stream-broker/{hostInit-C0SuwQhL.mjs → hostInit-B_b3PIZB.mjs} +3 -3
  29. package/dist/stream-broker/index.js +168 -121
  30. package/dist/stream-broker/index.mjs +159 -112
  31. package/dist/stream-broker/remoteEntry.js +1 -1
  32. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-Br2yi8ah.js → MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js} +1 -1
  33. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BSCcPdPf.js → MotionZonesSettings-NcxxQN8r-DTeuDGCU.js} +1 -1
  34. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-ztxvxSjS.js → PrivacyMaskSettings-APgPLF7p-CirtxO3e.js} +1 -1
  35. package/embed-dist/assets/{index-CeZV1B-2.js → index-CpUy8OIE.js} +9 -9
  36. package/embed-dist/index.html +1 -1
  37. package/package.json +1 -1
  38. package/dist/frame-dropper-7RTo_YyG.js +0 -68
  39. package/dist/frame-dropper-CwkBTPGV.mjs +0 -51
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-DxM2DSns.js");
6
- const require_dist = require("../dist-Biq62zt4.js");
6
+ const require_dist = require("../dist-Cwc0TUQr.js");
7
7
  const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
8
8
  let _camstack_shm_ring = require("@camstack/shm-ring");
9
9
  let node_fs = require("node:fs");
@@ -15,87 +15,6 @@ node_os = require_model_download_service_C_IHWnXx.__toESM(node_os);
15
15
  let node_child_process = require("node:child_process");
16
16
  let sharp = require("sharp");
17
17
  sharp = require_model_download_service_C_IHWnXx.__toESM(sharp);
18
- //#region src/detection-pipeline/engine-store-keys.ts
19
- /**
20
- * Per-node scoping for the detection-pipeline engine cascade.
21
- *
22
- * The detection addon's settings store is a single CLUSTER-SHARED blob (the
23
- * settings-store cap is hub-resident; every node's detection instance reads and
24
- * writes the same keys). That is correct for node-agnostic settings (pipeline
25
- * steps, tuning) but WRONG for the engine cascade — `engineBackend` /
26
- * `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
27
- * and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
28
- * selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
29
- * another node that has no NPU.
30
- *
31
- * These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
32
- * Everything else in the store stays shared. There is NO fallback to a legacy
33
- * un-scoped value for ANY node (the hub included) and no in-code migration: a
34
- * bare key (written before scoping, or by an older build) is inert — invisible
35
- * to every node. A node with no scoped key reads `undefined`, which drives its
36
- * OWN platform auto-pick (`autoPickAndPersist` / `reprobeEngine` persist the
37
- * node's own probe under `<key>@<node>`). Serving a bare value to any node
38
- * would leak one node's hardware pick (e.g. `openvino/auto`) onto a node that
39
- * cannot run it (the CB3 "Mac runs OpenVINO" / N100 AUTO-device bug). This
40
- * mirrors the decoder's `readNodeDecoderBackend` no-fallback rule.
41
- */
42
- var ENGINE_CASCADE_KEYS = [
43
- "engineBackend",
44
- "engineDevice",
45
- "probedBestEngine"
46
- ];
47
- function isEngineCascadeKey(key) {
48
- return ENGINE_CASCADE_KEYS.includes(key);
49
- }
50
- /**
51
- * Normalise a raw kernel node id to the bare node id used for scoping.
52
- * `localNodeId` can carry a `<node>/<addon>` suffix; the engine selection is
53
- * per-NODE, so strip the addon segment. Falls back to `hub`.
54
- */
55
- function normalizeEngineNodeId(rawNodeId) {
56
- const raw = rawNodeId ?? "hub";
57
- return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
58
- }
59
- /** The node-scoped store key for an engine cascade field. */
60
- function nodeEngineKey(base, nodeId) {
61
- return `${base}@${normalizeEngineNodeId(nodeId)}`;
62
- }
63
- /**
64
- * Read an engine cascade value for a node: the node-scoped key if present,
65
- * otherwise `undefined` — for EVERY node, the hub included. There is no
66
- * fallback to the legacy un-scoped key: a bare value in the store is inert.
67
- * A node with no scoped key gets `undefined` so its own platform-driven
68
- * auto-pick runs and persists a real per-node selection under `<key>@<node>`.
69
- * (A bare fallback would leak one node's engine selection cluster-wide — CB3.)
70
- */
71
- function readNodeEngineValue(store, base, nodeId) {
72
- return store[nodeEngineKey(base, nodeId)];
73
- }
74
- /**
75
- * Project a raw store onto the plain engine cascade keys for THIS node, so the
76
- * UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
77
- * the node's own selection. Non-engine keys are left untouched. Node-scoped
78
- * keys for OTHER nodes are dropped from the projection (not relevant to this
79
- * node's form).
80
- */
81
- function projectNodeEngine(store, nodeId) {
82
- const out = {};
83
- const scopedForAnyNode = /* @__PURE__ */ new Set();
84
- for (const key of Object.keys(store)) {
85
- const atIdx = key.indexOf("@");
86
- if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
87
- scopedForAnyNode.add(key);
88
- continue;
89
- }
90
- out[key] = store[key];
91
- }
92
- for (const base of ENGINE_CASCADE_KEYS) {
93
- const value = readNodeEngineValue(store, base, nodeId);
94
- if (value !== void 0) out[base] = value;
95
- }
96
- return out;
97
- }
98
- //#endregion
99
18
  //#region src/detection-pipeline/runtimes.ts
100
19
  var KNOWN_PLATFORMS = [
101
20
  "darwin",
@@ -267,6 +186,88 @@ function pickBestRuntime(env, bestBackendHint) {
267
186
  };
268
187
  }
269
188
  //#endregion
189
+ //#region src/detection-pipeline/engine-store-keys.ts
190
+ /**
191
+ * Per-node scoping for the detection-pipeline engine cascade.
192
+ *
193
+ * The detection addon's settings store is a single CLUSTER-SHARED blob (the
194
+ * settings-store cap is hub-resident; every node's detection instance reads and
195
+ * writes the same keys). That is correct for node-agnostic settings (pipeline
196
+ * steps, tuning) but WRONG for the engine cascade — `engineBackend` /
197
+ * `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
198
+ * and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
199
+ * selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
200
+ * another node that has no NPU.
201
+ *
202
+ * These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
203
+ * Everything else in the store stays shared. There is NO fallback to a legacy
204
+ * un-scoped value for ANY node (the hub included) and no in-code migration: a
205
+ * bare key (written before scoping, or by an older build) is inert — invisible
206
+ * to every node. A node with no scoped key reads `undefined`, which drives its
207
+ * OWN live resolve (`resolveAutoEngine` picks the node's runtime from the local
208
+ * kernel each boot; `reprobeEngine` persists an explicit probe under
209
+ * `<key>@<node>`). Serving a bare value to any node
210
+ * would leak one node's hardware pick (e.g. `openvino/auto`) onto a node that
211
+ * cannot run it (the CB3 "Mac runs OpenVINO" / N100 AUTO-device bug). This
212
+ * mirrors the decoder's `readNodeDecoderBackend` no-fallback rule.
213
+ */
214
+ var ENGINE_CASCADE_KEYS = [
215
+ "engineBackend",
216
+ "engineDevice",
217
+ "probedBestEngine"
218
+ ];
219
+ function isEngineCascadeKey(key) {
220
+ return ENGINE_CASCADE_KEYS.includes(key);
221
+ }
222
+ /**
223
+ * Normalise a raw kernel node id to the bare node id used for scoping.
224
+ * `localNodeId` can carry a `<node>/<addon>` suffix; the engine selection is
225
+ * per-NODE, so strip the addon segment. Falls back to `hub`.
226
+ */
227
+ function normalizeEngineNodeId(rawNodeId) {
228
+ const raw = rawNodeId ?? "hub";
229
+ return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
230
+ }
231
+ /** The node-scoped store key for an engine cascade field. */
232
+ function nodeEngineKey(base, nodeId) {
233
+ return `${base}@${normalizeEngineNodeId(nodeId)}`;
234
+ }
235
+ /**
236
+ * Read an engine cascade value for a node: the node-scoped key if present,
237
+ * otherwise `undefined` — for EVERY node, the hub included. There is no
238
+ * fallback to the legacy un-scoped key: a bare value in the store is inert.
239
+ * A node with no scoped key gets `undefined` so its own platform-driven
240
+ * auto-pick runs and persists a real per-node selection under `<key>@<node>`.
241
+ * (A bare fallback would leak one node's engine selection cluster-wide — CB3.)
242
+ */
243
+ function readNodeEngineValue(store, base, nodeId) {
244
+ return store[nodeEngineKey(base, nodeId)];
245
+ }
246
+ /**
247
+ * Project a raw store onto the plain engine cascade keys for THIS node, so the
248
+ * UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
249
+ * the node's own selection. Non-engine keys are left untouched. Node-scoped
250
+ * keys for OTHER nodes are dropped from the projection (not relevant to this
251
+ * node's form).
252
+ */
253
+ function projectNodeEngine(store, nodeId) {
254
+ const out = {};
255
+ const scopedForAnyNode = /* @__PURE__ */ new Set();
256
+ for (const key of Object.keys(store)) {
257
+ const atIdx = key.indexOf("@");
258
+ if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
259
+ scopedForAnyNode.add(key);
260
+ continue;
261
+ }
262
+ out[key] = store[key];
263
+ }
264
+ for (const base of ENGINE_CASCADE_KEYS) {
265
+ const value = readNodeEngineValue(store, base, nodeId);
266
+ if (value !== void 0) out[base] = value;
267
+ }
268
+ return out;
269
+ }
270
+ //#endregion
270
271
  //#region src/detection-pipeline/engine/shared-inference-pool.ts
271
272
  /**
272
273
  * SharedInferencePool — TypeScript wrapper for inference_pool.py.
@@ -4579,56 +4580,53 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4579
4580
  error: require_dist.errMsg(err)
4580
4581
  } });
4581
4582
  }
4582
- await this.autoPickAndPersist();
4583
+ await this.resolveAutoEngine();
4583
4584
  this.needsAutoPick = false;
4584
4585
  this.startProvisioningForCurrentEngine();
4585
4586
  }
4586
4587
  /**
4587
- * Auto-pick the best supported runtime at first boot (no stored engine).
4588
- * Uses the platform-probe cap's hardware + bestScore hint when available;
4589
- * falls back to platform/arch when the probe cap is not yet reachable.
4588
+ * Resolve the `'auto'` engine LIVE from this node's own kernel inference
4589
+ * resolver (`ctx.kernel.inferenceEngine`) the runtime mirror of
4590
+ * `ctx.kernel.hwaccel`. Each node (hub + forked worker + remote agent)
4591
+ * resolves its OWN accelerators in-process, so a forked agent child never
4592
+ * adopts the hub's runtime from the `platform-probe` singleton.
4590
4593
  *
4591
- * Persists the selection as `engineBackend` + `engineDevice` so subsequent
4592
- * boots load it via `loadEngine()` and skip this path but ONLY when the
4593
- * probe actually answered (real `hardware` or a `bestScore` hint). If the
4594
- * probe query failed (cold-start race, cap momentarily unreachable), the
4595
- * pick floors to onnx purely for lack of information; persisting that would
4596
- * LOCK onnx and skip auto-pick on every future boot even after the
4597
- * accelerator surfaces. In that case we set the in-memory floor for liveness
4598
- * but leave the store untouched so the next boot re-attempts the pick.
4594
+ * NEVER persists. `'auto'` is a live-resolved sentinel: the pick is recomputed
4595
+ * every boot from the local hardware, so it is inherently self-healing the
4596
+ * old `autoPickAndPersist` sticky-onnx bug (a premature onnx pick persisted
4597
+ * during a boot race, then locked forever) cannot recur because the store is
4598
+ * never written here. Only an explicit operator override is persisted, and
4599
+ * that write happens through the settings UI, not this path.
4599
4600
  */
4600
- async autoPickAndPersist() {
4601
+ async resolveAutoEngine() {
4601
4602
  let hardware = null;
4602
4603
  try {
4603
- const api = this.addonCtx?.api;
4604
- if (api) hardware = (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null;
4604
+ hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
4605
4605
  } catch {}
4606
- const pick = pickBestRuntime(runtimeEnvFromProcess(toProbedHardware(hardware)), null);
4607
- const engine = {
4606
+ const pick = pickBestRuntime(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null), null);
4607
+ this.currentEngine = {
4608
4608
  runtime: "python",
4609
4609
  backend: pick.runtimeId,
4610
4610
  format: modelFormatFor(pick.runtimeId),
4611
4611
  device: pick.device
4612
4612
  };
4613
- this.currentEngine = engine;
4614
- if (!(pick.runtimeId !== "onnx" || hardware !== null)) {
4615
- this.log.info("Auto-pick: onnx floor pending gpu probe — NOT persisting (next boot retries)", { meta: {
4616
- backend: pick.runtimeId,
4617
- device: pick.device
4618
- } });
4619
- return;
4620
- }
4621
- const apNode = this.localProbeNodeId();
4622
- await this.writeStore({
4623
- [nodeEngineKey("engineBackend", apNode)]: pick.runtimeId,
4624
- [nodeEngineKey("engineDevice", apNode)]: pick.device
4625
- });
4626
- this.log.info("Auto-picked engine (platform-deterministic)", { meta: {
4613
+ this.log.info("Resolved auto engine (local kernel)", { meta: {
4627
4614
  backend: pick.runtimeId,
4628
4615
  device: pick.device,
4629
- hadProbeHardware: hardware !== null
4616
+ hadHardware: hardware !== null
4630
4617
  } });
4631
4618
  }
4619
+ /**
4620
+ * The RUNTIME that `'auto'` actually resolved to on this node (the live
4621
+ * `currentEngine.backend`), narrowed to a {@link RuntimeId}. The addon feeds
4622
+ * this into `resolveBackendTuning` when the stored config is `'auto'`, so the
4623
+ * pool tuning + respawn detection track the RESOLVED backend rather than the
4624
+ * `'auto'` sentinel (which would otherwise floor to onnx tuning).
4625
+ */
4626
+ resolvedEngineBackend() {
4627
+ const backend = this.currentEngine.backend;
4628
+ return backend === "openvino" || backend === "coreml" ? backend : "onnx";
4629
+ }
4632
4630
  /** Map a backend string to a known RuntimeId, flooring to onnx. */
4633
4631
  toRuntimeId(backend) {
4634
4632
  return [
@@ -4702,6 +4700,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4702
4700
  const prev = this.currentEngine;
4703
4701
  const stored = await this.loadEngine();
4704
4702
  if (stored) this.currentEngine = stored;
4703
+ else await this.resolveAutoEngine();
4705
4704
  if ((prev.runtime !== this.currentEngine.runtime || prev.backend !== this.currentEngine.backend || prev.format !== this.currentEngine.format || (prev.device ?? "") !== (this.currentEngine.device ?? "")) && this.engineFactory) {
4706
4705
  this.log.info("engine selection changed — rebuilding pool in place", { meta: {
4707
4706
  from: `${prev.backend}/${prev.device ?? "default"}`,
@@ -4803,17 +4802,18 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4803
4802
  }, { ...state }));
4804
4803
  }
4805
4804
  /**
4806
- * Fetch the probed HARDWARE for engine + device gating. Returns null when the
4807
- * probe cap is not yet reachable (cold-start / probe addon not installed), so
4808
- * callers fall back to the registry's safe minimum. The engine OFFER derives
4809
- * from hardware ONLY — install state (probe `scores`) never gates it.
4805
+ * Fetch this node's inference HARDWARE (gpu/npu) for engine + device gating,
4806
+ * from the local kernel resolver. Returns null when the resolver is not wired
4807
+ * yet (cold-start), so callers fall back to the registry's safe minimum. The
4808
+ * engine OFFER derives from hardware ONLY — install state never gates it.
4810
4809
  */
4811
4810
  /**
4812
- * The local Moleculer node id (child-suffix stripped). MUST be passed to every
4813
- * `platformProbe.getCapabilities` query: the cap is a singleton and a query
4814
- * with no nodeId resolves to the HUB's probe — so on a remote agent the engine
4815
- * decision would use the HUB's hardware (e.g. an Intel NPU the agent doesn't
4816
- * have) and pin a device the node can't run, breaking provisioning.
4811
+ * The local Moleculer node id (child-suffix stripped). Used to scope this
4812
+ * node's engine-cascade store keys (`<key>@<node>`). Hardware itself is now
4813
+ * resolved in-process via `ctx.kernel.inferenceEngine` (never the
4814
+ * `platform-probe` singleton, whose no-nodeId query resolved to the HUB and
4815
+ * pinned a device a remote agent couldn't run) so this id is a store-scoping
4816
+ * concern, not a probe-routing one.
4817
4817
  */
4818
4818
  localProbeNodeId() {
4819
4819
  const raw = this.addonCtx?.kernel?.localNodeId ?? "hub";
@@ -4821,9 +4821,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4821
4821
  }
4822
4822
  async fetchProbeGatingData() {
4823
4823
  try {
4824
- const api = this.addonCtx?.api;
4825
- if (!api) return { hardware: null };
4826
- return { hardware: (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null };
4824
+ return { hardware: await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null };
4827
4825
  } catch {
4828
4826
  return { hardware: null };
4829
4827
  }
@@ -6209,15 +6207,20 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6209
6207
  * Phase 2b — resolve the engine from the addon's new schema-backed
6210
6208
  * fields (`engineRuntime`, `engineBackend`, `engineDevice`). Fallback
6211
6209
  * to the legacy `KEY_ENGINE` JSON blob for pre-migration stores.
6212
- * Returns null when neither source has anything; the caller keeps
6213
- * the onnx floor set at construction until autoPickAndPersist() runs.
6210
+ *
6211
+ * The `'auto'` sentinel is treated as NO override (returns null) so the
6212
+ * caller re-resolves LIVE via `resolveAutoEngine()` — `'auto'` is the
6213
+ * self-healing default, never a persisted concrete backend. An explicit
6214
+ * concrete backend (onnx/openvino/coreml) is honored as an operator override.
6215
+ * Returns null when neither source has a concrete selection; the caller keeps
6216
+ * the onnx floor set at construction until `resolveAutoEngine()` runs.
6214
6217
  */
6215
6218
  async loadEngine() {
6216
6219
  const store = await this.readStore();
6217
6220
  const storedRuntime = store["engineRuntime"];
6218
6221
  const node = this.localProbeNodeId();
6219
6222
  const storedBackend = readNodeEngineValue(store, "engineBackend", node);
6220
- if (typeof storedBackend === "string" && storedBackend.length > 0) {
6223
+ if (typeof storedBackend === "string" && storedBackend.length > 0 && storedBackend !== "auto") {
6221
6224
  const backend = storedBackend;
6222
6225
  const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
6223
6226
  const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
@@ -6371,12 +6374,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6371
6374
  return { success: true };
6372
6375
  }
6373
6376
  async reprobeEngine() {
6374
- const api = this.addonCtx?.api;
6375
6377
  let hardware = null;
6376
- if (api) try {
6377
- hardware = (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null;
6378
+ try {
6379
+ hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
6378
6380
  } catch {}
6379
- const pick = pickBestRuntime(runtimeEnvFromProcess(toProbedHardware(hardware)), null);
6381
+ const pick = pickBestRuntime(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null), null);
6380
6382
  const best = {
6381
6383
  runtime: "python",
6382
6384
  backend: pick.runtimeId,
@@ -6386,17 +6388,13 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6386
6388
  const probedLabel = `${best.backend}/${best.device ?? "default"}`;
6387
6389
  const rpNode = this.localProbeNodeId();
6388
6390
  if (pick.runtimeId !== "onnx" || hardware !== null) {
6389
- await this.writeStore({
6390
- [nodeEngineKey("probedBestEngine", rpNode)]: probedLabel,
6391
- [nodeEngineKey("engineBackend", rpNode)]: best.backend,
6392
- [nodeEngineKey("engineDevice", rpNode)]: best.device ?? "cpu"
6393
- });
6394
- this.log.info("Re-probed engine (platform-deterministic) — wrote back", { meta: {
6391
+ await this.writeStore({ [nodeEngineKey("probedBestEngine", rpNode)]: probedLabel });
6392
+ this.log.info("Re-probed engine (local kernel) — updated probedBestEngine hint", { meta: {
6395
6393
  backend: best.backend,
6396
6394
  device: best.device ?? null,
6397
6395
  probedBestEngine: probedLabel
6398
6396
  } });
6399
- } else this.log.info("Re-probe: onnx floor pending gpu probe — NOT persisting (next boot retries)", { meta: {
6397
+ } else this.log.info("Re-probe: onnx floor pending gpu probe — hint NOT persisted", { meta: {
6400
6398
  backend: best.backend,
6401
6399
  device: best.device ?? null
6402
6400
  } });
@@ -6813,7 +6811,7 @@ var DEFAULT_CONFIG = {
6813
6811
  intraOpThreads: 0,
6814
6812
  numWorkers: 0,
6815
6813
  engineRuntime: "python",
6816
- engineBackend: "onnx",
6814
+ engineBackend: "auto",
6817
6815
  engineDevice: "cpu",
6818
6816
  probedBestEngine: "",
6819
6817
  activeEngine: ""
@@ -6866,7 +6864,7 @@ var POOL_BOUND_KEYS = [
6866
6864
  "intraOpThreads",
6867
6865
  "numWorkers"
6868
6866
  ];
6869
- var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6867
+ var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.BaseAddon {
6870
6868
  provider = null;
6871
6869
  /** Last non-null probed hardware PER NODE — reused when the probe transiently
6872
6870
  * returns null so offered backends / device lists don't collapse. Keyed by
@@ -6941,7 +6939,10 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
6941
6939
  type: "select",
6942
6940
  key: "engineBackend",
6943
6941
  label: "Execution provider",
6944
- options: [...STATIC_BACKEND_OPTIONS],
6942
+ options: [{
6943
+ value: "auto",
6944
+ label: "Auto (best for this node)"
6945
+ }, ...STATIC_BACKEND_OPTIONS],
6945
6946
  default: DEFAULT_CONFIG.engineBackend,
6946
6947
  immediate: true,
6947
6948
  perNode: true
@@ -7082,12 +7083,17 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7082
7083
  const env = await this.probeHardwareEnv(targetNode);
7083
7084
  const hardware = env.hardware;
7084
7085
  const offered = supportedRuntimes(env);
7085
- const runtimeBackends = offered.map((id) => ({
7086
+ const runtimeBackends = [{
7087
+ value: "auto",
7088
+ label: "Auto (best for this node)"
7089
+ }, ...offered.map((id) => ({
7086
7090
  value: id,
7087
7091
  label: runtimeLabel(id)
7088
- }));
7092
+ }))];
7089
7093
  const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
7094
+ const isAuto = storedBackend === "auto";
7090
7095
  const backend = (() => {
7096
+ if (isAuto) return pickBestRuntime(env, null).runtimeId;
7091
7097
  const rid = toRuntimeId(storedBackend);
7092
7098
  if (rid === "onnx") return "onnx";
7093
7099
  if (offered.includes(rid)) return rid;
@@ -7098,9 +7104,10 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7098
7104
  const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
7099
7105
  const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
7100
7106
  const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
7107
+ const displayBackend = isAuto ? "auto" : backend;
7101
7108
  const raw = {
7102
7109
  ...merged,
7103
- engineBackend: backend,
7110
+ engineBackend: displayBackend,
7104
7111
  engineDevice: device,
7105
7112
  activeEngine
7106
7113
  };
@@ -7218,7 +7225,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7218
7225
  const api = this.ctxIfReady?.api;
7219
7226
  if (!api) return null;
7220
7227
  const node = nodeId ?? this.localNodeId();
7221
- const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query(void 0, require_dist.nodePin(node)))?.hardware;
7228
+ const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query({ nodeId: node }))?.hardware;
7222
7229
  if (!hw) return null;
7223
7230
  return {
7224
7231
  npu: hw.npu ? { type: hw.npu.type } : null,
@@ -7256,7 +7263,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7256
7263
  * a reason to disagree.
7257
7264
  */
7258
7265
  resolveBackendTuning() {
7259
- const t = tuningFor(toRuntimeId(this.config.engineBackend));
7266
+ const t = tuningFor(this.config.engineBackend === "auto" ? this.provider?.resolvedEngineBackend() ?? "onnx" : toRuntimeId(this.config.engineBackend));
7260
7267
  const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
7261
7268
  const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
7262
7269
  return {
@@ -7269,6 +7276,34 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7269
7276
  numWorkers: num(t["numWorkers"], 1)
7270
7277
  };
7271
7278
  }
7279
+ /**
7280
+ * Construct a fresh provider with the given effective tuning. Single source of
7281
+ * truth for the provider wiring — shared by `onInitialize`, the post-resolve
7282
+ * tuning reconciliation, and the `onConfigChanged` pool respawn.
7283
+ */
7284
+ buildProvider(modelsDir, tuning) {
7285
+ if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
7286
+ return new DetectionPipelineProvider(this.ctx.settings, modelsDir, this.ctx.logger, this.ctx.eventBus ?? null, () => ({ sections: [] }), {
7287
+ concurrency: tuning.concurrency,
7288
+ tuning: {
7289
+ batchMode: tuning.batchMode,
7290
+ windowMs: tuning.windowMs,
7291
+ maxBatchSize: tuning.maxBatchSize,
7292
+ numStreams: tuning.numStreams,
7293
+ intraOpThreads: tuning.intraOpThreads
7294
+ },
7295
+ numWorkers: tuning.numWorkers,
7296
+ pythonPath: this.pythonPath,
7297
+ pythonAddonDir: this.pythonAddonDir
7298
+ });
7299
+ }
7300
+ /**
7301
+ * Pool-bound tuning keys of `a` and `b` are equal (used to decide whether a
7302
+ * post-resolve / config-change respawn is actually needed).
7303
+ */
7304
+ static poolTuningEqual(a, b) {
7305
+ return a.concurrency === b.concurrency && a.batchMode === b.batchMode && a.windowMs === b.windowMs && a.maxBatchSize === b.maxBatchSize && a.numStreams === b.numStreams && a.intraOpThreads === b.intraOpThreads && a.numWorkers === b.numWorkers;
7306
+ }
7272
7307
  async onInitialize() {
7273
7308
  const modelsDir = await this.resolveModelsDir();
7274
7309
  if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
@@ -7277,22 +7312,20 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7277
7312
  if (py.ok && py.pythonPath) this.pythonPath = py.pythonPath;
7278
7313
  else this.ctx.logger.warn("Detection engine boot continues without Python — selection will provision on demand", {});
7279
7314
  await this.proactivelyInstallOpenvino();
7280
- const effectiveTuning = this.resolveBackendTuning();
7281
- this.provider = new DetectionPipelineProvider(this.ctx.settings, modelsDir, this.ctx.logger, this.ctx.eventBus ?? null, () => ({ sections: [] }), {
7282
- concurrency: effectiveTuning.concurrency,
7283
- tuning: {
7284
- batchMode: effectiveTuning.batchMode,
7285
- windowMs: effectiveTuning.windowMs,
7286
- maxBatchSize: effectiveTuning.maxBatchSize,
7287
- numStreams: effectiveTuning.numStreams,
7288
- intraOpThreads: effectiveTuning.intraOpThreads
7289
- },
7290
- numWorkers: effectiveTuning.numWorkers,
7291
- pythonPath: this.pythonPath,
7292
- pythonAddonDir: this.pythonAddonDir
7293
- });
7315
+ const initialTuning = this.resolveBackendTuning();
7316
+ this.provider = this.buildProvider(modelsDir, initialTuning);
7294
7317
  await this.provider.init();
7295
7318
  await this.provider.setApi(this.ctx);
7319
+ const resolvedTuning = this.resolveBackendTuning();
7320
+ if (!DetectionPipelineAddon.poolTuningEqual(initialTuning, resolvedTuning)) {
7321
+ this.ctx.logger.info("detection-pipeline tuning reconciled to resolved engine", { meta: {
7322
+ from: initialTuning,
7323
+ to: resolvedTuning
7324
+ } });
7325
+ this.provider = this.buildProvider(modelsDir, resolvedTuning);
7326
+ await this.provider.init();
7327
+ await this.provider.setApi(this.ctx);
7328
+ }
7296
7329
  if (!this.config.probedBestEngine) await this.provider.reprobeEngine().catch((err) => {
7297
7330
  this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
7298
7331
  });
@@ -7349,13 +7382,18 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7349
7382
  * first loaded (gated by `loadEngine`'s `isPythonBackendAvailable` check).
7350
7383
  *
7351
7384
  * Failure is non-fatal: a warning is logged and the addon continues with
7352
- * the onnx-cpu baseline. The hardware query itself is also best-effort —
7353
- * if the platform-probe cap isn't wired yet the error is caught here.
7385
+ * the onnx-cpu baseline. The hardware resolve itself is also best-effort —
7386
+ * if the kernel resolver isn't wired yet the error is caught here.
7354
7387
  */
7355
7388
  async proactivelyInstallOpenvino() {
7356
7389
  if (!this.pythonAddonDir || !this.pythonPath) return;
7357
7390
  try {
7358
- const hardware = await this.ctx.api.platformProbe.getHardware.query();
7391
+ const hw = await this.ctx.kernel?.inferenceEngine?.resolveHardware();
7392
+ const hardware = {
7393
+ platform: process.platform,
7394
+ gpu: hw?.gpu ?? null,
7395
+ npu: hw?.npu ?? null
7396
+ };
7359
7397
  if (!shouldInstallOpenvino(hardware)) return;
7360
7398
  this.ctx.logger.info("Intel hardware detected — proactively installing OpenVINO Python package", { meta: {
7361
7399
  gpu: hardware.gpu?.type ?? null,
@@ -7434,21 +7472,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
7434
7472
  this.ctx.logger.warn("provider shutdown failed during tuning respawn", { meta: { error: err instanceof Error ? err.message : String(err) } });
7435
7473
  }
7436
7474
  const modelsDir = await this.resolveModelsDir();
7437
- if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available during respawn");
7438
- const effectiveTuning = this.resolveBackendTuning();
7439
- this.provider = new DetectionPipelineProvider(this.ctx.settings, modelsDir, this.ctx.logger, this.ctx.eventBus ?? null, () => ({ sections: [] }), {
7440
- concurrency: effectiveTuning.concurrency,
7441
- tuning: {
7442
- batchMode: effectiveTuning.batchMode,
7443
- windowMs: effectiveTuning.windowMs,
7444
- maxBatchSize: effectiveTuning.maxBatchSize,
7445
- numStreams: effectiveTuning.numStreams,
7446
- intraOpThreads: effectiveTuning.intraOpThreads
7447
- },
7448
- numWorkers: effectiveTuning.numWorkers,
7449
- pythonPath: this.pythonPath,
7450
- pythonAddonDir: this.pythonAddonDir
7451
- });
7475
+ this.provider = this.buildProvider(modelsDir, this.resolveBackendTuning());
7452
7476
  await this.provider.init();
7453
7477
  await this.provider.setApi(this.ctx);
7454
7478
  await this.provider.warmPool();