@camstack/addon-pipeline 1.0.7 → 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 +2 -3
- package/dist/audio-analyzer/index.mjs +2 -3
- 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 +373 -184
- package/dist/detection-pipeline/index.mjs +373 -184
- package/dist/{dist-CjrjeaDd.mjs → dist-BA6DR_jV.mjs} +128 -5
- package/dist/{dist-G45MVm6i.js → dist-BLcTVvol.js} +151 -4
- 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 +3 -3
- package/dist/recorder/index.mjs +3 -3
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Tbqpu0v3.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/{hostInit-tIev5Gd9.mjs → hostInit-zRy9SzlX.mjs} +3 -3
- package/dist/stream-broker/index.js +19 -19
- package/dist/stream-broker/index.mjs +19 -19
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C0kKwNX_.js → MaskShapeCanvas-DI4BY7W2-DJ7ztnFv.js} +1 -1
- package/embed-dist/assets/MotionZonesSettings-NcxxQN8r-CQzEnQoq.js +1 -0
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-C2SRtNe6.js → PrivacyMaskSettings-APgPLF7p-Cl0eOy_U.js} +1 -1
- package/embed-dist/assets/{index-B2LRyXWh.js → index-CSuLwWK-.js} +3 -3
- 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/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DCsgcqTa.mjs +0 -26
- package/embed-dist/assets/MotionZonesSettings-C1EEbk2V-CYtJc892.js +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { B as
|
|
2
|
+
import { A as nodePin, B as BaseAddon, C as detectionPipelineCapability, J as hydrateSchema, L as supportedRuntimes$1, P as runtimeDevices$1, T as hfModelUrl, W as EventCategory, Z as parseJsonUnknown, a as COCO_TO_MACRO, et as sleep, i as COCO_80_LABELS, j as pipelineExecutorCapability, p as YAMNET_TO_MACRO, q as createEvent, r as AUDIO_MACRO_LABELS, t as APPLE_SA_TO_MACRO, w as evaluateZoneRules, x as defaultDeviceFor$1, z as errMsg } from "../dist-BA6DR_jV.mjs";
|
|
3
3
|
import { a as isModelDownloaded, i as ensureModel, n as deleteModelFromDisk } from "../model-download-service-C7AjBsX9-B0ekM6dF.mjs";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path$1 from "node:path";
|
|
@@ -2158,6 +2158,78 @@ var EngineFactory = class {
|
|
|
2158
2158
|
}
|
|
2159
2159
|
};
|
|
2160
2160
|
//#endregion
|
|
2161
|
+
//#region src/detection-pipeline/engine-store-keys.ts
|
|
2162
|
+
/**
|
|
2163
|
+
* Per-node scoping for the detection-pipeline engine cascade.
|
|
2164
|
+
*
|
|
2165
|
+
* The detection addon's settings store is a single CLUSTER-SHARED blob (the
|
|
2166
|
+
* settings-store cap is hub-resident; every node's detection instance reads and
|
|
2167
|
+
* writes the same keys). That is correct for node-agnostic settings (pipeline
|
|
2168
|
+
* steps, tuning) but WRONG for the engine cascade — `engineBackend` /
|
|
2169
|
+
* `engineDevice` / `probedBestEngine` are hardware-specific, so the hub (NPU)
|
|
2170
|
+
* and a remote agent (iGPU, or no accelerator at all) must hold INDEPENDENT
|
|
2171
|
+
* selections. Sharing them lets one node's pick (e.g. `openvino/npu`) override
|
|
2172
|
+
* another node that has no NPU.
|
|
2173
|
+
*
|
|
2174
|
+
* These three keys are therefore persisted node-scoped as `<key>@<nodeId>`.
|
|
2175
|
+
* Everything else in the store stays shared. A legacy un-scoped value (written
|
|
2176
|
+
* before this change, or by an older build) is read as a migration fallback and
|
|
2177
|
+
* re-persisted under the node-scoped key on the next write.
|
|
2178
|
+
*/
|
|
2179
|
+
var ENGINE_CASCADE_KEYS = [
|
|
2180
|
+
"engineBackend",
|
|
2181
|
+
"engineDevice",
|
|
2182
|
+
"probedBestEngine"
|
|
2183
|
+
];
|
|
2184
|
+
function isEngineCascadeKey(key) {
|
|
2185
|
+
return ENGINE_CASCADE_KEYS.includes(key);
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Normalise a raw kernel node id to the bare node id used for scoping.
|
|
2189
|
+
* `localNodeId` can carry a `<node>/<addon>` suffix; the engine selection is
|
|
2190
|
+
* per-NODE, so strip the addon segment. Falls back to `hub`.
|
|
2191
|
+
*/
|
|
2192
|
+
function normalizeEngineNodeId(rawNodeId) {
|
|
2193
|
+
const raw = rawNodeId ?? "hub";
|
|
2194
|
+
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
2195
|
+
}
|
|
2196
|
+
/** The node-scoped store key for an engine cascade field. */
|
|
2197
|
+
function nodeEngineKey(base, nodeId) {
|
|
2198
|
+
return `${base}@${normalizeEngineNodeId(nodeId)}`;
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* Read an engine cascade value for a node: the node-scoped key if present,
|
|
2202
|
+
* otherwise the legacy un-scoped value (migration), otherwise undefined.
|
|
2203
|
+
*/
|
|
2204
|
+
function readNodeEngineValue(store, base, nodeId) {
|
|
2205
|
+
const scoped = store[nodeEngineKey(base, nodeId)];
|
|
2206
|
+
return scoped !== void 0 ? scoped : store[base];
|
|
2207
|
+
}
|
|
2208
|
+
/**
|
|
2209
|
+
* Project a raw store onto the plain engine cascade keys for THIS node, so the
|
|
2210
|
+
* UI schema (whose field keys are the bare `engineBackend` etc.) hydrates from
|
|
2211
|
+
* the node's own selection. Non-engine keys are left untouched. Node-scoped
|
|
2212
|
+
* keys for OTHER nodes are dropped from the projection (not relevant to this
|
|
2213
|
+
* node's form).
|
|
2214
|
+
*/
|
|
2215
|
+
function projectNodeEngine(store, nodeId) {
|
|
2216
|
+
const out = {};
|
|
2217
|
+
const scopedForAnyNode = /* @__PURE__ */ new Set();
|
|
2218
|
+
for (const key of Object.keys(store)) {
|
|
2219
|
+
const atIdx = key.indexOf("@");
|
|
2220
|
+
if (isEngineCascadeKey(atIdx >= 0 ? key.slice(0, atIdx) : key)) {
|
|
2221
|
+
scopedForAnyNode.add(key);
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
out[key] = store[key];
|
|
2225
|
+
}
|
|
2226
|
+
for (const base of ENGINE_CASCADE_KEYS) {
|
|
2227
|
+
const value = readNodeEngineValue(store, base, nodeId);
|
|
2228
|
+
if (value !== void 0) out[base] = value;
|
|
2229
|
+
}
|
|
2230
|
+
return out;
|
|
2231
|
+
}
|
|
2232
|
+
//#endregion
|
|
2161
2233
|
//#region src/detection-pipeline/postprocess/dispatch.ts
|
|
2162
2234
|
var VALID_KINDS = new Set([
|
|
2163
2235
|
"detections",
|
|
@@ -2197,7 +2269,7 @@ function postprocessYolo(output, stepDef) {
|
|
|
2197
2269
|
const letterbox = output.letterbox;
|
|
2198
2270
|
const dets = [];
|
|
2199
2271
|
for (let i = 0; i < numBoxes; i++) {
|
|
2200
|
-
const cx = tensor[
|
|
2272
|
+
const cx = tensor[i];
|
|
2201
2273
|
const cy = tensor[1 * numBoxes + i];
|
|
2202
2274
|
const w = tensor[2 * numBoxes + i];
|
|
2203
2275
|
const h = tensor[3 * numBoxes + i];
|
|
@@ -2353,7 +2425,7 @@ function postprocessArcface(output, _stepDef) {
|
|
|
2353
2425
|
let sumSq = 0;
|
|
2354
2426
|
for (let i = 0; i < tensor.length; i++) sumSq += tensor[i] * tensor[i];
|
|
2355
2427
|
const norm = Math.sqrt(sumSq);
|
|
2356
|
-
const normalized =
|
|
2428
|
+
const normalized = Array.from({ length: tensor.length });
|
|
2357
2429
|
for (let i = 0; i < tensor.length; i++) normalized[i] = norm === 0 ? 0 : tensor[i] / norm;
|
|
2358
2430
|
return {
|
|
2359
2431
|
kind: "embedding",
|
|
@@ -3528,34 +3600,69 @@ function walkFieldsForDefaults(fields, out) {
|
|
|
3528
3600
|
}
|
|
3529
3601
|
//#endregion
|
|
3530
3602
|
//#region src/detection-pipeline/runtimes.ts
|
|
3531
|
-
var
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3603
|
+
var KNOWN_PLATFORMS = [
|
|
3604
|
+
"darwin",
|
|
3605
|
+
"linux",
|
|
3606
|
+
"win32"
|
|
3607
|
+
];
|
|
3608
|
+
var KNOWN_ARCHES = ["arm64", "x64"];
|
|
3609
|
+
var KNOWN_GPU_TYPES = [
|
|
3610
|
+
"nvidia",
|
|
3611
|
+
"amd",
|
|
3612
|
+
"intel",
|
|
3613
|
+
"apple"
|
|
3614
|
+
];
|
|
3615
|
+
var KNOWN_NPU_TYPES = ["apple-ane", "intel-npu"];
|
|
3616
|
+
function toKnownPlatform(p) {
|
|
3617
|
+
return KNOWN_PLATFORMS.find((v) => v === p) ?? "linux";
|
|
3618
|
+
}
|
|
3619
|
+
function toKnownArch(a) {
|
|
3620
|
+
return KNOWN_ARCHES.find((v) => v === a) ?? "x64";
|
|
3621
|
+
}
|
|
3622
|
+
function gpuInfoFrom(hw) {
|
|
3623
|
+
if (!hw.gpu) return null;
|
|
3624
|
+
const type = KNOWN_GPU_TYPES.find((v) => v === hw.gpu?.type);
|
|
3625
|
+
if (!type) return null;
|
|
3626
|
+
return {
|
|
3627
|
+
type,
|
|
3628
|
+
name: ""
|
|
3629
|
+
};
|
|
3630
|
+
}
|
|
3631
|
+
function npuInfoFrom(hw) {
|
|
3632
|
+
if (!hw.npu) return null;
|
|
3633
|
+
const type = KNOWN_NPU_TYPES.find((v) => v === hw.npu?.type);
|
|
3634
|
+
if (!type) return null;
|
|
3635
|
+
return { type };
|
|
3636
|
+
}
|
|
3637
|
+
function envToHardwareInfo(env) {
|
|
3638
|
+
if (!env.hardware) return null;
|
|
3639
|
+
return {
|
|
3640
|
+
platform: toKnownPlatform(env.platform),
|
|
3641
|
+
arch: toKnownArch(env.arch),
|
|
3642
|
+
cpuModel: "",
|
|
3643
|
+
cpuCores: 0,
|
|
3644
|
+
totalRAM_MB: 0,
|
|
3645
|
+
availableRAM_MB: 0,
|
|
3646
|
+
gpu: gpuInfoFrom(env.hardware),
|
|
3647
|
+
npu: npuInfoFrom(env.hardware)
|
|
3648
|
+
};
|
|
3649
|
+
}
|
|
3650
|
+
function probedToHardwareInfo(hw) {
|
|
3651
|
+
if (!hw) return null;
|
|
3652
|
+
return {
|
|
3653
|
+
platform: toKnownPlatform(process.platform),
|
|
3654
|
+
arch: toKnownArch(process.arch),
|
|
3655
|
+
cpuModel: "",
|
|
3656
|
+
cpuCores: 0,
|
|
3657
|
+
totalRAM_MB: 0,
|
|
3658
|
+
availableRAM_MB: 0,
|
|
3659
|
+
gpu: gpuInfoFrom(hw),
|
|
3660
|
+
npu: npuInfoFrom(hw)
|
|
3661
|
+
};
|
|
3662
|
+
}
|
|
3663
|
+
var RUNTIME_DETAIL = {
|
|
3664
|
+
onnx: {
|
|
3542
3665
|
label: "ONNX Runtime",
|
|
3543
|
-
supports: () => true,
|
|
3544
|
-
devices: (hw) => {
|
|
3545
|
-
if (!hw) return [CPU];
|
|
3546
|
-
const out = [CPU];
|
|
3547
|
-
if (hw.gpu?.type === "nvidia") out.push({
|
|
3548
|
-
value: "cuda",
|
|
3549
|
-
label: "CUDA"
|
|
3550
|
-
});
|
|
3551
|
-
if (hw.npu?.type === "apple-ane") out.push({
|
|
3552
|
-
value: "coreml",
|
|
3553
|
-
label: "CoreML EP"
|
|
3554
|
-
});
|
|
3555
|
-
return out;
|
|
3556
|
-
},
|
|
3557
|
-
defaultDevice: "cpu",
|
|
3558
|
-
modelFormat: "onnx",
|
|
3559
3666
|
pythonRequirements: ["requirements.txt", "requirements-onnxruntime.txt"],
|
|
3560
3667
|
tuning: {
|
|
3561
3668
|
concurrency: 4,
|
|
@@ -3564,25 +3671,8 @@ var RUNTIMES = [
|
|
|
3564
3671
|
intraOpThreads: 0
|
|
3565
3672
|
}
|
|
3566
3673
|
},
|
|
3567
|
-
{
|
|
3568
|
-
id: "openvino",
|
|
3674
|
+
openvino: {
|
|
3569
3675
|
label: "OpenVINO",
|
|
3570
|
-
supports: (env) => env.arch === "x64" && env.platform !== "darwin" && env.hardware?.gpu?.type === "intel",
|
|
3571
|
-
devices: (hw) => {
|
|
3572
|
-
if (!hw) return [AUTO];
|
|
3573
|
-
const out = [AUTO, CPU];
|
|
3574
|
-
if (hw.gpu?.type === "intel") out.push({
|
|
3575
|
-
value: "gpu",
|
|
3576
|
-
label: "GPU"
|
|
3577
|
-
});
|
|
3578
|
-
if (hw.npu?.type === "intel-npu") out.push({
|
|
3579
|
-
value: "npu",
|
|
3580
|
-
label: "NPU"
|
|
3581
|
-
});
|
|
3582
|
-
return out;
|
|
3583
|
-
},
|
|
3584
|
-
defaultDevice: "auto",
|
|
3585
|
-
modelFormat: "openvino",
|
|
3586
3676
|
pythonRequirements: ["requirements.txt", "requirements-openvino.txt"],
|
|
3587
3677
|
tuning: {
|
|
3588
3678
|
concurrency: 1,
|
|
@@ -3590,29 +3680,8 @@ var RUNTIMES = [
|
|
|
3590
3680
|
numStreams: 0
|
|
3591
3681
|
}
|
|
3592
3682
|
},
|
|
3593
|
-
{
|
|
3594
|
-
id: "coreml",
|
|
3683
|
+
coreml: {
|
|
3595
3684
|
label: "CoreML",
|
|
3596
|
-
supports: (env) => env.platform === "darwin",
|
|
3597
|
-
devices: (hw) => {
|
|
3598
|
-
const all = {
|
|
3599
|
-
value: "all",
|
|
3600
|
-
label: "All (ANE + GPU + CPU)"
|
|
3601
|
-
};
|
|
3602
|
-
if (!hw) return [all];
|
|
3603
|
-
const out = [all];
|
|
3604
|
-
if (hw.npu?.type === "apple-ane") out.push({
|
|
3605
|
-
value: "ane",
|
|
3606
|
-
label: "Apple Neural Engine"
|
|
3607
|
-
});
|
|
3608
|
-
out.push({
|
|
3609
|
-
value: "gpu",
|
|
3610
|
-
label: "GPU"
|
|
3611
|
-
}, CPU);
|
|
3612
|
-
return out;
|
|
3613
|
-
},
|
|
3614
|
-
defaultDevice: "all",
|
|
3615
|
-
modelFormat: "coreml",
|
|
3616
3685
|
pythonRequirements: ["requirements.txt", "requirements-coreml.txt"],
|
|
3617
3686
|
tuning: {
|
|
3618
3687
|
concurrency: 1,
|
|
@@ -3622,32 +3691,50 @@ var RUNTIMES = [
|
|
|
3622
3691
|
numWorkers: 1
|
|
3623
3692
|
}
|
|
3624
3693
|
}
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
}
|
|
3694
|
+
};
|
|
3695
|
+
/**
|
|
3696
|
+
* Returns the list of supported runtime IDs for the given hardware env.
|
|
3697
|
+
* Delegates to `@camstack/types` `supportedRuntimes`.
|
|
3698
|
+
*/
|
|
3631
3699
|
function supportedRuntimes(env) {
|
|
3632
|
-
return
|
|
3700
|
+
return supportedRuntimes$1(envToHardwareInfo(env));
|
|
3633
3701
|
}
|
|
3702
|
+
/**
|
|
3703
|
+
* Returns the device options for a given runtime and probed hardware.
|
|
3704
|
+
* Delegates to `@camstack/types` `runtimeDevices`.
|
|
3705
|
+
*/
|
|
3634
3706
|
function runtimeDevices(id, hardware) {
|
|
3635
|
-
return
|
|
3707
|
+
return runtimeDevices$1(id, probedToHardwareInfo(hardware));
|
|
3636
3708
|
}
|
|
3709
|
+
/**
|
|
3710
|
+
* Returns the default device string for a given runtime.
|
|
3711
|
+
* Delegates to `@camstack/types` `defaultDeviceFor`.
|
|
3712
|
+
*/
|
|
3637
3713
|
function defaultDeviceFor(id) {
|
|
3638
|
-
return
|
|
3639
|
-
}
|
|
3640
|
-
function pythonRequirementsFor(id) {
|
|
3641
|
-
return def(id).pythonRequirements;
|
|
3714
|
+
return defaultDeviceFor$1(id);
|
|
3642
3715
|
}
|
|
3716
|
+
/** Model format for each inference runtime supported by the detection pipeline. */
|
|
3717
|
+
var RUNTIME_FORMAT = {
|
|
3718
|
+
onnx: "onnx",
|
|
3719
|
+
openvino: "openvino",
|
|
3720
|
+
coreml: "coreml"
|
|
3721
|
+
};
|
|
3722
|
+
/**
|
|
3723
|
+
* Returns the model format required for a given runtime.
|
|
3724
|
+
* Returns the locally-typed format string ('onnx' | 'openvino' | 'coreml')
|
|
3725
|
+
* matching the engine-provisioner's own ModelFormat type.
|
|
3726
|
+
*/
|
|
3643
3727
|
function modelFormatFor(id) {
|
|
3644
|
-
return
|
|
3728
|
+
return RUNTIME_FORMAT[id];
|
|
3729
|
+
}
|
|
3730
|
+
function pythonRequirementsFor(id) {
|
|
3731
|
+
return RUNTIME_DETAIL[id].pythonRequirements;
|
|
3645
3732
|
}
|
|
3646
3733
|
function tuningFor(id) {
|
|
3647
|
-
return
|
|
3734
|
+
return RUNTIME_DETAIL[id].tuning;
|
|
3648
3735
|
}
|
|
3649
3736
|
function runtimeLabel(id) {
|
|
3650
|
-
return
|
|
3737
|
+
return RUNTIME_DETAIL[id].label;
|
|
3651
3738
|
}
|
|
3652
3739
|
/**
|
|
3653
3740
|
* Proactive-install hint kept for back-compat (re-exported from index.ts).
|
|
@@ -3988,6 +4075,27 @@ function toProbedHardware(hw) {
|
|
|
3988
4075
|
gpu: hw.gpu ? { type: hw.gpu.type } : null
|
|
3989
4076
|
};
|
|
3990
4077
|
}
|
|
4078
|
+
var ONNX_FLOOR = {
|
|
4079
|
+
runtime: "python",
|
|
4080
|
+
backend: "onnx",
|
|
4081
|
+
format: "onnx",
|
|
4082
|
+
device: "cpu"
|
|
4083
|
+
};
|
|
4084
|
+
/**
|
|
4085
|
+
* Build the onnx-cpu floor pick using `pickBestRuntime` with a null hardware
|
|
4086
|
+
* env. Used wherever the old `detectBestEngine()` sync probe fell back — the
|
|
4087
|
+
* result is identical (onnx / cpu) but is now derived through the shared rules
|
|
4088
|
+
* instead of duplicated inline.
|
|
4089
|
+
*/
|
|
4090
|
+
function onnxFloorPick() {
|
|
4091
|
+
const pick = pickBestRuntime(runtimeEnvFromProcess(null), null);
|
|
4092
|
+
return {
|
|
4093
|
+
runtime: "python",
|
|
4094
|
+
backend: pick.runtimeId,
|
|
4095
|
+
format: modelFormatFor(pick.runtimeId),
|
|
4096
|
+
device: pick.device
|
|
4097
|
+
};
|
|
4098
|
+
}
|
|
3991
4099
|
var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
3992
4100
|
modelsDir;
|
|
3993
4101
|
eventBus;
|
|
@@ -4077,7 +4185,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4077
4185
|
this.readStore = () => settings.readAddonStore();
|
|
4078
4186
|
this.writeStore = (patch) => settings.writeAddonStore(patch);
|
|
4079
4187
|
this.readDeviceStore = settings.readDeviceStore ?? (async () => ({}));
|
|
4080
|
-
this.currentEngine =
|
|
4188
|
+
this.currentEngine = ONNX_FLOOR;
|
|
4081
4189
|
this.log.info("Engine selected (default)", { meta: {
|
|
4082
4190
|
runtime: this.currentEngine.runtime,
|
|
4083
4191
|
backend: this.currentEngine.backend,
|
|
@@ -4143,62 +4251,27 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4143
4251
|
isReady() {
|
|
4144
4252
|
return this.ready;
|
|
4145
4253
|
}
|
|
4146
|
-
/** Detect the best inference engine for this platform. */
|
|
4147
|
-
static detectBestEngine() {
|
|
4148
|
-
const platform = process.platform;
|
|
4149
|
-
const arch = process.arch;
|
|
4150
|
-
if (platform === "darwin" && arch === "arm64") return {
|
|
4151
|
-
runtime: "python",
|
|
4152
|
-
backend: "coreml",
|
|
4153
|
-
format: "coreml",
|
|
4154
|
-
device: "all"
|
|
4155
|
-
};
|
|
4156
|
-
if (platform === "darwin") return {
|
|
4157
|
-
runtime: "python",
|
|
4158
|
-
backend: "coreml",
|
|
4159
|
-
format: "coreml",
|
|
4160
|
-
device: "gpu"
|
|
4161
|
-
};
|
|
4162
|
-
try {
|
|
4163
|
-
const { execFileSync } = __require("node:child_process");
|
|
4164
|
-
execFileSync("nvidia-smi", ["--query-gpu=name", "--format=csv,noheader"], {
|
|
4165
|
-
timeout: 3e3,
|
|
4166
|
-
stdio: "pipe"
|
|
4167
|
-
});
|
|
4168
|
-
return {
|
|
4169
|
-
runtime: "python",
|
|
4170
|
-
backend: "onnx",
|
|
4171
|
-
format: "onnx",
|
|
4172
|
-
device: "cuda"
|
|
4173
|
-
};
|
|
4174
|
-
} catch {}
|
|
4175
|
-
try {
|
|
4176
|
-
const fsmod = __require("node:fs");
|
|
4177
|
-
const isIntel = __require("node:os").cpus()[0]?.model?.includes("Intel") ?? false;
|
|
4178
|
-
const hasIgpu = fsmod.existsSync("/dev/dri/renderD128");
|
|
4179
|
-
const hasNpu = fsmod.existsSync("/dev/accel/accel0") || fsmod.existsSync("/dev/accel");
|
|
4180
|
-
if (isIntel && (hasIgpu || hasNpu)) return {
|
|
4181
|
-
runtime: "python",
|
|
4182
|
-
backend: "openvino",
|
|
4183
|
-
format: "openvino",
|
|
4184
|
-
device: "auto"
|
|
4185
|
-
};
|
|
4186
|
-
} catch {}
|
|
4187
|
-
return {
|
|
4188
|
-
runtime: "python",
|
|
4189
|
-
backend: "onnx",
|
|
4190
|
-
format: "onnx",
|
|
4191
|
-
device: "cpu"
|
|
4192
|
-
};
|
|
4193
|
-
}
|
|
4194
4254
|
/** Store the addon context. ctx.api is a lazy getter resolved at call time. */
|
|
4195
4255
|
async setApi(addonCtx) {
|
|
4196
4256
|
this.addonCtx = addonCtx;
|
|
4197
|
-
if (this.needsAutoPick) {
|
|
4257
|
+
if (this.needsAutoPick) if (this.addonCtx.useCapability("platform-probe").isReady) {
|
|
4198
4258
|
await this.autoPickAndPersist();
|
|
4199
4259
|
this.needsAutoPick = false;
|
|
4260
|
+
this.startProvisioningForCurrentEngine();
|
|
4261
|
+
} else {
|
|
4262
|
+
this.startProvisioningForCurrentEngine();
|
|
4263
|
+
const unsubscribe = this.addonCtx.onCapabilityStateChange("platform-probe", { type: "global" }, (state) => {
|
|
4264
|
+
if (state !== "ready") return;
|
|
4265
|
+
unsubscribe();
|
|
4266
|
+
if (!this.needsAutoPick) return;
|
|
4267
|
+
this.autoPickAndPersist().then(() => {
|
|
4268
|
+
this.needsAutoPick = false;
|
|
4269
|
+
this.startProvisioningForCurrentEngine();
|
|
4270
|
+
});
|
|
4271
|
+
});
|
|
4272
|
+
this.addonCtx.addDisposer(unsubscribe);
|
|
4200
4273
|
}
|
|
4201
|
-
this.startProvisioningForCurrentEngine();
|
|
4274
|
+
else this.startProvisioningForCurrentEngine();
|
|
4202
4275
|
}
|
|
4203
4276
|
/**
|
|
4204
4277
|
* Auto-pick the best supported runtime at first boot (no stored engine).
|
|
@@ -4213,7 +4286,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4213
4286
|
try {
|
|
4214
4287
|
const api = this.addonCtx?.api;
|
|
4215
4288
|
if (api) {
|
|
4216
|
-
const caps = await api.platformProbe.getCapabilities.query();
|
|
4289
|
+
const caps = await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() });
|
|
4217
4290
|
hardware = caps?.hardware ?? null;
|
|
4218
4291
|
const bs = caps?.bestScore;
|
|
4219
4292
|
if (bs && bs.runtime === "python") bestBackendHint = bs.backend;
|
|
@@ -4227,9 +4300,10 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4227
4300
|
device: pick.device
|
|
4228
4301
|
};
|
|
4229
4302
|
this.currentEngine = engine;
|
|
4303
|
+
const apNode = this.localProbeNodeId();
|
|
4230
4304
|
await this.writeStore({
|
|
4231
|
-
engineBackend: pick.runtimeId,
|
|
4232
|
-
engineDevice: pick.device
|
|
4305
|
+
[nodeEngineKey("engineBackend", apNode)]: pick.runtimeId,
|
|
4306
|
+
[nodeEngineKey("engineDevice", apNode)]: pick.device
|
|
4233
4307
|
});
|
|
4234
4308
|
this.log.info("Auto-picked engine at first boot", { meta: {
|
|
4235
4309
|
backend: pick.runtimeId,
|
|
@@ -4402,11 +4476,22 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
4402
4476
|
* callers fall back to the registry's safe minimum. The engine OFFER derives
|
|
4403
4477
|
* from hardware ONLY — install state (probe `scores`) never gates it.
|
|
4404
4478
|
*/
|
|
4479
|
+
/**
|
|
4480
|
+
* The local Moleculer node id (child-suffix stripped). MUST be passed to every
|
|
4481
|
+
* `platformProbe.getCapabilities` query: the cap is a singleton and a query
|
|
4482
|
+
* with no nodeId resolves to the HUB's probe — so on a remote agent the engine
|
|
4483
|
+
* decision would use the HUB's hardware (e.g. an Intel NPU the agent doesn't
|
|
4484
|
+
* have) and pin a device the node can't run, breaking provisioning.
|
|
4485
|
+
*/
|
|
4486
|
+
localProbeNodeId() {
|
|
4487
|
+
const raw = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
4488
|
+
return raw.includes("/") ? raw.split("/")[0] : raw;
|
|
4489
|
+
}
|
|
4405
4490
|
async fetchProbeGatingData() {
|
|
4406
4491
|
try {
|
|
4407
4492
|
const api = this.addonCtx?.api;
|
|
4408
4493
|
if (!api) return { hardware: null };
|
|
4409
|
-
return { hardware: (await api.platformProbe.getCapabilities.query())?.hardware ?? null };
|
|
4494
|
+
return { hardware: (await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() }))?.hardware ?? null };
|
|
4410
4495
|
} catch {
|
|
4411
4496
|
return { hardware: null };
|
|
4412
4497
|
}
|
|
@@ -5471,7 +5556,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5471
5556
|
this.engineFactory = null;
|
|
5472
5557
|
this.executor = null;
|
|
5473
5558
|
}
|
|
5474
|
-
for (const id of
|
|
5559
|
+
for (const id of Array.from(this.deviceProxies.keys())) this.releaseDeviceProxy(id);
|
|
5475
5560
|
}
|
|
5476
5561
|
/**
|
|
5477
5562
|
* Resolve and cache a {@link DeviceProxy} for the given camera. Pins
|
|
@@ -5655,25 +5740,27 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5655
5740
|
* fields (`engineRuntime`, `engineBackend`, `engineDevice`). Fallback
|
|
5656
5741
|
* to the legacy `KEY_ENGINE` JSON blob for pre-migration stores.
|
|
5657
5742
|
* Returns null when neither source has anything; the caller keeps
|
|
5658
|
-
*
|
|
5743
|
+
* the onnx floor set at construction until autoPickAndPersist() runs.
|
|
5659
5744
|
*/
|
|
5660
5745
|
async loadEngine() {
|
|
5661
5746
|
const store = await this.readStore();
|
|
5662
5747
|
const storedRuntime = store["engineRuntime"];
|
|
5663
|
-
const
|
|
5748
|
+
const node = this.localProbeNodeId();
|
|
5749
|
+
const storedBackend = readNodeEngineValue(store, "engineBackend", node);
|
|
5664
5750
|
if (typeof storedBackend === "string" && storedBackend.length > 0) {
|
|
5665
5751
|
const backend = storedBackend;
|
|
5666
|
-
const
|
|
5667
|
-
const
|
|
5752
|
+
const storedDeviceRaw = readNodeEngineValue(store, "engineDevice", node);
|
|
5753
|
+
const storedDevice = typeof storedDeviceRaw === "string" ? storedDeviceRaw : "";
|
|
5754
|
+
const floor = onnxFloorPick();
|
|
5668
5755
|
const migratedBackend = typeof storedRuntime === "string" && storedRuntime === "node" && backend === "cpu" ? "onnx" : backend;
|
|
5669
5756
|
if (!DetectionPipelineProvider.isPythonBackendAvailable(migratedBackend, this.executorOptions.pythonPath ?? "")) {
|
|
5670
|
-
this.log.warn("Stored engine backend unavailable on this node — falling back to
|
|
5757
|
+
this.log.warn("Stored engine backend unavailable on this node — falling back to onnx floor", { meta: {
|
|
5671
5758
|
stored: migratedBackend,
|
|
5672
|
-
fallback: `${
|
|
5759
|
+
fallback: `${floor.backend}`
|
|
5673
5760
|
} });
|
|
5674
|
-
return
|
|
5761
|
+
return floor;
|
|
5675
5762
|
}
|
|
5676
|
-
const device = storedDevice ||
|
|
5763
|
+
const device = storedDevice || floor.device;
|
|
5677
5764
|
return {
|
|
5678
5765
|
runtime: "python",
|
|
5679
5766
|
backend: migratedBackend,
|
|
@@ -5845,18 +5932,14 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5845
5932
|
const api = this.addonCtx?.api;
|
|
5846
5933
|
let best;
|
|
5847
5934
|
if (api) try {
|
|
5848
|
-
const caps = await api.platformProbe.getCapabilities.query();
|
|
5935
|
+
const caps = await api.platformProbe.getCapabilities.query({ nodeId: this.localProbeNodeId() });
|
|
5849
5936
|
const bs = caps?.bestScore;
|
|
5850
5937
|
if (bs && bs.runtime === "python") {
|
|
5851
5938
|
const probeBackend = bs.backend;
|
|
5852
5939
|
const probeDevice = (() => {
|
|
5853
5940
|
const hw = caps.hardware;
|
|
5854
|
-
if (probeBackend === "
|
|
5855
|
-
if (probeBackend === "
|
|
5856
|
-
if (hw?.npu?.type === "intel-npu") return "npu";
|
|
5857
|
-
if (hw?.gpu?.type === "intel") return "gpu";
|
|
5858
|
-
return "auto";
|
|
5859
|
-
}
|
|
5941
|
+
if (probeBackend === "openvino") return defaultDeviceFor("openvino");
|
|
5942
|
+
if (probeBackend === "coreml") return defaultDeviceFor("coreml");
|
|
5860
5943
|
if (probeBackend === "onnx") return hw?.gpu?.type === "nvidia" ? "cuda" : "cpu";
|
|
5861
5944
|
return "cpu";
|
|
5862
5945
|
})();
|
|
@@ -5866,16 +5949,17 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
5866
5949
|
format: backendToFormat(probeBackend),
|
|
5867
5950
|
device: probeDevice
|
|
5868
5951
|
};
|
|
5869
|
-
} else best =
|
|
5952
|
+
} else best = onnxFloorPick();
|
|
5870
5953
|
} catch {
|
|
5871
|
-
best =
|
|
5954
|
+
best = onnxFloorPick();
|
|
5872
5955
|
}
|
|
5873
|
-
else best =
|
|
5956
|
+
else best = onnxFloorPick();
|
|
5874
5957
|
const probedLabel = `${best.backend}/${best.device ?? "default"}`;
|
|
5958
|
+
const rpNode = this.localProbeNodeId();
|
|
5875
5959
|
await this.writeStore({
|
|
5876
|
-
probedBestEngine: probedLabel,
|
|
5877
|
-
engineBackend: best.backend,
|
|
5878
|
-
engineDevice: best.device ?? "cpu"
|
|
5960
|
+
[nodeEngineKey("probedBestEngine", rpNode)]: probedLabel,
|
|
5961
|
+
[nodeEngineKey("engineBackend", rpNode)]: best.backend,
|
|
5962
|
+
[nodeEngineKey("engineDevice", rpNode)]: best.device ?? "cpu"
|
|
5879
5963
|
});
|
|
5880
5964
|
this.log.info("Re-probed engine — wrote back engineBackend + engineDevice", { meta: {
|
|
5881
5965
|
backend: best.backend,
|
|
@@ -6298,7 +6382,8 @@ var DEFAULT_CONFIG = {
|
|
|
6298
6382
|
engineRuntime: "python",
|
|
6299
6383
|
engineBackend: "onnx",
|
|
6300
6384
|
engineDevice: "cpu",
|
|
6301
|
-
probedBestEngine: ""
|
|
6385
|
+
probedBestEngine: "",
|
|
6386
|
+
activeEngine: ""
|
|
6302
6387
|
};
|
|
6303
6388
|
/** Derive the model-format from a backend value. Called by the provider. */
|
|
6304
6389
|
function backendToFormat(backend) {
|
|
@@ -6341,6 +6426,15 @@ var POOL_BOUND_KEYS = [
|
|
|
6341
6426
|
];
|
|
6342
6427
|
var DetectionPipelineAddon = class extends BaseAddon {
|
|
6343
6428
|
provider = null;
|
|
6429
|
+
/** Last non-null probed hardware PER NODE — reused when the probe transiently
|
|
6430
|
+
* returns null so offered backends / device lists don't collapse. Keyed by
|
|
6431
|
+
* node because the hub addon probes other nodes when serving their config. */
|
|
6432
|
+
lastGoodHardwareByNode = /* @__PURE__ */ new Map();
|
|
6433
|
+
/** This node's effective engine selection, cached from the node-scoped store
|
|
6434
|
+
* so the synchronous `resolveBackendTuning` and the reprobe gate don't read
|
|
6435
|
+
* the cluster-shared bare keys. Refreshed at init + on every config change. */
|
|
6436
|
+
nodeEngineBackend = DEFAULT_CONFIG.engineBackend;
|
|
6437
|
+
nodeProbedBestEngine = "";
|
|
6344
6438
|
engineMetricsTimer = null;
|
|
6345
6439
|
/** Snapshot-equality cache for engine-metrics emit. Most ticks
|
|
6346
6440
|
* the engine inventory is unchanged (no model load/unload), so
|
|
@@ -6395,6 +6489,14 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6395
6489
|
tooltip: "Re-probe engine"
|
|
6396
6490
|
}]
|
|
6397
6491
|
}),
|
|
6492
|
+
this.field({
|
|
6493
|
+
type: "text",
|
|
6494
|
+
key: "activeEngine",
|
|
6495
|
+
label: "Active engine",
|
|
6496
|
+
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.",
|
|
6497
|
+
readonlyField: true,
|
|
6498
|
+
default: ""
|
|
6499
|
+
}),
|
|
6398
6500
|
this.field({
|
|
6399
6501
|
type: "select",
|
|
6400
6502
|
key: "engineBackend",
|
|
@@ -6531,14 +6633,18 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6531
6633
|
* probe is unreachable). Stored backend / device snap back to the registry
|
|
6532
6634
|
* floor / default when they fall outside the offered set.
|
|
6533
6635
|
*/
|
|
6534
|
-
async getGlobalSettings(overlay) {
|
|
6535
|
-
const
|
|
6536
|
-
const
|
|
6636
|
+
async getGlobalSettings(overlay, _cap, nodeId) {
|
|
6637
|
+
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6638
|
+
const rawStored = await this.resolveUiSettingsStore();
|
|
6639
|
+
if (rawStored === null) return null;
|
|
6640
|
+
const stored = projectNodeEngine(rawStored, targetNode);
|
|
6641
|
+
const storedBackendRaw = stored["engineBackend"];
|
|
6642
|
+
if (typeof storedBackendRaw !== "string" || storedBackendRaw === "") return null;
|
|
6537
6643
|
const merged = overlay ? {
|
|
6538
6644
|
...stored,
|
|
6539
6645
|
...overlay
|
|
6540
6646
|
} : stored;
|
|
6541
|
-
const env = await this.probeHardwareEnv();
|
|
6647
|
+
const env = await this.probeHardwareEnv(targetNode);
|
|
6542
6648
|
const hardware = env.hardware;
|
|
6543
6649
|
const offered = supportedRuntimes(env);
|
|
6544
6650
|
const runtimeBackends = offered.map((id) => ({
|
|
@@ -6546,14 +6652,22 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6546
6652
|
label: runtimeLabel(id)
|
|
6547
6653
|
}));
|
|
6548
6654
|
const storedBackend = typeof merged.engineBackend === "string" ? merged.engineBackend : "";
|
|
6549
|
-
const backend =
|
|
6655
|
+
const backend = (() => {
|
|
6656
|
+
const rid = toRuntimeId(storedBackend);
|
|
6657
|
+
if (rid === "onnx") return "onnx";
|
|
6658
|
+
if (offered.includes(rid)) return rid;
|
|
6659
|
+
return rid === "openvino" && env.platform !== "darwin" && env.arch === "x64" || rid === "coreml" && env.platform === "darwin" ? rid : offered[0] ?? "onnx";
|
|
6660
|
+
})();
|
|
6550
6661
|
const deviceOptions = runtimeDevices(backend, hardware);
|
|
6551
6662
|
const storedDevice = typeof merged.engineDevice === "string" ? merged.engineDevice : "";
|
|
6552
6663
|
const device = deviceOptions.find((d) => d.value === storedDevice)?.value ?? defaultDeviceFor(backend);
|
|
6664
|
+
const prov = targetNode === this.localNodeId() ? this.provider?.getEngineProvisioning() : void 0;
|
|
6665
|
+
const activeEngine = prov && prov.runtimeId ? `${prov.runtimeId}/${prov.device ?? "default"} (${prov.state})` : "";
|
|
6553
6666
|
const raw = {
|
|
6554
6667
|
...merged,
|
|
6555
6668
|
engineBackend: backend,
|
|
6556
|
-
engineDevice: device
|
|
6669
|
+
engineDevice: device,
|
|
6670
|
+
activeEngine
|
|
6557
6671
|
};
|
|
6558
6672
|
const schema = this.globalSettingsSchema();
|
|
6559
6673
|
if (!schema) return { sections: [] };
|
|
@@ -6584,15 +6698,15 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6584
6698
|
if (field.type === "slider" && "key" in field) {
|
|
6585
6699
|
const tuned = tuning[field.key];
|
|
6586
6700
|
const sliderField = field;
|
|
6587
|
-
let
|
|
6701
|
+
let patchedField = typeof tuned === "number" ? {
|
|
6588
6702
|
...sliderField,
|
|
6589
6703
|
default: tuned
|
|
6590
6704
|
} : sliderField;
|
|
6591
|
-
if (sliderField.key === "concurrency" && backend === "coreml")
|
|
6592
|
-
...
|
|
6705
|
+
if (sliderField.key === "concurrency" && backend === "coreml") patchedField = {
|
|
6706
|
+
...patchedField,
|
|
6593
6707
|
max: 4
|
|
6594
6708
|
};
|
|
6595
|
-
return
|
|
6709
|
+
return patchedField;
|
|
6596
6710
|
}
|
|
6597
6711
|
return field;
|
|
6598
6712
|
})
|
|
@@ -6609,23 +6723,41 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6609
6723
|
* Protected seam — overridable by tests (canned hardware) and reused by the
|
|
6610
6724
|
* Phase 2 auto-pick path. NEVER reads install state.
|
|
6611
6725
|
*/
|
|
6612
|
-
|
|
6613
|
-
|
|
6726
|
+
/**
|
|
6727
|
+
* Resolve the persisted UI settings store, or `null` when it can't be read
|
|
6728
|
+
* right now (ctx/settings not wired — mid-restart). A readable-but-empty
|
|
6729
|
+
* store (genuine first boot) returns `{}`, never null. `getGlobalSettings`
|
|
6730
|
+
* uses the null signal to avoid serving fabricated empty-store defaults
|
|
6731
|
+
* during the restart window. Seam kept protected so tests can drive the
|
|
6732
|
+
* unreadable vs empty vs populated cases without faking a full AddonContext.
|
|
6733
|
+
*/
|
|
6734
|
+
async resolveUiSettingsStore() {
|
|
6735
|
+
const settings = this.ctxIfReady?.settings;
|
|
6736
|
+
if (!settings) return null;
|
|
6737
|
+
return await settings.readAddonStore() ?? {};
|
|
6738
|
+
}
|
|
6739
|
+
async probeHardwareEnv(nodeId) {
|
|
6740
|
+
const node = nodeId ?? this.localNodeId();
|
|
6741
|
+
const hardware = await this.resolveProbeHardware(node);
|
|
6742
|
+
if (hardware) this.lastGoodHardwareByNode.set(node, hardware);
|
|
6743
|
+
const effective = hardware ?? this.lastGoodHardwareByNode.get(node) ?? null;
|
|
6614
6744
|
return {
|
|
6615
6745
|
platform: process.platform,
|
|
6616
6746
|
arch: process.arch,
|
|
6617
|
-
hardware
|
|
6747
|
+
hardware: effective
|
|
6618
6748
|
};
|
|
6619
6749
|
}
|
|
6620
6750
|
/**
|
|
6621
|
-
* Fetch the probed hardware from the platform-probe cap
|
|
6622
|
-
* the cap is not reachable (caller falls back to
|
|
6751
|
+
* Fetch the probed hardware from the platform-probe cap for `nodeId` (default
|
|
6752
|
+
* = self). Returns null when the cap is not reachable (caller falls back to
|
|
6753
|
+
* the registry's safe minimum).
|
|
6623
6754
|
*/
|
|
6624
|
-
async resolveProbeHardware() {
|
|
6755
|
+
async resolveProbeHardware(nodeId) {
|
|
6625
6756
|
try {
|
|
6626
6757
|
const api = this.ctxIfReady?.api;
|
|
6627
6758
|
if (!api) return null;
|
|
6628
|
-
const
|
|
6759
|
+
const node = nodeId ?? this.localNodeId();
|
|
6760
|
+
const hw = (node === this.localNodeId() ? await api.platformProbe.getCapabilities.query() : await api.platformProbe.getCapabilities.query(void 0, nodePin(node)))?.hardware;
|
|
6629
6761
|
if (!hw) return null;
|
|
6630
6762
|
return {
|
|
6631
6763
|
npu: hw.npu ? { type: hw.npu.type } : null,
|
|
@@ -6636,6 +6768,61 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6636
6768
|
}
|
|
6637
6769
|
}
|
|
6638
6770
|
/**
|
|
6771
|
+
* Bare node id used to scope the engine cascade in the shared store. MUST
|
|
6772
|
+
* match the provider's `localProbeNodeId()` (kernel.localNodeId, default
|
|
6773
|
+
* 'hub') so the UI write path (this) and the provider read/write path
|
|
6774
|
+
* (loadEngine / autoPick / reprobe) target the SAME `<key>@<nodeId>`. The old
|
|
6775
|
+
* `?? this.ctx.id` fallback resolved to the ADDON id ('detection-pipeline')
|
|
6776
|
+
* when kernel.localNodeId was absent, so UI saves landed on a key the
|
|
6777
|
+
* provider never read — silently losing the per-node selection.
|
|
6778
|
+
*/
|
|
6779
|
+
localNodeId() {
|
|
6780
|
+
return normalizeEngineNodeId(this.ctxIfReady?.kernel?.localNodeId ?? "hub");
|
|
6781
|
+
}
|
|
6782
|
+
/**
|
|
6783
|
+
* Refresh this node's cached engine selection from the node-scoped store.
|
|
6784
|
+
* `resolveBackendTuning` is synchronous and the reprobe gate runs before the
|
|
6785
|
+
* provider exists, so both read these cached fields instead of the
|
|
6786
|
+
* cluster-shared bare keys (which belong to no single node). Best-effort: a
|
|
6787
|
+
* transiently-unreadable store leaves the last cached values in place.
|
|
6788
|
+
*/
|
|
6789
|
+
async refreshNodeEngineFromStore() {
|
|
6790
|
+
const store = await this.resolveUiSettingsStore();
|
|
6791
|
+
if (store === null) return;
|
|
6792
|
+
const node = this.localNodeId();
|
|
6793
|
+
const backend = readNodeEngineValue(store, "engineBackend", node);
|
|
6794
|
+
if (typeof backend === "string" && backend !== "") this.nodeEngineBackend = backend;
|
|
6795
|
+
const probed = readNodeEngineValue(store, "probedBestEngine", node);
|
|
6796
|
+
this.nodeProbedBestEngine = typeof probed === "string" ? probed : "";
|
|
6797
|
+
}
|
|
6798
|
+
/**
|
|
6799
|
+
* Persist a settings patch, mirroring the engine cascade fields to the TARGET
|
|
6800
|
+
* node's scoped keys so each node keeps an INDEPENDENT engine selection in the
|
|
6801
|
+
* cluster-central store. The hub addon serves writes for every node, so it
|
|
6802
|
+
* scopes by the requested `nodeId` (default self).
|
|
6803
|
+
*
|
|
6804
|
+
* When the target IS this node, `super.updateGlobalSettings` drives the normal
|
|
6805
|
+
* apply path (`resolveConfig` / `onConfigChanged` / `requiresRestart` restart),
|
|
6806
|
+
* and the bare engine keys it writes are shadowed by the node-scoped keys on
|
|
6807
|
+
* read. When the target is a SIBLING node, we persist the scoped engine keys +
|
|
6808
|
+
* the non-engine bare keys but DON'T run this node's restart/reprovision — the
|
|
6809
|
+
* owning node applies its own engine selection on its next (re)start.
|
|
6810
|
+
*/
|
|
6811
|
+
async updateGlobalSettings(patch, nodeId) {
|
|
6812
|
+
const targetNode = nodeId ? normalizeEngineNodeId(nodeId) : this.localNodeId();
|
|
6813
|
+
const patchRecord = patch;
|
|
6814
|
+
const scopedEngine = {};
|
|
6815
|
+
for (const key of ENGINE_CASCADE_KEYS) if (key in patchRecord) scopedEngine[nodeEngineKey(key, targetNode)] = patchRecord[key];
|
|
6816
|
+
if (Object.keys(scopedEngine).length > 0) await this.ctxIfReady?.settings?.writeAddonStore(scopedEngine);
|
|
6817
|
+
if (targetNode === this.localNodeId()) {
|
|
6818
|
+
await super.updateGlobalSettings(patch, nodeId);
|
|
6819
|
+
return;
|
|
6820
|
+
}
|
|
6821
|
+
const sharedPatch = {};
|
|
6822
|
+
for (const [k, v] of Object.entries(patchRecord)) if (!ENGINE_CASCADE_KEYS.includes(k)) sharedPatch[k] = v;
|
|
6823
|
+
if (Object.keys(sharedPatch).length > 0) await this.ctxIfReady?.settings?.writeAddonStore(sharedPatch);
|
|
6824
|
+
}
|
|
6825
|
+
/**
|
|
6639
6826
|
* Resolve the effective pool tuning for the configured backend.
|
|
6640
6827
|
*
|
|
6641
6828
|
* Reads the registry's `tuningFor(backend)` and ignores any persisted
|
|
@@ -6649,7 +6836,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6649
6836
|
* a reason to disagree.
|
|
6650
6837
|
*/
|
|
6651
6838
|
resolveBackendTuning() {
|
|
6652
|
-
const t = tuningFor(toRuntimeId(this.
|
|
6839
|
+
const t = tuningFor(toRuntimeId(this.nodeEngineBackend ?? DEFAULT_CONFIG.engineBackend));
|
|
6653
6840
|
const num = (v, dflt) => typeof v === "number" && v > 0 ? v : dflt;
|
|
6654
6841
|
const batch = (v, dflt) => v === "none" || v === "list" || v === "window" ? v : dflt;
|
|
6655
6842
|
return {
|
|
@@ -6668,6 +6855,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6668
6855
|
relativePath: ""
|
|
6669
6856
|
}).catch(() => "camstack-data/models");
|
|
6670
6857
|
if (!this.ctx.settings) throw new Error("DetectionPipelineAddon: ctx.settings not available");
|
|
6858
|
+
await this.refreshNodeEngineFromStore();
|
|
6671
6859
|
this.pythonAddonDir = resolveAddonPythonDir();
|
|
6672
6860
|
const py = await ensurePythonReady(this.ctx.deps, this.ctx.logger);
|
|
6673
6861
|
if (py.ok && py.pythonPath) this.pythonPath = py.pythonPath;
|
|
@@ -6689,7 +6877,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6689
6877
|
});
|
|
6690
6878
|
await this.provider.init();
|
|
6691
6879
|
await this.provider.setApi(this.ctx);
|
|
6692
|
-
if (!this.
|
|
6880
|
+
if (!this.nodeProbedBestEngine) await this.provider.reprobeEngine().catch((err) => {
|
|
6693
6881
|
this.ctx.logger.warn("auto-reprobe engine failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6694
6882
|
});
|
|
6695
6883
|
await this.provider.warmPool();
|
|
@@ -6741,8 +6929,8 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6741
6929
|
/**
|
|
6742
6930
|
* Proactively install the OpenVINO Python package when Intel hardware
|
|
6743
6931
|
* (iGPU or NPU) is detected. Called once from `onInitialize`, before the
|
|
6744
|
-
* provider is constructed, so the module is
|
|
6745
|
-
*
|
|
6932
|
+
* provider is constructed, so the module is RUNNABLE when the engine is
|
|
6933
|
+
* first loaded (gated by `loadEngine`'s `isPythonBackendAvailable` check).
|
|
6746
6934
|
*
|
|
6747
6935
|
* Failure is non-fatal: a warning is logged and the addon continues with
|
|
6748
6936
|
* the onnx-cpu baseline. The hardware query itself is also best-effort —
|
|
@@ -6811,6 +6999,7 @@ var DetectionPipelineAddon = class extends BaseAddon {
|
|
|
6811
6999
|
* lifecycle (engineFactory rebuild on next runPipeline).
|
|
6812
7000
|
*/
|
|
6813
7001
|
async onConfigChanged() {
|
|
7002
|
+
await this.refreshNodeEngineFromStore();
|
|
6814
7003
|
if (this.provider) await this.provider.onEngineSelectionChanged().catch((err) => {
|
|
6815
7004
|
this.ctx.logger.warn("engine provisioning re-select failed on config change", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
6816
7005
|
});
|