@camstack/addon-pipeline 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +7 -9
- package/dist/audio-analyzer/index.mjs +3 -4
- package/dist/audio-codec-nodeav/index.js +2 -2
- package/dist/audio-codec-nodeav/index.mjs +2 -2
- package/dist/decoder-nodeav/index.js +1 -1
- package/dist/decoder-nodeav/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +386 -198
- package/dist/detection-pipeline/index.mjs +374 -185
- package/dist/{dist-DsDFrG0I.mjs → dist-BA6DR_jV.mjs} +1798 -1638
- package/dist/{dist-BiUtYscO.js → dist-BLcTVvol.js} +1821 -1637
- package/dist/model-download-service-C7AjBsX9-B0ekM6dF.mjs +301 -0
- package/dist/model-download-service-C7AjBsX9-rXY-VFDk.js +358 -0
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +1 -1
- package/dist/pipeline-runner/index.mjs +1 -1
- package/dist/recorder/index.js +6 -7
- package/dist/recorder/index.mjs +4 -4
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CbTGCEnd.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BFy9iszl.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-COa17XL2.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dsz9DmNr.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CHcXI1Wf.mjs} +1 -1
- package/dist/stream-broker/{hostInit-BJ3QDdFs.mjs → hostInit-zRy9SzlX.mjs} +3 -3
- package/dist/stream-broker/index.js +27 -28
- package/dist/stream-broker/index.mjs +21 -21
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-B4oJIlgF.js → MaskShapeCanvas-DI4BY7W2-DJ7ztnFv.js} +1 -1
- package/embed-dist/assets/MotionZonesSettings-NcxxQN8r-CQzEnQoq.js +1 -0
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-CyTsHaor.js → PrivacyMaskSettings-APgPLF7p-Cl0eOy_U.js} +1 -1
- package/embed-dist/assets/index-CSuLwWK-.js +80 -0
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
- package/dist/chunk-D6vf50IK.js +0 -28
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-3STWM0yI.mjs +0 -26
- package/embed-dist/assets/MotionZonesSettings-C1EEbk2V-CUopGB1R.js +0 -1
- package/embed-dist/assets/index-hwJEVIPM.js +0 -80
|
@@ -2,18 +2,17 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const
|
|
6
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_model_download_service_C7AjBsX9 = require("../model-download-service-C7AjBsX9-rXY-VFDk.js");
|
|
6
|
+
const require_dist = require("../dist-BLcTVvol.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
|
-
node_fs =
|
|
8
|
+
node_fs = require_model_download_service_C7AjBsX9.__toESM(node_fs);
|
|
9
9
|
let node_path = require("node:path");
|
|
10
|
-
node_path =
|
|
10
|
+
node_path = require_model_download_service_C7AjBsX9.__toESM(node_path);
|
|
11
11
|
let node_os = require("node:os");
|
|
12
|
-
node_os =
|
|
13
|
-
let _camstack_system = require("@camstack/system");
|
|
12
|
+
node_os = require_model_download_service_C7AjBsX9.__toESM(node_os);
|
|
14
13
|
let node_child_process = require("node:child_process");
|
|
15
14
|
let sharp = require("sharp");
|
|
16
|
-
sharp =
|
|
15
|
+
sharp = require_model_download_service_C7AjBsX9.__toESM(sharp);
|
|
17
16
|
//#region src/detection-pipeline/engine/shared-inference-pool.ts
|
|
18
17
|
var MSG_COMMAND = 0;
|
|
19
18
|
var MSG_INFER_JPEG = 1;
|
|
@@ -2166,6 +2165,78 @@ var EngineFactory = class {
|
|
|
2166
2165
|
}
|
|
2167
2166
|
};
|
|
2168
2167
|
//#endregion
|
|
2168
|
+
//#region src/detection-pipeline/engine-store-keys.ts
|
|
2169
|
+
/**
|
|
2170
|
+
* Per-node scoping for the detection-pipeline engine cascade.
|
|
2171
|
+
*
|
|
2172
|
+
* The detection addon's settings store is a single CLUSTER-SHARED blob (the
|
|
2173
|
+
* settings-store cap is hub-resident; every node's detection instance reads and
|
|
2174
|
+
* writes the same keys). That is correct for node-agnostic settings (pipeline
|
|
2175
|
+
* steps, tuning) but WRONG for the engine cascade — `engineBackend` /
|
|
2176
|
+
* `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
|
|
2177
|
+
* and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
|
|
2178
|
+
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
2179
|
+
* another node that has no NPU.
|
|
2180
|
+
*
|
|
2181
|
+
* These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
|
|
2182
|
+
* Everything else in the store stays shared. A legacy un-scoped value (written
|
|
2183
|
+
* before this change, or by an older build) is read as a migration fallback and
|
|
2184
|
+
* re-persisted under the node-scoped key on the next write.
|
|
2185
|
+
*/
|
|
2186
|
+
var ENGINE_CASCADE_KEYS = [
|
|
2187
|
+
"engineBackend",
|
|
2188
|
+
"engineDevice",
|
|
2189
|
+
"probedBestEngine"
|
|
2190
|
+
];
|
|
2191
|
+
function isEngineCascadeKey(key) {
|
|
2192
|
+
return ENGINE_CASCADE_KEYS.includes(key);
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Normalise a raw kernel node id to the bare node id used for scoping.
|
|
2196
|
+
* `localNodeId` can carry a `<node>/<addon>` suffix; the engine selection is
|
|
2197
|
+
* per-NODE, so strip the addon segment. Falls back to `hub`.
|
|
2198
|
+
*/
|
|
2199
|
+
function normalizeEngineNodeId(rawNodeId) {
|
|
2200
|
+
const raw = rawNodeId ?? "hub";
|
|
2201
|
+
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
2202
|
+
}
|
|
2203
|
+
/** The node-scoped store key for an engine cascade field. */
|
|
2204
|
+
function nodeEngineKey(base, nodeId) {
|
|
2205
|
+
return `${base}@${normalizeEngineNodeId(nodeId)}`;
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* Read an engine cascade value for a node: the node-scoped key if present,
|
|
2209
|
+
* otherwise the legacy un-scoped value (migration), otherwise undefined.
|
|
2210
|
+
*/
|
|
2211
|
+
function readNodeEngineValue(store, base, nodeId) {
|
|
2212
|
+
const scoped = store[nodeEngineKey(base, nodeId)];
|
|
2213
|
+
return scoped !== void 0 ? scoped : store[base];
|
|
2214
|
+
}
|
|
2215
|
+
/**
|
|
2216
|
+
* Project a raw store onto the plain engine cascade keys for THIS node, so the
|
|
2217
|
+
* UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
|
|
2218
|
+
* the node's own selection. Non-engine keys are left untouched. Node-scoped
|
|
2219
|
+
* keys for OTHER nodes are dropped from the projection (not relevant to this
|
|
2220
|
+
* node's form).
|
|
2221
|
+
*/
|
|
2222
|
+
function projectNodeEngine(store, nodeId) {
|
|
2223
|
+
const out = {};
|
|
2224
|
+
const scopedForAnyNode = /* @__PURE__ */ new Set();
|
|
2225
|
+
for (const key of Object.keys(store)) {
|
|
2226
|
+
const atIdx = key.indexOf("@");
|
|
2227
|
+
if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
|
|
2228
|
+
scopedForAnyNode.add(key);
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
out[key] = store[key];
|
|
2232
|
+
}
|
|
2233
|
+
for (const base of ENGINE_CASCADE_KEYS) {
|
|
2234
|
+
const value = readNodeEngineValue(store, base, nodeId);
|
|
2235
|
+
if (value !== void 0) out[base] = value;
|
|
2236
|
+
}
|
|
2237
|
+
return out;
|
|
2238
|
+
}
|
|
2239
|
+
//#endregion
|
|
2169
2240
|
//#region src/detection-pipeline/postprocess/dispatch.ts
|
|
2170
2241
|
var VALID_KINDS = new Set([
|
|
2171
2242
|
"detections",
|
|
@@ -2205,7 +2276,7 @@ function postprocessYolo(output, stepDef) {
|
|
|
2205
2276
|
const letterbox = output.letterbox;
|
|
2206
2277
|
const dets = [];
|
|
2207
2278
|
for (let i = 0; i < numBoxes; i++) {
|
|
2208
|
-
const cx = tensor[
|
|
2279
|
+
const cx = tensor[i];
|
|
2209
2280
|
const cy = tensor[1 * numBoxes + i];
|
|
2210
2281
|
const w = tensor[2 * numBoxes + i];
|
|
2211
2282
|
const h = tensor[3 * numBoxes + i];
|
|
@@ -2361,7 +2432,7 @@ function postprocessArcface(output, _stepDef) {
|
|
|
2361
2432
|
let sumSq = 0;
|
|
2362
2433
|
for (let i = 0; i < tensor.length; i++) sumSq += tensor[i] * tensor[i];
|
|
2363
2434
|
const norm = Math.sqrt(sumSq);
|
|
2364
|
-
const normalized =
|
|
2435
|
+
const normalized = Array.from({ length: tensor.length });
|
|
2365
2436
|
for (let i = 0; i < tensor.length; i++) normalized[i] = norm === 0 ? 0 : tensor[i] / norm;
|
|
2366
2437
|
return {
|
|
2367
2438
|
kind: "embedding",
|
|
@@ -3536,34 +3607,69 @@ function walkFieldsForDefaults(fields, out) {
|
|
|
3536
3607
|
}
|
|
3537
3608
|
//#endregion
|
|
3538
3609
|
//#region src/detection-pipeline/runtimes.ts
|
|
3539
|
-
var
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3610
|
+
var KNOWN_PLATFORMS = [
|
|
3611
|
+
"darwin",
|
|
3612
|
+
"linux",
|
|
3613
|
+
"win32"
|
|
3614
|
+
];
|
|
3615
|
+
var KNOWN_ARCHES = ["arm64", "x64"];
|
|
3616
|
+
var KNOWN_GPU_TYPES = [
|
|
3617
|
+
"nvidia",
|
|
3618
|
+
"amd",
|
|
3619
|
+
"intel",
|
|
3620
|
+
"apple"
|
|
3621
|
+
];
|
|
3622
|
+
var KNOWN_NPU_TYPES = ["apple-ane", "intel-npu"];
|
|
3623
|
+
function toKnownPlatform(p) {
|
|
3624
|
+
return KNOWN_PLATFORMS.find((v) => v === p) ?? "linux";
|
|
3625
|
+
}
|
|
3626
|
+
function toKnownArch(a) {
|
|
3627
|
+
return KNOWN_ARCHES.find((v) => v === a) ?? "x64";
|
|
3628
|
+
}
|
|
3629
|
+
function gpuInfoFrom(hw) {
|
|
3630
|
+
if (!hw.gpu) return null;
|
|
3631
|
+
const type = KNOWN_GPU_TYPES.find((v) => v === hw.gpu?.type);
|
|
3632
|
+
if (!type) return null;
|
|
3633
|
+
return {
|
|
3634
|
+
type,
|
|
3635
|
+
name: ""
|
|
3636
|
+
};
|
|
3637
|
+
}
|
|
3638
|
+
function npuInfoFrom(hw) {
|
|
3639
|
+
if (!hw.npu) return null;
|
|
3640
|
+
const type = KNOWN_NPU_TYPES.find((v) => v === hw.npu?.type);
|
|
3641
|
+
if (!type) return null;
|
|
3642
|
+
return { type };
|
|
3643
|
+
}
|
|
3644
|
+
function envToHardwareInfo(env) {
|
|
3645
|
+
if (!env.hardware) return null;
|
|
3646
|
+
return {
|
|
3647
|
+
platform: toKnownPlatform(env.platform),
|
|
3648
|
+
arch: toKnownArch(env.arch),
|
|
3649
|
+
cpuModel: "",
|
|
3650
|
+
cpuCores: 0,
|
|
3651
|
+
totalRAM_MB: 0,
|
|
3652
|
+
availableRAM_MB: 0,
|
|
3653
|
+
gpu: gpuInfoFrom(env.hardware),
|
|
3654
|
+
npu: npuInfoFrom(env.hardware)
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
function probedToHardwareInfo(hw) {
|
|
3658
|
+
if (!hw) return null;
|
|
3659
|
+
return {
|
|
3660
|
+
platform: toKnownPlatform(process.platform),
|
|
3661
|
+
arch: toKnownArch(process.arch),
|
|
3662
|
+
cpuModel: "",
|
|
3663
|
+
cpuCores: 0,
|
|
3664
|
+
totalRAM_MB: 0,
|
|
3665
|
+
availableRAM_MB: 0,
|
|
3666
|
+
gpu: gpuInfoFrom(hw),
|
|
3667
|
+
npu: npuInfoFrom(hw)
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
var RUNTIME_DETAIL = {
|
|
3671
|
+
onnx: {
|
|
3550
3672
|
label: "ONNX Runtime",
|
|
3551
|
-
supports: () => true,
|
|
3552
|
-
devices: (hw) => {
|
|
3553
|
-
if (!hw) return [CPU];
|
|
3554
|
-
const out = [CPU];
|
|
3555
|
-
if (hw.gpu?.type === "nvidia") out.push({
|
|
3556
|
-
value: "cuda",
|
|
3557
|
-
label: "CUDA"
|
|
3558
|
-
});
|
|
3559
|
-
if (hw.npu?.type === "apple-ane") out.push({
|
|
3560
|
-
value: "coreml",
|
|
3561
|
-
label: "CoreML EP"
|
|
3562
|
-
});
|
|
3563
|
-
return out;
|
|
3564
|
-
},
|
|
3565
|
-
defaultDevice: "cpu",
|
|
3566
|
-
modelFormat: "onnx",
|
|
3567
3673
|
pythonRequirements: ["requirements.txt", "requirements-onnxruntime.txt"],
|
|
3568
3674
|
tuning: {
|
|
3569
3675
|
concurrency: 4,
|
|
@@ -3572,25 +3678,8 @@ var RUNTIMES = [
|
|
|
3572
3678
|
intraOpThreads: 0
|
|
3573
3679
|
}
|
|
3574
3680
|
},
|
|
3575
|
-
{
|
|
3576
|
-
id: "openvino",
|
|
3681
|
+
openvino: {
|
|
3577
3682
|
label: "OpenVINO",
|
|
3578
|
-
supports: (env) => env.arch === "x64" && env.platform !== "darwin" && env.hardware?.gpu?.type === "intel",
|
|
3579
|
-
devices: (hw) => {
|
|
3580
|
-
if (!hw) return [AUTO];
|
|
3581
|
-
const out = [AUTO, CPU];
|
|
3582
|
-
if (hw.gpu?.type === "intel") out.push({
|
|
3583
|
-
value: "gpu",
|
|
3584
|
-
label: "GPU"
|
|
3585
|
-
});
|
|
3586
|
-
if (hw.npu?.type === "intel-npu") out.push({
|
|
3587
|
-
value: "npu",
|
|
3588
|
-
label: "NPU"
|
|
3589
|
-
});
|
|
3590
|
-
return out;
|
|
3591
|
-
},
|
|
3592
|
-
defaultDevice: "auto",
|
|
3593
|
-
modelFormat: "openvino",
|
|
3594
3683
|
pythonRequirements: ["requirements.txt", "requirements-openvino.txt"],
|
|
3595
3684
|
tuning: {
|
|
3596
3685
|
concurrency: 1,
|
|
@@ -3598,29 +3687,8 @@ var RUNTIMES = [
|
|
|
3598
3687
|
numStreams: 0
|
|
3599
3688
|
}
|
|
3600
3689
|
},
|
|
3601
|
-
{
|
|
3602
|
-
id: "coreml",
|
|
3690
|
+
coreml: {
|
|
3603
3691
|
label: "CoreML",
|
|
3604
|
-
supports: (env) => env.platform === "darwin",
|
|
3605
|
-
devices: (hw) => {
|
|
3606
|
-
const all = {
|
|
3607
|
-
value: "all",
|
|
3608
|
-
label: "All (ANE + GPU + CPU)"
|
|
3609
|
-
};
|
|
3610
|
-
if (!hw) return [all];
|
|
3611
|
-
const out = [all];
|
|
3612
|
-
if (hw.npu?.type === "apple-ane") out.push({
|
|
3613
|
-
value: "ane",
|
|
3614
|
-
label: "Apple Neural Engine"
|
|
3615
|
-
});
|
|
3616
|
-
out.push({
|
|
3617
|
-
value: "gpu",
|
|
3618
|
-
label: "GPU"
|
|
3619
|
-
}, CPU);
|
|
3620
|
-
return out;
|
|
3621
|
-
},
|
|
3622
|
-
defaultDevice: "all",
|
|
3623
|
-
modelFormat: "coreml",
|
|
3624
3692
|
pythonRequirements: ["requirements.txt", "requirements-coreml.txt"],
|
|
3625
3693
|
tuning: {
|
|
3626
3694
|
concurrency: 1,
|
|
@@ -3630,32 +3698,50 @@ var RUNTIMES = [
|
|
|
3630
3698
|
numWorkers: 1
|
|
3631
3699
|
}
|
|
3632
3700
|
}
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
}
|
|
3701
|
+
};
|
|
3702
|
+
/**
|
|
3703
|
+
* Returns the list of supported runtime IDs for the given hardware env.
|
|
3704
|
+
* Delegates to `@camstack/types` `supportedRuntimes`.
|
|
3705
|
+
*/
|
|
3639
3706
|
function supportedRuntimes(env) {
|
|
3640
|
-
return
|
|
3707
|
+
return require_dist.supportedRuntimes(envToHardwareInfo(env));
|
|
3641
3708
|
}
|
|
3709
|
+
/**
|
|
3710
|
+
* Returns the device options for a given runtime and probed hardware.
|
|
3711
|
+
* Delegates to `@camstack/types` `runtimeDevices`.
|
|
3712
|
+
*/
|
|
3642
3713
|
function runtimeDevices(id, hardware) {
|
|
3643
|
-
return
|
|
3714
|
+
return require_dist.runtimeDevices(id, probedToHardwareInfo(hardware));
|
|
3644
3715
|
}
|
|
3716
|
+
/**
|
|
3717
|
+
* Returns the default device string for a given runtime.
|
|
3718
|
+
* Delegates to `@camstack/types` `defaultDeviceFor`.
|
|
3719
|
+
*/
|
|
3645
3720
|
function defaultDeviceFor(id) {
|
|
3646
|
-
return
|
|
3647
|
-
}
|
|
3648
|
-
function pythonRequirementsFor(id) {
|
|
3649
|
-
return def(id).pythonRequirements;
|
|
3721
|
+
return require_dist.defaultDeviceFor(id);
|
|
3650
3722
|
}
|
|
3723
|
+
/** Model format for each inference runtime supported by the detection pipeline. */
|
|
3724
|
+
var RUNTIME_FORMAT = {
|
|
3725
|
+
onnx: "onnx",
|
|
3726
|
+
openvino: "openvino",
|
|
3727
|
+
coreml: "coreml"
|
|
3728
|
+
};
|
|
3729
|
+
/**
|
|
3730
|
+
* Returns the model format required for a given runtime.
|
|
3731
|
+
* Returns the locally-typed format string ('onnx' | 'openvino' | 'coreml')
|
|
3732
|
+
* matching the engine-provisioner's own ModelFormat type.
|
|
3733
|
+
*/
|
|
3651
3734
|
function modelFormatFor(id) {
|
|
3652
|
-
return
|
|
3735
|
+
return RUNTIME_FORMAT[id];
|
|
3736
|
+
}
|
|
3737
|
+
function pythonRequirementsFor(id) {
|
|
3738
|
+
return RUNTIME_DETAIL[id].pythonRequirements;
|
|
3653
3739
|
}
|
|
3654
3740
|
function tuningFor(id) {
|
|
3655
|
-
return
|
|
3741
|
+
return RUNTIME_DETAIL[id].tuning;
|
|
3656
3742
|
}
|
|
3657
3743
|
function runtimeLabel(id) {
|
|
3658
|
-
return
|
|
3744
|
+
return RUNTIME_DETAIL[id].label;
|
|
3659
3745
|
}
|
|
3660
3746
|
/**
|
|
3661
3747
|
* Proactive-install hint kept for back-compat (re-exported from index.ts).
|
|
@@ -3996,6 +4082,27 @@ function toProbedHardware(hw) {
|
|
|
3996
4082
|
gpu: hw.gpu ? { type: hw.gpu.type } : null
|
|
3997
4083
|
};
|
|
3998
4084
|
}
|
|
4085
|
+
var ONNX_FLOOR = {
|
|
4086
|
+
runtime: "python",
|
|
4087
|
+
backend: "onnx",
|
|
4088
|
+
format: "onnx",
|
|
4089
|
+
device: "cpu"
|
|
4090
|
+
};
|
|
4091
|
+
/**
|
|
4092
|
+
* Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
|
|
4093
|
+
* env. Used wherever the old `detectBestEngine()` sync probe fell back — the
|
|
4094
|
+
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
4095
|
+
* instead of duplicated inline.
|
|
4096
|
+
*/
|
|
4097
|
+
function onnxFloorPick() {
|
|
4098
|
+
const pick = pickBestRuntime(runtimeEnvFromProcess(null), null);
|
|
4099
|
+
return {
|
|
4100
|
+
runtime: "python",
|
|
4101
|
+
backend: pick.runtimeId,
|
|
4102
|
+
format: modelFormatFor(pick.runtimeId),
|
|
4103
|
+
device: pick.device
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
3999
4106
|
var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
4000
4107
|
modelsDir;
|
|
4001
4108
|
eventBus;
|
|
@@ -4085,7 +4192,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4085
4192
|
this.readStore = () => settings.readAddonStore();
|
|
4086
4193
|
this.writeStore = (patch) => settings.writeAddonStore(patch);
|
|
4087
4194
|
this.readDeviceStore = settings.readDeviceStore ?? (async () => ({}));
|
|
4088
|
-
this.currentEngine =
|
|
4195
|
+
this.currentEngine = ONNX_FLOOR;
|
|
4089
4196
|
this.log.info("Engine selected (default)", { meta: {
|
|
4090
4197
|
runtime: this.currentEngine.runtime,
|
|
4091
4198
|
backend: this.currentEngine.backend,
|
|
@@ -4151,62 +4258,27 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4151
4258
|
isReady() {
|
|
4152
4259
|
return this.ready;
|
|
4153
4260
|
}
|
|
4154
|
-
/** Detect the best inference engine for this platform. */
|
|
4155
|
-
static detectBestEngine() {
|
|
4156
|
-
const platform = process.platform;
|
|
4157
|
-
const arch = process.arch;
|
|
4158
|
-
if (platform === "darwin" && arch === "arm64") return {
|
|
4159
|
-
runtime: "python",
|
|
4160
|
-
backend: "coreml",
|
|
4161
|
-
format: "coreml",
|
|
4162
|
-
device: "all"
|
|
4163
|
-
};
|
|
4164
|
-
if (platform === "darwin") return {
|
|
4165
|
-
runtime: "python",
|
|
4166
|
-
backend: "coreml",
|
|
4167
|
-
format: "coreml",
|
|
4168
|
-
device: "gpu"
|
|
4169
|
-
};
|
|
4170
|
-
try {
|
|
4171
|
-
const { execFileSync } = require("node:child_process");
|
|
4172
|
-
execFileSync("nvidia-smi", ["--query-gpu=name", "--format=csv,noheader"], {
|
|
4173
|
-
timeout: 3e3,
|
|
4174
|
-
stdio: "pipe"
|
|
4175
|
-
});
|
|
4176
|
-
return {
|
|
4177
|
-
runtime: "python",
|
|
4178
|
-
backend: "onnx",
|
|
4179
|
-
format: "onnx",
|
|
4180
|
-
device: "cuda"
|
|
4181
|
-
};
|
|
4182
|
-
} catch {}
|
|
4183
|
-
try {
|
|
4184
|
-
const fsmod = require("node:fs");
|
|
4185
|
-
const isIntel = require("node:os").cpus()[0]?.model?.includes("Intel") ?? false;
|
|
4186
|
-
const hasIgpu = fsmod.existsSync("/dev/dri/renderD128");
|
|
4187
|
-
const hasNpu = fsmod.existsSync("/dev/accel/accel0") || fsmod.existsSync("/dev/accel");
|
|
4188
|
-
if (isIntel && (hasIgpu || hasNpu)) return {
|
|
4189
|
-
runtime: "python",
|
|
4190
|
-
backend: "openvino",
|
|
4191
|
-
format: "openvino",
|
|
4192
|
-
device: "auto"
|
|
4193
|
-
};
|
|
4194
|
-
} catch {}
|
|
4195
|
-
return {
|
|
4196
|
-
runtime: "python",
|
|
4197
|
-
backend: "onnx",
|
|
4198
|
-
format: "onnx",
|
|
4199
|
-
device: "cpu"
|
|
4200
|
-
};
|
|
4201
|
-
}
|
|
4202
4261
|
/** Store the addon context. ctx.api is a lazy getter resolved at call time. */
|
|
4203
4262
|
async setApi(addonCtx) {
|
|
4204
4263
|
this.addonCtx = addonCtx;
|
|
4205
|
-
if (this.needsAutoPick) {
|
|
4264
|
+
if (this.needsAutoPick) if (this.addonCtx.useCapability("platform-probe").isReady) {
|
|
4206
4265
|
await this.autoPickAndPersist();
|
|
4207
4266
|
this.needsAutoPick = false;
|
|
4267
|
+
this.startProvisioningForCurrentEngine();
|
|
4268
|
+
} else {
|
|
4269
|
+
this.startProvisioningForCurrentEngine();
|
|
4270
|
+
const unsubscribe = this.addonCtx.onCapabilityStateChange("platform-probe", { type: "global" }, (state) => {
|
|
4271
|
+
if (state !== "ready") return;
|
|
4272
|
+
unsubscribe();
|
|
4273
|
+
if (!this.needsAutoPick) return;
|
|
4274
|
+
this.autoPickAndPersist().then(() => {
|
|
4275
|
+
this.needsAutoPick = false;
|
|
4276
|
+
this.startProvisioningForCurrentEngine();
|
|
4277
|
+
});
|
|
4278
|
+
});
|
|
4279
|
+
this.addonCtx.addDisposer(unsubscribe);
|
|
4208
4280
|
}
|
|
4209
|
-
this.startProvisioningForCurrentEngine();
|
|
4281
|
+
else this.startProvisioningForCurrentEngine();
|
|
4210
4282
|
}
|
|
4211
4283
|
/**
|
|
4212
4284
|
* Auto-pick the best supported runtime at first boot (no stored engine).
|
|
@@ -4221,7 +4293,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4221
4293
|
try {
|
|
4222
4294
|
const api = this.addonCtx?.api;
|
|
4223
4295
|
if (api) {
|
|
4224
|
-
const caps = await api.platformProbe.getCapabilities.query();
|
|
4296
|
+
const caps = await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() });
|
|
4225
4297
|
hardware = caps?.hardware ?? null;
|
|
4226
4298
|
const bs = caps?.bestScore;
|
|
4227
4299
|
if (bs && bs.runtime === "python") bestBackendHint = bs.backend;
|
|
@@ -4235,9 +4307,10 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4235
4307
|
device: pick.device
|
|
4236
4308
|
};
|
|
4237
4309
|
this.currentEngine = engine;
|
|
4310
|
+
const apNode = this.localProbeNodeId();
|
|
4238
4311
|
await this.writeStore({
|
|
4239
|
-
engineBackend: pick.runtimeId,
|
|
4240
|
-
engineDevice: pick.device
|
|
4312
|
+
[nodeEngineKey("engineBackend", apNode)]: pick.runtimeId,
|
|
4313
|
+
[nodeEngineKey("engineDevice", apNode)]: pick.device
|
|
4241
4314
|
});
|
|
4242
4315
|
this.log.info("Auto-picked engine at first boot", { meta: {
|
|
4243
4316
|
backend: pick.runtimeId,
|
|
@@ -4354,7 +4427,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4354
4427
|
if (!step.enabled) continue;
|
|
4355
4428
|
const modelEntry = getStepDefinition(step.addonId).models.find((m) => m.id === step.modelId);
|
|
4356
4429
|
if (!modelEntry) continue;
|
|
4357
|
-
if (
|
|
4430
|
+
if (require_model_download_service_C7AjBsX9.isModelDownloaded(this.modelsDir, modelEntry, format)) continue;
|
|
4358
4431
|
await this.downloadWithRetry(modelEntry, format, 3);
|
|
4359
4432
|
}
|
|
4360
4433
|
}
|
|
@@ -4410,11 +4483,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4410
4483
|
* callers fall back to the registry's safe minimum. The engine OFFER derives
|
|
4411
4484
|
* from hardware ONLY — install state (probe `scores`) never gates it.
|
|
4412
4485
|
*/
|
|
4486
|
+
/**
|
|
4487
|
+
* The local Moleculer node id (child-suffix stripped). MUST be passed to every
|
|
4488
|
+
* `platformProbe.getCapabilities` query: the cap is a singleton and a query
|
|
4489
|
+
* with no nodeId resolves to the HUB's probe — so on a remote agent the engine
|
|
4490
|
+
* decision would use the HUB's hardware (e.g. an Intel NPU the agent doesn't
|
|
4491
|
+
* have) and pin a device the node can't run, breaking provisioning.
|
|
4492
|
+
*/
|
|
4493
|
+
localProbeNodeId() {
|
|
4494
|
+
const raw = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
4495
|
+
return raw.includes("/") ? raw.split("/")[0] : raw;
|
|
4496
|
+
}
|
|
4413
4497
|
async fetchProbeGatingData() {
|
|
4414
4498
|
try {
|
|
4415
4499
|
const api = this.addonCtx?.api;
|
|
4416
4500
|
if (!api) return { hardware: null };
|
|
4417
|
-
return { hardware: (await api.platformProbe.getCapabilities.query())?.hardware ?? null };
|
|
4501
|
+
return { hardware: (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null };
|
|
4418
4502
|
} catch {
|
|
4419
4503
|
return { hardware: null };
|
|
4420
4504
|
}
|
|
@@ -4551,7 +4635,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4551
4635
|
const formats = {};
|
|
4552
4636
|
for (const [formatKey, entry] of Object.entries(m.formats)) {
|
|
4553
4637
|
if (!entry) continue;
|
|
4554
|
-
const downloaded =
|
|
4638
|
+
const downloaded = require_model_download_service_C7AjBsX9.isModelDownloaded(this.modelsDir, m, formatKey);
|
|
4555
4639
|
formats[formatKey] = {
|
|
4556
4640
|
url: entry.url,
|
|
4557
4641
|
sizeMB: entry.sizeMB,
|
|
@@ -4674,7 +4758,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4674
4758
|
const { modelId, format, addonId } = input;
|
|
4675
4759
|
const modelEntry = getStepDefinition(addonId).models.find((m) => m.id === modelId);
|
|
4676
4760
|
if (!modelEntry) throw new Error(`Model "${modelId}" not found in step "${addonId}" catalog`);
|
|
4677
|
-
if (!
|
|
4761
|
+
if (!require_model_download_service_C7AjBsX9.deleteModelFromDisk(this.modelsDir, modelEntry, format)) throw new Error(`Model "${modelId}" (${format}) is not downloaded — nothing to delete`);
|
|
4678
4762
|
this.log.info("Model deleted from disk", { meta: {
|
|
4679
4763
|
modelId,
|
|
4680
4764
|
format
|
|
@@ -5129,7 +5213,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5129
5213
|
const format = this.currentEngine?.format ?? "onnx";
|
|
5130
5214
|
for (const step of needed) {
|
|
5131
5215
|
const modelEntry = getStepDefinition(step.addonId).models.find((m) => m.id === step.modelId);
|
|
5132
|
-
if (modelEntry && !
|
|
5216
|
+
if (modelEntry && !require_model_download_service_C7AjBsX9.isModelDownloaded(this.modelsDir, modelEntry, format)) {
|
|
5133
5217
|
this.log.info("Downloading model for step", { meta: {
|
|
5134
5218
|
modelId: step.modelId,
|
|
5135
5219
|
format,
|
|
@@ -5154,7 +5238,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5154
5238
|
/** Download a model with retry + exponential backoff */
|
|
5155
5239
|
async downloadWithRetry(entry, format, maxRetries, onProgress) {
|
|
5156
5240
|
for (let attempt = 1; attempt <= maxRetries; attempt++) try {
|
|
5157
|
-
await
|
|
5241
|
+
await require_model_download_service_C7AjBsX9.ensureModel(this.modelsDir, entry, format, onProgress);
|
|
5158
5242
|
this.log.info("Model downloaded successfully", { meta: { modelId: entry.id } });
|
|
5159
5243
|
return;
|
|
5160
5244
|
} catch (err) {
|
|
@@ -5479,7 +5563,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5479
5563
|
this.engineFactory = null;
|
|
5480
5564
|
this.executor = null;
|
|
5481
5565
|
}
|
|
5482
|
-
for (const id of
|
|
5566
|
+
for (const id of Array.from(this.deviceProxies.keys())) this.releaseDeviceProxy(id);
|
|
5483
5567
|
}
|
|
5484
5568
|
/**
|
|
5485
5569
|
* Resolve and cache a {@link DeviceProxy} for the given camera. Pins
|
|
@@ -5631,11 +5715,11 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5631
5715
|
} });
|
|
5632
5716
|
continue;
|
|
5633
5717
|
}
|
|
5634
|
-
if (!
|
|
5718
|
+
if (!require_model_download_service_C7AjBsX9.isModelDownloaded(this.modelsDir, modelEntry, format)) this.log.info("Downloading model", { meta: {
|
|
5635
5719
|
modelId: step.modelId,
|
|
5636
5720
|
format
|
|
5637
5721
|
} });
|
|
5638
|
-
downloads.push(
|
|
5722
|
+
downloads.push(require_model_download_service_C7AjBsX9.ensureModel(this.modelsDir, modelEntry, format).then(() => {}));
|
|
5639
5723
|
}
|
|
5640
5724
|
await Promise.all(downloads);
|
|
5641
5725
|
await this.ensureBackendDeps(this.currentEngine);
|
|
@@ -5663,25 +5747,27 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5663
5747
|
* fields (`engineRuntime`, `engineBackend`, `engineDevice`). Fallback
|
|
5664
5748
|
* to the legacy `KEY_ENGINE` JSON blob for pre-migration stores.
|
|
5665
5749
|
* Returns null when neither source has anything; the caller keeps
|
|
5666
|
-
*
|
|
5750
|
+
* the onnx floor set at construction until autoPickAndPersist() runs.
|
|
5667
5751
|
*/
|
|
5668
5752
|
async loadEngine() {
|
|
5669
5753
|
const store = await this.readStore();
|
|
5670
5754
|
const storedRuntime = store["engineRuntime"];
|
|
5671
|
-
const
|
|
5755
|
+
const node = this.localProbeNodeId();
|
|
5756
|
+
const storedBackend = readNodeEngineValue(store, "engineBackend", node);
|
|
5672
5757
|
if (typeof storedBackend === "string" && storedBackend.length > 0) {
|
|
5673
5758
|
const backend = storedBackend;
|
|
5674
|
-
const
|
|
5675
|
-
const
|
|
5759
|
+
const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
|
|
5760
|
+
const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
|
|
5761
|
+
const floor = onnxFloorPick();
|
|
5676
5762
|
const migratedBackend = typeof storedRuntime === "string" && storedRuntime === "node" && backend === "cpu" ? "onnx" : backend;
|
|
5677
5763
|
if (!DetectionPipelineProvider.isPythonBackendAvailable(migratedBackend, this.executorOptions.pythonPath ?? "")) {
|
|
5678
|
-
this.log.warn("Stored engine backend unavailable on this node — falling back to
|
|
5764
|
+
this.log.warn("Stored engine backend unavailable on this node — falling back to onnx floor", { meta: {
|
|
5679
5765
|
stored: migratedBackend,
|
|
5680
|
-
fallback: `${
|
|
5766
|
+
fallback: `${floor.backend}`
|
|
5681
5767
|
} });
|
|
5682
|
-
return
|
|
5768
|
+
return floor;
|
|
5683
5769
|
}
|
|
5684
|
-
const device = storedDevice ||
|
|
5770
|
+
const device = storedDevice || floor.device;
|
|
5685
5771
|
return {
|
|
5686
5772
|
runtime: "python",
|
|
5687
5773
|
backend: migratedBackend,
|
|
@@ -5853,18 +5939,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5853
5939
|
const api = this.addonCtx?.api;
|
|
5854
5940
|
let best;
|
|
5855
5941
|
if (api) try {
|
|
5856
|
-
const caps = await api.platformProbe.getCapabilities.query();
|
|
5942
|
+
const caps = await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() });
|
|
5857
5943
|
const bs = caps?.bestScore;
|
|
5858
5944
|
if (bs && bs.runtime === "python") {
|
|
5859
5945
|
const probeBackend = bs.backend;
|
|
5860
5946
|
const probeDevice = (() => {
|
|
5861
5947
|
const hw = caps.hardware;
|
|
5862
|
-
if (probeBackend === "
|
|
5863
|
-
if (probeBackend === "
|
|
5864
|
-
if (hw?.npu?.type === "intel-npu") return "npu";
|
|
5865
|
-
if (hw?.gpu?.type === "intel") return "gpu";
|
|
5866
|
-
return "auto";
|
|
5867
|
-
}
|
|
5948
|
+
if (probeBackend === "openvino") return defaultDeviceFor("openvino");
|
|
5949
|
+
if (probeBackend === "coreml") return defaultDeviceFor("coreml");
|
|
5868
5950
|
if (probeBackend === "onnx") return hw?.gpu?.type === "nvidia" ? "cuda" : "cpu";
|
|
5869
5951
|
return "cpu";
|
|
5870
5952
|
})();
|
|
@@ -5874,16 +5956,17 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5874
5956
|
format: backendToFormat(probeBackend),
|
|
5875
5957
|
device: probeDevice
|
|
5876
5958
|
};
|
|
5877
|
-
} else best =
|
|
5959
|
+
} else best = onnxFloorPick();
|
|
5878
5960
|
} catch {
|
|
5879
|
-
best =
|
|
5961
|
+
best = onnxFloorPick();
|
|
5880
5962
|
}
|
|
5881
|
-
else best =
|
|
5963
|
+
else best = onnxFloorPick();
|
|
5882
5964
|
const probedLabel = `${best.backend}/${best.device ?? "default"}`;
|
|
5965
|
+
const rpNode = this.localProbeNodeId();
|
|
5883
5966
|
await this.writeStore({
|
|
5884
|
-
probedBestEngine: probedLabel,
|
|
5885
|
-
engineBackend: best.backend,
|
|
5886
|
-
engineDevice: best.device ?? "cpu"
|
|
5967
|
+
[nodeEngineKey("probedBestEngine", rpNode)]: probedLabel,
|
|
5968
|
+
[nodeEngineKey("engineBackend", rpNode)]: best.backend,
|
|
5969
|
+
[nodeEngineKey("engineDevice", rpNode)]: best.device ?? "cpu"
|
|
5887
5970
|
});
|
|
5888
5971
|
this.log.info("Re-probed engine — wrote back engineBackend + engineDevice", { meta: {
|
|
5889
5972
|
backend: best.backend,
|
|
@@ -6047,7 +6130,7 @@ function buildSchemaSlots(format, modelsDir) {
|
|
|
6047
6130
|
id: m.id,
|
|
6048
6131
|
name: m.name,
|
|
6049
6132
|
formats: Object.fromEntries(Object.entries(m.formats).map(([f, entry]) => [f, {
|
|
6050
|
-
downloaded:
|
|
6133
|
+
downloaded: require_model_download_service_C7AjBsX9.isModelDownloaded(modelsDir, m, f),
|
|
6051
6134
|
sizeMB: entry.sizeMB
|
|
6052
6135
|
}]))
|
|
6053
6136
|
})),
|
|
@@ -6306,7 +6389,8 @@ var DEFAULT_CONFIG = {
|
|
|
6306
6389
|
engineRuntime: "python",
|
|
6307
6390
|
engineBackend: "onnx",
|
|
6308
6391
|
engineDevice: "cpu",
|
|
6309
|
-
probedBestEngine: ""
|
|
6392
|
+
probedBestEngine: "",
|
|
6393
|
+
activeEngine: ""
|
|
6310
6394
|
};
|
|
6311
6395
|
/** Derive the model-format from a backend value. Called by the provider. */
|
|
6312
6396
|
function backendToFormat(backend) {
|
|
@@ -6349,6 +6433,15 @@ var POOL_BOUND_KEYS = [
|
|
|
6349
6433
|
];
|
|
6350
6434
|
var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
6351
6435
|
provider = null;
|
|
6436
|
+
/** Last non-null probed hardware PER NODE — reused when the probe transiently
|
|
6437
|
+
* returns null so offered backends / device lists don't collapse. Keyed by
|
|
6438
|
+
* node because the hub addon probes other nodes when serving their config. */
|
|
6439
|
+
lastGoodHardwareByNode = /* @__PURE__ */ new Map();
|
|
6440
|
+
/** This node's effective engine selection, cached from the node-scoped store
|
|
6441
|
+
* so the synchronous `resolveBackendTuning` and the reprobe gate don't read
|
|
6442
|
+
* the cluster-shared bare keys. Refreshed at init + on every config change. */
|
|
6443
|
+
nodeEngineBackend = DEFAULT_CONFIG.engineBackend;
|
|
6444
|
+
nodeProbedBestEngine = "";
|
|
6352
6445
|
engineMetricsTimer = null;
|
|
6353
6446
|
/** Snapshot-equality cache for engine-metrics emit. Most ticks
|
|
6354
6447
|
* the engine inventory is unchanged (no model load/unload), so
|
|
@@ -6403,6 +6496,14 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6403
6496
|
tooltip: "Re-probe engine"
|
|
6404
6497
|
}]
|
|
6405
6498
|
}),
|
|
6499
|
+
this.field({
|
|
6500
|
+
type: "text",
|
|
6501
|
+
key: "activeEngine",
|
|
6502
|
+
label: "Active engine",
|
|
6503
|
+
description: "The runtime/device the inference pool ACTUALLY loaded on this node, with its provisioning state (format: provider/device (state)). If this differs from the selected provider below — e.g. \"onnx/cpu (ready)\" while OpenVINO is selected — the chosen accelerator could not load on this host and the engine fell back to the ONNX-CPU baseline.",
|
|
6504
|
+
readonlyField: true,
|
|
6505
|
+
default: ""
|
|
6506
|
+
}),
|
|
6406
6507
|
this.field({
|
|
6407
6508
|
type: "select",
|
|
6408
6509
|
key: "engineBackend",
|
|
@@ -6539,14 +6640,18 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6539
6640
|
* probe is unreachable). Stored backend / device snap back to the registry
|
|
6540
6641
|
* floor / default when they fall outside the offered set.
|
|
6541
6642
|
*/
|
|
6542
|
-
async getGlobalSettings(overlay) {
|
|
6543
|
-
const
|
|
6544
|
-
const
|
|
6643
|
+
async getGlobalSettings(overlay, _cap, nodeId) {
|
|
6644
|
+
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6645
|
+
const rawStored = await this.resolveUiSettingsStore();
|
|
6646
|
+
if (rawStored === null) return null;
|
|
6647
|
+
const stored = projectNodeEngine(rawStored, targetNode);
|
|
6648
|
+
const storedBackendRaw = stored["engineBackend"];
|
|
6649
|
+
if (typeof storedBackendRaw !== "string" || storedBackendRaw === "") return null;
|
|
6545
6650
|
const merged = overlay ? {
|
|
6546
6651
|
...stored,
|
|
6547
6652
|
...overlay
|
|
6548
6653
|
} : stored;
|
|
6549
|
-
const env = await this.probeHardwareEnv();
|
|
6654
|
+
const env = await this.probeHardwareEnv(targetNode);
|
|
6550
6655
|
const hardware = env.hardware;
|
|
6551
6656
|
const offered = supportedRuntimes(env);
|
|
6552
6657
|
const runtimeBackends = offered.map((id) => ({
|
|
@@ -6554,14 +6659,22 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6554
6659
|
label: runtimeLabel(id)
|
|
6555
6660
|
}));
|
|
6556
6661
|
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
6557
|
-
const backend =
|
|
6662
|
+
const backend = (() => {
|
|
6663
|
+
const rid = toRuntimeId(storedBackend);
|
|
6664
|
+
if (rid === "onnx") return "onnx";
|
|
6665
|
+
if (offered.includes(rid)) return rid;
|
|
6666
|
+
return rid === "openvino" && env.platform !== "darwin" && env.arch === "x64" || rid === "coreml" && env.platform === "darwin" ? rid : offered[0] ?? "onnx";
|
|
6667
|
+
})();
|
|
6558
6668
|
const deviceOptions = runtimeDevices(backend, hardware);
|
|
6559
6669
|
const storedDevice = typeof merged.engineDevice === "string" ? merged.engineDevice : "";
|
|
6560
6670
|
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
|
|
6671
|
+
const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
|
|
6672
|
+
const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
|
|
6561
6673
|
const raw = {
|
|
6562
6674
|
...merged,
|
|
6563
6675
|
engineBackend: backend,
|
|
6564
|
-
engineDevice: device
|
|
6676
|
+
engineDevice: device,
|
|
6677
|
+
activeEngine
|
|
6565
6678
|
};
|
|
6566
6679
|
const schema = this.globalSettingsSchema();
|
|
6567
6680
|
if (!schema) return { sections: [] };
|
|
@@ -6592,15 +6705,15 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6592
6705
|
if (field.type === "slider" && "key" in field) {
|
|
6593
6706
|
const tuned = tuning[field.key];
|
|
6594
6707
|
const sliderField = field;
|
|
6595
|
-
let
|
|
6708
|
+
let patchedField = typeof tuned === "number" ? {
|
|
6596
6709
|
...sliderField,
|
|
6597
6710
|
default: tuned
|
|
6598
6711
|
} : sliderField;
|
|
6599
|
-
if (sliderField.key === "concurrency" && backend === "coreml")
|
|
6600
|
-
...
|
|
6712
|
+
if (sliderField.key === "concurrency" && backend === "coreml") patchedField = {
|
|
6713
|
+
...patchedField,
|
|
6601
6714
|
max: 4
|
|
6602
6715
|
};
|
|
6603
|
-
return
|
|
6716
|
+
return patchedField;
|
|
6604
6717
|
}
|
|
6605
6718
|
return field;
|
|
6606
6719
|
})
|
|
@@ -6617,23 +6730,41 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6617
6730
|
* Protected seam — overridable by tests (canned hardware) and reused by the
|
|
6618
6731
|
* Phase 2 auto-pick path. NEVER reads install state.
|
|
6619
6732
|
*/
|
|
6620
|
-
|
|
6621
|
-
|
|
6733
|
+
/**
|
|
6734
|
+
* Resolve the persisted UI settings store, or `null` when it can't be read
|
|
6735
|
+
* right now (ctx/settings not wired — mid-restart). A readable-but-empty
|
|
6736
|
+
* store (genuine first boot) returns `{}`, never null. `getGlobalSettings`
|
|
6737
|
+
* uses the null signal to avoid serving fabricated empty-store defaults
|
|
6738
|
+
* during the restart window. Seam kept protected so tests can drive the
|
|
6739
|
+
* unreadable vs empty vs populated cases without faking a full AddonContext.
|
|
6740
|
+
*/
|
|
6741
|
+
async resolveUiSettingsStore() {
|
|
6742
|
+
const settings = this.ctxIfReady?.settings;
|
|
6743
|
+
if (!settings) return null;
|
|
6744
|
+
return await settings.readAddonStore() ?? {};
|
|
6745
|
+
}
|
|
6746
|
+
async probeHardwareEnv(nodeId) {
|
|
6747
|
+
const node = nodeId ?? this.localNodeId();
|
|
6748
|
+
const hardware = await this.resolveProbeHardware(node);
|
|
6749
|
+
if (hardware) this.lastGoodHardwareByNode.set(node, hardware);
|
|
6750
|
+
const effective = hardware ?? this.lastGoodHardwareByNode.get(node) ?? null;
|
|
6622
6751
|
return {
|
|
6623
6752
|
platform: process.platform,
|
|
6624
6753
|
arch: process.arch,
|
|
6625
|
-
hardware
|
|
6754
|
+
hardware: effective
|
|
6626
6755
|
};
|
|
6627
6756
|
}
|
|
6628
6757
|
/**
|
|
6629
|
-
* Fetch the probed hardware from the platform-probe cap
|
|
6630
|
-
* the cap is not reachable (caller falls back to
|
|
6758
|
+
* Fetch the probed hardware from the platform-probe cap for `nodeId` (default
|
|
6759
|
+
* = self). Returns null when the cap is not reachable (caller falls back to
|
|
6760
|
+
* the registry's safe minimum).
|
|
6631
6761
|
*/
|
|
6632
|
-
async resolveProbeHardware() {
|
|
6762
|
+
async resolveProbeHardware(nodeId) {
|
|
6633
6763
|
try {
|
|
6634
6764
|
const api = this.ctxIfReady?.api;
|
|
6635
6765
|
if (!api) return null;
|
|
6636
|
-
const
|
|
6766
|
+
const node = nodeId ?? this.localNodeId();
|
|
6767
|
+
const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query(void 0, require_dist.nodePin(node)))?.hardware;
|
|
6637
6768
|
if (!hw) return null;
|
|
6638
6769
|
return {
|
|
6639
6770
|
npu: hw.npu ? { type: hw.npu.type } : null,
|
|
@@ -6644,6 +6775,61 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6644
6775
|
}
|
|
6645
6776
|
}
|
|
6646
6777
|
/**
|
|
6778
|
+
* Bare node id used to scope the engine cascade in the shared store. MUST
|
|
6779
|
+
* match the provider's `localProbeNodeId()` (kernel.localNodeId, default
|
|
6780
|
+
* 'hub') so the UI write path (this) and the provider read/write path
|
|
6781
|
+
* (loadEngine / autoPick / reprobe) target the SAME `<key>@<nodeId>`. The old
|
|
6782
|
+
* `?? this.ctx.id` fallback resolved to the ADDON id ('detection-pipeline')
|
|
6783
|
+
* when kernel.localNodeId was absent, so UI saves landed on a key the
|
|
6784
|
+
* provider never read — silently losing the per-node selection.
|
|
6785
|
+
*/
|
|
6786
|
+
localNodeId() {
|
|
6787
|
+
return normalizeEngineNodeId(this.ctxIfReady?.kernel?.localNodeId ?? "hub");
|
|
6788
|
+
}
|
|
6789
|
+
/**
|
|
6790
|
+
* Refresh this node's cached engine selection from the node-scoped store.
|
|
6791
|
+
* `resolveBackendTuning` is synchronous and the reprobe gate runs before the
|
|
6792
|
+
* provider exists, so both read these cached fields instead of the
|
|
6793
|
+
* cluster-shared bare keys (which belong to no single node). Best-effort: a
|
|
6794
|
+
* transiently-unreadable store leaves the last cached values in place.
|
|
6795
|
+
*/
|
|
6796
|
+
async refreshNodeEngineFromStore() {
|
|
6797
|
+
const store = await this.resolveUiSettingsStore();
|
|
6798
|
+
if (store === null) return;
|
|
6799
|
+
const node = this.localNodeId();
|
|
6800
|
+
const backend = readNodeEngineValue(store, "engineBackend", node);
|
|
6801
|
+
if (typeof backend === "string" && backend !== "") this.nodeEngineBackend = backend;
|
|
6802
|
+
const probed = readNodeEngineValue(store, "probedBestEngine", node);
|
|
6803
|
+
this.nodeProbedBestEngine = typeof probed === "string" ? probed : "";
|
|
6804
|
+
}
|
|
6805
|
+
/**
|
|
6806
|
+
* Persist a settings patch, mirroring the engine cascade fields to the TARGET
|
|
6807
|
+
* node's scoped keys so each node keeps an INDEPENDENT engine selection in the
|
|
6808
|
+
* cluster-central store. The hub addon serves writes for every node, so it
|
|
6809
|
+
* scopes by the requested `nodeId` (default self).
|
|
6810
|
+
*
|
|
6811
|
+
* When the target IS this node, `super.updateGlobalSettings` drives the normal
|
|
6812
|
+
* apply path (`resolveConfig` / `onConfigChanged` / `requiresRestart` restart),
|
|
6813
|
+
* and the bare engine keys it writes are shadowed by the node-scoped keys on
|
|
6814
|
+
* read. When the target is a SIBLING node, we persist the scoped engine keys +
|
|
6815
|
+
* the non-engine bare keys but DON'T run this node's restart/reprovision — the
|
|
6816
|
+
* owning node applies its own engine selection on its next (re)start.
|
|
6817
|
+
*/
|
|
6818
|
+
async updateGlobalSettings(patch, nodeId) {
|
|
6819
|
+
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6820
|
+
const patchRecord = patch;
|
|
6821
|
+
const scopedEngine = {};
|
|
6822
|
+
for (const key of ENGINE_CASCADE_KEYS) if (key in patchRecord) scopedEngine[nodeEngineKey(key, targetNode)] = patchRecord[key];
|
|
6823
|
+
if (Object.keys(scopedEngine).length > 0) await this.ctxIfReady?.settings?.writeAddonStore(scopedEngine);
|
|
6824
|
+
if (targetNode === this.localNodeId()) {
|
|
6825
|
+
await super.updateGlobalSettings(patch, nodeId);
|
|
6826
|
+
return;
|
|
6827
|
+
}
|
|
6828
|
+
const sharedPatch = {};
|
|
6829
|
+
for (const [k, v] of Object.entries(patchRecord)) if (!ENGINE_CASCADE_KEYS.includes(k)) sharedPatch[k] = v;
|
|
6830
|
+
if (Object.keys(sharedPatch).length > 0) await this.ctxIfReady?.settings?.writeAddonStore(sharedPatch);
|
|
6831
|
+
}
|
|
6832
|
+
/**
|
|
6647
6833
|
* Resolve the effective pool tuning for the configured backend.
|
|
6648
6834
|
*
|
|
6649
6835
|
* Reads the registry's `tuningFor(backend)` and ignores any persisted
|
|
@@ -6657,7 +6843,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6657
6843
|
* a reason to disagree.
|
|
6658
6844
|
*/
|
|
6659
6845
|
resolveBackendTuning() {
|
|
6660
|
-
const t = tuningFor(toRuntimeId(this.
|
|
6846
|
+
const t = tuningFor(toRuntimeId(this.nodeEngineBackend ?? DEFAULT_CONFIG.engineBackend));
|
|
6661
6847
|
const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
|
|
6662
6848
|
const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
|
|
6663
6849
|
return {
|
|
@@ -6676,6 +6862,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6676
6862
|
relativePath: ""
|
|
6677
6863
|
}).catch(() => "camstack-data/models");
|
|
6678
6864
|
if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
|
|
6865
|
+
await this.refreshNodeEngineFromStore();
|
|
6679
6866
|
this.pythonAddonDir = resolveAddonPythonDir();
|
|
6680
6867
|
const py = await ensurePythonReady(this.ctx.deps, this.ctx.logger);
|
|
6681
6868
|
if (py.ok && py.pythonPath) this.pythonPath = py.pythonPath;
|
|
@@ -6697,7 +6884,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6697
6884
|
});
|
|
6698
6885
|
await this.provider.init();
|
|
6699
6886
|
await this.provider.setApi(this.ctx);
|
|
6700
|
-
if (!this.
|
|
6887
|
+
if (!this.nodeProbedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
6701
6888
|
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6702
6889
|
});
|
|
6703
6890
|
await this.provider.warmPool();
|
|
@@ -6749,8 +6936,8 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6749
6936
|
/**
|
|
6750
6937
|
* Proactively install the OpenVINO Python package when Intel hardware
|
|
6751
6938
|
* (iGPU or NPU) is detected. Called once from `onInitialize`, before the
|
|
6752
|
-
* provider is constructed, so the module is
|
|
6753
|
-
*
|
|
6939
|
+
* provider is constructed, so the module is RUNNABLE when the engine is
|
|
6940
|
+
* first loaded (gated by `loadEngine`'s `isPythonBackendAvailable` check).
|
|
6754
6941
|
*
|
|
6755
6942
|
* Failure is non-fatal: a warning is logged and the addon continues with
|
|
6756
6943
|
* the onnx-cpu baseline. The hardware query itself is also best-effort —
|
|
@@ -6819,6 +7006,7 @@ var DetectionPipelineAddon = class extends require_dist.BaseAddon {
|
|
|
6819
7006
|
* lifecycle (engineFactory rebuild on next runPipeline).
|
|
6820
7007
|
*/
|
|
6821
7008
|
async onConfigChanged() {
|
|
7009
|
+
await this.refreshNodeEngineFromStore();
|
|
6822
7010
|
if (this.provider) await this.provider.onEngineSelectionChanged().catch((err) => {
|
|
6823
7011
|
this.ctx.logger.warn("engine provisioning re-select failed on config change", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6824
7012
|
});
|