@camstack/addon-pipeline 1.1.27 → 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.
- package/dist/audio-analyzer/index.js +7 -6
- package/dist/audio-analyzer/index.mjs +7 -6
- package/dist/audio-codec-ffmpeg/index.js +1 -5
- package/dist/audio-codec-ffmpeg/index.mjs +1 -5
- package/dist/decoder-ffmpeg/index.js +138 -356
- package/dist/decoder-ffmpeg/index.mjs +118 -336
- package/dist/decoder-nodeav/index.js +17 -10
- package/dist/decoder-nodeav/index.mjs +17 -10
- package/dist/detection-pipeline/index.js +341 -379
- package/dist/detection-pipeline/index.mjs +341 -379
- package/dist/{dist-DAIlCdAx.js → dist-Cwc0TUQr.js} +378 -76
- package/dist/{dist-CgEP_0OL.mjs → dist-DjuGmyG9.mjs} +379 -65
- package/dist/ffmpeg-args-C5GPp8Cw.mjs +323 -0
- package/dist/ffmpeg-args-D6h1edXK.js +418 -0
- package/dist/frame-dropper-AjheBGMG.mjs +22 -0
- package/dist/frame-dropper-DKLM6pMz.js +27 -0
- package/dist/{frame-handle-plane-Dq20KtKL.mjs → frame-handle-plane-Bkxz-TTD.mjs} +13 -5
- package/dist/{frame-handle-plane-DtTRX_0n.js → frame-handle-plane-DQNCTrpC.js} +13 -5
- package/dist/{frame-ring-sink-BbzrqTWj.js → frame-ring-sink-8LLV-cvH.js} +128 -23
- package/dist/{frame-ring-sink-B_NvPTJZ.mjs → frame-ring-sink-ClEWjiRU.mjs} +117 -24
- package/dist/motion-wasm/index.js +55 -3
- package/dist/motion-wasm/index.mjs +55 -3
- package/dist/node-topology-platform-BkR_k6WT.mjs +15 -0
- package/dist/node-topology-platform-CFZ7F4xW.js +20 -0
- package/dist/pipeline-runner/index.js +50 -16
- package/dist/pipeline-runner/index.mjs +50 -16
- package/dist/recorder/index.js +36 -40
- package/dist/recorder/index.mjs +36 -40
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-5tQlh9h4.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D567z31g.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DhY3MZ2C.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BJK0-svt.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DtoYGpTp.mjs} +1 -1
- package/dist/stream-broker/{hostInit-DyLqyJaS.mjs → hostInit-B_b3PIZB.mjs} +3 -3
- package/dist/stream-broker/index.js +198 -115
- package/dist/stream-broker/index.mjs +189 -106
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-BDLNwJ_F.js → MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CoLjNiUN.js → MotionZonesSettings-NcxxQN8r-DTeuDGCU.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-DJE3OU-q.js → PrivacyMaskSettings-APgPLF7p-CirtxO3e.js} +1 -1
- package/embed-dist/assets/index-C5UpuPr8.css +2 -0
- package/embed-dist/assets/{index-C-pL8ETk.js → index-CpUy8OIE.js} +10 -10
- package/embed-dist/index.html +2 -2
- package/package.json +1 -1
- package/python/inference_pool.py +93 -102
- package/python/test_inference_pool_device_selection.py +43 -38
- package/dist/frame-dropper-7RTo_YyG.js +0 -68
- package/dist/frame-dropper-CwkBTPGV.mjs +0 -51
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CkOPfV8r.mjs +0 -26
- package/embed-dist/assets/index-DrJ0ee3f.css +0 -2
|
@@ -3,7 +3,9 @@ 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-
|
|
6
|
+
const require_dist = require("../dist-Cwc0TUQr.js");
|
|
7
|
+
const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
|
|
8
|
+
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
7
9
|
let node_fs = require("node:fs");
|
|
8
10
|
node_fs = require_model_download_service_C_IHWnXx.__toESM(node_fs);
|
|
9
11
|
let node_path = require("node:path");
|
|
@@ -13,98 +15,6 @@ node_os = require_model_download_service_C_IHWnXx.__toESM(node_os);
|
|
|
13
15
|
let node_child_process = require("node:child_process");
|
|
14
16
|
let sharp = require("sharp");
|
|
15
17
|
sharp = require_model_download_service_C_IHWnXx.__toESM(sharp);
|
|
16
|
-
//#region src/detection-pipeline/engine-store-keys.ts
|
|
17
|
-
/**
|
|
18
|
-
* Per-node scoping for the detection-pipeline engine cascade.
|
|
19
|
-
*
|
|
20
|
-
* The detection addon's settings store is a single CLUSTER-SHARED blob (the
|
|
21
|
-
* settings-store cap is hub-resident; every node's detection instance reads and
|
|
22
|
-
* writes the same keys). That is correct for node-agnostic settings (pipeline
|
|
23
|
-
* steps, tuning) but WRONG for the engine cascade — `engineBackend` /
|
|
24
|
-
* `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
|
|
25
|
-
* and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
|
|
26
|
-
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
27
|
-
* another node that has no NPU.
|
|
28
|
-
*
|
|
29
|
-
* These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
|
|
30
|
-
* Everything else in the store stays shared. A legacy un-scoped value (written
|
|
31
|
-
* before this change, or by an older build) is read as a migration fallback
|
|
32
|
-
* for the HUB ONLY — the pre-scoping store was only ever written by a
|
|
33
|
-
* hub-only deployment, so serving the bare value to any other node leaks the
|
|
34
|
-
* hub's hardware pick (e.g. `openvino/auto`) onto a node that cannot run it
|
|
35
|
-
* (the CB3 "Mac runs OpenVINO" / N100 AUTO-device bug). A non-hub node with
|
|
36
|
-
* no scoped key reads `undefined`, which drives its own auto-pick.
|
|
37
|
-
*/
|
|
38
|
-
var ENGINE_CASCADE_KEYS = [
|
|
39
|
-
"engineBackend",
|
|
40
|
-
"engineDevice",
|
|
41
|
-
"probedBestEngine"
|
|
42
|
-
];
|
|
43
|
-
function isEngineCascadeKey(key) {
|
|
44
|
-
return ENGINE_CASCADE_KEYS.includes(key);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Normalise a raw kernel node id to the bare node id used for scoping.
|
|
48
|
-
* `localNodeId` can carry a `<node>/<addon>` suffix; the engine selection is
|
|
49
|
-
* per-NODE, so strip the addon segment. Falls back to `hub`.
|
|
50
|
-
*/
|
|
51
|
-
function normalizeEngineNodeId(rawNodeId) {
|
|
52
|
-
const raw = rawNodeId ?? "hub";
|
|
53
|
-
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
54
|
-
}
|
|
55
|
-
/** The node-scoped store key for an engine cascade field. */
|
|
56
|
-
function nodeEngineKey(base, nodeId) {
|
|
57
|
-
return `${base}@${normalizeEngineNodeId(nodeId)}`;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Read an engine cascade value for a node: the node-scoped key if present,
|
|
61
|
-
* otherwise — for the HUB only — the legacy un-scoped value (migration),
|
|
62
|
-
* otherwise undefined. The bare fallback is hub-restricted because only the
|
|
63
|
-
* hub ever wrote the pre-scoping store; serving it to other nodes leaked the
|
|
64
|
-
* hub's engine selection cluster-wide (CB3). A non-hub node with no scoped
|
|
65
|
-
* key gets `undefined` so its own platform-driven auto-pick runs and persists
|
|
66
|
-
* a real per-node selection.
|
|
67
|
-
*/
|
|
68
|
-
function readNodeEngineValue(store, base, nodeId) {
|
|
69
|
-
const scoped = store[nodeEngineKey(base, nodeId)];
|
|
70
|
-
if (scoped !== void 0) return scoped;
|
|
71
|
-
return normalizeEngineNodeId(nodeId) === "hub" ? store[base] : void 0;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Re-map a UI/settings patch so engine cascade fields persist under the
|
|
75
|
-
* node-scoped key; all other keys pass through unchanged. Used on the write
|
|
76
|
-
* path so a UI save on one node never clobbers another node's engine.
|
|
77
|
-
*/
|
|
78
|
-
function scopeEnginePatch(patch, nodeId) {
|
|
79
|
-
const out = {};
|
|
80
|
-
for (const [key, value] of Object.entries(patch)) out[isEngineCascadeKey(key) ? nodeEngineKey(key, nodeId) : key] = value;
|
|
81
|
-
return out;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Project a raw store onto the plain engine cascade keys for THIS node, so the
|
|
85
|
-
* UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
|
|
86
|
-
* the node's own selection. Non-engine keys are left untouched. Node-scoped
|
|
87
|
-
* keys for OTHER nodes are dropped from the projection (not relevant to this
|
|
88
|
-
* node's form).
|
|
89
|
-
*/
|
|
90
|
-
function projectNodeEngine(store, nodeId) {
|
|
91
|
-
const out = {};
|
|
92
|
-
const scopedForAnyNode = /* @__PURE__ */ new Set();
|
|
93
|
-
for (const key of Object.keys(store)) {
|
|
94
|
-
const atIdx = key.indexOf("@");
|
|
95
|
-
if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
|
|
96
|
-
scopedForAnyNode.add(key);
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
out[key] = store[key];
|
|
100
|
-
}
|
|
101
|
-
for (const base of ENGINE_CASCADE_KEYS) {
|
|
102
|
-
const value = readNodeEngineValue(store, base, nodeId);
|
|
103
|
-
if (value !== void 0) out[base] = value;
|
|
104
|
-
}
|
|
105
|
-
return out;
|
|
106
|
-
}
|
|
107
|
-
//#endregion
|
|
108
18
|
//#region src/detection-pipeline/runtimes.ts
|
|
109
19
|
var KNOWN_PLATFORMS = [
|
|
110
20
|
"darwin",
|
|
@@ -276,6 +186,88 @@ function pickBestRuntime(env, bestBackendHint) {
|
|
|
276
186
|
};
|
|
277
187
|
}
|
|
278
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
|
|
279
271
|
//#region src/detection-pipeline/engine/shared-inference-pool.ts
|
|
280
272
|
/**
|
|
281
273
|
* SharedInferencePool — TypeScript wrapper for inference_pool.py.
|
|
@@ -4348,6 +4340,15 @@ var ONNX_FLOOR = {
|
|
|
4348
4340
|
device: "cpu"
|
|
4349
4341
|
};
|
|
4350
4342
|
/**
|
|
4343
|
+
* How long the first-boot pick waits for the node's `platform-probe` to flip
|
|
4344
|
+
* `ready` (= its FULL async probe done, embedded-Python install included).
|
|
4345
|
+
* MUST stay finite — `readinessRegistry.awaitReady` defaults to Infinity,
|
|
4346
|
+
* which would wedge `setApi` (and the addon's onInitialize) forever on a
|
|
4347
|
+
* node whose probe never completes. On timeout the pick proceeds
|
|
4348
|
+
* conservatively (platform floor, never persisted when non-authoritative).
|
|
4349
|
+
*/
|
|
4350
|
+
var PROBE_READY_TIMEOUT_MS = 12e4;
|
|
4351
|
+
/**
|
|
4351
4352
|
* Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
|
|
4352
4353
|
* env. Used wherever the old `detectBestEngine()` sync probe fell back — the
|
|
4353
4354
|
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
@@ -4426,6 +4427,16 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4426
4427
|
*/
|
|
4427
4428
|
lastStepsSignature = null;
|
|
4428
4429
|
/**
|
|
4430
|
+
* CB5 shm passthrough — the process-local, LONG-LIVED reader cache for
|
|
4431
|
+
* `runPipeline({ frameHandle })`. Opens each named shm segment ONCE and
|
|
4432
|
+
* reuses the mapping for every later handle on the same ring (never
|
|
4433
|
+
* open/close per-call). Lazily created on first handle, unmapped in
|
|
4434
|
+
* {@link shutdown}. The executor and the runner are both hub-local
|
|
4435
|
+
* processes sharing `/dev/shm`, so a LOCAL handle resolves to a zero-copy
|
|
4436
|
+
* read here.
|
|
4437
|
+
*/
|
|
4438
|
+
frameHandleReaders = null;
|
|
4439
|
+
/**
|
|
4429
4440
|
* True once the engine + models are fully ready for inference. No
|
|
4430
4441
|
* longer gates runtime dispatch (Phase 4 removed the legacy `runFrame`
|
|
4431
4442
|
* which read this flag); kept as a diagnostic the admin UI / tests
|
|
@@ -4451,13 +4462,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4451
4462
|
*/
|
|
4452
4463
|
needsAutoPick = false;
|
|
4453
4464
|
/**
|
|
4454
|
-
* Unsubscribe handle for the deferred-auto-pick `platform-probe` ready
|
|
4455
|
-
* listener (armed in `setApi` when the probe isn't ready yet). Cleared once
|
|
4456
|
-
* the engine is resolved — either by the listener firing or by the boot
|
|
4457
|
-
* safety-net `ensureBootEngineProvisioned`.
|
|
4458
|
-
*/
|
|
4459
|
-
deferredAutoPickUnsub = null;
|
|
4460
|
-
/**
|
|
4461
4465
|
* Warm cache for benchmark engine-override runs.
|
|
4462
4466
|
*
|
|
4463
4467
|
* Each override rebuild costs a full Python pool spin-up (~300-500ms)
|
|
@@ -4558,108 +4562,71 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4558
4562
|
/** Store the addon context. ctx.api is a lazy getter resolved at call time. */
|
|
4559
4563
|
async setApi(addonCtx) {
|
|
4560
4564
|
this.addonCtx = addonCtx;
|
|
4561
|
-
if (this.needsAutoPick)
|
|
4562
|
-
await this.autoPickAndPersist();
|
|
4563
|
-
this.needsAutoPick = false;
|
|
4565
|
+
if (!this.needsAutoPick) {
|
|
4564
4566
|
this.startProvisioningForCurrentEngine();
|
|
4565
|
-
|
|
4566
|
-
const unsubscribe = this.addonCtx.onCapabilityStateChange("platform-probe", { type: "global" }, (state) => {
|
|
4567
|
-
if (state !== "ready") return;
|
|
4568
|
-
this.cancelDeferredAutoPick();
|
|
4569
|
-
if (!this.needsAutoPick) return;
|
|
4570
|
-
this.autoPickAndPersist().then(() => {
|
|
4571
|
-
this.needsAutoPick = false;
|
|
4572
|
-
this.startProvisioningForCurrentEngine();
|
|
4573
|
-
});
|
|
4574
|
-
});
|
|
4575
|
-
this.deferredAutoPickUnsub = unsubscribe;
|
|
4576
|
-
this.addonCtx.addDisposer(() => this.cancelDeferredAutoPick());
|
|
4567
|
+
return;
|
|
4577
4568
|
}
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4569
|
+
const nodeId = this.localProbeNodeId();
|
|
4570
|
+
const registry = this.addonCtx.kernel?.readinessRegistry;
|
|
4571
|
+
if (registry) try {
|
|
4572
|
+
await registry.awaitReady("platform-probe", {
|
|
4573
|
+
type: "node",
|
|
4574
|
+
nodeId
|
|
4575
|
+
}, { timeoutMs: PROBE_READY_TIMEOUT_MS });
|
|
4576
|
+
} catch (err) {
|
|
4577
|
+
this.log.warn("platform-probe readiness gate timed out — conservative engine pick (floor is not persisted)", { meta: {
|
|
4578
|
+
nodeId,
|
|
4579
|
+
timeoutMs: PROBE_READY_TIMEOUT_MS,
|
|
4580
|
+
error: require_dist.errMsg(err)
|
|
4581
|
+
} });
|
|
4585
4582
|
}
|
|
4586
|
-
|
|
4587
|
-
/**
|
|
4588
|
-
* Boot safety-net: deterministically provision the hardware-probed engine.
|
|
4589
|
-
*
|
|
4590
|
-
* Called from the addon's `onInitialize` right after `reprobeEngine` has
|
|
4591
|
-
* written this node's probe-driven selection (e.g. `engineBackend=openvino`)
|
|
4592
|
-
* to the store. When first-boot auto-pick was DEFERRED (probe not ready at
|
|
4593
|
-
* `setApi`), the engine would otherwise stay `idle` — selected but never
|
|
4594
|
-
* provisioned — because the `onCapabilityStateChange('platform-probe')` ready
|
|
4595
|
-
* edge can be missed when the cap is already 'ready' by the time we subscribe
|
|
4596
|
-
* (the old eager-onnx-floor masked this; removing it surfaced a node that
|
|
4597
|
-
* boots with no engine at all). This loads the now-persisted selection and
|
|
4598
|
-
* starts provisioning it, so the node reliably comes up on its real best
|
|
4599
|
-
* engine (openvino on Intel) with no onnx floor and no missed boot. No-op
|
|
4600
|
-
* when provisioning already started (persisted-engine path / listener fired)
|
|
4601
|
-
* or when nothing has been selected yet.
|
|
4602
|
-
*/
|
|
4603
|
-
async ensureBootEngineProvisioned() {
|
|
4604
|
-
if (this.getEngineProvisioning().state !== "idle") return;
|
|
4605
|
-
const stored = await this.loadEngine();
|
|
4606
|
-
if (!stored) return;
|
|
4607
|
-
this.currentEngine = stored;
|
|
4583
|
+
await this.resolveAutoEngine();
|
|
4608
4584
|
this.needsAutoPick = false;
|
|
4609
|
-
this.cancelDeferredAutoPick();
|
|
4610
|
-
this.log.info("Boot engine provisioning from probed selection", { meta: {
|
|
4611
|
-
runtime: stored.runtime,
|
|
4612
|
-
backend: stored.backend,
|
|
4613
|
-
device: stored.device ?? null
|
|
4614
|
-
} });
|
|
4615
4585
|
this.startProvisioningForCurrentEngine();
|
|
4616
4586
|
}
|
|
4617
4587
|
/**
|
|
4618
|
-
*
|
|
4619
|
-
*
|
|
4620
|
-
*
|
|
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.
|
|
4621
4593
|
*
|
|
4622
|
-
*
|
|
4623
|
-
*
|
|
4624
|
-
*
|
|
4625
|
-
*
|
|
4626
|
-
*
|
|
4627
|
-
*
|
|
4628
|
-
* accelerator surfaces. In that case we set the in-memory floor for liveness
|
|
4629
|
-
* 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.
|
|
4630
4600
|
*/
|
|
4631
|
-
async
|
|
4601
|
+
async resolveAutoEngine() {
|
|
4632
4602
|
let hardware = null;
|
|
4633
4603
|
try {
|
|
4634
|
-
|
|
4635
|
-
if (api) hardware = (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null;
|
|
4604
|
+
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
4636
4605
|
} catch {}
|
|
4637
|
-
const pick = pickBestRuntime(runtimeEnvFromProcess(toProbedHardware(hardware)), null);
|
|
4638
|
-
|
|
4606
|
+
const pick = pickBestRuntime(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null), null);
|
|
4607
|
+
this.currentEngine = {
|
|
4639
4608
|
runtime: "python",
|
|
4640
4609
|
backend: pick.runtimeId,
|
|
4641
4610
|
format: modelFormatFor(pick.runtimeId),
|
|
4642
4611
|
device: pick.device
|
|
4643
4612
|
};
|
|
4644
|
-
this.
|
|
4645
|
-
if (!(pick.runtimeId !== "onnx" || hardware !== null)) {
|
|
4646
|
-
this.log.info("Auto-pick: onnx floor pending gpu probe — NOT persisting (re-pick on done)", { meta: {
|
|
4647
|
-
backend: pick.runtimeId,
|
|
4648
|
-
device: pick.device
|
|
4649
|
-
} });
|
|
4650
|
-
return;
|
|
4651
|
-
}
|
|
4652
|
-
const apNode = this.localProbeNodeId();
|
|
4653
|
-
await this.writeStore({
|
|
4654
|
-
[nodeEngineKey("engineBackend", apNode)]: pick.runtimeId,
|
|
4655
|
-
[nodeEngineKey("engineDevice", apNode)]: pick.device
|
|
4656
|
-
});
|
|
4657
|
-
this.log.info("Auto-picked engine (platform-deterministic)", { meta: {
|
|
4613
|
+
this.log.info("Resolved auto engine (local kernel)", { meta: {
|
|
4658
4614
|
backend: pick.runtimeId,
|
|
4659
4615
|
device: pick.device,
|
|
4660
|
-
|
|
4616
|
+
hadHardware: hardware !== null
|
|
4661
4617
|
} });
|
|
4662
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
|
+
}
|
|
4663
4630
|
/** Map a backend string to a known RuntimeId, flooring to onnx. */
|
|
4664
4631
|
toRuntimeId(backend) {
|
|
4665
4632
|
return [
|
|
@@ -4733,6 +4700,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4733
4700
|
const prev = this.currentEngine;
|
|
4734
4701
|
const stored = await this.loadEngine();
|
|
4735
4702
|
if (stored) this.currentEngine = stored;
|
|
4703
|
+
else await this.resolveAutoEngine();
|
|
4736
4704
|
if ((prev.runtime !== this.currentEngine.runtime || prev.backend !== this.currentEngine.backend || prev.format !== this.currentEngine.format || (prev.device ?? "") !== (this.currentEngine.device ?? "")) && this.engineFactory) {
|
|
4737
4705
|
this.log.info("engine selection changed — rebuilding pool in place", { meta: {
|
|
4738
4706
|
from: `${prev.backend}/${prev.device ?? "default"}`,
|
|
@@ -4834,17 +4802,18 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4834
4802
|
}, { ...state }));
|
|
4835
4803
|
}
|
|
4836
4804
|
/**
|
|
4837
|
-
* Fetch
|
|
4838
|
-
*
|
|
4839
|
-
* callers fall back to the registry's safe minimum. The
|
|
4840
|
-
* from hardware ONLY — install state
|
|
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.
|
|
4841
4809
|
*/
|
|
4842
4810
|
/**
|
|
4843
|
-
* The local Moleculer node id (child-suffix stripped).
|
|
4844
|
-
*
|
|
4845
|
-
*
|
|
4846
|
-
*
|
|
4847
|
-
*
|
|
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.
|
|
4848
4817
|
*/
|
|
4849
4818
|
localProbeNodeId() {
|
|
4850
4819
|
const raw = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
@@ -4852,9 +4821,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4852
4821
|
}
|
|
4853
4822
|
async fetchProbeGatingData() {
|
|
4854
4823
|
try {
|
|
4855
|
-
|
|
4856
|
-
if (!api) return { hardware: null };
|
|
4857
|
-
return { hardware: (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null };
|
|
4824
|
+
return { hardware: await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null };
|
|
4858
4825
|
} catch {
|
|
4859
4826
|
return { hardware: null };
|
|
4860
4827
|
}
|
|
@@ -5194,6 +5161,53 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5194
5161
|
} });
|
|
5195
5162
|
return { success: true };
|
|
5196
5163
|
}
|
|
5164
|
+
/** This executor's cluster node id (suffix-stripped, matching the runner). */
|
|
5165
|
+
ownNodeId() {
|
|
5166
|
+
const raw = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
5167
|
+
return raw.includes("/") ? raw.split("/")[0] : raw;
|
|
5168
|
+
}
|
|
5169
|
+
/**
|
|
5170
|
+
* CB5 — resolve a shm `FrameHandle` to pixels zero-copy. Returns `null`
|
|
5171
|
+
* when:
|
|
5172
|
+
* - the handle is FOREIGN (its segment lives on another node — a local
|
|
5173
|
+
* `shm_open` would flood ENOENT; the runner only sends LOCAL handles, so
|
|
5174
|
+
* this is a defensive guard against an unexpected topology);
|
|
5175
|
+
* - the ring slot was recycled before the read (latest-wins drop) or the
|
|
5176
|
+
* segment could not be mapped.
|
|
5177
|
+
* A `null` degrades the caller to an empty `FrameResult` — the runner ships
|
|
5178
|
+
* pixels via `frame` as the fallback.
|
|
5179
|
+
*/
|
|
5180
|
+
resolveFrameHandle(handle) {
|
|
5181
|
+
if (handle.nodeId !== this.ownNodeId()) return null;
|
|
5182
|
+
if (!this.frameHandleReaders) this.frameHandleReaders = new _camstack_shm_ring.FrameRingReaderCache(this.log);
|
|
5183
|
+
const decoded = this.frameHandleReaders.read(handle);
|
|
5184
|
+
if (!decoded) return null;
|
|
5185
|
+
return {
|
|
5186
|
+
data: decoded.data,
|
|
5187
|
+
width: decoded.width,
|
|
5188
|
+
height: decoded.height,
|
|
5189
|
+
format: decoded.format,
|
|
5190
|
+
timestamp: decoded.timestamp
|
|
5191
|
+
};
|
|
5192
|
+
}
|
|
5193
|
+
/**
|
|
5194
|
+
* Minimal detection-free `FrameResult` returned when a `frameHandle` read
|
|
5195
|
+
* misses (recycled slot / foreign / unmappable). Shape-identical to a
|
|
5196
|
+
* zero-detection runtime result so downstream consumers treat it as a
|
|
5197
|
+
* dropped frame, not an error.
|
|
5198
|
+
*/
|
|
5199
|
+
emptyFrameHandleResult(input) {
|
|
5200
|
+
return {
|
|
5201
|
+
kind: "frame",
|
|
5202
|
+
frameId: `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
|
|
5203
|
+
deviceId: input.deviceId ?? 0,
|
|
5204
|
+
timestamp: Date.now(),
|
|
5205
|
+
width: input.frameHandle?.width ?? 0,
|
|
5206
|
+
height: input.frameHandle?.height ?? 0,
|
|
5207
|
+
detections: [],
|
|
5208
|
+
debug: { totalInferenceMs: 0 }
|
|
5209
|
+
};
|
|
5210
|
+
}
|
|
5197
5211
|
async runPipeline(input, onProgress) {
|
|
5198
5212
|
const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
5199
5213
|
const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -5229,15 +5243,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5229
5243
|
let jpegProvider;
|
|
5230
5244
|
const sources = [
|
|
5231
5245
|
input.frame ? "frame" : null,
|
|
5246
|
+
input.frameHandle ? "frameHandle" : null,
|
|
5232
5247
|
input.image ? "image" : null,
|
|
5233
5248
|
input.imageBase64 ? "imageBase64" : null,
|
|
5234
5249
|
input.referenceImage ? "referenceImage" : null
|
|
5235
5250
|
].filter((s) => s !== null);
|
|
5236
|
-
if (sources.length === 0) throw new Error("runPipeline requires exactly one of: frame, image, imageBase64, referenceImage");
|
|
5251
|
+
if (sources.length === 0) throw new Error("runPipeline requires exactly one of: frame, frameHandle, image, imageBase64, referenceImage");
|
|
5237
5252
|
if (sources.length > 1) throw new Error(`runPipeline received conflicting image sources: ${sources.join(", ")}`);
|
|
5253
|
+
let handleFrame = null;
|
|
5254
|
+
if (input.frameHandle) {
|
|
5255
|
+
handleFrame = this.resolveFrameHandle(input.frameHandle);
|
|
5256
|
+
if (!handleFrame) return this.emptyFrameHandleResult(input);
|
|
5257
|
+
}
|
|
5258
|
+
const runtimeFrame = input.frame ?? handleFrame ?? void 0;
|
|
5238
5259
|
const decodeT0 = performance.now();
|
|
5239
|
-
if (
|
|
5240
|
-
const frame =
|
|
5260
|
+
if (runtimeFrame) {
|
|
5261
|
+
const frame = runtimeFrame;
|
|
5241
5262
|
imageWidth = frame.width;
|
|
5242
5263
|
imageHeight = frame.height;
|
|
5243
5264
|
const data = Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength);
|
|
@@ -5431,7 +5452,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5431
5452
|
emit("Running inference...");
|
|
5432
5453
|
const tree = buildExecutableTree(benchmarkSteps, (stepId) => this.engineFactory.getEngine(stepId));
|
|
5433
5454
|
setupMs = performance.now() - wallT0 - decodeMs;
|
|
5434
|
-
const isRuntime = Boolean(
|
|
5455
|
+
const isRuntime = Boolean(runtimeFrame);
|
|
5435
5456
|
const effectiveDeviceId = input.deviceId ?? 0;
|
|
5436
5457
|
const deviceOverrides = effectiveDeviceId > 0 ? await this.readDeviceStore(effectiveDeviceId) : {};
|
|
5437
5458
|
const effectiveTree = Object.keys(deviceOverrides).length > 0 ? applyDeviceOverridesToTree(tree, "object-detection", deviceOverrides) : tree;
|
|
@@ -6002,6 +6023,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6002
6023
|
this.executor = null;
|
|
6003
6024
|
}
|
|
6004
6025
|
for (const id of Array.from(this.deviceProxies.keys())) this.releaseDeviceProxy(id);
|
|
6026
|
+
this.frameHandleReaders?.close();
|
|
6027
|
+
this.frameHandleReaders = null;
|
|
6005
6028
|
}
|
|
6006
6029
|
/**
|
|
6007
6030
|
* Resolve and cache a {@link DeviceProxy} for the given camera. Pins
|
|
@@ -6184,15 +6207,20 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6184
6207
|
* Phase 2b — resolve the engine from the addon's new schema-backed
|
|
6185
6208
|
* fields (`engineRuntime`, `engineBackend`, `engineDevice`). Fallback
|
|
6186
6209
|
* to the legacy `KEY_ENGINE` JSON blob for pre-migration stores.
|
|
6187
|
-
*
|
|
6188
|
-
*
|
|
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.
|
|
6189
6217
|
*/
|
|
6190
6218
|
async loadEngine() {
|
|
6191
6219
|
const store = await this.readStore();
|
|
6192
6220
|
const storedRuntime = store["engineRuntime"];
|
|
6193
6221
|
const node = this.localProbeNodeId();
|
|
6194
6222
|
const storedBackend = readNodeEngineValue(store, "engineBackend", node);
|
|
6195
|
-
if (typeof storedBackend === "string" && storedBackend.length > 0) {
|
|
6223
|
+
if (typeof storedBackend === "string" && storedBackend.length > 0 && storedBackend !== "auto") {
|
|
6196
6224
|
const backend = storedBackend;
|
|
6197
6225
|
const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
|
|
6198
6226
|
const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
|
|
@@ -6346,12 +6374,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6346
6374
|
return { success: true };
|
|
6347
6375
|
}
|
|
6348
6376
|
async reprobeEngine() {
|
|
6349
|
-
const api = this.addonCtx?.api;
|
|
6350
6377
|
let hardware = null;
|
|
6351
|
-
|
|
6352
|
-
hardware =
|
|
6378
|
+
try {
|
|
6379
|
+
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
6353
6380
|
} catch {}
|
|
6354
|
-
const pick = pickBestRuntime(runtimeEnvFromProcess(toProbedHardware(hardware)), null);
|
|
6381
|
+
const pick = pickBestRuntime(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null), null);
|
|
6355
6382
|
const best = {
|
|
6356
6383
|
runtime: "python",
|
|
6357
6384
|
backend: pick.runtimeId,
|
|
@@ -6361,47 +6388,18 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6361
6388
|
const probedLabel = `${best.backend}/${best.device ?? "default"}`;
|
|
6362
6389
|
const rpNode = this.localProbeNodeId();
|
|
6363
6390
|
if (pick.runtimeId !== "onnx" || hardware !== null) {
|
|
6364
|
-
await this.writeStore({
|
|
6365
|
-
|
|
6366
|
-
[nodeEngineKey("engineBackend", rpNode)]: best.backend,
|
|
6367
|
-
[nodeEngineKey("engineDevice", rpNode)]: best.device ?? "cpu"
|
|
6368
|
-
});
|
|
6369
|
-
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: {
|
|
6370
6393
|
backend: best.backend,
|
|
6371
6394
|
device: best.device ?? null,
|
|
6372
6395
|
probedBestEngine: probedLabel
|
|
6373
6396
|
} });
|
|
6374
|
-
} else this.log.info("Re-probe: onnx floor pending gpu probe — NOT
|
|
6397
|
+
} else this.log.info("Re-probe: onnx floor pending gpu probe — hint NOT persisted", { meta: {
|
|
6375
6398
|
backend: best.backend,
|
|
6376
6399
|
device: best.device ?? null
|
|
6377
6400
|
} });
|
|
6378
6401
|
return best;
|
|
6379
6402
|
}
|
|
6380
|
-
/**
|
|
6381
|
-
* Re-pick the engine when the platform-probe finishes its async hardware +
|
|
6382
|
-
* Python detection (the `platform-probe.phase` `done` event). At boot the
|
|
6383
|
-
* probe's accelerator result may not be ready yet, so the engine floored to
|
|
6384
|
-
* onnx; once the probe answers (e.g. a Mac's CoreML/ANE surfaces after the
|
|
6385
|
-
* embedded Python is installed) this re-runs the probe-driven pick and
|
|
6386
|
-
* re-provisions. Idempotent: `startProvisioningForCurrentEngine` skips a
|
|
6387
|
-
* no-op when the selection is unchanged.
|
|
6388
|
-
*/
|
|
6389
|
-
async repickEngineOnProbeReady() {
|
|
6390
|
-
const before = `${this.currentEngine.backend}/${this.currentEngine.device ?? "default"}`;
|
|
6391
|
-
await this.reprobeEngine();
|
|
6392
|
-
const stored = await this.loadEngine();
|
|
6393
|
-
if (stored) {
|
|
6394
|
-
this.currentEngine = stored;
|
|
6395
|
-
this.needsAutoPick = false;
|
|
6396
|
-
this.cancelDeferredAutoPick();
|
|
6397
|
-
}
|
|
6398
|
-
const after = `${this.currentEngine.backend}/${this.currentEngine.device ?? "default"}`;
|
|
6399
|
-
if (before !== after) this.log.info("Engine re-picked after platform-probe completed", { meta: {
|
|
6400
|
-
before,
|
|
6401
|
-
after
|
|
6402
|
-
} });
|
|
6403
|
-
this.startProvisioningForCurrentEngine();
|
|
6404
|
-
}
|
|
6405
6403
|
async getReferenceAudioFiles() {
|
|
6406
6404
|
const dir = resolveReferenceAudioDir();
|
|
6407
6405
|
if (!dir) return [];
|
|
@@ -6813,7 +6811,7 @@ var DEFAULT_CONFIG = {
|
|
|
6813
6811
|
intraOpThreads: 0,
|
|
6814
6812
|
numWorkers: 0,
|
|
6815
6813
|
engineRuntime: "python",
|
|
6816
|
-
engineBackend: "
|
|
6814
|
+
engineBackend: "auto",
|
|
6817
6815
|
engineDevice: "cpu",
|
|
6818
6816
|
probedBestEngine: "",
|
|
6819
6817
|
activeEngine: ""
|
|
@@ -6843,6 +6841,15 @@ function stableEngineKey(engines) {
|
|
|
6843
6841
|
return JSON.stringify(stripped);
|
|
6844
6842
|
}
|
|
6845
6843
|
/**
|
|
6844
|
+
* Narrow a topology-reported platform string to the runtime-relevant
|
|
6845
|
+
* `NodeJS.Platform` values the engine cascade branches on (darwin/linux/win32).
|
|
6846
|
+
* A type guard, not a cast; any other value → undefined so the caller floors to
|
|
6847
|
+
* `process.platform`. These three match `HardwarePlatformSchema` in the cap.
|
|
6848
|
+
*/
|
|
6849
|
+
function toNodePlatform(value) {
|
|
6850
|
+
return value === "darwin" || value === "linux" || value === "win32" ? value : void 0;
|
|
6851
|
+
}
|
|
6852
|
+
/**
|
|
6846
6853
|
* Tuning fields that bind into the Python pool at construction. Any
|
|
6847
6854
|
* change requires a pool respawn to take effect (the executor caches
|
|
6848
6855
|
* `tuning` at engine-factory build time). `onConfigChanged` watches
|
|
@@ -6857,7 +6864,7 @@ var POOL_BOUND_KEYS = [
|
|
|
6857
6864
|
"intraOpThreads",
|
|
6858
6865
|
"numWorkers"
|
|
6859
6866
|
];
|
|
6860
|
-
var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
6867
|
+
var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.BaseAddon {
|
|
6861
6868
|
provider = null;
|
|
6862
6869
|
/** Last non-null probed hardware PER NODE — reused when the probe transiently
|
|
6863
6870
|
* returns null so offered backends / device lists don't collapse. Keyed by
|
|
@@ -6865,13 +6872,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6865
6872
|
* Carries the probed node's platform/arch too, so a transient null probe
|
|
6866
6873
|
* doesn't flip a remote node's env back to the serving hub's platform. */
|
|
6867
6874
|
lastGoodHardwareByNode = /* @__PURE__ */ new Map();
|
|
6868
|
-
/** This node's effective engine selection, cached from the node-scoped store
|
|
6869
|
-
* so the synchronous `resolveBackendTuning` and the reprobe gate don't read
|
|
6870
|
-
* the cluster-shared bare keys. Refreshed at init + on every config change. */
|
|
6871
|
-
nodeEngineBackend = DEFAULT_CONFIG.engineBackend;
|
|
6872
|
-
nodeProbedBestEngine = "";
|
|
6873
6875
|
engineMetricsTimer = null;
|
|
6874
|
-
probePhaseUnsub = null;
|
|
6875
6876
|
/** Snapshot-equality cache for engine-metrics emit. Most ticks
|
|
6876
6877
|
* the engine inventory is unchanged (no model load/unload), so
|
|
6877
6878
|
* we skip the bus emit and let the heartbeat re-emit at
|
|
@@ -6919,6 +6920,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6919
6920
|
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.",
|
|
6920
6921
|
readonlyField: true,
|
|
6921
6922
|
default: "",
|
|
6923
|
+
perNode: true,
|
|
6922
6924
|
actions: [{
|
|
6923
6925
|
action: "reprobe-engine",
|
|
6924
6926
|
icon: "refresh-cw",
|
|
@@ -6937,9 +6939,13 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6937
6939
|
type: "select",
|
|
6938
6940
|
key: "engineBackend",
|
|
6939
6941
|
label: "Execution provider",
|
|
6940
|
-
options: [
|
|
6942
|
+
options: [{
|
|
6943
|
+
value: "auto",
|
|
6944
|
+
label: "Auto (best for this node)"
|
|
6945
|
+
}, ...STATIC_BACKEND_OPTIONS],
|
|
6941
6946
|
default: DEFAULT_CONFIG.engineBackend,
|
|
6942
|
-
immediate: true
|
|
6947
|
+
immediate: true,
|
|
6948
|
+
perNode: true
|
|
6943
6949
|
}),
|
|
6944
6950
|
this.field({
|
|
6945
6951
|
type: "select",
|
|
@@ -6947,7 +6953,8 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6947
6953
|
label: "Hardware device",
|
|
6948
6954
|
options: [...STATIC_DEFAULT_DEVICE_OPTIONS],
|
|
6949
6955
|
default: DEFAULT_CONFIG.engineDevice,
|
|
6950
|
-
immediate: true
|
|
6956
|
+
immediate: true,
|
|
6957
|
+
perNode: true
|
|
6951
6958
|
})
|
|
6952
6959
|
]
|
|
6953
6960
|
}, {
|
|
@@ -7076,12 +7083,17 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7076
7083
|
const env = await this.probeHardwareEnv(targetNode);
|
|
7077
7084
|
const hardware = env.hardware;
|
|
7078
7085
|
const offered = supportedRuntimes(env);
|
|
7079
|
-
const runtimeBackends =
|
|
7086
|
+
const runtimeBackends = [{
|
|
7087
|
+
value: "auto",
|
|
7088
|
+
label: "Auto (best for this node)"
|
|
7089
|
+
}, ...offered.map((id) => ({
|
|
7080
7090
|
value: id,
|
|
7081
7091
|
label: runtimeLabel(id)
|
|
7082
|
-
}));
|
|
7092
|
+
}))];
|
|
7083
7093
|
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
7094
|
+
const isAuto = storedBackend === "auto";
|
|
7084
7095
|
const backend = (() => {
|
|
7096
|
+
if (isAuto) return pickBestRuntime(env, null).runtimeId;
|
|
7085
7097
|
const rid = toRuntimeId(storedBackend);
|
|
7086
7098
|
if (rid === "onnx") return "onnx";
|
|
7087
7099
|
if (offered.includes(rid)) return rid;
|
|
@@ -7092,9 +7104,10 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7092
7104
|
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
|
|
7093
7105
|
const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
|
|
7094
7106
|
const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
|
|
7107
|
+
const displayBackend = isAuto ? "auto" : backend;
|
|
7095
7108
|
const raw = {
|
|
7096
7109
|
...merged,
|
|
7097
|
-
engineBackend:
|
|
7110
|
+
engineBackend: displayBackend,
|
|
7098
7111
|
engineDevice: device,
|
|
7099
7112
|
activeEngine
|
|
7100
7113
|
};
|
|
@@ -7170,14 +7183,37 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7170
7183
|
const hardware = await this.resolveProbeHardware(node);
|
|
7171
7184
|
if (hardware) this.lastGoodHardwareByNode.set(node, hardware);
|
|
7172
7185
|
const effective = hardware ?? this.lastGoodHardwareByNode.get(node) ?? null;
|
|
7173
|
-
const
|
|
7186
|
+
const isRemote = node !== this.localNodeId();
|
|
7187
|
+
const topo = isRemote ? await this.resolveTopologyPlatform(node) : null;
|
|
7174
7188
|
return {
|
|
7175
|
-
platform:
|
|
7176
|
-
arch:
|
|
7189
|
+
platform: (isRemote ? topo?.platform ?? effective?.platform : void 0) ?? process.platform,
|
|
7190
|
+
arch: (isRemote ? topo?.arch ?? effective?.arch : void 0) ?? process.arch,
|
|
7177
7191
|
hardware: effective
|
|
7178
7192
|
};
|
|
7179
7193
|
}
|
|
7180
7194
|
/**
|
|
7195
|
+
* Read the TARGET node's platform/arch from cluster topology — the
|
|
7196
|
+
* registration self-report (`nodes.topology`), which is hub-local and
|
|
7197
|
+
* available immediately, independent of the node's async accelerator probe.
|
|
7198
|
+
* Mirrors the audio analyzer's `resolveNodePlatform` (the D6 reference
|
|
7199
|
+
* pattern) so detection + audio share ONE topology-read path. Returns null
|
|
7200
|
+
* when topology is unreachable (caller falls back to the probe report, then
|
|
7201
|
+
* `process.platform`). Narrows the reported platform to the runtime-relevant
|
|
7202
|
+
* `NodeJS.Platform` values via a type guard (no cast); any other value is
|
|
7203
|
+
* dropped so the caller floors safely.
|
|
7204
|
+
*/
|
|
7205
|
+
async resolveTopologyPlatform(nodeId) {
|
|
7206
|
+
try {
|
|
7207
|
+
const picked = require_node_topology_platform.pickNodePlatformArch(await this.ctxIfReady?.api?.nodes?.topology.query(), nodeId);
|
|
7208
|
+
return {
|
|
7209
|
+
platform: toNodePlatform(picked.platform),
|
|
7210
|
+
arch: picked.arch
|
|
7211
|
+
};
|
|
7212
|
+
} catch {
|
|
7213
|
+
return null;
|
|
7214
|
+
}
|
|
7215
|
+
}
|
|
7216
|
+
/**
|
|
7181
7217
|
* Fetch the probed hardware from the platform-probe cap for `nodeId` (default
|
|
7182
7218
|
* = self). Returns null when the cap is not reachable (caller falls back to
|
|
7183
7219
|
* the registry's safe minimum). Carries the probed node's `platform`/`arch`
|
|
@@ -7189,7 +7225,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7189
7225
|
const api = this.ctxIfReady?.api;
|
|
7190
7226
|
if (!api) return null;
|
|
7191
7227
|
const node = nodeId ?? this.localNodeId();
|
|
7192
|
-
const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query(
|
|
7228
|
+
const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query({ nodeId: node }))?.hardware;
|
|
7193
7229
|
if (!hw) return null;
|
|
7194
7230
|
return {
|
|
7195
7231
|
npu: hw.npu ? { type: hw.npu.type } : null,
|
|
@@ -7214,81 +7250,6 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7214
7250
|
return normalizeEngineNodeId(this.ctxIfReady?.kernel?.localNodeId ?? "hub");
|
|
7215
7251
|
}
|
|
7216
7252
|
/**
|
|
7217
|
-
* One-time store migration (hub only, F1/CB3): the pre-scoping store
|
|
7218
|
-
* persisted the engine cascade under BARE keys (`engineBackend`, …) that
|
|
7219
|
-
* only the hub ever wrote. `readNodeEngineValue` used to serve those bare
|
|
7220
|
-
* values as a fallback to EVERY node, leaking the hub's engine (e.g.
|
|
7221
|
-
* `openvino/auto`) onto agents with no scoped key. The fallback is now
|
|
7222
|
-
* hub-only (engine-store-keys.ts) and this migration re-homes the bare
|
|
7223
|
-
* values to their `<key>@hub` scoped form and deletes the bare keys so the
|
|
7224
|
-
* legacy values cannot resurface. Guarded: runs only while a bare key is
|
|
7225
|
-
* still present, so post-migration boots are a no-op. Best-effort — an
|
|
7226
|
-
* unreadable store skips the migration (retried on the next boot).
|
|
7227
|
-
*/
|
|
7228
|
-
async migrateLegacyBareEngineKeys() {
|
|
7229
|
-
if (this.localNodeId() !== "hub") return;
|
|
7230
|
-
const store = await this.resolveUiSettingsStore();
|
|
7231
|
-
if (store === null) return;
|
|
7232
|
-
const patch = {};
|
|
7233
|
-
for (const base of ENGINE_CASCADE_KEYS) {
|
|
7234
|
-
const bare = store[base];
|
|
7235
|
-
if (bare === void 0 || bare === null) continue;
|
|
7236
|
-
const scopedKey = nodeEngineKey(base, "hub");
|
|
7237
|
-
if (store[scopedKey] === void 0) patch[scopedKey] = bare;
|
|
7238
|
-
patch[base] = void 0;
|
|
7239
|
-
}
|
|
7240
|
-
if (Object.keys(patch).length === 0) return;
|
|
7241
|
-
await this.ctxIfReady?.settings?.writeAddonStore(patch);
|
|
7242
|
-
this.ctx.logger.info("Migrated legacy bare engine keys to their @hub scoped form", { meta: { keys: Object.keys(patch) } });
|
|
7243
|
-
}
|
|
7244
|
-
/**
|
|
7245
|
-
* Refresh this node's cached engine selection from the node-scoped store.
|
|
7246
|
-
* `resolveBackendTuning` is synchronous and the reprobe gate runs before the
|
|
7247
|
-
* provider exists, so both read these cached fields instead of the
|
|
7248
|
-
* cluster-shared bare keys (which belong to no single node). Best-effort: a
|
|
7249
|
-
* transiently-unreadable store leaves the last cached values in place.
|
|
7250
|
-
*/
|
|
7251
|
-
async refreshNodeEngineFromStore() {
|
|
7252
|
-
const store = await this.resolveUiSettingsStore();
|
|
7253
|
-
if (store === null) return;
|
|
7254
|
-
const node = this.localNodeId();
|
|
7255
|
-
const backend = readNodeEngineValue(store, "engineBackend", node);
|
|
7256
|
-
if (typeof backend === "string" && backend !== "") this.nodeEngineBackend = backend;
|
|
7257
|
-
const probed = readNodeEngineValue(store, "probedBestEngine", node);
|
|
7258
|
-
this.nodeProbedBestEngine = typeof probed === "string" ? probed : "";
|
|
7259
|
-
}
|
|
7260
|
-
/**
|
|
7261
|
-
* Persist a settings patch, mirroring the engine cascade fields to the TARGET
|
|
7262
|
-
* node's scoped keys so each node keeps an INDEPENDENT engine selection in the
|
|
7263
|
-
* cluster-central store. The hub addon serves writes for every node, so it
|
|
7264
|
-
* scopes by the requested `nodeId` (default self).
|
|
7265
|
-
*
|
|
7266
|
-
* The engine cascade keys are NEVER written bare (F1, CB3): the bare keys are
|
|
7267
|
-
* the legacy pre-scoping store whose values used to leak to every node with
|
|
7268
|
-
* no scoped key. When the target IS this node, `super.updateGlobalSettings`
|
|
7269
|
-
* drives the normal apply path (`resolveConfig` / `onConfigChanged` /
|
|
7270
|
-
* `requiresRestart` restart) with the engine keys STRIPPED from its patch —
|
|
7271
|
-
* they were already persisted node-scoped, and `onConfigChanged` re-reads
|
|
7272
|
-
* them from the scoped keys. When the target is a SIBLING node, one scoped
|
|
7273
|
-
* write persists the engine keys under `<key>@<target>` + the non-engine
|
|
7274
|
-
* keys bare, and this node's restart/reprovision is NOT run — the owning
|
|
7275
|
-
* node applies its own engine selection on its next (re)start.
|
|
7276
|
-
*/
|
|
7277
|
-
async updateGlobalSettings(patch, nodeId) {
|
|
7278
|
-
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
7279
|
-
const patchRecord = patch;
|
|
7280
|
-
if (targetNode !== this.localNodeId()) {
|
|
7281
|
-
const scoped = scopeEnginePatch(patchRecord, targetNode);
|
|
7282
|
-
if (Object.keys(scoped).length > 0) await this.ctxIfReady?.settings?.writeAddonStore(scoped);
|
|
7283
|
-
return;
|
|
7284
|
-
}
|
|
7285
|
-
const scopedEngine = {};
|
|
7286
|
-
for (const key of ENGINE_CASCADE_KEYS) if (key in patchRecord) scopedEngine[nodeEngineKey(key, targetNode)] = patchRecord[key];
|
|
7287
|
-
if (Object.keys(scopedEngine).length > 0) await this.ctxIfReady?.settings?.writeAddonStore(scopedEngine);
|
|
7288
|
-
const { engineBackend: _engineBackend, engineDevice: _engineDevice, probedBestEngine: _probedBestEngine, ...sharedPatch } = patch;
|
|
7289
|
-
await super.updateGlobalSettings(sharedPatch, nodeId);
|
|
7290
|
-
}
|
|
7291
|
-
/**
|
|
7292
7253
|
* Resolve the effective pool tuning for the configured backend.
|
|
7293
7254
|
*
|
|
7294
7255
|
* Reads the registry's `tuningFor(backend)` and ignores any persisted
|
|
@@ -7302,7 +7263,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7302
7263
|
* a reason to disagree.
|
|
7303
7264
|
*/
|
|
7304
7265
|
resolveBackendTuning() {
|
|
7305
|
-
const t = tuningFor(
|
|
7266
|
+
const t = tuningFor(this.config.engineBackend === "auto" ? this.provider?.resolvedEngineBackend() ?? "onnx" : toRuntimeId(this.config.engineBackend));
|
|
7306
7267
|
const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
|
|
7307
7268
|
const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
|
|
7308
7269
|
return {
|
|
@@ -7315,44 +7276,59 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7315
7276
|
numWorkers: num(t["numWorkers"], 1)
|
|
7316
7277
|
};
|
|
7317
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
|
+
}
|
|
7318
7307
|
async onInitialize() {
|
|
7319
7308
|
const modelsDir = await this.resolveModelsDir();
|
|
7320
7309
|
if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
|
|
7321
|
-
await this.migrateLegacyBareEngineKeys();
|
|
7322
|
-
await this.refreshNodeEngineFromStore();
|
|
7323
7310
|
this.pythonAddonDir = resolveAddonPythonDir();
|
|
7324
7311
|
const py = await ensurePythonReady(this.ctx.deps, this.ctx.logger);
|
|
7325
7312
|
if (py.ok && py.pythonPath) this.pythonPath = py.pythonPath;
|
|
7326
7313
|
else this.ctx.logger.warn("Detection engine boot continues without Python — selection will provision on demand", {});
|
|
7327
7314
|
await this.proactivelyInstallOpenvino();
|
|
7328
|
-
const
|
|
7329
|
-
this.provider =
|
|
7330
|
-
concurrency: effectiveTuning.concurrency,
|
|
7331
|
-
tuning: {
|
|
7332
|
-
batchMode: effectiveTuning.batchMode,
|
|
7333
|
-
windowMs: effectiveTuning.windowMs,
|
|
7334
|
-
maxBatchSize: effectiveTuning.maxBatchSize,
|
|
7335
|
-
numStreams: effectiveTuning.numStreams,
|
|
7336
|
-
intraOpThreads: effectiveTuning.intraOpThreads
|
|
7337
|
-
},
|
|
7338
|
-
numWorkers: effectiveTuning.numWorkers,
|
|
7339
|
-
pythonPath: this.pythonPath,
|
|
7340
|
-
pythonAddonDir: this.pythonAddonDir
|
|
7341
|
-
});
|
|
7315
|
+
const initialTuning = this.resolveBackendTuning();
|
|
7316
|
+
this.provider = this.buildProvider(modelsDir, initialTuning);
|
|
7342
7317
|
await this.provider.init();
|
|
7343
7318
|
await this.provider.setApi(this.ctx);
|
|
7344
|
-
|
|
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
|
+
}
|
|
7329
|
+
if (!this.config.probedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
7345
7330
|
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7346
7331
|
});
|
|
7347
|
-
await this.provider.ensureBootEngineProvisioned().catch((err) => {
|
|
7348
|
-
this.ctx.logger.warn("ensureBootEngineProvisioned failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7349
|
-
});
|
|
7350
|
-
this.probePhaseUnsub = this.ctx.eventBus?.subscribe({ category: require_dist.EventCategory.PlatformProbePhase }, (event) => {
|
|
7351
|
-
if (event.data?.phase !== "done") return;
|
|
7352
|
-
this.provider?.repickEngineOnProbeReady().catch((err) => {
|
|
7353
|
-
this.ctx.logger.warn("repick on platform-probe done failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7354
|
-
});
|
|
7355
|
-
}) ?? null;
|
|
7356
7332
|
await this.provider.warmPool();
|
|
7357
7333
|
this.engineMetricsTimer = setInterval(() => this.emitEngineMetricsSnapshot(), ENGINE_METRICS_SNAPSHOT_INTERVAL_MS);
|
|
7358
7334
|
this.lastAppliedPoolConfig = this.snapshotPoolConfig();
|
|
@@ -7406,13 +7382,18 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7406
7382
|
* first loaded (gated by `loadEngine`'s `isPythonBackendAvailable` check).
|
|
7407
7383
|
*
|
|
7408
7384
|
* Failure is non-fatal: a warning is logged and the addon continues with
|
|
7409
|
-
* the onnx-cpu baseline. The hardware
|
|
7410
|
-
* if the
|
|
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.
|
|
7411
7387
|
*/
|
|
7412
7388
|
async proactivelyInstallOpenvino() {
|
|
7413
7389
|
if (!this.pythonAddonDir || !this.pythonPath) return;
|
|
7414
7390
|
try {
|
|
7415
|
-
const
|
|
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
|
+
};
|
|
7416
7397
|
if (!shouldInstallOpenvino(hardware)) return;
|
|
7417
7398
|
this.ctx.logger.info("Intel hardware detected — proactively installing OpenVINO Python package", { meta: {
|
|
7418
7399
|
gpu: hardware.gpu?.type ?? null,
|
|
@@ -7426,10 +7407,6 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7426
7407
|
}
|
|
7427
7408
|
}
|
|
7428
7409
|
async onShutdown() {
|
|
7429
|
-
if (this.probePhaseUnsub) {
|
|
7430
|
-
this.probePhaseUnsub();
|
|
7431
|
-
this.probePhaseUnsub = null;
|
|
7432
|
-
}
|
|
7433
7410
|
if (this.engineMetricsTimer) {
|
|
7434
7411
|
clearInterval(this.engineMetricsTimer);
|
|
7435
7412
|
this.engineMetricsTimer = null;
|
|
@@ -7477,7 +7454,6 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7477
7454
|
* re-spin, frames are dropped (never crashed), no addon bounce.
|
|
7478
7455
|
*/
|
|
7479
7456
|
async onConfigChanged() {
|
|
7480
|
-
await this.refreshNodeEngineFromStore();
|
|
7481
7457
|
if (this.provider) await this.provider.onEngineSelectionChanged().catch((err) => {
|
|
7482
7458
|
this.ctx.logger.warn("engine provisioning re-select failed on config change", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7483
7459
|
});
|
|
@@ -7496,21 +7472,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7496
7472
|
this.ctx.logger.warn("provider shutdown failed during tuning respawn", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7497
7473
|
}
|
|
7498
7474
|
const modelsDir = await this.resolveModelsDir();
|
|
7499
|
-
|
|
7500
|
-
const effectiveTuning = this.resolveBackendTuning();
|
|
7501
|
-
this.provider = new DetectionPipelineProvider(this.ctx.settings, modelsDir, this.ctx.logger, this.ctx.eventBus ?? null, () => ({ sections: [] }), {
|
|
7502
|
-
concurrency: effectiveTuning.concurrency,
|
|
7503
|
-
tuning: {
|
|
7504
|
-
batchMode: effectiveTuning.batchMode,
|
|
7505
|
-
windowMs: effectiveTuning.windowMs,
|
|
7506
|
-
maxBatchSize: effectiveTuning.maxBatchSize,
|
|
7507
|
-
numStreams: effectiveTuning.numStreams,
|
|
7508
|
-
intraOpThreads: effectiveTuning.intraOpThreads
|
|
7509
|
-
},
|
|
7510
|
-
numWorkers: effectiveTuning.numWorkers,
|
|
7511
|
-
pythonPath: this.pythonPath,
|
|
7512
|
-
pythonAddonDir: this.pythonAddonDir
|
|
7513
|
-
});
|
|
7475
|
+
this.provider = this.buildProvider(modelsDir, this.resolveBackendTuning());
|
|
7514
7476
|
await this.provider.init();
|
|
7515
7477
|
await this.provider.setApi(this.ctx);
|
|
7516
7478
|
await this.provider.warmPool();
|