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