@camstack/addon-pipeline 1.1.70 → 1.2.0

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 (34) 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 +181 -469
  4. package/dist/detection-pipeline/index.mjs +182 -469
  5. package/dist/{dist-CurVt3xC.mjs → dist-DLWXeXC1.mjs} +239 -60
  6. package/dist/{dist-BWn5k0A5.js → dist-DWy-H6bo.js} +250 -59
  7. package/dist/motion-wasm/index.js +1 -1
  8. package/dist/motion-wasm/index.mjs +1 -1
  9. package/dist/pipeline-runner/index.js +24 -2
  10. package/dist/pipeline-runner/index.mjs +24 -2
  11. package/dist/recorder/index.js +1 -1
  12. package/dist/recorder/index.mjs +1 -1
  13. package/dist/session-decode/decode-worker-child.js +177 -12
  14. package/dist/session-decode/decode-worker-child.mjs +177 -12
  15. package/dist/{step-definitions-B7e8rPe3.js → step-definitions-BQG_6dmZ.js} +6 -2
  16. package/dist/{step-definitions-BKm17nce.mjs → step-definitions-CVk19Y0u.mjs} +6 -2
  17. package/dist/stream-broker/_stub.js +2 -2
  18. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-yAAGMET5.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CpB1jG0N.mjs} +3 -3
  19. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BflyNuZv.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-5Zc9wj-A.mjs +26 -0
  21. package/dist/stream-broker/{hostInit-DUzRsTmt.mjs → hostInit--Zair8Av.mjs} +3 -3
  22. package/dist/stream-broker/index.js +1 -1
  23. package/dist/stream-broker/index.mjs +1 -1
  24. package/dist/stream-broker/remoteEntry.js +1 -1
  25. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-D6bWMuew.js → MaskShapeCanvas-DI4BY7W2-sOOxiGvP.js} +1 -1
  26. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-C-1enV21.js → MotionZonesSettings-NcxxQN8r-DdTQuT23.js} +1 -1
  27. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-By9pdGP7.js → PrivacyMaskSettings-APgPLF7p-BrR0L3ql.js} +1 -1
  28. package/embed-dist/assets/index-D4MGuECB.css +2 -0
  29. package/embed-dist/assets/{index-BPGh79kn.js → index-Dk21l1_3.js} +14 -14
  30. package/embed-dist/index.html +2 -2
  31. package/package.json +1 -1
  32. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D68Lj69v.mjs +0 -26
  33. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CDBALHSV.mjs +0 -26
  34. package/embed-dist/assets/index-D2pF9Z3W.css +0 -2
@@ -1,6 +1,6 @@
1
1
  import { t as __require } from "../chunk-BdkLduGY.mjs";
2
- import { $ as sleep, A as pipelineExecutorCapability, B as errMsg, C as evaluateZoneRules, F as runtimeDevices$1, G as createEvent, K as hydrateSchema, R as supportedRuntimes$1, S as detectionPipelineCapability, V as BaseAddon, X as parseJsonUnknown, b as defaultDeviceFor$1, dt as EventCategory, k as nodePin, lt as string, m as YAMNET_TO_MACRO, st as object, t as APPLE_SA_TO_MACRO, tt as array, ut as union } from "../dist-CurVt3xC.mjs";
3
- import { a as getStepDefinition, i as getStep, n as ALL_STEPS, o as resolveModelForFormat, r as getDefaultModelForFormat, t as ALL_PIPELINE_STEPS } from "../step-definitions-BKm17nce.mjs";
2
+ import { B as supportedRuntimes$1, C as detectionPipelineCapability, H as errMsg, J as hydrateSchema, L as runtimeDevices$1, M as pipelineExecutorCapability, Q as parseJsonUnknown, T as evaluateZoneRules, U as BaseAddon, dt as string, ft as union, h as YAMNET_TO_MACRO, j as nodePin, lt as object, pt as EventCategory, q as createEvent, rt as array, s as DEVICE_BACKEND_TO_FORMAT, t as APPLE_SA_TO_MACRO, tt as sleep, w as enumerateInferenceDevices, x as defaultDeviceFor$1 } from "../dist-DLWXeXC1.mjs";
3
+ import { a as getStepDefinition, i as getStep, n as ALL_STEPS, o as resolveModelForFormat, r as getDefaultModelForFormat, t as ALL_PIPELINE_STEPS } from "../step-definitions-CVk19Y0u.mjs";
4
4
  import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
5
5
  import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../model-download-service-Cp9f4dk6-CwUb5v3Y.mjs";
6
6
  import { spawn } from "node:child_process";
@@ -156,9 +156,6 @@ function pythonRequirementsFor(id) {
156
156
  function tuningFor(id) {
157
157
  return RUNTIME_DETAIL[id].tuning;
158
158
  }
159
- function runtimeLabel(id) {
160
- return RUNTIME_DETAIL[id].label;
161
- }
162
159
  /**
163
160
  * Proactive-install hint kept for back-compat (re-exported from index.ts).
164
161
  * Intel on Linux warrants installing openvino; coremltools covers macOS;
@@ -194,117 +191,27 @@ function pickBestRuntime(env, bestBackendHint) {
194
191
  device: defaultDeviceFor(chosen)
195
192
  };
196
193
  }
197
- /**
198
- * The ordered device candidates a runtime will try for an `'auto'`/`'all'`
199
- * device pick — mirrors what the python inference pool actually resolves
200
- * (openvino `_resolve_ov_devices`: NPU>GPU>CPU; coreml compute-units `all`:
201
- * ANE>GPU>CPU). Surfaced in `probedBestEngine` so the UI shows the real
202
- * accelerator order (`openvino/auto [NPU, GPU, CPU]`) instead of a bare `auto`
203
- * tag. Returns `[]` for runtimes with a single fixed device (onnx → CPU) — the
204
- * caller then keeps the plain `backend/device` label.
205
- */
206
- function engineDeviceSequence(runtimeId, env) {
207
- const hw = env.hardware;
208
- switch (runtimeId) {
209
- case "openvino": {
210
- const seq = [];
211
- if (hw?.npu?.type === "intel-npu") seq.push("NPU");
212
- if (hw?.gpu?.type === "intel") seq.push("GPU");
213
- seq.push("CPU");
214
- return seq;
215
- }
216
- case "coreml": {
217
- const seq = [];
218
- if (hw?.npu?.type === "apple-ane") seq.push("ANE");
219
- if (hw?.gpu != null) seq.push("GPU");
220
- seq.push("CPU");
221
- return seq;
222
- }
223
- default: return [];
224
- }
225
- }
226
194
  //#endregion
227
195
  //#region src/detection-pipeline/engine-store-keys.ts
228
196
  /**
229
- * Per-node scoping for the detection-pipeline engine cascade.
230
- *
231
- * The detection addon's settings store is a single CLUSTER-SHARED blob (the
232
- * settings-store cap is hub-resident; every node's detection instance reads and
233
- * writes the same keys). That is correct for node-agnostic settings (pipeline
234
- * steps, tuning) but WRONG for the engine cascade — `engineBackend` /
235
- * `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
236
- * and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
237
- * selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
238
- * another node that has no NPU.
197
+ * Node-id normalisation for the detection pipeline.
239
198
  *
240
- * These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
241
- * Everything else in the store stays shared. There is NO fallback to a legacy
242
- * un-scoped value for ANY node (the hub included) and no in-code migration: a
243
- * bare key (written before scoping, or by an older build) is inert — invisible
244
- * to every node. A node with no scoped key reads `undefined`, which drives its
245
- * OWN live resolve (`resolveAutoEngine` picks the node's runtime from the local
246
- * kernel each boot; `reprobeEngine` persists an explicit probe under
247
- * `<key>@<node>`). Serving a bare value to any node
248
- * would leak one node's hardware pick (e.g. `openvino/auto`) onto a node that
249
- * cannot run it (the CB3 "Mac runs OpenVINO" / N100 AUTO-device bug). This
250
- * mirrors the decoder's `readNodeDecoderBackend` no-fallback rule.
199
+ * The legacy per-node engine CASCADE keys (`engineBackend` / `engineDevice` /
200
+ * `probedBestEngine`) were removed together with the operator-facing engine
201
+ * election: each node now runs a device-array of inference pools balanced
202
+ * per-session, and the node-default pool is derived live from the node's own
203
+ * hardware (`resolveAutoEngine`). All that survives from the old cascade module
204
+ * is the node-id normaliser, still used to target the platform-probe per node.
251
205
  */
252
- var ENGINE_CASCADE_KEYS = [
253
- "engineBackend",
254
- "engineDevice",
255
- "probedBestEngine"
256
- ];
257
- function isEngineCascadeKey(key) {
258
- return ENGINE_CASCADE_KEYS.includes(key);
259
- }
260
206
  /**
261
- * Normalise a raw kernel node id to the bare node id used for scoping.
262
- * `localNodeId` can carry a `<node>/<addon>` suffix; the engine selection is
263
- * per-NODE, so strip the addon segment. Falls back to `hub`.
207
+ * Normalise a raw kernel node id to the bare node id. `localNodeId` can carry a
208
+ * `<node>/<addon>` suffix; probe targeting is per-NODE, so strip the addon
209
+ * segment. Falls back to `hub`.
264
210
  */
265
211
  function normalizeEngineNodeId(rawNodeId) {
266
212
  const raw = rawNodeId ?? "hub";
267
213
  return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
268
214
  }
269
- /** The node-scoped store key for an engine cascade field. */
270
- function nodeEngineKey(base, nodeId) {
271
- return `${base}@${normalizeEngineNodeId(nodeId)}`;
272
- }
273
- /**
274
- * Read an engine cascade value for a node: the node-scoped key if present,
275
- * otherwise `undefined` — for EVERY node, the hub included. There is no
276
- * fallback to the legacy un-scoped key: a bare value in the store is inert.
277
- * A node with no scoped key gets `undefined` so its own platform-driven
278
- * auto-pick runs and persists a real per-node selection under `<key>@<node>`.
279
- * (A bare fallback would leak one node's engine selection cluster-wide — CB3.)
280
- */
281
- function readNodeEngineValue(store, base, nodeId) {
282
- return store[nodeEngineKey(base, nodeId)];
283
- }
284
- /**
285
- * Project a raw store onto the plain engine cascade keys for THIS node, so the
286
- * UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
287
- * the node's own selection. Non-engine keys are left untouched. Node-scoped
288
- * keys for OTHER nodes are dropped from the projection (not relevant to this
289
- * node's form).
290
- */
291
- function projectNodeEngine(store, nodeId) {
292
- const out = {};
293
- const scopedForAnyNode = /* @__PURE__ */ new Set();
294
- for (const key of Object.keys(store)) {
295
- const atIdx = key.indexOf("@");
296
- if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
297
- scopedForAnyNode.add(key);
298
- continue;
299
- }
300
- out[key] = store[key];
301
- }
302
- for (const base of ENGINE_CASCADE_KEYS) {
303
- const value = readNodeEngineValue(store, base, nodeId);
304
- if (value !== void 0) out[base] = value;
305
- }
306
- return out;
307
- }
308
215
  //#endregion
309
216
  //#region src/detection-pipeline/default-detection-model.ts
310
217
  /** The object-detection step id — the only slot this resolver applies to. */
@@ -1542,28 +1449,17 @@ var EngineFactory = class {
1542
1449
  //#endregion
1543
1450
  //#region src/detection-pipeline/engine/resolve-device-engine.ts
1544
1451
  /**
1545
- * Backend → model format for a device pool. Mirrors the Phase-1 device
1546
- * enumeration (`enumerateInferenceDevices`) and the pool-runtime mapping.
1547
- */
1548
- var BACKEND_FORMAT = {
1549
- openvino: "openvino",
1550
- edgetpu: "tflite",
1551
- onnx: "onnx",
1552
- cpu: "onnx",
1553
- cuda: "onnx",
1554
- coreml: "coreml"
1555
- };
1556
- /**
1557
1452
  * Resolve a Phase-1 device key (`<backend>:<device>`, or bare `cpu`) into the
1558
1453
  * {@link PipelineEngineChoice} used to build that device's inference pool.
1559
1454
  * Throws on an unknown backend token so a bad `deviceKey` fails loud rather
1560
- * than silently building a wrong pool.
1455
+ * than silently building a wrong pool. Format comes from the shared
1456
+ * {@link DEVICE_BACKEND_TO_FORMAT} SSOT (`@camstack/types`) — no local copy.
1561
1457
  */
1562
1458
  function resolveDeviceEngine(deviceKey) {
1563
1459
  const colon = deviceKey.indexOf(":");
1564
1460
  const backend = colon >= 0 ? deviceKey.slice(0, colon) : deviceKey;
1565
1461
  const device = colon >= 0 ? deviceKey.slice(colon + 1) : deviceKey;
1566
- const format = BACKEND_FORMAT[backend];
1462
+ const format = DEVICE_BACKEND_TO_FORMAT[backend];
1567
1463
  if (format === void 0) throw new Error(`resolveDeviceEngine: unknown backend "${backend}" in deviceKey "${deviceKey}"`);
1568
1464
  return {
1569
1465
  runtime: "python",
@@ -1573,6 +1469,69 @@ function resolveDeviceEngine(deviceKey) {
1573
1469
  };
1574
1470
  }
1575
1471
  //#endregion
1472
+ //#region src/detection-pipeline/engine/idle-pool-reaper.ts
1473
+ var IdlePoolReaper = class {
1474
+ lastUsed = /* @__PURE__ */ new Map();
1475
+ timers = /* @__PURE__ */ new Map();
1476
+ ttlMs;
1477
+ disposeFn;
1478
+ now;
1479
+ setTimer;
1480
+ clearTimer;
1481
+ onError;
1482
+ constructor(opts) {
1483
+ this.ttlMs = opts.ttlMs;
1484
+ this.disposeFn = opts.dispose;
1485
+ this.now = opts.now ?? (() => Date.now());
1486
+ this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
1487
+ this.clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
1488
+ this.onError = opts.onError;
1489
+ }
1490
+ /** Record a use of `key` and (re)arm its idle-eviction timer. */
1491
+ touch(key) {
1492
+ this.lastUsed.set(key, this.now());
1493
+ this.arm(key);
1494
+ }
1495
+ /** Stop tracking `key` (the pool was disposed/removed elsewhere). */
1496
+ cancel(key) {
1497
+ const timer = this.timers.get(key);
1498
+ if (timer) this.clearTimer(timer);
1499
+ this.timers.delete(key);
1500
+ this.lastUsed.delete(key);
1501
+ }
1502
+ /** Clear every timer (shutdown). Does not dispose pools — the caller does. */
1503
+ disposeAll() {
1504
+ for (const timer of this.timers.values()) this.clearTimer(timer);
1505
+ this.timers.clear();
1506
+ this.lastUsed.clear();
1507
+ }
1508
+ arm(key) {
1509
+ const existing = this.timers.get(key);
1510
+ if (existing) this.clearTimer(existing);
1511
+ this.timers.set(key, this.setTimer(() => {
1512
+ this.reap(key);
1513
+ }, this.ttlMs));
1514
+ }
1515
+ async reap(key) {
1516
+ const last = this.lastUsed.get(key);
1517
+ if (last === void 0) return;
1518
+ const idleMs = this.now() - last;
1519
+ if (idleMs < this.ttlMs) {
1520
+ this.timers.set(key, this.setTimer(() => {
1521
+ this.reap(key);
1522
+ }, this.ttlMs - idleMs));
1523
+ return;
1524
+ }
1525
+ this.timers.delete(key);
1526
+ this.lastUsed.delete(key);
1527
+ try {
1528
+ await this.disposeFn(key);
1529
+ } catch (err) {
1530
+ this.onError?.(key, err);
1531
+ }
1532
+ }
1533
+ };
1534
+ //#endregion
1576
1535
  //#region src/detection-pipeline/engine-provisioner.ts
1577
1536
  /** Incremental backoff growing to a ~5 min cap; retries indefinitely at cap. */
1578
1537
  var BACKOFF_SCHEDULE_MS = [
@@ -3929,26 +3888,6 @@ var PROBE_DIRECT_RPC_TIMEOUT_MS = 6e4;
3929
3888
  * result is identical (onnx / cpu) but is now derived through the shared rules
3930
3889
  * instead of duplicated inline.
3931
3890
  */
3932
- function onnxFloorPick() {
3933
- return ONNX_FLOOR;
3934
- }
3935
- /**
3936
- * The platform-deterministic engine pick computed SYNCHRONOUSLY from this node's
3937
- * own `process.platform`/`arch` alone (no probe): darwin → coreml, else → onnx
3938
- * (gpu-dependent openvino/cuda need the probe and converge via the auto-pick).
3939
- * Used as the ENGINE fallback when a persisted selection is unsupported on this
3940
- * node — so a stale GLOBAL engine config (e.g. the cluster's OpenVINO choice)
3941
- * can never force an impossible engine onto a node whose platform rejects it.
3942
- */
3943
- function platformDefaultPick() {
3944
- const pick = pickBestRuntime(runtimeEnvFromProcess(null), null);
3945
- return {
3946
- runtime: "python",
3947
- backend: pick.runtimeId,
3948
- format: modelFormatFor(pick.runtimeId),
3949
- device: pick.device
3950
- };
3951
- }
3952
3891
  /**
3953
3892
  * Is `backend` even POSSIBLE on this node's OS/arch (ignoring gpu detail)?
3954
3893
  * coreml ⇒ darwin only; openvino ⇒ x64 non-darwin only; onnx ⇒ anywhere. Used to
@@ -4064,6 +4003,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4064
4003
  */
4065
4004
  overrideCache = null;
4066
4005
  overrideCacheTimer = null;
4006
+ /**
4007
+ * Idle-TTL for a per-device inference pool (multi-device C3). A device pool
4008
+ * with no dispatch for this long is disposed and recreated on next use — the
4009
+ * memory guardrail that keeps `factoriesByDevice` from accumulating warm pools
4010
+ * forever. Longer than the override cache (60s) since a device pool is heavier
4011
+ * to respin; still well above any real inter-session gap for an active camera.
4012
+ */
4013
+ static DEVICE_POOL_IDLE_TTL_MS = 12e4;
4014
+ deviceReaper = new IdlePoolReaper({
4015
+ ttlMs: DetectionPipelineProvider.DEVICE_POOL_IDLE_TTL_MS,
4016
+ dispose: (deviceKey) => this.disposeDevicePool(deviceKey),
4017
+ onError: (deviceKey, err) => this.log.warn("Idle device pool dispose failed", { meta: {
4018
+ deviceKey,
4019
+ error: err instanceof Error ? err.message : String(err)
4020
+ } })
4021
+ });
4067
4022
  static OVERRIDE_CACHE_TTL_MS = 6e4;
4068
4023
  /** Read the addon settings store (all keys). */
4069
4024
  readStore;
@@ -4091,20 +4046,15 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4091
4046
  format: this.currentEngine.format
4092
4047
  } });
4093
4048
  }
4094
- /** Load persisted engine choice. Call after construction. */
4049
+ /**
4050
+ * Call after construction. The node-default engine is always DERIVED live
4051
+ * from this node's own hardware (there is no persisted operator override
4052
+ * anymore — the elected single-engine cascade was removed). Defer the derive
4053
+ * to `setApi()` where the probe cap / kernel resolver is reachable.
4054
+ */
4095
4055
  async init() {
4096
- const stored = await this.loadEngine();
4097
- if (stored) {
4098
- this.currentEngine = stored;
4099
- this.log.info("Engine loaded (persisted)", { meta: {
4100
- runtime: stored.runtime,
4101
- backend: stored.backend,
4102
- format: stored.format
4103
- } });
4104
- } else {
4105
- this.needsAutoPick = true;
4106
- this.log.info("No persisted engine — auto-pick deferred to setApi()");
4107
- }
4056
+ this.needsAutoPick = true;
4057
+ this.log.info("Node-default engine derive deferred to setApi()");
4108
4058
  }
4109
4059
  /**
4110
4060
  * Lazy-install the pip requirements file matching `engine.backend` into
@@ -4178,6 +4128,9 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4178
4128
  await this.resolveAutoEngine();
4179
4129
  this.needsAutoPick = false;
4180
4130
  this.startProvisioningForCurrentEngine();
4131
+ await this.recomputeConfigIssues().catch((err) => {
4132
+ this.log.warn("config-issue recompute failed after node-default derive", { meta: { error: errMsg(err) } });
4133
+ });
4181
4134
  }
4182
4135
  /**
4183
4136
  * Authoritative platform-probe reconcile: query the probe provider
@@ -4205,34 +4158,38 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4205
4158
  }
4206
4159
  }
4207
4160
  /**
4208
- * Resolve the `'auto'` engine LIVE from this node's own kernel inference
4161
+ * Derive the node-default engine LIVE from this node's own kernel inference
4209
4162
  * resolver (`ctx.kernel.inferenceEngine`) — the runtime mirror of
4210
4163
  * `ctx.kernel.hwaccel`. Each node (hub + forked worker + remote agent)
4211
4164
  * resolves its OWN accelerators in-process, so a forked agent child never
4212
4165
  * adopts the hub's runtime from the `platform-probe` singleton.
4213
4166
  *
4214
- * NEVER persists. `'auto'` is a live-resolved sentinel: the pick is recomputed
4215
- * every boot from the local hardware, so it is inherently self-healing — the
4216
- * old `autoPickAndPersist` sticky-onnx bug (a premature onnx pick persisted
4217
- * during a boot race, then locked forever) cannot recur because the store is
4218
- * never written here. Only an explicit operator override is persisted, and
4219
- * that write happens through the settings UI, not this path.
4167
+ * The default is the TOP-SCORED CONCRETE device from the SAME
4168
+ * `enumerateInferenceDevices` enumeration the orchestrator balances over
4169
+ * (e.g. `openvino:npu`, not `openvino:auto`) NOT `pickBestRuntime`, whose
4170
+ * `defaultDeviceFor('openvino')` is the abstract `'auto'` sentinel. This is
4171
+ * load-bearing for pool COLLAPSE (D2/NEW-1): a `deviceKey`-less/lone session
4172
+ * routes through the default pool, and a balanced session that picks the same
4173
+ * top device (`openvino:npu`) must reuse THAT pool via `resolveDeviceFactory`'s
4174
+ * `enginesEqual(engine, currentEngine)` check — which only matches when the
4175
+ * default carries the concrete device, not `'auto'`. An abstract `'auto'`
4176
+ * default would spawn a THIRD pool contending with the per-device npu/gpu pools
4177
+ * on the same physical accelerators (double model load / VRAM). Falls back to
4178
+ * the onnx/cpu floor when the enumeration is empty. NEVER persists — recomputed
4179
+ * every boot from the local hardware, inherently self-healing.
4220
4180
  */
4221
4181
  async resolveAutoEngine() {
4222
4182
  let hardware = null;
4223
4183
  try {
4224
4184
  hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
4225
4185
  } catch {}
4226
- const pick = pickBestRuntime(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null), null);
4227
- this.currentEngine = {
4228
- runtime: "python",
4229
- backend: pick.runtimeId,
4230
- format: modelFormatFor(pick.runtimeId),
4231
- device: pick.device
4232
- };
4233
- this.log.info("Resolved auto engine (local kernel)", { meta: {
4234
- backend: pick.runtimeId,
4235
- device: pick.device,
4186
+ const devices = enumerateInferenceDevices(envToHardwareInfo(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null)));
4187
+ const top = devices.find((d) => d.available) ?? devices[0];
4188
+ this.currentEngine = top ? resolveDeviceEngine(top.key) : ONNX_FLOOR;
4189
+ this.log.info("Derived node-default engine (local kernel)", { meta: {
4190
+ deviceKey: top?.key ?? "onnx:cpu(floor)",
4191
+ backend: this.currentEngine.backend,
4192
+ device: this.currentEngine.device ?? null,
4236
4193
  hadHardware: hardware !== null
4237
4194
  } });
4238
4195
  }
@@ -4311,33 +4268,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
4311
4268
  this.provisioner.select(runtimeId, device);
4312
4269
  }
4313
4270
  /**
4314
- * Re-trigger provisioning after the operator changes the engine
4315
- * cascade (`engineBackend`/`engineDevice`). Reloads the persisted
4316
- * selection into `this.currentEngine`, then `select()`s it — the
4317
- * machine cancels any superseded provisioning and drives the new
4318
- * runtime through installing → verifying → ready.
4319
- */
4320
- async onEngineSelectionChanged() {
4321
- const prev = this.currentEngine;
4322
- const stored = await this.loadEngine();
4323
- if (stored) this.currentEngine = stored;
4324
- else await this.resolveAutoEngine();
4325
- const engineChanged = prev.runtime !== this.currentEngine.runtime || prev.backend !== this.currentEngine.backend || prev.format !== this.currentEngine.format || (prev.device ?? "") !== (this.currentEngine.device ?? "");
4326
- if (engineChanged && this.engineFactory) {
4327
- this.log.info("engine selection changed — rebuilding pool in place", { meta: {
4328
- from: `${prev.backend}/${prev.device ?? "default"}`,
4329
- to: `${this.currentEngine.backend}/${this.currentEngine.device ?? "default"}`
4330
- } });
4331
- try {
4332
- await this.engineFactory.dispose();
4333
- } catch {}
4334
- this.engineFactory = null;
4335
- this.executor = null;
4336
- }
4337
- this.startProvisioningForCurrentEngine();
4338
- if (engineChanged) await this.recomputeConfigIssues();
4339
- }
4340
- /**
4341
4271
  * Gate A (config-correctness gate at engine change) — eagerly re-resolve
4342
4272
  * the node-default step tree for `this.currentEngine.format` and compute
4343
4273
  * human-readable config issues from the result:
@@ -5781,6 +5711,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5781
5711
  this.engineFactory = null;
5782
5712
  this.executor = null;
5783
5713
  }
5714
+ this.deviceReaper.disposeAll();
5784
5715
  const deviceFactories = Array.from(this.factoriesByDevice.values());
5785
5716
  this.factoriesByDevice.clear();
5786
5717
  await Promise.all(deviceFactories.map((f) => f.dispose().catch(() => void 0)));
@@ -5924,7 +5855,10 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5924
5855
  return this.engineFactory;
5925
5856
  }
5926
5857
  const existing = this.factoriesByDevice.get(deviceKey);
5927
- if (existing) return existing;
5858
+ if (existing) {
5859
+ this.deviceReaper.touch(deviceKey);
5860
+ return existing;
5861
+ }
5928
5862
  const inflight = this.deviceFactoryInflight.get(deviceKey);
5929
5863
  if (inflight) return inflight;
5930
5864
  const creation = (async () => {
@@ -5945,6 +5879,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5945
5879
  throw err;
5946
5880
  }
5947
5881
  this.factoriesByDevice.set(deviceKey, factory);
5882
+ this.deviceReaper.touch(deviceKey);
5948
5883
  return factory;
5949
5884
  })();
5950
5885
  this.deviceFactoryInflight.set(deviceKey, creation);
@@ -5955,66 +5890,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
5955
5890
  }
5956
5891
  }
5957
5892
  /**
5958
- * Phase 2b resolve the engine from the addon's new schema-backed
5959
- * fields (`engineRuntime`, `engineBackend`, `engineDevice`). The legacy
5960
- * `pipelineEngine` JSON-blob fallback was removed with the executor seed
5961
- * (any store predating the per-node cascade simply re-runs auto-pick).
5962
- *
5963
- * The `'auto'` sentinel is treated as NO override (returns null) so the
5964
- * caller re-resolves LIVE via `resolveAutoEngine()` — `'auto'` is the
5965
- * self-healing default, never a persisted concrete backend. An explicit
5966
- * concrete backend (onnx/openvino/coreml) is honored as an operator override.
5967
- * Returns null when neither source has a concrete selection; the caller keeps
5968
- * the onnx floor set at construction until `resolveAutoEngine()` runs.
5893
+ * Dispose a single idle device pool (invoked by the {@link deviceReaper} when
5894
+ * `deviceKey` has had no dispatch for the idle-TTL). Removed from the map
5895
+ * FIRST so a concurrent `resolveDeviceFactory` recreates it fresh via the
5896
+ * single-flight guard rather than handing back the disposing instance.
5969
5897
  */
5970
- async loadEngine() {
5971
- const store = await this.readStore();
5972
- const storedRuntime = store["engineRuntime"];
5973
- const node = this.localProbeNodeId();
5974
- const storedBackend = readNodeEngineValue(store, "engineBackend", node);
5975
- if (storedBackend === "auto") return null;
5976
- if (typeof storedBackend === "string" && storedBackend.length > 0 && storedBackend !== "auto") {
5977
- const backend = storedBackend;
5978
- const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
5979
- const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
5980
- const floor = onnxFloorPick();
5981
- const migratedBackend = typeof storedRuntime === "string" && storedRuntime === "node" && backend === "cpu" ? "onnx" : backend;
5982
- if (!backendPossibleOnPlatform(migratedBackend)) {
5983
- const platformDefault = platformDefaultPick();
5984
- this.log.warn("Stored engine backend impossible on this platform — using platform default", { meta: {
5985
- stored: migratedBackend,
5986
- fallback: platformDefault.backend
5987
- } });
5988
- this.writeStore({
5989
- [nodeEngineKey("engineBackend", node)]: platformDefault.backend,
5990
- ...platformDefault.device !== void 0 ? { [nodeEngineKey("engineDevice", node)]: platformDefault.device } : {}
5991
- }).catch((err) => {
5992
- this.log.warn("Failed to persist platform-default engine after impossible stored pick", { meta: { error: err instanceof Error ? err.message : String(err) } });
5993
- });
5994
- return platformDefault;
5995
- }
5996
- const device = storedDevice || floor.device;
5997
- return {
5998
- runtime: "python",
5999
- backend: migratedBackend,
6000
- format: backendToFormat(migratedBackend),
6001
- ...device ? { device } : {}
6002
- };
6003
- }
6004
- return null;
5898
+ async disposeDevicePool(deviceKey) {
5899
+ const factory = this.factoriesByDevice.get(deviceKey);
5900
+ if (!factory) return;
5901
+ this.factoriesByDevice.delete(deviceKey);
5902
+ await factory.dispose();
5903
+ this.log.info("Idle device pool evicted", { meta: {
5904
+ deviceKey,
5905
+ idleTtlMs: DetectionPipelineProvider.DEVICE_POOL_IDLE_TTL_MS
5906
+ } });
6005
5907
  }
6006
5908
  /**
6007
- * Re-run the platform probe for the inference engine and persist the
6008
- * result into the addon's own global-settings keys. Because those
6009
- * fields are declared `requiresRestart: true`, the
6010
- * `updateGlobalSettings` pathway auto-schedules an addon restart —
6011
- * here we do the write directly via the settings store and trigger
6012
- * restart manually through the addons cap.
6013
- *
6014
- * Returns the chosen engine so the UI can confirm the pick without a
6015
- * second round-trip.
6016
- */
6017
- /**
6018
5909
  * Phase 2c — flat per-addonId map holding the video-pipeline step
6019
5910
  * config (`modelId` / `settings`). No `enabled` field: whether an
6020
5911
  * addon step runs is a capability-binding concern, not a
@@ -6199,35 +6090,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
6199
6090
  } });
6200
6091
  return { success: true };
6201
6092
  }
6202
- async reprobeEngine() {
6203
- let hardware = null;
6204
- try {
6205
- hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
6206
- } catch {}
6207
- const env = runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null);
6208
- const pick = pickBestRuntime(env, null);
6209
- const best = {
6210
- runtime: "python",
6211
- backend: pick.runtimeId,
6212
- format: modelFormatFor(pick.runtimeId),
6213
- device: pick.device
6214
- };
6215
- const deviceSeq = engineDeviceSequence(pick.runtimeId, env);
6216
- const probedLabel = deviceSeq.length > 1 ? `${best.backend}/${best.device ?? "default"} [${deviceSeq.join(", ")}]` : `${best.backend}/${best.device ?? "default"}`;
6217
- const rpNode = this.localProbeNodeId();
6218
- if (pick.runtimeId !== "onnx" || hardware !== null) {
6219
- await this.writeStore({ [nodeEngineKey("probedBestEngine", rpNode)]: probedLabel });
6220
- this.log.info("Re-probed engine (local kernel) — updated probedBestEngine hint", { meta: {
6221
- backend: best.backend,
6222
- device: best.device ?? null,
6223
- probedBestEngine: probedLabel
6224
- } });
6225
- } else this.log.info("Re-probe: onnx floor pending gpu probe — hint NOT persisted", { meta: {
6226
- backend: best.backend,
6227
- device: best.device ?? null
6228
- } });
6229
- return best;
6230
- }
6231
6093
  async getReferenceAudioFiles() {
6232
6094
  const dir = resolveReferenceAudioDir();
6233
6095
  if (!dir) return [];
@@ -6582,47 +6444,6 @@ function resolveAddonPythonDir() {
6582
6444
  for (const c of candidates) if (fs.existsSync(path$1.join(c, "inference_pool.py"))) return c;
6583
6445
  throw new Error(`addon-pipeline/detection-pipeline: python/ dir not found. Searched:\n${candidates.join("\n")}`);
6584
6446
  }
6585
- var BACKEND_TO_FORMAT = {
6586
- cpu: "onnx",
6587
- coreml: "coreml",
6588
- openvino: "openvino",
6589
- onnx: "onnx",
6590
- edgetpu: "tflite"
6591
- };
6592
- /**
6593
- * Every runtime the registry knows about, in offer order. Used only to build
6594
- * the STATIC base schema (returned by `globalSettingsSchema()` before a live
6595
- * ctx exists, e.g. addon-registry boot introspection). The live, hardware-
6596
- * gated subset is injected by `getGlobalSettings()` via `supportedRuntimes(env)`.
6597
- */
6598
- var ALL_RUNTIME_IDS = [
6599
- "onnx",
6600
- "openvino",
6601
- "coreml",
6602
- "edgetpu"
6603
- ];
6604
- /**
6605
- * Narrow an arbitrary stored backend string to a known `RuntimeId`. Legacy or
6606
- * unknown values (e.g. the removed `'cpu'` backend) fall back to the `onnx`
6607
- * floor so the registry lookup never throws.
6608
- */
6609
- function toRuntimeId(backend) {
6610
- return ALL_RUNTIME_IDS.find((id) => id === backend) ?? "onnx";
6611
- }
6612
- /** Static universe of backend options (every runtime). Labels from the registry. */
6613
- var STATIC_BACKEND_OPTIONS = ALL_RUNTIME_IDS.map((id) => ({
6614
- value: id,
6615
- label: runtimeLabel(id)
6616
- }));
6617
- /**
6618
- * Static device options for the schema's default backend. The live device
6619
- * list per the selected backend + probed hardware is injected by
6620
- * `getGlobalSettings()` via `runtimeDevices(backend, hardware)`.
6621
- */
6622
- var STATIC_DEFAULT_DEVICE_OPTIONS = runtimeDevices(ALL_RUNTIME_IDS[0], null).map((d) => ({
6623
- value: d.value,
6624
- label: d.label
6625
- }));
6626
6447
  var DEFAULT_CONFIG = {
6627
6448
  concurrency: 0,
6628
6449
  batchMode: "",
@@ -6631,17 +6452,8 @@ var DEFAULT_CONFIG = {
6631
6452
  numStreams: 0,
6632
6453
  intraOpThreads: 0,
6633
6454
  numWorkers: 0,
6634
- engineRuntime: "python",
6635
- engineBackend: "auto",
6636
- engineDevice: "cpu",
6637
- probedBestEngine: "",
6638
- activeEngine: "",
6639
6455
  activeModelSubstitutions: ""
6640
6456
  };
6641
- /** Derive the model-format from a backend value. Called by the provider. */
6642
- function backendToFormat(backend) {
6643
- return BACKEND_TO_FORMAT[backend] ?? "onnx";
6644
- }
6645
6457
  /**
6646
6458
  * Cadence for the per-node engine-inventory snapshot. Engines change
6647
6459
  * slowly (manual spin/kill, idle eviction, attach/detach). 5 s strikes
@@ -6734,63 +6546,18 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
6734
6546
  tab: "engine",
6735
6547
  order: 0,
6736
6548
  description: "Vision-detection execution provider (object detection, face detection, plate OCR). Inference always runs via the embedded Python runtime. Provider and hardware options are gated by what the platform probe reports as available on this host. Changes restart the addon.",
6737
- fields: [
6738
- this.field({
6739
- type: "text",
6740
- key: "probedBestEngine",
6741
- label: "Probed best",
6742
- description: "Auto-detected best engine for this host (format: provider/device, e.g. \"openvino/npu\"). Click the refresh icon to re-run the probe — the detected provider and hardware device are written back into the two fields below, overwriting any manual override.",
6743
- readonlyField: true,
6744
- default: "",
6745
- perNode: true,
6746
- actions: [{
6747
- action: "reprobe-engine",
6748
- icon: "refresh-cw",
6749
- tooltip: "Re-probe engine"
6750
- }]
6751
- }),
6752
- this.field({
6753
- type: "text",
6754
- key: "activeEngine",
6755
- label: "Active engine",
6756
- description: "The runtime/device the inference pool ACTUALLY loaded on this node, with its provisioning state (format: provider/device (state)). If this differs from the selected provider below — e.g. \"onnx/cpu (ready)\" while OpenVINO is selected — the chosen accelerator could not load on this host and the engine fell back to the ONNX-CPU baseline.",
6757
- readonlyField: true,
6758
- default: ""
6759
- }),
6760
- this.field({
6761
- type: "text",
6762
- key: "activeModelSubstitutions",
6763
- label: "Model substitutions",
6764
- description: "Node-local model substitutions currently in effect: a step's chosen model has no build for this node's engine format, so a compatible default is running instead (format: \"step: chosen→running\"). Hidden when every step matches.",
6765
- readonlyField: true,
6766
- default: "",
6767
- showWhen: {
6768
- field: "activeModelSubstitutions",
6769
- notEquals: ""
6770
- }
6771
- }),
6772
- this.field({
6773
- type: "select",
6774
- key: "engineBackend",
6775
- label: "Execution provider",
6776
- options: [{
6777
- value: "auto",
6778
- label: "Auto (best for this node)"
6779
- }, ...STATIC_BACKEND_OPTIONS],
6780
- default: DEFAULT_CONFIG.engineBackend,
6781
- immediate: true,
6782
- perNode: true
6783
- }),
6784
- this.field({
6785
- type: "select",
6786
- key: "engineDevice",
6787
- label: "Hardware device",
6788
- options: [...STATIC_DEFAULT_DEVICE_OPTIONS],
6789
- default: DEFAULT_CONFIG.engineDevice,
6790
- immediate: true,
6791
- perNode: true
6792
- })
6793
- ]
6549
+ fields: [this.field({
6550
+ type: "text",
6551
+ key: "activeModelSubstitutions",
6552
+ label: "Model substitutions",
6553
+ description: "Node-local model substitutions currently in effect: a step's chosen model has no build for this node's engine format, so a compatible default is running instead (format: \"step: chosen→running\"). Hidden when every step matches.",
6554
+ readonlyField: true,
6555
+ default: "",
6556
+ showWhen: {
6557
+ field: "activeModelSubstitutions",
6558
+ notEquals: ""
6559
+ }
6560
+ })]
6794
6561
  }, {
6795
6562
  id: "inference-executor",
6796
6563
  title: "Inference executor",
@@ -6819,15 +6586,7 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
6819
6586
  label: "Window (cross-camera accumulator)"
6820
6587
  }
6821
6588
  ],
6822
- default: "",
6823
- showWhen: {
6824
- field: "engineBackend",
6825
- in: [
6826
- "coreml",
6827
- "onnx",
6828
- "cpu"
6829
- ]
6830
- }
6589
+ default: ""
6831
6590
  }),
6832
6591
  this.field({
6833
6592
  type: "slider",
@@ -6893,59 +6652,31 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
6893
6652
  }] });
6894
6653
  }
6895
6654
  /**
6896
- * Override to inject the hardware-driven backend + device options.
6655
+ * Override to inject the read-only model-substitution status + the
6656
+ * per-backend tuning-slider defaults.
6897
6657
  *
6898
- * The OFFERED backend list comes from `supportedRuntimes(env)` platform,
6899
- * arch and probed hardware ONLY, never from which Python packages are
6900
- * installed. A host's hardware is what it can physically run; install state
6901
- * is provisioned on demand (Phase 2). Device options come from
6902
- * `runtimeDevices(backend, hardware)` (probe-driven; default-only when the
6903
- * probe is unreachable). Stored backend / device snap back to the registry
6904
- * floor / default when they fall outside the offered set.
6658
+ * Engine SELECTION is no longer operator-facing (the elected single-engine
6659
+ * cascade was removed): each node runs a device-array of inference pools
6660
+ * balanced per-session, and the node-default pool is derived live from the
6661
+ * node's own hardware. So this override no longer offers a backend/device
6662
+ * dropdown or a probed/active-engine readout — it only:
6663
+ * 1. resolves the node's derived-default backend (via the probe) to suggest
6664
+ * the matrix-tuned slider defaults that won the bench for that backend, and
6665
+ * 2. surfaces the live `activeModelSubstitutions` readout.
6905
6666
  */
6906
6667
  async getGlobalSettings(overlay, _cap, nodeId) {
6907
6668
  const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
6908
6669
  const rawStored = await this.resolveUiSettingsStore();
6909
6670
  if (rawStored === null) return null;
6910
- const stored = projectNodeEngine(rawStored, targetNode);
6911
- const storedBackendRaw = stored["engineBackend"];
6912
- if (typeof storedBackendRaw !== "string" || storedBackendRaw === "") return null;
6913
6671
  const merged = overlay ? {
6914
- ...stored,
6672
+ ...rawStored,
6915
6673
  ...overlay
6916
- } : stored;
6917
- const env = await this.probeHardwareEnv(targetNode);
6918
- const hardware = env.hardware;
6919
- const offered = supportedRuntimes(env);
6920
- const runtimeBackends = [{
6921
- value: "auto",
6922
- label: "Auto (best for this node)"
6923
- }, ...offered.map((id) => ({
6924
- value: id,
6925
- label: runtimeLabel(id)
6926
- }))];
6927
- const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
6928
- const isAuto = storedBackend === "auto";
6929
- const backend = (() => {
6930
- if (isAuto) return pickBestRuntime(env, null).runtimeId;
6931
- const rid = toRuntimeId(storedBackend);
6932
- if (rid === "onnx") return "onnx";
6933
- if (offered.includes(rid)) return rid;
6934
- return rid === "openvino" && env.platform !== "darwin" && env.arch === "x64" || rid === "coreml" && env.platform === "darwin" ? rid : offered[0] ?? "onnx";
6935
- })();
6936
- const deviceOptions = runtimeDevices(backend, hardware);
6937
- const storedDevice = typeof merged.engineDevice === "string" ? merged.engineDevice : "";
6938
- const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
6939
- const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
6940
- const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
6674
+ } : rawStored;
6675
+ const backend = pickBestRuntime(await this.probeHardwareEnv(targetNode), null).runtimeId;
6941
6676
  const substitutions = this.provider?.getActiveModelSubstitutions() ?? [];
6942
6677
  const activeModelSubstitutions = substitutions.length === 0 ? "" : substitutions.map((s) => `${s.addonId}: ${s.chosen}→${s.running}`).join(", ");
6943
- const displayBackend = isAuto ? "auto" : backend;
6944
6678
  const raw = {
6945
6679
  ...merged,
6946
- engineBackend: displayBackend,
6947
- engineDevice: device,
6948
- activeEngine,
6949
6680
  activeModelSubstitutions
6950
6681
  };
6951
6682
  const schema = this.globalSettingsSchema();
@@ -6957,18 +6688,6 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
6957
6688
  ...section,
6958
6689
  fields: section.fields.map((field) => {
6959
6690
  if (field.type === "select") {
6960
- if (field.key === "engineBackend") return {
6961
- ...field,
6962
- options: [...runtimeBackends]
6963
- };
6964
- if (field.key === "engineDevice") return {
6965
- ...field,
6966
- options: deviceOptions.map((d) => ({
6967
- value: d.value,
6968
- label: d.label
6969
- })),
6970
- default: defaultDeviceFor(backend)
6971
- };
6972
6691
  if (field.key === "batchMode" && tuning["batchMode"] !== void 0) return {
6973
6692
  ...field,
6974
6693
  default: tuning["batchMode"]
@@ -7101,7 +6820,7 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
7101
6820
  * a reason to disagree.
7102
6821
  */
7103
6822
  resolveBackendTuning() {
7104
- const t = tuningFor(this.config.engineBackend === "auto" ? this.provider?.resolvedEngineBackend() ?? "onnx" : toRuntimeId(this.config.engineBackend));
6823
+ const t = tuningFor(this.provider?.resolvedEngineBackend() ?? "onnx");
7105
6824
  const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
7106
6825
  const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
7107
6826
  return {
@@ -7164,9 +6883,6 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
7164
6883
  await this.provider.init();
7165
6884
  await this.provider.setApi(this.ctx);
7166
6885
  }
7167
- if (!this.config.probedBestEngine) await this.provider.reprobeEngine().catch((err) => {
7168
- this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
7169
- });
7170
6886
  await this.provider.warmPool();
7171
6887
  this.engineMetricsTimer = setInterval(() => this.emitEngineMetricsSnapshot(), ENGINE_METRICS_SNAPSHOT_INTERVAL_MS);
7172
6888
  this.lastAppliedPoolConfig = this.snapshotPoolConfig();
@@ -7280,21 +6996,18 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
7280
6996
  }
7281
6997
  /**
7282
6998
  * BaseAddon calls `onConfigChanged` after every settings write. This is the
7283
- * SOLE apply path for engine + tuning changes — none of those fields declare
6999
+ * SOLE apply path for tuning changes — none of those fields declare
7284
7000
  * `requiresRestart` anymore (an addon restart re-probes hardware + reloads
7285
- * every model on every set, which is exactly what we want to avoid). Instead:
7286
- * - Pool-bound tuning (`concurrency`/`batchMode`/`windowMs`/…) in-place
7287
- * pool respawn when the snapshot flips (`poolConfigChanged` below).
7288
- * - Engine cascade (`engineBackend`/`engineDevice`) the provider's
7289
- * `onEngineSelectionChanged` disposes the device-bound factory so the next
7290
- * runPipeline rebuilds the pool on the new selection, in place.
7291
- * Both apply optimistically — the inference gate stays closed for the short
7292
- * re-spin, frames are dropped (never crashed), no addon bounce.
7001
+ * every model on every set, which is exactly what we want to avoid): pool-bound
7002
+ * tuning (`concurrency`/`batchMode`/`windowMs`/…) drives an in-place pool
7003
+ * respawn when the snapshot flips (`poolConfigChanged` below). The engine
7004
+ * SELECTION is no longer operator-facing — the node-default backend is derived
7005
+ * once at boot (`setApi` `resolveAutoEngine`) from static hardware, so there
7006
+ * is no per-write engine re-select. The respawn applies optimistically — the
7007
+ * inference gate stays closed for the short re-spin, frames are dropped (never
7008
+ * crashed), no addon bounce.
7293
7009
  */
7294
7010
  async onConfigChanged() {
7295
- if (this.provider) await this.provider.onEngineSelectionChanged().catch((err) => {
7296
- this.ctx.logger.warn("engine provisioning re-select failed on config change", { meta: { error: err instanceof Error ? err.message : String(err) } });
7297
- });
7298
7011
  if (!this.poolConfigChanged()) return;
7299
7012
  if (!this.provider) {
7300
7013
  this.lastAppliedPoolConfig = this.snapshotPoolConfig();
@@ -7319,4 +7032,4 @@ var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
7319
7032
  }
7320
7033
  };
7321
7034
  //#endregion
7322
- export { ALL_STEPS, DetectionPipelineProvider, backendToFormat, DetectionPipelineAddon as default, getDefaultModelForFormat, getStepDefinition, shouldInstallOpenvino };
7035
+ export { ALL_STEPS, DetectionPipelineProvider, DetectionPipelineAddon as default, getDefaultModelForFormat, getStepDefinition, shouldInstallOpenvino };