@camstack/addon-pipeline 1.1.28 → 1.1.30
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 +254 -186
- package/dist/detection-pipeline/index.mjs +254 -186
- 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.
|
|
@@ -335,6 +336,51 @@ var PoolHandle = class {
|
|
|
335
336
|
}
|
|
336
337
|
async dispose() {}
|
|
337
338
|
};
|
|
339
|
+
/**
|
|
340
|
+
* How long to wait for a worker to exit on SIGTERM before escalating to
|
|
341
|
+
* SIGKILL. A worker idle between frames exits well under this; only a worker
|
|
342
|
+
* stuck in a native inference call (openvino/onnx C++) needs the escalation.
|
|
343
|
+
*/
|
|
344
|
+
var POOL_WORKER_TERM_GRACE_MS = 2e3;
|
|
345
|
+
/**
|
|
346
|
+
* Terminate a spawned child GRACEFULLY THEN FORCEFULLY: end stdin, send
|
|
347
|
+
* SIGTERM, and if the process hasn't exited within `graceMs`, send SIGKILL.
|
|
348
|
+
* Resolves once the process has exited (or was already dead).
|
|
349
|
+
*
|
|
350
|
+
* The old teardown sent SIGTERM and dropped the reference immediately — a
|
|
351
|
+
* worker busy in a native inference loop ignores SIGTERM and ORPHANS at full
|
|
352
|
+
* CPU (observed after an engine re-spin). SIGKILL cannot be ignored, so the
|
|
353
|
+
* escalation guarantees the process dies. Exported for tests.
|
|
354
|
+
*/
|
|
355
|
+
async function terminateChild(proc, graceMs) {
|
|
356
|
+
if (proc.exitCode !== null || proc.signalCode !== null) return;
|
|
357
|
+
try {
|
|
358
|
+
proc.stdin?.end();
|
|
359
|
+
} catch {}
|
|
360
|
+
await new Promise((resolve) => {
|
|
361
|
+
let settled = false;
|
|
362
|
+
let timer = null;
|
|
363
|
+
const done = () => {
|
|
364
|
+
if (settled) return;
|
|
365
|
+
settled = true;
|
|
366
|
+
if (timer) clearTimeout(timer);
|
|
367
|
+
resolve();
|
|
368
|
+
};
|
|
369
|
+
proc.once("exit", done);
|
|
370
|
+
try {
|
|
371
|
+
proc.kill("SIGTERM");
|
|
372
|
+
} catch {
|
|
373
|
+
done();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
timer = setTimeout(() => {
|
|
377
|
+
try {
|
|
378
|
+
proc.kill("SIGKILL");
|
|
379
|
+
} catch {}
|
|
380
|
+
done();
|
|
381
|
+
}, graceMs);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
338
384
|
var PoolWorker = class {
|
|
339
385
|
process = null;
|
|
340
386
|
receiveBuffer = Buffer.alloc(0);
|
|
@@ -490,12 +536,11 @@ var PoolWorker = class {
|
|
|
490
536
|
return await this.dispatch(MSG_COMMAND, payload);
|
|
491
537
|
}
|
|
492
538
|
async dispose() {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
539
|
+
const proc = this.process;
|
|
540
|
+
if (!proc) return;
|
|
541
|
+
this.process = null;
|
|
542
|
+
this.ready = false;
|
|
543
|
+
await terminateChild(proc, POOL_WORKER_TERM_GRACE_MS);
|
|
499
544
|
}
|
|
500
545
|
dispatch(msgType, payload) {
|
|
501
546
|
const reqId = this.allocRequestId();
|
|
@@ -4579,56 +4624,53 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4579
4624
|
error: require_dist.errMsg(err)
|
|
4580
4625
|
} });
|
|
4581
4626
|
}
|
|
4582
|
-
await this.
|
|
4627
|
+
await this.resolveAutoEngine();
|
|
4583
4628
|
this.needsAutoPick = false;
|
|
4584
4629
|
this.startProvisioningForCurrentEngine();
|
|
4585
4630
|
}
|
|
4586
4631
|
/**
|
|
4587
|
-
*
|
|
4588
|
-
*
|
|
4589
|
-
*
|
|
4632
|
+
* Resolve the `'auto'` engine LIVE from this node's own kernel inference
|
|
4633
|
+
* resolver (`ctx.kernel.inferenceEngine`) — the runtime mirror of
|
|
4634
|
+
* `ctx.kernel.hwaccel`. Each node (hub + forked worker + remote agent)
|
|
4635
|
+
* resolves its OWN accelerators in-process, so a forked agent child never
|
|
4636
|
+
* adopts the hub's runtime from the `platform-probe` singleton.
|
|
4590
4637
|
*
|
|
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.
|
|
4638
|
+
* NEVER persists. `'auto'` is a live-resolved sentinel: the pick is recomputed
|
|
4639
|
+
* every boot from the local hardware, so it is inherently self-healing — the
|
|
4640
|
+
* old `autoPickAndPersist` sticky-onnx bug (a premature onnx pick persisted
|
|
4641
|
+
* during a boot race, then locked forever) cannot recur because the store is
|
|
4642
|
+
* never written here. Only an explicit operator override is persisted, and
|
|
4643
|
+
* that write happens through the settings UI, not this path.
|
|
4599
4644
|
*/
|
|
4600
|
-
async
|
|
4645
|
+
async resolveAutoEngine() {
|
|
4601
4646
|
let hardware = null;
|
|
4602
4647
|
try {
|
|
4603
|
-
|
|
4604
|
-
if (api) hardware = (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null;
|
|
4648
|
+
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
4605
4649
|
} catch {}
|
|
4606
|
-
const pick = pickBestRuntime(runtimeEnvFromProcess(toProbedHardware(hardware)), null);
|
|
4607
|
-
|
|
4650
|
+
const pick = pickBestRuntime(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null), null);
|
|
4651
|
+
this.currentEngine = {
|
|
4608
4652
|
runtime: "python",
|
|
4609
4653
|
backend: pick.runtimeId,
|
|
4610
4654
|
format: modelFormatFor(pick.runtimeId),
|
|
4611
4655
|
device: pick.device
|
|
4612
4656
|
};
|
|
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: {
|
|
4657
|
+
this.log.info("Resolved auto engine (local kernel)", { meta: {
|
|
4627
4658
|
backend: pick.runtimeId,
|
|
4628
4659
|
device: pick.device,
|
|
4629
|
-
|
|
4660
|
+
hadHardware: hardware !== null
|
|
4630
4661
|
} });
|
|
4631
4662
|
}
|
|
4663
|
+
/**
|
|
4664
|
+
* The RUNTIME that `'auto'` actually resolved to on this node (the live
|
|
4665
|
+
* `currentEngine.backend`), narrowed to a {@link RuntimeId}. The addon feeds
|
|
4666
|
+
* this into `resolveBackendTuning` when the stored config is `'auto'`, so the
|
|
4667
|
+
* pool tuning + respawn detection track the RESOLVED backend rather than the
|
|
4668
|
+
* `'auto'` sentinel (which would otherwise floor to onnx tuning).
|
|
4669
|
+
*/
|
|
4670
|
+
resolvedEngineBackend() {
|
|
4671
|
+
const backend = this.currentEngine.backend;
|
|
4672
|
+
return backend === "openvino" || backend === "coreml" ? backend : "onnx";
|
|
4673
|
+
}
|
|
4632
4674
|
/** Map a backend string to a known RuntimeId, flooring to onnx. */
|
|
4633
4675
|
toRuntimeId(backend) {
|
|
4634
4676
|
return [
|
|
@@ -4702,6 +4744,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4702
4744
|
const prev = this.currentEngine;
|
|
4703
4745
|
const stored = await this.loadEngine();
|
|
4704
4746
|
if (stored) this.currentEngine = stored;
|
|
4747
|
+
else await this.resolveAutoEngine();
|
|
4705
4748
|
if ((prev.runtime !== this.currentEngine.runtime || prev.backend !== this.currentEngine.backend || prev.format !== this.currentEngine.format || (prev.device ?? "") !== (this.currentEngine.device ?? "")) && this.engineFactory) {
|
|
4706
4749
|
this.log.info("engine selection changed — rebuilding pool in place", { meta: {
|
|
4707
4750
|
from: `${prev.backend}/${prev.device ?? "default"}`,
|
|
@@ -4803,17 +4846,18 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4803
4846
|
}, { ...state }));
|
|
4804
4847
|
}
|
|
4805
4848
|
/**
|
|
4806
|
-
* Fetch
|
|
4807
|
-
*
|
|
4808
|
-
* callers fall back to the registry's safe minimum. The
|
|
4809
|
-
* from hardware ONLY — install state
|
|
4849
|
+
* Fetch this node's inference HARDWARE (gpu/npu) for engine + device gating,
|
|
4850
|
+
* from the local kernel resolver. Returns null when the resolver is not wired
|
|
4851
|
+
* yet (cold-start), so callers fall back to the registry's safe minimum. The
|
|
4852
|
+
* engine OFFER derives from hardware ONLY — install state never gates it.
|
|
4810
4853
|
*/
|
|
4811
4854
|
/**
|
|
4812
|
-
* The local Moleculer node id (child-suffix stripped).
|
|
4813
|
-
*
|
|
4814
|
-
*
|
|
4815
|
-
*
|
|
4816
|
-
*
|
|
4855
|
+
* The local Moleculer node id (child-suffix stripped). Used to scope this
|
|
4856
|
+
* node's engine-cascade store keys (`<key>@<node>`). Hardware itself is now
|
|
4857
|
+
* resolved in-process via `ctx.kernel.inferenceEngine` (never the
|
|
4858
|
+
* `platform-probe` singleton, whose no-nodeId query resolved to the HUB and
|
|
4859
|
+
* pinned a device a remote agent couldn't run) — so this id is a store-scoping
|
|
4860
|
+
* concern, not a probe-routing one.
|
|
4817
4861
|
*/
|
|
4818
4862
|
localProbeNodeId() {
|
|
4819
4863
|
const raw = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
@@ -4821,9 +4865,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4821
4865
|
}
|
|
4822
4866
|
async fetchProbeGatingData() {
|
|
4823
4867
|
try {
|
|
4824
|
-
|
|
4825
|
-
if (!api) return { hardware: null };
|
|
4826
|
-
return { hardware: (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null };
|
|
4868
|
+
return { hardware: await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null };
|
|
4827
4869
|
} catch {
|
|
4828
4870
|
return { hardware: null };
|
|
4829
4871
|
}
|
|
@@ -6209,15 +6251,20 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6209
6251
|
* Phase 2b — resolve the engine from the addon's new schema-backed
|
|
6210
6252
|
* fields (`engineRuntime`, `engineBackend`, `engineDevice`). Fallback
|
|
6211
6253
|
* to the legacy `KEY_ENGINE` JSON blob for pre-migration stores.
|
|
6212
|
-
*
|
|
6213
|
-
*
|
|
6254
|
+
*
|
|
6255
|
+
* The `'auto'` sentinel is treated as NO override (returns null) so the
|
|
6256
|
+
* caller re-resolves LIVE via `resolveAutoEngine()` — `'auto'` is the
|
|
6257
|
+
* self-healing default, never a persisted concrete backend. An explicit
|
|
6258
|
+
* concrete backend (onnx/openvino/coreml) is honored as an operator override.
|
|
6259
|
+
* Returns null when neither source has a concrete selection; the caller keeps
|
|
6260
|
+
* the onnx floor set at construction until `resolveAutoEngine()` runs.
|
|
6214
6261
|
*/
|
|
6215
6262
|
async loadEngine() {
|
|
6216
6263
|
const store = await this.readStore();
|
|
6217
6264
|
const storedRuntime = store["engineRuntime"];
|
|
6218
6265
|
const node = this.localProbeNodeId();
|
|
6219
6266
|
const storedBackend = readNodeEngineValue(store, "engineBackend", node);
|
|
6220
|
-
if (typeof storedBackend === "string" && storedBackend.length > 0) {
|
|
6267
|
+
if (typeof storedBackend === "string" && storedBackend.length > 0 && storedBackend !== "auto") {
|
|
6221
6268
|
const backend = storedBackend;
|
|
6222
6269
|
const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
|
|
6223
6270
|
const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
|
|
@@ -6371,12 +6418,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6371
6418
|
return { success: true };
|
|
6372
6419
|
}
|
|
6373
6420
|
async reprobeEngine() {
|
|
6374
|
-
const api = this.addonCtx?.api;
|
|
6375
6421
|
let hardware = null;
|
|
6376
|
-
|
|
6377
|
-
hardware =
|
|
6422
|
+
try {
|
|
6423
|
+
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
6378
6424
|
} catch {}
|
|
6379
|
-
const pick = pickBestRuntime(runtimeEnvFromProcess(toProbedHardware(hardware)), null);
|
|
6425
|
+
const pick = pickBestRuntime(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null), null);
|
|
6380
6426
|
const best = {
|
|
6381
6427
|
runtime: "python",
|
|
6382
6428
|
backend: pick.runtimeId,
|
|
@@ -6386,17 +6432,13 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6386
6432
|
const probedLabel = `${best.backend}/${best.device ?? "default"}`;
|
|
6387
6433
|
const rpNode = this.localProbeNodeId();
|
|
6388
6434
|
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: {
|
|
6435
|
+
await this.writeStore({ [nodeEngineKey("probedBestEngine", rpNode)]: probedLabel });
|
|
6436
|
+
this.log.info("Re-probed engine (local kernel) — updated probedBestEngine hint", { meta: {
|
|
6395
6437
|
backend: best.backend,
|
|
6396
6438
|
device: best.device ?? null,
|
|
6397
6439
|
probedBestEngine: probedLabel
|
|
6398
6440
|
} });
|
|
6399
|
-
} else this.log.info("Re-probe: onnx floor pending gpu probe — NOT
|
|
6441
|
+
} else this.log.info("Re-probe: onnx floor pending gpu probe — hint NOT persisted", { meta: {
|
|
6400
6442
|
backend: best.backend,
|
|
6401
6443
|
device: best.device ?? null
|
|
6402
6444
|
} });
|
|
@@ -6813,7 +6855,7 @@ var DEFAULT_CONFIG = {
|
|
|
6813
6855
|
intraOpThreads: 0,
|
|
6814
6856
|
numWorkers: 0,
|
|
6815
6857
|
engineRuntime: "python",
|
|
6816
|
-
engineBackend: "
|
|
6858
|
+
engineBackend: "auto",
|
|
6817
6859
|
engineDevice: "cpu",
|
|
6818
6860
|
probedBestEngine: "",
|
|
6819
6861
|
activeEngine: ""
|
|
@@ -6866,7 +6908,7 @@ var POOL_BOUND_KEYS = [
|
|
|
6866
6908
|
"intraOpThreads",
|
|
6867
6909
|
"numWorkers"
|
|
6868
6910
|
];
|
|
6869
|
-
var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
6911
|
+
var DetectionPipelineAddon = class DetectionPipelineAddon extends require_dist.BaseAddon {
|
|
6870
6912
|
provider = null;
|
|
6871
6913
|
/** Last non-null probed hardware PER NODE — reused when the probe transiently
|
|
6872
6914
|
* returns null so offered backends / device lists don't collapse. Keyed by
|
|
@@ -6941,7 +6983,10 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6941
6983
|
type: "select",
|
|
6942
6984
|
key: "engineBackend",
|
|
6943
6985
|
label: "Execution provider",
|
|
6944
|
-
options: [
|
|
6986
|
+
options: [{
|
|
6987
|
+
value: "auto",
|
|
6988
|
+
label: "Auto (best for this node)"
|
|
6989
|
+
}, ...STATIC_BACKEND_OPTIONS],
|
|
6945
6990
|
default: DEFAULT_CONFIG.engineBackend,
|
|
6946
6991
|
immediate: true,
|
|
6947
6992
|
perNode: true
|
|
@@ -7082,12 +7127,17 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7082
7127
|
const env = await this.probeHardwareEnv(targetNode);
|
|
7083
7128
|
const hardware = env.hardware;
|
|
7084
7129
|
const offered = supportedRuntimes(env);
|
|
7085
|
-
const runtimeBackends =
|
|
7130
|
+
const runtimeBackends = [{
|
|
7131
|
+
value: "auto",
|
|
7132
|
+
label: "Auto (best for this node)"
|
|
7133
|
+
}, ...offered.map((id) => ({
|
|
7086
7134
|
value: id,
|
|
7087
7135
|
label: runtimeLabel(id)
|
|
7088
|
-
}));
|
|
7136
|
+
}))];
|
|
7089
7137
|
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
7138
|
+
const isAuto = storedBackend === "auto";
|
|
7090
7139
|
const backend = (() => {
|
|
7140
|
+
if (isAuto) return pickBestRuntime(env, null).runtimeId;
|
|
7091
7141
|
const rid = toRuntimeId(storedBackend);
|
|
7092
7142
|
if (rid === "onnx") return "onnx";
|
|
7093
7143
|
if (offered.includes(rid)) return rid;
|
|
@@ -7098,9 +7148,10 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7098
7148
|
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
|
|
7099
7149
|
const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
|
|
7100
7150
|
const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
|
|
7151
|
+
const displayBackend = isAuto ? "auto" : backend;
|
|
7101
7152
|
const raw = {
|
|
7102
7153
|
...merged,
|
|
7103
|
-
engineBackend:
|
|
7154
|
+
engineBackend: displayBackend,
|
|
7104
7155
|
engineDevice: device,
|
|
7105
7156
|
activeEngine
|
|
7106
7157
|
};
|
|
@@ -7218,7 +7269,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7218
7269
|
const api = this.ctxIfReady?.api;
|
|
7219
7270
|
if (!api) return null;
|
|
7220
7271
|
const node = nodeId ?? this.localNodeId();
|
|
7221
|
-
const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query(
|
|
7272
|
+
const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query({ nodeId: node }))?.hardware;
|
|
7222
7273
|
if (!hw) return null;
|
|
7223
7274
|
return {
|
|
7224
7275
|
npu: hw.npu ? { type: hw.npu.type } : null,
|
|
@@ -7256,7 +7307,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7256
7307
|
* a reason to disagree.
|
|
7257
7308
|
*/
|
|
7258
7309
|
resolveBackendTuning() {
|
|
7259
|
-
const t = tuningFor(toRuntimeId(this.config.engineBackend));
|
|
7310
|
+
const t = tuningFor(this.config.engineBackend === "auto" ? this.provider?.resolvedEngineBackend() ?? "onnx" : toRuntimeId(this.config.engineBackend));
|
|
7260
7311
|
const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
|
|
7261
7312
|
const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
|
|
7262
7313
|
return {
|
|
@@ -7269,6 +7320,34 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7269
7320
|
numWorkers: num(t["numWorkers"], 1)
|
|
7270
7321
|
};
|
|
7271
7322
|
}
|
|
7323
|
+
/**
|
|
7324
|
+
* Construct a fresh provider with the given effective tuning. Single source of
|
|
7325
|
+
* truth for the provider wiring — shared by `onInitialize`, the post-resolve
|
|
7326
|
+
* tuning reconciliation, and the `onConfigChanged` pool respawn.
|
|
7327
|
+
*/
|
|
7328
|
+
buildProvider(modelsDir, tuning) {
|
|
7329
|
+
if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
|
|
7330
|
+
return new DetectionPipelineProvider(this.ctx.settings, modelsDir, this.ctx.logger, this.ctx.eventBus ?? null, () => ({ sections: [] }), {
|
|
7331
|
+
concurrency: tuning.concurrency,
|
|
7332
|
+
tuning: {
|
|
7333
|
+
batchMode: tuning.batchMode,
|
|
7334
|
+
windowMs: tuning.windowMs,
|
|
7335
|
+
maxBatchSize: tuning.maxBatchSize,
|
|
7336
|
+
numStreams: tuning.numStreams,
|
|
7337
|
+
intraOpThreads: tuning.intraOpThreads
|
|
7338
|
+
},
|
|
7339
|
+
numWorkers: tuning.numWorkers,
|
|
7340
|
+
pythonPath: this.pythonPath,
|
|
7341
|
+
pythonAddonDir: this.pythonAddonDir
|
|
7342
|
+
});
|
|
7343
|
+
}
|
|
7344
|
+
/**
|
|
7345
|
+
* Pool-bound tuning keys of `a` and `b` are equal (used to decide whether a
|
|
7346
|
+
* post-resolve / config-change respawn is actually needed).
|
|
7347
|
+
*/
|
|
7348
|
+
static poolTuningEqual(a, b) {
|
|
7349
|
+
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;
|
|
7350
|
+
}
|
|
7272
7351
|
async onInitialize() {
|
|
7273
7352
|
const modelsDir = await this.resolveModelsDir();
|
|
7274
7353
|
if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
|
|
@@ -7277,22 +7356,20 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7277
7356
|
if (py.ok && py.pythonPath) this.pythonPath = py.pythonPath;
|
|
7278
7357
|
else this.ctx.logger.warn("Detection engine boot continues without Python — selection will provision on demand", {});
|
|
7279
7358
|
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
|
-
});
|
|
7359
|
+
const initialTuning = this.resolveBackendTuning();
|
|
7360
|
+
this.provider = this.buildProvider(modelsDir, initialTuning);
|
|
7294
7361
|
await this.provider.init();
|
|
7295
7362
|
await this.provider.setApi(this.ctx);
|
|
7363
|
+
const resolvedTuning = this.resolveBackendTuning();
|
|
7364
|
+
if (!DetectionPipelineAddon.poolTuningEqual(initialTuning, resolvedTuning)) {
|
|
7365
|
+
this.ctx.logger.info("detection-pipeline tuning reconciled to resolved engine", { meta: {
|
|
7366
|
+
from: initialTuning,
|
|
7367
|
+
to: resolvedTuning
|
|
7368
|
+
} });
|
|
7369
|
+
this.provider = this.buildProvider(modelsDir, resolvedTuning);
|
|
7370
|
+
await this.provider.init();
|
|
7371
|
+
await this.provider.setApi(this.ctx);
|
|
7372
|
+
}
|
|
7296
7373
|
if (!this.config.probedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
7297
7374
|
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7298
7375
|
});
|
|
@@ -7349,13 +7426,18 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7349
7426
|
* first loaded (gated by `loadEngine`'s `isPythonBackendAvailable` check).
|
|
7350
7427
|
*
|
|
7351
7428
|
* Failure is non-fatal: a warning is logged and the addon continues with
|
|
7352
|
-
* the onnx-cpu baseline. The hardware
|
|
7353
|
-
* if the
|
|
7429
|
+
* the onnx-cpu baseline. The hardware resolve itself is also best-effort —
|
|
7430
|
+
* if the kernel resolver isn't wired yet the error is caught here.
|
|
7354
7431
|
*/
|
|
7355
7432
|
async proactivelyInstallOpenvino() {
|
|
7356
7433
|
if (!this.pythonAddonDir || !this.pythonPath) return;
|
|
7357
7434
|
try {
|
|
7358
|
-
const
|
|
7435
|
+
const hw = await this.ctx.kernel?.inferenceEngine?.resolveHardware();
|
|
7436
|
+
const hardware = {
|
|
7437
|
+
platform: process.platform,
|
|
7438
|
+
gpu: hw?.gpu ?? null,
|
|
7439
|
+
npu: hw?.npu ?? null
|
|
7440
|
+
};
|
|
7359
7441
|
if (!shouldInstallOpenvino(hardware)) return;
|
|
7360
7442
|
this.ctx.logger.info("Intel hardware detected — proactively installing OpenVINO Python package", { meta: {
|
|
7361
7443
|
gpu: hardware.gpu?.type ?? null,
|
|
@@ -7434,21 +7516,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
7434
7516
|
this.ctx.logger.warn("provider shutdown failed during tuning respawn", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7435
7517
|
}
|
|
7436
7518
|
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
|
-
});
|
|
7519
|
+
this.provider = this.buildProvider(modelsDir, this.resolveBackendTuning());
|
|
7452
7520
|
await this.provider.init();
|
|
7453
7521
|
await this.provider.setApi(this.ctx);
|
|
7454
7522
|
await this.provider.warmPool();
|