@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { $ as sleep, B as errMsg, E as hfModelUrl, F as runtimeDevices$1, G as EventCategory, K as createEvent, M as pipelineExecutorCapability, R as supportedRuntimes$1, S as defaultDeviceFor$1, T as evaluateZoneRules, V as BaseAddon, X as parseJsonUnknown, a as COCO_TO_MACRO, i as COCO_80_LABELS,
|
|
2
|
+
import { $ as sleep, B as errMsg, E as hfModelUrl, F as runtimeDevices$1, G as EventCategory, K as createEvent, M as pipelineExecutorCapability, R as supportedRuntimes$1, S as defaultDeviceFor$1, T as evaluateZoneRules, V as BaseAddon, X as parseJsonUnknown, a as COCO_TO_MACRO, i as COCO_80_LABELS, m as YAMNET_TO_MACRO, q as hydrateSchema, r as AUDIO_MACRO_LABELS, t as APPLE_SA_TO_MACRO, w as detectionPipelineCapability } from "../dist-DjuGmyG9.mjs";
|
|
3
3
|
import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
|
|
4
4
|
import { a as isModelDownloaded, i as ensureModel, n as deleteModelFromDisk } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
|
|
5
5
|
import { FrameRingReaderCache } from "@camstack/shm-ring";
|
|
@@ -8,87 +8,6 @@ import * as path$1 from "node:path";
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
10
|
import sharp from "sharp";
|
|
11
|
-
//#region src/detection-pipeline/engine-store-keys.ts
|
|
12
|
-
/**
|
|
13
|
-
* Per-node scoping for the detection-pipeline engine cascade.
|
|
14
|
-
*
|
|
15
|
-
* The detection addon's settings store is a single CLUSTER-SHARED blob (the
|
|
16
|
-
* settings-store cap is hub-resident; every node's detection instance reads and
|
|
17
|
-
* writes the same keys). That is correct for node-agnostic settings (pipeline
|
|
18
|
-
* steps, tuning) but WRONG for the engine cascade — `engineBackend` /
|
|
19
|
-
* `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
|
|
20
|
-
* and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
|
|
21
|
-
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
22
|
-
* another node that has no NPU.
|
|
23
|
-
*
|
|
24
|
-
* These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
|
|
25
|
-
* Everything else in the store stays shared. There is NO fallback to a legacy
|
|
26
|
-
* un-scoped value for ANY node (the hub included) and no in-code migration: a
|
|
27
|
-
* bare key (written before scoping, or by an older build) is inert — invisible
|
|
28
|
-
* to every node. A node with no scoped key reads `undefined`, which drives its
|
|
29
|
-
* OWN platform auto-pick (`autoPickAndPersist` / `reprobeEngine` persist the
|
|
30
|
-
* node's own probe under `<key>@<node>`). Serving a bare value to any node
|
|
31
|
-
* would leak one node's hardware pick (e.g. `openvino/auto`) onto a node that
|
|
32
|
-
* cannot run it (the CB3 "Mac runs OpenVINO" / N100 AUTO-device bug). This
|
|
33
|
-
* mirrors the decoder's `readNodeDecoderBackend` no-fallback rule.
|
|
34
|
-
*/
|
|
35
|
-
var ENGINE_CASCADE_KEYS = [
|
|
36
|
-
"engineBackend",
|
|
37
|
-
"engineDevice",
|
|
38
|
-
"probedBestEngine"
|
|
39
|
-
];
|
|
40
|
-
function isEngineCascadeKey(key) {
|
|
41
|
-
return ENGINE_CASCADE_KEYS.includes(key);
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Normalise a raw kernel node id to the bare node id used for scoping.
|
|
45
|
-
* `localNodeId` can carry a `<node>/<addon>` suffix; the engine selection is
|
|
46
|
-
* per-NODE, so strip the addon segment. Falls back to `hub`.
|
|
47
|
-
*/
|
|
48
|
-
function normalizeEngineNodeId(rawNodeId) {
|
|
49
|
-
const raw = rawNodeId ?? "hub";
|
|
50
|
-
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
51
|
-
}
|
|
52
|
-
/** The node-scoped store key for an engine cascade field. */
|
|
53
|
-
function nodeEngineKey(base, nodeId) {
|
|
54
|
-
return `${base}@${normalizeEngineNodeId(nodeId)}`;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Read an engine cascade value for a node: the node-scoped key if present,
|
|
58
|
-
* otherwise `undefined` — for EVERY node, the hub included. There is no
|
|
59
|
-
* fallback to the legacy un-scoped key: a bare value in the store is inert.
|
|
60
|
-
* A node with no scoped key gets `undefined` so its own platform-driven
|
|
61
|
-
* auto-pick runs and persists a real per-node selection under `<key>@<node>`.
|
|
62
|
-
* (A bare fallback would leak one node's engine selection cluster-wide — CB3.)
|
|
63
|
-
*/
|
|
64
|
-
function readNodeEngineValue(store, base, nodeId) {
|
|
65
|
-
return store[nodeEngineKey(base, nodeId)];
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Project a raw store onto the plain engine cascade keys for THIS node, so the
|
|
69
|
-
* UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
|
|
70
|
-
* the node's own selection. Non-engine keys are left untouched. Node-scoped
|
|
71
|
-
* keys for OTHER nodes are dropped from the projection (not relevant to this
|
|
72
|
-
* node's form).
|
|
73
|
-
*/
|
|
74
|
-
function projectNodeEngine(store, nodeId) {
|
|
75
|
-
const out = {};
|
|
76
|
-
const scopedForAnyNode = /* @__PURE__ */ new Set();
|
|
77
|
-
for (const key of Object.keys(store)) {
|
|
78
|
-
const atIdx = key.indexOf("@");
|
|
79
|
-
if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
|
|
80
|
-
scopedForAnyNode.add(key);
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
out[key] = store[key];
|
|
84
|
-
}
|
|
85
|
-
for (const base of ENGINE_CASCADE_KEYS) {
|
|
86
|
-
const value = readNodeEngineValue(store, base, nodeId);
|
|
87
|
-
if (value !== void 0) out[base] = value;
|
|
88
|
-
}
|
|
89
|
-
return out;
|
|
90
|
-
}
|
|
91
|
-
//#endregion
|
|
92
11
|
//#region src/detection-pipeline/runtimes.ts
|
|
93
12
|
var KNOWN_PLATFORMS = [
|
|
94
13
|
"darwin",
|
|
@@ -260,6 +179,88 @@ function pickBestRuntime(env, bestBackendHint) {
|
|
|
260
179
|
};
|
|
261
180
|
}
|
|
262
181
|
//#endregion
|
|
182
|
+
//#region src/detection-pipeline/engine-store-keys.ts
|
|
183
|
+
/**
|
|
184
|
+
* Per-node scoping for the detection-pipeline engine cascade.
|
|
185
|
+
*
|
|
186
|
+
* The detection addon's settings store is a single CLUSTER-SHARED blob (the
|
|
187
|
+
* settings-store cap is hub-resident; every node's detection instance reads and
|
|
188
|
+
* writes the same keys). That is correct for node-agnostic settings (pipeline
|
|
189
|
+
* steps, tuning) but WRONG for the engine cascade — `engineBackend` /
|
|
190
|
+
* `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
|
|
191
|
+
* and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
|
|
192
|
+
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
193
|
+
* another node that has no NPU.
|
|
194
|
+
*
|
|
195
|
+
* These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
|
|
196
|
+
* Everything else in the store stays shared. There is NO fallback to a legacy
|
|
197
|
+
* un-scoped value for ANY node (the hub included) and no in-code migration: a
|
|
198
|
+
* bare key (written before scoping, or by an older build) is inert — invisible
|
|
199
|
+
* to every node. A node with no scoped key reads `undefined`, which drives its
|
|
200
|
+
* OWN live resolve (`resolveAutoEngine` picks the node's runtime from the local
|
|
201
|
+
* kernel each boot; `reprobeEngine` persists an explicit probe under
|
|
202
|
+
* `<key>@<node>`). Serving a bare value to any node
|
|
203
|
+
* would leak one node's hardware pick (e.g. `openvino/auto`) onto a node that
|
|
204
|
+
* cannot run it (the CB3 "Mac runs OpenVINO" / N100 AUTO-device bug). This
|
|
205
|
+
* mirrors the decoder's `readNodeDecoderBackend` no-fallback rule.
|
|
206
|
+
*/
|
|
207
|
+
var ENGINE_CASCADE_KEYS = [
|
|
208
|
+
"engineBackend",
|
|
209
|
+
"engineDevice",
|
|
210
|
+
"probedBestEngine"
|
|
211
|
+
];
|
|
212
|
+
function isEngineCascadeKey(key) {
|
|
213
|
+
return ENGINE_CASCADE_KEYS.includes(key);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Normalise a raw kernel node id to the bare node id used for scoping.
|
|
217
|
+
* `localNodeId` can carry a `<node>/<addon>` suffix; the engine selection is
|
|
218
|
+
* per-NODE, so strip the addon segment. Falls back to `hub`.
|
|
219
|
+
*/
|
|
220
|
+
function normalizeEngineNodeId(rawNodeId) {
|
|
221
|
+
const raw = rawNodeId ?? "hub";
|
|
222
|
+
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
223
|
+
}
|
|
224
|
+
/** The node-scoped store key for an engine cascade field. */
|
|
225
|
+
function nodeEngineKey(base, nodeId) {
|
|
226
|
+
return `${base}@${normalizeEngineNodeId(nodeId)}`;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Read an engine cascade value for a node: the node-scoped key if present,
|
|
230
|
+
* otherwise `undefined` — for EVERY node, the hub included. There is no
|
|
231
|
+
* fallback to the legacy un-scoped key: a bare value in the store is inert.
|
|
232
|
+
* A node with no scoped key gets `undefined` so its own platform-driven
|
|
233
|
+
* auto-pick runs and persists a real per-node selection under `<key>@<node>`.
|
|
234
|
+
* (A bare fallback would leak one node's engine selection cluster-wide — CB3.)
|
|
235
|
+
*/
|
|
236
|
+
function readNodeEngineValue(store, base, nodeId) {
|
|
237
|
+
return store[nodeEngineKey(base, nodeId)];
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Project a raw store onto the plain engine cascade keys for THIS node, so the
|
|
241
|
+
* UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
|
|
242
|
+
* the node's own selection. Non-engine keys are left untouched. Node-scoped
|
|
243
|
+
* keys for OTHER nodes are dropped from the projection (not relevant to this
|
|
244
|
+
* node's form).
|
|
245
|
+
*/
|
|
246
|
+
function projectNodeEngine(store, nodeId) {
|
|
247
|
+
const out = {};
|
|
248
|
+
const scopedForAnyNode = /* @__PURE__ */ new Set();
|
|
249
|
+
for (const key of Object.keys(store)) {
|
|
250
|
+
const atIdx = key.indexOf("@");
|
|
251
|
+
if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
|
|
252
|
+
scopedForAnyNode.add(key);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
out[key] = store[key];
|
|
256
|
+
}
|
|
257
|
+
for (const base of ENGINE_CASCADE_KEYS) {
|
|
258
|
+
const value = readNodeEngineValue(store, base, nodeId);
|
|
259
|
+
if (value !== void 0) out[base] = value;
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
//#endregion
|
|
263
264
|
//#region src/detection-pipeline/engine/shared-inference-pool.ts
|
|
264
265
|
/**
|
|
265
266
|
* SharedInferencePool — TypeScript wrapper for inference_pool.py.
|
|
@@ -328,6 +329,51 @@ var PoolHandle = class {
|
|
|
328
329
|
}
|
|
329
330
|
async dispose() {}
|
|
330
331
|
};
|
|
332
|
+
/**
|
|
333
|
+
* How long to wait for a worker to exit on SIGTERM before escalating to
|
|
334
|
+
* SIGKILL. A worker idle between frames exits well under this; only a worker
|
|
335
|
+
* stuck in a native inference call (openvino/onnx C++) needs the escalation.
|
|
336
|
+
*/
|
|
337
|
+
var POOL_WORKER_TERM_GRACE_MS = 2e3;
|
|
338
|
+
/**
|
|
339
|
+
* Terminate a spawned child GRACEFULLY THEN FORCEFULLY: end stdin, send
|
|
340
|
+
* SIGTERM, and if the process hasn't exited within `graceMs`, send SIGKILL.
|
|
341
|
+
* Resolves once the process has exited (or was already dead).
|
|
342
|
+
*
|
|
343
|
+
* The old teardown sent SIGTERM and dropped the reference immediately — a
|
|
344
|
+
* worker busy in a native inference loop ignores SIGTERM and ORPHANS at full
|
|
345
|
+
* CPU (observed after an engine re-spin). SIGKILL cannot be ignored, so the
|
|
346
|
+
* escalation guarantees the process dies. Exported for tests.
|
|
347
|
+
*/
|
|
348
|
+
async function terminateChild(proc, graceMs) {
|
|
349
|
+
if (proc.exitCode !== null || proc.signalCode !== null) return;
|
|
350
|
+
try {
|
|
351
|
+
proc.stdin?.end();
|
|
352
|
+
} catch {}
|
|
353
|
+
await new Promise((resolve) => {
|
|
354
|
+
let settled = false;
|
|
355
|
+
let timer = null;
|
|
356
|
+
const done = () => {
|
|
357
|
+
if (settled) return;
|
|
358
|
+
settled = true;
|
|
359
|
+
if (timer) clearTimeout(timer);
|
|
360
|
+
resolve();
|
|
361
|
+
};
|
|
362
|
+
proc.once("exit", done);
|
|
363
|
+
try {
|
|
364
|
+
proc.kill("SIGTERM");
|
|
365
|
+
} catch {
|
|
366
|
+
done();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
timer = setTimeout(() => {
|
|
370
|
+
try {
|
|
371
|
+
proc.kill("SIGKILL");
|
|
372
|
+
} catch {}
|
|
373
|
+
done();
|
|
374
|
+
}, graceMs);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
331
377
|
var PoolWorker = class {
|
|
332
378
|
process = null;
|
|
333
379
|
receiveBuffer = Buffer.alloc(0);
|
|
@@ -483,12 +529,11 @@ var PoolWorker = class {
|
|
|
483
529
|
return await this.dispatch(MSG_COMMAND, payload);
|
|
484
530
|
}
|
|
485
531
|
async dispose() {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
532
|
+
const proc = this.process;
|
|
533
|
+
if (!proc) return;
|
|
534
|
+
this.process = null;
|
|
535
|
+
this.ready = false;
|
|
536
|
+
await terminateChild(proc, POOL_WORKER_TERM_GRACE_MS);
|
|
492
537
|
}
|
|
493
538
|
dispatch(msgType, payload) {
|
|
494
539
|
const reqId = this.allocRequestId();
|
|
@@ -4572,56 +4617,53 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4572
4617
|
error: errMsg(err)
|
|
4573
4618
|
} });
|
|
4574
4619
|
}
|
|
4575
|
-
await this.
|
|
4620
|
+
await this.resolveAutoEngine();
|
|
4576
4621
|
this.needsAutoPick = false;
|
|
4577
4622
|
this.startProvisioningForCurrentEngine();
|
|
4578
4623
|
}
|
|
4579
4624
|
/**
|
|
4580
|
-
*
|
|
4581
|
-
*
|
|
4582
|
-
*
|
|
4625
|
+
* Resolve the `'auto'` engine LIVE from this node's own kernel inference
|
|
4626
|
+
* resolver (`ctx.kernel.inferenceEngine`) — the runtime mirror of
|
|
4627
|
+
* `ctx.kernel.hwaccel`. Each node (hub + forked worker + remote agent)
|
|
4628
|
+
* resolves its OWN accelerators in-process, so a forked agent child never
|
|
4629
|
+
* adopts the hub's runtime from the `platform-probe` singleton.
|
|
4583
4630
|
*
|
|
4584
|
-
*
|
|
4585
|
-
*
|
|
4586
|
-
*
|
|
4587
|
-
*
|
|
4588
|
-
*
|
|
4589
|
-
*
|
|
4590
|
-
* accelerator surfaces. In that case we set the in-memory floor for liveness
|
|
4591
|
-
* but leave the store untouched so the next boot re-attempts the pick.
|
|
4631
|
+
* NEVER persists. `'auto'` is a live-resolved sentinel: the pick is recomputed
|
|
4632
|
+
* every boot from the local hardware, so it is inherently self-healing — the
|
|
4633
|
+
* old `autoPickAndPersist` sticky-onnx bug (a premature onnx pick persisted
|
|
4634
|
+
* during a boot race, then locked forever) cannot recur because the store is
|
|
4635
|
+
* never written here. Only an explicit operator override is persisted, and
|
|
4636
|
+
* that write happens through the settings UI, not this path.
|
|
4592
4637
|
*/
|
|
4593
|
-
async
|
|
4638
|
+
async resolveAutoEngine() {
|
|
4594
4639
|
let hardware = null;
|
|
4595
4640
|
try {
|
|
4596
|
-
|
|
4597
|
-
if (api) hardware = (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null;
|
|
4641
|
+
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
4598
4642
|
} catch {}
|
|
4599
|
-
const pick = pickBestRuntime(runtimeEnvFromProcess(toProbedHardware(hardware)), null);
|
|
4600
|
-
|
|
4643
|
+
const pick = pickBestRuntime(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null), null);
|
|
4644
|
+
this.currentEngine = {
|
|
4601
4645
|
runtime: "python",
|
|
4602
4646
|
backend: pick.runtimeId,
|
|
4603
4647
|
format: modelFormatFor(pick.runtimeId),
|
|
4604
4648
|
device: pick.device
|
|
4605
4649
|
};
|
|
4606
|
-
this.
|
|
4607
|
-
if (!(pick.runtimeId !== "onnx" || hardware !== null)) {
|
|
4608
|
-
this.log.info("Auto-pick: onnx floor pending gpu probe — NOT persisting (next boot retries)", { meta: {
|
|
4609
|
-
backend: pick.runtimeId,
|
|
4610
|
-
device: pick.device
|
|
4611
|
-
} });
|
|
4612
|
-
return;
|
|
4613
|
-
}
|
|
4614
|
-
const apNode = this.localProbeNodeId();
|
|
4615
|
-
await this.writeStore({
|
|
4616
|
-
[nodeEngineKey("engineBackend", apNode)]: pick.runtimeId,
|
|
4617
|
-
[nodeEngineKey("engineDevice", apNode)]: pick.device
|
|
4618
|
-
});
|
|
4619
|
-
this.log.info("Auto-picked engine (platform-deterministic)", { meta: {
|
|
4650
|
+
this.log.info("Resolved auto engine (local kernel)", { meta: {
|
|
4620
4651
|
backend: pick.runtimeId,
|
|
4621
4652
|
device: pick.device,
|
|
4622
|
-
|
|
4653
|
+
hadHardware: hardware !== null
|
|
4623
4654
|
} });
|
|
4624
4655
|
}
|
|
4656
|
+
/**
|
|
4657
|
+
* The RUNTIME that `'auto'` actually resolved to on this node (the live
|
|
4658
|
+
* `currentEngine.backend`), narrowed to a {@link RuntimeId}. The addon feeds
|
|
4659
|
+
* this into `resolveBackendTuning` when the stored config is `'auto'`, so the
|
|
4660
|
+
* pool tuning + respawn detection track the RESOLVED backend rather than the
|
|
4661
|
+
* `'auto'` sentinel (which would otherwise floor to onnx tuning).
|
|
4662
|
+
*/
|
|
4663
|
+
resolvedEngineBackend() {
|
|
4664
|
+
const backend = this.currentEngine.backend;
|
|
4665
|
+
return backend === "openvino" || backend === "coreml" ? backend : "onnx";
|
|
4666
|
+
}
|
|
4625
4667
|
/** Map a backend string to a known RuntimeId, flooring to onnx. */
|
|
4626
4668
|
toRuntimeId(backend) {
|
|
4627
4669
|
return [
|
|
@@ -4695,6 +4737,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4695
4737
|
const prev = this.currentEngine;
|
|
4696
4738
|
const stored = await this.loadEngine();
|
|
4697
4739
|
if (stored) this.currentEngine = stored;
|
|
4740
|
+
else await this.resolveAutoEngine();
|
|
4698
4741
|
if ((prev.runtime !== this.currentEngine.runtime || prev.backend !== this.currentEngine.backend || prev.format !== this.currentEngine.format || (prev.device ?? "") !== (this.currentEngine.device ?? "")) && this.engineFactory) {
|
|
4699
4742
|
this.log.info("engine selection changed — rebuilding pool in place", { meta: {
|
|
4700
4743
|
from: `${prev.backend}/${prev.device ?? "default"}`,
|
|
@@ -4796,17 +4839,18 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4796
4839
|
}, { ...state }));
|
|
4797
4840
|
}
|
|
4798
4841
|
/**
|
|
4799
|
-
* Fetch
|
|
4800
|
-
*
|
|
4801
|
-
* callers fall back to the registry's safe minimum. The
|
|
4802
|
-
* from hardware ONLY — install state
|
|
4842
|
+
* Fetch this node's inference HARDWARE (gpu/npu) for engine + device gating,
|
|
4843
|
+
* from the local kernel resolver. Returns null when the resolver is not wired
|
|
4844
|
+
* yet (cold-start), so callers fall back to the registry's safe minimum. The
|
|
4845
|
+
* engine OFFER derives from hardware ONLY — install state never gates it.
|
|
4803
4846
|
*/
|
|
4804
4847
|
/**
|
|
4805
|
-
* The local Moleculer node id (child-suffix stripped).
|
|
4806
|
-
*
|
|
4807
|
-
*
|
|
4808
|
-
*
|
|
4809
|
-
*
|
|
4848
|
+
* The local Moleculer node id (child-suffix stripped). Used to scope this
|
|
4849
|
+
* node's engine-cascade store keys (`<key>@<node>`). Hardware itself is now
|
|
4850
|
+
* resolved in-process via `ctx.kernel.inferenceEngine` (never the
|
|
4851
|
+
* `platform-probe` singleton, whose no-nodeId query resolved to the HUB and
|
|
4852
|
+
* pinned a device a remote agent couldn't run) — so this id is a store-scoping
|
|
4853
|
+
* concern, not a probe-routing one.
|
|
4810
4854
|
*/
|
|
4811
4855
|
localProbeNodeId() {
|
|
4812
4856
|
const raw = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
@@ -4814,9 +4858,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4814
4858
|
}
|
|
4815
4859
|
async fetchProbeGatingData() {
|
|
4816
4860
|
try {
|
|
4817
|
-
|
|
4818
|
-
if (!api) return { hardware: null };
|
|
4819
|
-
return { hardware: (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null };
|
|
4861
|
+
return { hardware: await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null };
|
|
4820
4862
|
} catch {
|
|
4821
4863
|
return { hardware: null };
|
|
4822
4864
|
}
|
|
@@ -6202,15 +6244,20 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6202
6244
|
* Phase 2b — resolve the engine from the addon's new schema-backed
|
|
6203
6245
|
* fields (`engineRuntime`, `engineBackend`, `engineDevice`). Fallback
|
|
6204
6246
|
* to the legacy `KEY_ENGINE` JSON blob for pre-migration stores.
|
|
6205
|
-
*
|
|
6206
|
-
*
|
|
6247
|
+
*
|
|
6248
|
+
* The `'auto'` sentinel is treated as NO override (returns null) so the
|
|
6249
|
+
* caller re-resolves LIVE via `resolveAutoEngine()` — `'auto'` is the
|
|
6250
|
+
* self-healing default, never a persisted concrete backend. An explicit
|
|
6251
|
+
* concrete backend (onnx/openvino/coreml) is honored as an operator override.
|
|
6252
|
+
* Returns null when neither source has a concrete selection; the caller keeps
|
|
6253
|
+
* the onnx floor set at construction until `resolveAutoEngine()` runs.
|
|
6207
6254
|
*/
|
|
6208
6255
|
async loadEngine() {
|
|
6209
6256
|
const store = await this.readStore();
|
|
6210
6257
|
const storedRuntime = store["engineRuntime"];
|
|
6211
6258
|
const node = this.localProbeNodeId();
|
|
6212
6259
|
const storedBackend = readNodeEngineValue(store, "engineBackend", node);
|
|
6213
|
-
if (typeof storedBackend === "string" && storedBackend.length > 0) {
|
|
6260
|
+
if (typeof storedBackend === "string" && storedBackend.length > 0 && storedBackend !== "auto") {
|
|
6214
6261
|
const backend = storedBackend;
|
|
6215
6262
|
const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
|
|
6216
6263
|
const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
|
|
@@ -6364,12 +6411,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6364
6411
|
return { success: true };
|
|
6365
6412
|
}
|
|
6366
6413
|
async reprobeEngine() {
|
|
6367
|
-
const api = this.addonCtx?.api;
|
|
6368
6414
|
let hardware = null;
|
|
6369
|
-
|
|
6370
|
-
hardware =
|
|
6415
|
+
try {
|
|
6416
|
+
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
6371
6417
|
} catch {}
|
|
6372
|
-
const pick = pickBestRuntime(runtimeEnvFromProcess(toProbedHardware(hardware)), null);
|
|
6418
|
+
const pick = pickBestRuntime(runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null), null);
|
|
6373
6419
|
const best = {
|
|
6374
6420
|
runtime: "python",
|
|
6375
6421
|
backend: pick.runtimeId,
|
|
@@ -6379,17 +6425,13 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6379
6425
|
const probedLabel = `${best.backend}/${best.device ?? "default"}`;
|
|
6380
6426
|
const rpNode = this.localProbeNodeId();
|
|
6381
6427
|
if (pick.runtimeId !== "onnx" || hardware !== null) {
|
|
6382
|
-
await this.writeStore({
|
|
6383
|
-
|
|
6384
|
-
[nodeEngineKey("engineBackend", rpNode)]: best.backend,
|
|
6385
|
-
[nodeEngineKey("engineDevice", rpNode)]: best.device ?? "cpu"
|
|
6386
|
-
});
|
|
6387
|
-
this.log.info("Re-probed engine (platform-deterministic) — wrote back", { meta: {
|
|
6428
|
+
await this.writeStore({ [nodeEngineKey("probedBestEngine", rpNode)]: probedLabel });
|
|
6429
|
+
this.log.info("Re-probed engine (local kernel) — updated probedBestEngine hint", { meta: {
|
|
6388
6430
|
backend: best.backend,
|
|
6389
6431
|
device: best.device ?? null,
|
|
6390
6432
|
probedBestEngine: probedLabel
|
|
6391
6433
|
} });
|
|
6392
|
-
} else this.log.info("Re-probe: onnx floor pending gpu probe — NOT
|
|
6434
|
+
} else this.log.info("Re-probe: onnx floor pending gpu probe — hint NOT persisted", { meta: {
|
|
6393
6435
|
backend: best.backend,
|
|
6394
6436
|
device: best.device ?? null
|
|
6395
6437
|
} });
|
|
@@ -6806,7 +6848,7 @@ var DEFAULT_CONFIG = {
|
|
|
6806
6848
|
intraOpThreads: 0,
|
|
6807
6849
|
numWorkers: 0,
|
|
6808
6850
|
engineRuntime: "python",
|
|
6809
|
-
engineBackend: "
|
|
6851
|
+
engineBackend: "auto",
|
|
6810
6852
|
engineDevice: "cpu",
|
|
6811
6853
|
probedBestEngine: "",
|
|
6812
6854
|
activeEngine: ""
|
|
@@ -6859,7 +6901,7 @@ var POOL_BOUND_KEYS = [
|
|
|
6859
6901
|
"intraOpThreads",
|
|
6860
6902
|
"numWorkers"
|
|
6861
6903
|
];
|
|
6862
|
-
var DetectionPipelineAddon = class extends BaseAddon {
|
|
6904
|
+
var DetectionPipelineAddon = class DetectionPipelineAddon extends BaseAddon {
|
|
6863
6905
|
provider = null;
|
|
6864
6906
|
/** Last non-null probed hardware PER NODE — reused when the probe transiently
|
|
6865
6907
|
* returns null so offered backends / device lists don't collapse. Keyed by
|
|
@@ -6934,7 +6976,10 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6934
6976
|
type: "select",
|
|
6935
6977
|
key: "engineBackend",
|
|
6936
6978
|
label: "Execution provider",
|
|
6937
|
-
options: [
|
|
6979
|
+
options: [{
|
|
6980
|
+
value: "auto",
|
|
6981
|
+
label: "Auto (best for this node)"
|
|
6982
|
+
}, ...STATIC_BACKEND_OPTIONS],
|
|
6938
6983
|
default: DEFAULT_CONFIG.engineBackend,
|
|
6939
6984
|
immediate: true,
|
|
6940
6985
|
perNode: true
|
|
@@ -7075,12 +7120,17 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
7075
7120
|
const env = await this.probeHardwareEnv(targetNode);
|
|
7076
7121
|
const hardware = env.hardware;
|
|
7077
7122
|
const offered = supportedRuntimes(env);
|
|
7078
|
-
const runtimeBackends =
|
|
7123
|
+
const runtimeBackends = [{
|
|
7124
|
+
value: "auto",
|
|
7125
|
+
label: "Auto (best for this node)"
|
|
7126
|
+
}, ...offered.map((id) => ({
|
|
7079
7127
|
value: id,
|
|
7080
7128
|
label: runtimeLabel(id)
|
|
7081
|
-
}));
|
|
7129
|
+
}))];
|
|
7082
7130
|
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
7131
|
+
const isAuto = storedBackend === "auto";
|
|
7083
7132
|
const backend = (() => {
|
|
7133
|
+
if (isAuto) return pickBestRuntime(env, null).runtimeId;
|
|
7084
7134
|
const rid = toRuntimeId(storedBackend);
|
|
7085
7135
|
if (rid === "onnx") return "onnx";
|
|
7086
7136
|
if (offered.includes(rid)) return rid;
|
|
@@ -7091,9 +7141,10 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
7091
7141
|
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
|
|
7092
7142
|
const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
|
|
7093
7143
|
const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
|
|
7144
|
+
const displayBackend = isAuto ? "auto" : backend;
|
|
7094
7145
|
const raw = {
|
|
7095
7146
|
...merged,
|
|
7096
|
-
engineBackend:
|
|
7147
|
+
engineBackend: displayBackend,
|
|
7097
7148
|
engineDevice: device,
|
|
7098
7149
|
activeEngine
|
|
7099
7150
|
};
|
|
@@ -7211,7 +7262,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
7211
7262
|
const api = this.ctxIfReady?.api;
|
|
7212
7263
|
if (!api) return null;
|
|
7213
7264
|
const node = nodeId ?? this.localNodeId();
|
|
7214
|
-
const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query(
|
|
7265
|
+
const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query({ nodeId: node }))?.hardware;
|
|
7215
7266
|
if (!hw) return null;
|
|
7216
7267
|
return {
|
|
7217
7268
|
npu: hw.npu ? { type: hw.npu.type } : null,
|
|
@@ -7249,7 +7300,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
7249
7300
|
* a reason to disagree.
|
|
7250
7301
|
*/
|
|
7251
7302
|
resolveBackendTuning() {
|
|
7252
|
-
const t = tuningFor(toRuntimeId(this.config.engineBackend));
|
|
7303
|
+
const t = tuningFor(this.config.engineBackend === "auto" ? this.provider?.resolvedEngineBackend() ?? "onnx" : toRuntimeId(this.config.engineBackend));
|
|
7253
7304
|
const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
|
|
7254
7305
|
const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
|
|
7255
7306
|
return {
|
|
@@ -7262,6 +7313,34 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
7262
7313
|
numWorkers: num(t["numWorkers"], 1)
|
|
7263
7314
|
};
|
|
7264
7315
|
}
|
|
7316
|
+
/**
|
|
7317
|
+
* Construct a fresh provider with the given effective tuning. Single source of
|
|
7318
|
+
* truth for the provider wiring — shared by `onInitialize`, the post-resolve
|
|
7319
|
+
* tuning reconciliation, and the `onConfigChanged` pool respawn.
|
|
7320
|
+
*/
|
|
7321
|
+
buildProvider(modelsDir, tuning) {
|
|
7322
|
+
if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
|
|
7323
|
+
return new DetectionPipelineProvider(this.ctx.settings, modelsDir, this.ctx.logger, this.ctx.eventBus ?? null, () => ({ sections: [] }), {
|
|
7324
|
+
concurrency: tuning.concurrency,
|
|
7325
|
+
tuning: {
|
|
7326
|
+
batchMode: tuning.batchMode,
|
|
7327
|
+
windowMs: tuning.windowMs,
|
|
7328
|
+
maxBatchSize: tuning.maxBatchSize,
|
|
7329
|
+
numStreams: tuning.numStreams,
|
|
7330
|
+
intraOpThreads: tuning.intraOpThreads
|
|
7331
|
+
},
|
|
7332
|
+
numWorkers: tuning.numWorkers,
|
|
7333
|
+
pythonPath: this.pythonPath,
|
|
7334
|
+
pythonAddonDir: this.pythonAddonDir
|
|
7335
|
+
});
|
|
7336
|
+
}
|
|
7337
|
+
/**
|
|
7338
|
+
* Pool-bound tuning keys of `a` and `b` are equal (used to decide whether a
|
|
7339
|
+
* post-resolve / config-change respawn is actually needed).
|
|
7340
|
+
*/
|
|
7341
|
+
static poolTuningEqual(a, b) {
|
|
7342
|
+
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;
|
|
7343
|
+
}
|
|
7265
7344
|
async onInitialize() {
|
|
7266
7345
|
const modelsDir = await this.resolveModelsDir();
|
|
7267
7346
|
if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
|
|
@@ -7270,22 +7349,20 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
7270
7349
|
if (py.ok && py.pythonPath) this.pythonPath = py.pythonPath;
|
|
7271
7350
|
else this.ctx.logger.warn("Detection engine boot continues without Python — selection will provision on demand", {});
|
|
7272
7351
|
await this.proactivelyInstallOpenvino();
|
|
7273
|
-
const
|
|
7274
|
-
this.provider =
|
|
7275
|
-
concurrency: effectiveTuning.concurrency,
|
|
7276
|
-
tuning: {
|
|
7277
|
-
batchMode: effectiveTuning.batchMode,
|
|
7278
|
-
windowMs: effectiveTuning.windowMs,
|
|
7279
|
-
maxBatchSize: effectiveTuning.maxBatchSize,
|
|
7280
|
-
numStreams: effectiveTuning.numStreams,
|
|
7281
|
-
intraOpThreads: effectiveTuning.intraOpThreads
|
|
7282
|
-
},
|
|
7283
|
-
numWorkers: effectiveTuning.numWorkers,
|
|
7284
|
-
pythonPath: this.pythonPath,
|
|
7285
|
-
pythonAddonDir: this.pythonAddonDir
|
|
7286
|
-
});
|
|
7352
|
+
const initialTuning = this.resolveBackendTuning();
|
|
7353
|
+
this.provider = this.buildProvider(modelsDir, initialTuning);
|
|
7287
7354
|
await this.provider.init();
|
|
7288
7355
|
await this.provider.setApi(this.ctx);
|
|
7356
|
+
const resolvedTuning = this.resolveBackendTuning();
|
|
7357
|
+
if (!DetectionPipelineAddon.poolTuningEqual(initialTuning, resolvedTuning)) {
|
|
7358
|
+
this.ctx.logger.info("detection-pipeline tuning reconciled to resolved engine", { meta: {
|
|
7359
|
+
from: initialTuning,
|
|
7360
|
+
to: resolvedTuning
|
|
7361
|
+
} });
|
|
7362
|
+
this.provider = this.buildProvider(modelsDir, resolvedTuning);
|
|
7363
|
+
await this.provider.init();
|
|
7364
|
+
await this.provider.setApi(this.ctx);
|
|
7365
|
+
}
|
|
7289
7366
|
if (!this.config.probedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
7290
7367
|
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7291
7368
|
});
|
|
@@ -7342,13 +7419,18 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
7342
7419
|
* first loaded (gated by `loadEngine`'s `isPythonBackendAvailable` check).
|
|
7343
7420
|
*
|
|
7344
7421
|
* Failure is non-fatal: a warning is logged and the addon continues with
|
|
7345
|
-
* the onnx-cpu baseline. The hardware
|
|
7346
|
-
* if the
|
|
7422
|
+
* the onnx-cpu baseline. The hardware resolve itself is also best-effort —
|
|
7423
|
+
* if the kernel resolver isn't wired yet the error is caught here.
|
|
7347
7424
|
*/
|
|
7348
7425
|
async proactivelyInstallOpenvino() {
|
|
7349
7426
|
if (!this.pythonAddonDir || !this.pythonPath) return;
|
|
7350
7427
|
try {
|
|
7351
|
-
const
|
|
7428
|
+
const hw = await this.ctx.kernel?.inferenceEngine?.resolveHardware();
|
|
7429
|
+
const hardware = {
|
|
7430
|
+
platform: process.platform,
|
|
7431
|
+
gpu: hw?.gpu ?? null,
|
|
7432
|
+
npu: hw?.npu ?? null
|
|
7433
|
+
};
|
|
7352
7434
|
if (!shouldInstallOpenvino(hardware)) return;
|
|
7353
7435
|
this.ctx.logger.info("Intel hardware detected — proactively installing OpenVINO Python package", { meta: {
|
|
7354
7436
|
gpu: hardware.gpu?.type ?? null,
|
|
@@ -7427,21 +7509,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
7427
7509
|
this.ctx.logger.warn("provider shutdown failed during tuning respawn", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
7428
7510
|
}
|
|
7429
7511
|
const modelsDir = await this.resolveModelsDir();
|
|
7430
|
-
|
|
7431
|
-
const effectiveTuning = this.resolveBackendTuning();
|
|
7432
|
-
this.provider = new DetectionPipelineProvider(this.ctx.settings, modelsDir, this.ctx.logger, this.ctx.eventBus ?? null, () => ({ sections: [] }), {
|
|
7433
|
-
concurrency: effectiveTuning.concurrency,
|
|
7434
|
-
tuning: {
|
|
7435
|
-
batchMode: effectiveTuning.batchMode,
|
|
7436
|
-
windowMs: effectiveTuning.windowMs,
|
|
7437
|
-
maxBatchSize: effectiveTuning.maxBatchSize,
|
|
7438
|
-
numStreams: effectiveTuning.numStreams,
|
|
7439
|
-
intraOpThreads: effectiveTuning.intraOpThreads
|
|
7440
|
-
},
|
|
7441
|
-
numWorkers: effectiveTuning.numWorkers,
|
|
7442
|
-
pythonPath: this.pythonPath,
|
|
7443
|
-
pythonAddonDir: this.pythonAddonDir
|
|
7444
|
-
});
|
|
7512
|
+
this.provider = this.buildProvider(modelsDir, this.resolveBackendTuning());
|
|
7445
7513
|
await this.provider.init();
|
|
7446
7514
|
await this.provider.setApi(this.ctx);
|
|
7447
7515
|
await this.provider.warmPool();
|