@camstack/system 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/addon-runner.js +40 -23
- package/dist/addon-runner.mjs +20 -4
- package/dist/addon-utils.d.ts +20 -0
- package/dist/addon-utils.js +11 -0
- package/dist/addon-utils.mjs +3 -0
- package/dist/builtins/device-manager/device-manager.addon.js +8 -8
- package/dist/builtins/device-manager/device-manager.addon.mjs +8 -8
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts +8 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +50 -3
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +50 -3
- package/dist/builtins/platform-probe/index.js +27 -139
- package/dist/builtins/platform-probe/index.mjs +28 -140
- package/dist/builtins/platform-probe/platform-scorer.d.ts +17 -10
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -2
- package/dist/custom-action-registry-BEXwC-oo.mjs +38 -0
- package/dist/custom-action-registry-vLYEFTtv.js +43 -0
- package/dist/index.js +129 -779
- package/dist/index.mjs +100 -750
- package/dist/kernel/config-manager.d.ts +4 -4
- package/dist/kernel/fs-utils.d.ts +16 -6
- package/dist/kernel/index.d.ts +1 -1
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -1
- package/dist/kernel/moleculer/readiness-context.d.ts +2 -1
- package/dist/kernel/transport/child-cap-protocol.d.ts +10 -0
- package/dist/{manifest-python-deps-B4BmMoGT.js → manifest-python-deps-BWURo7dc.js} +62 -88
- package/dist/{manifest-python-deps-CXbKrOdk.mjs → manifest-python-deps-BcrTzHH_.mjs} +55 -75
- package/dist/model-download-service-C7AjBsX9.mjs +668 -0
- package/dist/model-download-service-JtVQtbb6.js +752 -0
- package/dist/process/resource-monitor.d.ts +9 -0
- package/dist/{resource-monitor-ClDGFyf6.mjs → resource-monitor-BkP504Vq.mjs} +20 -1
- package/dist/{resource-monitor-IIEanuJt.js → resource-monitor-DNNomR-i.js} +21 -1
- package/package.json +6 -1
|
@@ -60,8 +60,8 @@ async function getAvailableRAM_MB() {
|
|
|
60
60
|
const match = readFileSync("/proc/meminfo", "utf8").match(/MemAvailable:\s+(\d+)\s+kB/);
|
|
61
61
|
if (match) return Math.round(parseInt(match[1]) / 1024);
|
|
62
62
|
}
|
|
63
|
-
} catch (
|
|
64
|
-
console.debug(`RAM probe failed, using total RAM fallback: ${(0, _camstack_types.errMsg)(
|
|
63
|
+
} catch (probeErr) {
|
|
64
|
+
console.debug(`RAM probe failed, using total RAM fallback: ${(0, _camstack_types.errMsg)(probeErr)}`);
|
|
65
65
|
}
|
|
66
66
|
return Math.round(node_os.totalmem() / 1024 / 1024);
|
|
67
67
|
}
|
|
@@ -73,10 +73,9 @@ var PlatformScorer = class {
|
|
|
73
73
|
* Path to the embedded portable Python (from `ctx.deps.ensurePython()`).
|
|
74
74
|
* The Docker hub ships NO system `python3` — inference runs on the
|
|
75
75
|
* downloaded portable build — so probing system `python3`/`python` would
|
|
76
|
-
* wrongly report "Python not found"
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* environments where Python is on PATH).
|
|
76
|
+
* wrongly report "Python not found". When set, the interpreter lookup
|
|
77
|
+
* tries THIS binary first; `null` falls back to a system `python3`/`python`
|
|
78
|
+
* lookup (dev / agent environments where Python is on PATH).
|
|
80
79
|
*/
|
|
81
80
|
embeddedPythonPath;
|
|
82
81
|
constructor(logger = noopLogger, embeddedPythonPath = null) {
|
|
@@ -84,14 +83,16 @@ var PlatformScorer = class {
|
|
|
84
83
|
this.embeddedPythonPath = embeddedPythonPath;
|
|
85
84
|
}
|
|
86
85
|
/**
|
|
87
|
-
* Probe hardware +
|
|
86
|
+
* Probe hardware + derive scores from pure rules and score all backend combos.
|
|
88
87
|
*
|
|
89
88
|
* An optional `onPhase` callback is invoked at each step so consumers
|
|
90
89
|
* (e.g. the platform-probe addon) can emit live progress events on
|
|
91
90
|
* the event bus. The callback takes a phase id + a typed payload; all
|
|
92
|
-
* phases fire in strict order: `started` → `hardware` → `
|
|
93
|
-
*
|
|
94
|
-
*
|
|
91
|
+
* phases fire in strict order: `started` → `hardware` → `scored` → `done`.
|
|
92
|
+
* On failure, `error` fires once with the exception. Cached after first call.
|
|
93
|
+
*
|
|
94
|
+
* Scores are hardware-driven only (no Python module import subprocess):
|
|
95
|
+
* `scoreRuntimes` from `@camstack/types` is the single source of truth.
|
|
95
96
|
*/
|
|
96
97
|
async probe(onPhase) {
|
|
97
98
|
if (this.cached) return this.cached;
|
|
@@ -109,19 +110,10 @@ var PlatformScorer = class {
|
|
|
109
110
|
if (hardware.gpu) this.logger.info("GPU detected", { meta: { name: hardware.gpu.name } });
|
|
110
111
|
if (hardware.npu) this.logger.info("NPU detected", { meta: { type: hardware.npu.type } });
|
|
111
112
|
onPhase?.("hardware", { hardware });
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
115
|
-
pythonPath: pythonInfo.pythonPath,
|
|
116
|
-
backends: pythonInfo.backends.filter((b) => b.available).map((b) => b.id)
|
|
117
|
-
} });
|
|
113
|
+
const { scores, best: bestScore } = (0, _camstack_types.scoreRuntimes)(hardware);
|
|
114
|
+
const pythonPath = await this.resolvePythonPath();
|
|
115
|
+
if (pythonPath) this.logger.info("Python interpreter located", { meta: { pythonPath } });
|
|
118
116
|
else this.logger.info("Python: not found");
|
|
119
|
-
onPhase?.("python-backends", {
|
|
120
|
-
pythonPath: pythonInfo.pythonPath,
|
|
121
|
-
backends: pythonInfo.backends
|
|
122
|
-
});
|
|
123
|
-
const scores = this.scoreBackends(hardware, pythonInfo.backends);
|
|
124
|
-
const bestScore = scores.find((s) => s.available) ?? scores[scores.length - 1];
|
|
125
117
|
const elapsed = Date.now() - start;
|
|
126
118
|
this.logger.info("Scoring complete", { meta: {
|
|
127
119
|
elapsedMs: elapsed,
|
|
@@ -144,9 +136,9 @@ var PlatformScorer = class {
|
|
|
144
136
|
} });
|
|
145
137
|
this.cached = {
|
|
146
138
|
hardware,
|
|
147
|
-
scores,
|
|
139
|
+
scores: [...scores],
|
|
148
140
|
bestScore,
|
|
149
|
-
pythonPath
|
|
141
|
+
pythonPath
|
|
150
142
|
};
|
|
151
143
|
onPhase?.("scored", {
|
|
152
144
|
scores,
|
|
@@ -231,130 +223,26 @@ var PlatformScorer = class {
|
|
|
231
223
|
npu
|
|
232
224
|
};
|
|
233
225
|
}
|
|
234
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Locates a Python interpreter without importing any module.
|
|
228
|
+
*
|
|
229
|
+
* Tries: embedded path (if configured) → `python3` → `python`.
|
|
230
|
+
* Returns the first executable found, or `null` if none respond.
|
|
231
|
+
* MUST NOT run `-c "import ..."` — interpreter location only.
|
|
232
|
+
*/
|
|
233
|
+
async resolvePythonPath() {
|
|
235
234
|
const candidates = [
|
|
236
235
|
...this.embeddedPythonPath ? [this.embeddedPythonPath] : [],
|
|
237
236
|
"python3",
|
|
238
237
|
"python"
|
|
239
238
|
];
|
|
240
|
-
let pythonPath = null;
|
|
241
239
|
for (const cmd of candidates) try {
|
|
242
240
|
await execFileAsync$2(cmd, ["--version"], { timeout: 5e3 });
|
|
243
|
-
|
|
244
|
-
break;
|
|
241
|
+
return cmd;
|
|
245
242
|
} catch (err) {
|
|
246
|
-
|
|
243
|
+
this.logger.debug(`Python command "${cmd}" not found`, { meta: { error: (0, _camstack_types.errMsg)(err) } });
|
|
247
244
|
}
|
|
248
|
-
|
|
249
|
-
pythonPath: null,
|
|
250
|
-
backends: []
|
|
251
|
-
};
|
|
252
|
-
const results = await Promise.all([
|
|
253
|
-
[
|
|
254
|
-
"coremltools",
|
|
255
|
-
"coreml",
|
|
256
|
-
"coreml"
|
|
257
|
-
],
|
|
258
|
-
[
|
|
259
|
-
"openvino.runtime",
|
|
260
|
-
"openvino",
|
|
261
|
-
"openvino"
|
|
262
|
-
],
|
|
263
|
-
[
|
|
264
|
-
"torch",
|
|
265
|
-
"pytorch",
|
|
266
|
-
"onnx"
|
|
267
|
-
],
|
|
268
|
-
[
|
|
269
|
-
"onnxruntime",
|
|
270
|
-
"onnx-py",
|
|
271
|
-
"onnx"
|
|
272
|
-
]
|
|
273
|
-
].map(async ([mod, id, format]) => {
|
|
274
|
-
const probeStart = Date.now();
|
|
275
|
-
let available = false;
|
|
276
|
-
try {
|
|
277
|
-
await execFileAsync$2(pythonPath, ["-c", `import ${mod}`], { timeout: 3e4 });
|
|
278
|
-
available = true;
|
|
279
|
-
this.logger.info("Python backend confirmed", { meta: {
|
|
280
|
-
backend: id,
|
|
281
|
-
module: mod
|
|
282
|
-
} });
|
|
283
|
-
} catch (err) {
|
|
284
|
-
console.debug(`Python module "${mod}" not installed: ${(0, _camstack_types.errMsg)(err)}`);
|
|
285
|
-
if (id === "openvino") this.logger.info("Python backend not yet confirmed: openvino (may be pending proactive install on Intel hardware — falling back to onnx-cpu)", { meta: { module: mod } });
|
|
286
|
-
}
|
|
287
|
-
const probeMs = Date.now() - probeStart;
|
|
288
|
-
this.logger.debug("Python module probed", { meta: {
|
|
289
|
-
module: mod,
|
|
290
|
-
available,
|
|
291
|
-
probeMs
|
|
292
|
-
} });
|
|
293
|
-
return {
|
|
294
|
-
mod,
|
|
295
|
-
id,
|
|
296
|
-
format,
|
|
297
|
-
available
|
|
298
|
-
};
|
|
299
|
-
}));
|
|
300
|
-
const backends = [];
|
|
301
|
-
for (const r of results) if (r.id === "coreml" && node_os.platform() === "darwin") backends.push({
|
|
302
|
-
id: r.id,
|
|
303
|
-
format: r.format,
|
|
304
|
-
available: r.available
|
|
305
|
-
});
|
|
306
|
-
else if (r.available) backends.push({
|
|
307
|
-
id: r.id,
|
|
308
|
-
format: r.format,
|
|
309
|
-
available: r.available
|
|
310
|
-
});
|
|
311
|
-
return {
|
|
312
|
-
pythonPath,
|
|
313
|
-
backends
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
scoreBackends(hardware, pythonBackends) {
|
|
317
|
-
const scores = [];
|
|
318
|
-
if (hardware.platform === "darwin" && hardware.arch === "arm64") {
|
|
319
|
-
const pyCoreMl = pythonBackends.find((b) => b.id === "coreml");
|
|
320
|
-
if (pyCoreMl) scores.push({
|
|
321
|
-
runtime: "python",
|
|
322
|
-
backend: "coreml",
|
|
323
|
-
format: "coreml",
|
|
324
|
-
score: 95,
|
|
325
|
-
reason: "Apple Neural Engine (Python CoreML)",
|
|
326
|
-
available: pyCoreMl.available
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
if (hardware.gpu?.type === "nvidia") scores.push({
|
|
330
|
-
runtime: "python",
|
|
331
|
-
backend: "cuda",
|
|
332
|
-
format: "onnx",
|
|
333
|
-
score: 85,
|
|
334
|
-
reason: "NVIDIA CUDA (Python ONNX Runtime)",
|
|
335
|
-
available: true
|
|
336
|
-
});
|
|
337
|
-
const openvino = pythonBackends.find((b) => b.id === "openvino");
|
|
338
|
-
if (openvino) {
|
|
339
|
-
const score = hardware.npu?.type === "intel-npu" ? 90 : 80;
|
|
340
|
-
scores.push({
|
|
341
|
-
runtime: "python",
|
|
342
|
-
backend: "openvino",
|
|
343
|
-
format: "openvino",
|
|
344
|
-
score,
|
|
345
|
-
reason: "Intel OpenVINO",
|
|
346
|
-
available: openvino.available
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
scores.push({
|
|
350
|
-
runtime: "python",
|
|
351
|
-
backend: "cpu",
|
|
352
|
-
format: "onnx",
|
|
353
|
-
score: 50,
|
|
354
|
-
reason: "CPU (Python ONNX Runtime)",
|
|
355
|
-
available: true
|
|
356
|
-
});
|
|
357
|
-
return scores.toSorted((a, b) => b.score - a.score);
|
|
245
|
+
return null;
|
|
358
246
|
}
|
|
359
247
|
};
|
|
360
248
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import { BaseAddon, EventCategory, errMsg, platformProbeCapability } from "@camstack/types";
|
|
2
|
+
import { BaseAddon, EventCategory, errMsg, platformProbeCapability, scoreRuntimes } from "@camstack/types";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import * as os from "node:os";
|
|
@@ -53,8 +53,8 @@ async function getAvailableRAM_MB() {
|
|
|
53
53
|
const match = readFileSync("/proc/meminfo", "utf8").match(/MemAvailable:\s+(\d+)\s+kB/);
|
|
54
54
|
if (match) return Math.round(parseInt(match[1]) / 1024);
|
|
55
55
|
}
|
|
56
|
-
} catch (
|
|
57
|
-
console.debug(`RAM probe failed, using total RAM fallback: ${errMsg(
|
|
56
|
+
} catch (probeErr) {
|
|
57
|
+
console.debug(`RAM probe failed, using total RAM fallback: ${errMsg(probeErr)}`);
|
|
58
58
|
}
|
|
59
59
|
return Math.round(os.totalmem() / 1024 / 1024);
|
|
60
60
|
}
|
|
@@ -66,10 +66,9 @@ var PlatformScorer = class {
|
|
|
66
66
|
* Path to the embedded portable Python (from `ctx.deps.ensurePython()`).
|
|
67
67
|
* The Docker hub ships NO system `python3` — inference runs on the
|
|
68
68
|
* downloaded portable build — so probing system `python3`/`python` would
|
|
69
|
-
* wrongly report "Python not found"
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* environments where Python is on PATH).
|
|
69
|
+
* wrongly report "Python not found". When set, the interpreter lookup
|
|
70
|
+
* tries THIS binary first; `null` falls back to a system `python3`/`python`
|
|
71
|
+
* lookup (dev / agent environments where Python is on PATH).
|
|
73
72
|
*/
|
|
74
73
|
embeddedPythonPath;
|
|
75
74
|
constructor(logger = noopLogger, embeddedPythonPath = null) {
|
|
@@ -77,14 +76,16 @@ var PlatformScorer = class {
|
|
|
77
76
|
this.embeddedPythonPath = embeddedPythonPath;
|
|
78
77
|
}
|
|
79
78
|
/**
|
|
80
|
-
* Probe hardware +
|
|
79
|
+
* Probe hardware + derive scores from pure rules and score all backend combos.
|
|
81
80
|
*
|
|
82
81
|
* An optional `onPhase` callback is invoked at each step so consumers
|
|
83
82
|
* (e.g. the platform-probe addon) can emit live progress events on
|
|
84
83
|
* the event bus. The callback takes a phase id + a typed payload; all
|
|
85
|
-
* phases fire in strict order: `started` → `hardware` → `
|
|
86
|
-
*
|
|
87
|
-
*
|
|
84
|
+
* phases fire in strict order: `started` → `hardware` → `scored` → `done`.
|
|
85
|
+
* On failure, `error` fires once with the exception. Cached after first call.
|
|
86
|
+
*
|
|
87
|
+
* Scores are hardware-driven only (no Python module import subprocess):
|
|
88
|
+
* `scoreRuntimes` from `@camstack/types` is the single source of truth.
|
|
88
89
|
*/
|
|
89
90
|
async probe(onPhase) {
|
|
90
91
|
if (this.cached) return this.cached;
|
|
@@ -102,19 +103,10 @@ var PlatformScorer = class {
|
|
|
102
103
|
if (hardware.gpu) this.logger.info("GPU detected", { meta: { name: hardware.gpu.name } });
|
|
103
104
|
if (hardware.npu) this.logger.info("NPU detected", { meta: { type: hardware.npu.type } });
|
|
104
105
|
onPhase?.("hardware", { hardware });
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
pythonPath: pythonInfo.pythonPath,
|
|
109
|
-
backends: pythonInfo.backends.filter((b) => b.available).map((b) => b.id)
|
|
110
|
-
} });
|
|
106
|
+
const { scores, best: bestScore } = scoreRuntimes(hardware);
|
|
107
|
+
const pythonPath = await this.resolvePythonPath();
|
|
108
|
+
if (pythonPath) this.logger.info("Python interpreter located", { meta: { pythonPath } });
|
|
111
109
|
else this.logger.info("Python: not found");
|
|
112
|
-
onPhase?.("python-backends", {
|
|
113
|
-
pythonPath: pythonInfo.pythonPath,
|
|
114
|
-
backends: pythonInfo.backends
|
|
115
|
-
});
|
|
116
|
-
const scores = this.scoreBackends(hardware, pythonInfo.backends);
|
|
117
|
-
const bestScore = scores.find((s) => s.available) ?? scores[scores.length - 1];
|
|
118
110
|
const elapsed = Date.now() - start;
|
|
119
111
|
this.logger.info("Scoring complete", { meta: {
|
|
120
112
|
elapsedMs: elapsed,
|
|
@@ -137,9 +129,9 @@ var PlatformScorer = class {
|
|
|
137
129
|
} });
|
|
138
130
|
this.cached = {
|
|
139
131
|
hardware,
|
|
140
|
-
scores,
|
|
132
|
+
scores: [...scores],
|
|
141
133
|
bestScore,
|
|
142
|
-
pythonPath
|
|
134
|
+
pythonPath
|
|
143
135
|
};
|
|
144
136
|
onPhase?.("scored", {
|
|
145
137
|
scores,
|
|
@@ -224,130 +216,26 @@ var PlatformScorer = class {
|
|
|
224
216
|
npu
|
|
225
217
|
};
|
|
226
218
|
}
|
|
227
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Locates a Python interpreter without importing any module.
|
|
221
|
+
*
|
|
222
|
+
* Tries: embedded path (if configured) → `python3` → `python`.
|
|
223
|
+
* Returns the first executable found, or `null` if none respond.
|
|
224
|
+
* MUST NOT run `-c "import ..."` — interpreter location only.
|
|
225
|
+
*/
|
|
226
|
+
async resolvePythonPath() {
|
|
228
227
|
const candidates = [
|
|
229
228
|
...this.embeddedPythonPath ? [this.embeddedPythonPath] : [],
|
|
230
229
|
"python3",
|
|
231
230
|
"python"
|
|
232
231
|
];
|
|
233
|
-
let pythonPath = null;
|
|
234
232
|
for (const cmd of candidates) try {
|
|
235
233
|
await execFileAsync$2(cmd, ["--version"], { timeout: 5e3 });
|
|
236
|
-
|
|
237
|
-
break;
|
|
234
|
+
return cmd;
|
|
238
235
|
} catch (err) {
|
|
239
|
-
|
|
236
|
+
this.logger.debug(`Python command "${cmd}" not found`, { meta: { error: errMsg(err) } });
|
|
240
237
|
}
|
|
241
|
-
|
|
242
|
-
pythonPath: null,
|
|
243
|
-
backends: []
|
|
244
|
-
};
|
|
245
|
-
const results = await Promise.all([
|
|
246
|
-
[
|
|
247
|
-
"coremltools",
|
|
248
|
-
"coreml",
|
|
249
|
-
"coreml"
|
|
250
|
-
],
|
|
251
|
-
[
|
|
252
|
-
"openvino.runtime",
|
|
253
|
-
"openvino",
|
|
254
|
-
"openvino"
|
|
255
|
-
],
|
|
256
|
-
[
|
|
257
|
-
"torch",
|
|
258
|
-
"pytorch",
|
|
259
|
-
"onnx"
|
|
260
|
-
],
|
|
261
|
-
[
|
|
262
|
-
"onnxruntime",
|
|
263
|
-
"onnx-py",
|
|
264
|
-
"onnx"
|
|
265
|
-
]
|
|
266
|
-
].map(async ([mod, id, format]) => {
|
|
267
|
-
const probeStart = Date.now();
|
|
268
|
-
let available = false;
|
|
269
|
-
try {
|
|
270
|
-
await execFileAsync$2(pythonPath, ["-c", `import ${mod}`], { timeout: 3e4 });
|
|
271
|
-
available = true;
|
|
272
|
-
this.logger.info("Python backend confirmed", { meta: {
|
|
273
|
-
backend: id,
|
|
274
|
-
module: mod
|
|
275
|
-
} });
|
|
276
|
-
} catch (err) {
|
|
277
|
-
console.debug(`Python module "${mod}" not installed: ${errMsg(err)}`);
|
|
278
|
-
if (id === "openvino") this.logger.info("Python backend not yet confirmed: openvino (may be pending proactive install on Intel hardware — falling back to onnx-cpu)", { meta: { module: mod } });
|
|
279
|
-
}
|
|
280
|
-
const probeMs = Date.now() - probeStart;
|
|
281
|
-
this.logger.debug("Python module probed", { meta: {
|
|
282
|
-
module: mod,
|
|
283
|
-
available,
|
|
284
|
-
probeMs
|
|
285
|
-
} });
|
|
286
|
-
return {
|
|
287
|
-
mod,
|
|
288
|
-
id,
|
|
289
|
-
format,
|
|
290
|
-
available
|
|
291
|
-
};
|
|
292
|
-
}));
|
|
293
|
-
const backends = [];
|
|
294
|
-
for (const r of results) if (r.id === "coreml" && os.platform() === "darwin") backends.push({
|
|
295
|
-
id: r.id,
|
|
296
|
-
format: r.format,
|
|
297
|
-
available: r.available
|
|
298
|
-
});
|
|
299
|
-
else if (r.available) backends.push({
|
|
300
|
-
id: r.id,
|
|
301
|
-
format: r.format,
|
|
302
|
-
available: r.available
|
|
303
|
-
});
|
|
304
|
-
return {
|
|
305
|
-
pythonPath,
|
|
306
|
-
backends
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
scoreBackends(hardware, pythonBackends) {
|
|
310
|
-
const scores = [];
|
|
311
|
-
if (hardware.platform === "darwin" && hardware.arch === "arm64") {
|
|
312
|
-
const pyCoreMl = pythonBackends.find((b) => b.id === "coreml");
|
|
313
|
-
if (pyCoreMl) scores.push({
|
|
314
|
-
runtime: "python",
|
|
315
|
-
backend: "coreml",
|
|
316
|
-
format: "coreml",
|
|
317
|
-
score: 95,
|
|
318
|
-
reason: "Apple Neural Engine (Python CoreML)",
|
|
319
|
-
available: pyCoreMl.available
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
if (hardware.gpu?.type === "nvidia") scores.push({
|
|
323
|
-
runtime: "python",
|
|
324
|
-
backend: "cuda",
|
|
325
|
-
format: "onnx",
|
|
326
|
-
score: 85,
|
|
327
|
-
reason: "NVIDIA CUDA (Python ONNX Runtime)",
|
|
328
|
-
available: true
|
|
329
|
-
});
|
|
330
|
-
const openvino = pythonBackends.find((b) => b.id === "openvino");
|
|
331
|
-
if (openvino) {
|
|
332
|
-
const score = hardware.npu?.type === "intel-npu" ? 90 : 80;
|
|
333
|
-
scores.push({
|
|
334
|
-
runtime: "python",
|
|
335
|
-
backend: "openvino",
|
|
336
|
-
format: "openvino",
|
|
337
|
-
score,
|
|
338
|
-
reason: "Intel OpenVINO",
|
|
339
|
-
available: openvino.available
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
scores.push({
|
|
343
|
-
runtime: "python",
|
|
344
|
-
backend: "cpu",
|
|
345
|
-
format: "onnx",
|
|
346
|
-
score: 50,
|
|
347
|
-
reason: "CPU (Python ONNX Runtime)",
|
|
348
|
-
available: true
|
|
349
|
-
});
|
|
350
|
-
return scores.toSorted((a, b) => b.score - a.score);
|
|
238
|
+
return null;
|
|
351
239
|
}
|
|
352
240
|
};
|
|
353
241
|
//#endregion
|
|
@@ -6,25 +6,32 @@ export declare class PlatformScorer {
|
|
|
6
6
|
* Path to the embedded portable Python (from `ctx.deps.ensurePython()`).
|
|
7
7
|
* The Docker hub ships NO system `python3` — inference runs on the
|
|
8
8
|
* downloaded portable build — so probing system `python3`/`python` would
|
|
9
|
-
* wrongly report "Python not found"
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* environments where Python is on PATH).
|
|
9
|
+
* wrongly report "Python not found". When set, the interpreter lookup
|
|
10
|
+
* tries THIS binary first; `null` falls back to a system `python3`/`python`
|
|
11
|
+
* lookup (dev / agent environments where Python is on PATH).
|
|
13
12
|
*/
|
|
14
13
|
private readonly embeddedPythonPath;
|
|
15
14
|
constructor(logger?: IScopedLogger, embeddedPythonPath?: string | null);
|
|
16
15
|
/**
|
|
17
|
-
* Probe hardware +
|
|
16
|
+
* Probe hardware + derive scores from pure rules and score all backend combos.
|
|
18
17
|
*
|
|
19
18
|
* An optional `onPhase` callback is invoked at each step so consumers
|
|
20
19
|
* (e.g. the platform-probe addon) can emit live progress events on
|
|
21
20
|
* the event bus. The callback takes a phase id + a typed payload; all
|
|
22
|
-
* phases fire in strict order: `started` → `hardware` → `
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* phases fire in strict order: `started` → `hardware` → `scored` → `done`.
|
|
22
|
+
* On failure, `error` fires once with the exception. Cached after first call.
|
|
23
|
+
*
|
|
24
|
+
* Scores are hardware-driven only (no Python module import subprocess):
|
|
25
|
+
* `scoreRuntimes` from `@camstack/types` is the single source of truth.
|
|
25
26
|
*/
|
|
26
27
|
probe(onPhase?: (phase: string, payload?: Record<string, unknown>) => void): Promise<PlatformCapabilities>;
|
|
27
28
|
probeHardware(): Promise<HardwareInfo>;
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Locates a Python interpreter without importing any module.
|
|
31
|
+
*
|
|
32
|
+
* Tries: embedded path (if configured) → `python3` → `python`.
|
|
33
|
+
* Returns the first executable found, or `null` if none respond.
|
|
34
|
+
* MUST NOT run `-c "import ..."` — interpreter location only.
|
|
35
|
+
*/
|
|
36
|
+
private resolvePythonPath;
|
|
30
37
|
}
|
|
@@ -161,7 +161,7 @@ var StorageOrchestratorService = class {
|
|
|
161
161
|
const resolver = this.nodeLocalResolver;
|
|
162
162
|
if (!resolver) return;
|
|
163
163
|
let stamped = 0;
|
|
164
|
-
for (const [id, loc] of
|
|
164
|
+
for (const [id, loc] of Array.from(this.locations)) {
|
|
165
165
|
if (loc.nodeId) continue;
|
|
166
166
|
if (resolver(loc.providerId) !== true) continue;
|
|
167
167
|
const updated = {
|
|
@@ -301,7 +301,7 @@ var StorageOrchestratorService = class {
|
|
|
301
301
|
this.logger.warn("storage-orchestrator: skipping prune — no location declarations loaded (fail-safe)");
|
|
302
302
|
return;
|
|
303
303
|
}
|
|
304
|
-
for (const [id, loc] of
|
|
304
|
+
for (const [id, loc] of Array.from(this.locations)) {
|
|
305
305
|
if (this.registry.cardinalityOf(loc.type) !== null) continue;
|
|
306
306
|
if (loc.isSystem) {
|
|
307
307
|
this.locations.delete(id);
|
|
@@ -154,7 +154,7 @@ var StorageOrchestratorService = class {
|
|
|
154
154
|
const resolver = this.nodeLocalResolver;
|
|
155
155
|
if (!resolver) return;
|
|
156
156
|
let stamped = 0;
|
|
157
|
-
for (const [id, loc] of
|
|
157
|
+
for (const [id, loc] of Array.from(this.locations)) {
|
|
158
158
|
if (loc.nodeId) continue;
|
|
159
159
|
if (resolver(loc.providerId) !== true) continue;
|
|
160
160
|
const updated = {
|
|
@@ -294,7 +294,7 @@ var StorageOrchestratorService = class {
|
|
|
294
294
|
this.logger.warn("storage-orchestrator: skipping prune — no location declarations loaded (fail-safe)");
|
|
295
295
|
return;
|
|
296
296
|
}
|
|
297
|
-
for (const [id, loc] of
|
|
297
|
+
for (const [id, loc] of Array.from(this.locations)) {
|
|
298
298
|
if (this.registry.cardinalityOf(loc.type) !== null) continue;
|
|
299
299
|
if (loc.isSystem) {
|
|
300
300
|
this.locations.delete(id);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/kernel/custom-action-registry.ts
|
|
2
|
+
/**
|
|
3
|
+
* CustomActionRegistry — per-process registry of addon custom actions.
|
|
4
|
+
*
|
|
5
|
+
* Populated at boot from each addon's `AddonInitResult.customActions` +
|
|
6
|
+
* `handleCustomAction` handler. Rejects actions declared with scope other
|
|
7
|
+
* than 'system' (today only 'system' is runtime-supported; the descriptor
|
|
8
|
+
* allows future scopes for forward compat).
|
|
9
|
+
*/
|
|
10
|
+
var CustomActionRegistry = class {
|
|
11
|
+
byAddon = /* @__PURE__ */ new Map();
|
|
12
|
+
registerAddon(addonId, catalog, handler) {
|
|
13
|
+
const actions = /* @__PURE__ */ new Map();
|
|
14
|
+
for (const [name, spec] of Object.entries(catalog)) {
|
|
15
|
+
const scope = spec.scope ?? { kind: "system" };
|
|
16
|
+
if (scope.kind !== "system") throw new Error(`custom action '${addonId}.${name}' declared scope '${scope.kind}' — not yet implemented`);
|
|
17
|
+
actions.set(name, {
|
|
18
|
+
spec,
|
|
19
|
+
handler: (input) => handler(name, input)
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
this.byAddon.set(addonId, actions);
|
|
23
|
+
}
|
|
24
|
+
unregisterAddon(addonId) {
|
|
25
|
+
this.byAddon.delete(addonId);
|
|
26
|
+
}
|
|
27
|
+
resolve(addonId, action) {
|
|
28
|
+
return this.byAddon.get(addonId)?.get(action) ?? null;
|
|
29
|
+
}
|
|
30
|
+
listActions(addonId) {
|
|
31
|
+
return [...this.byAddon.get(addonId)?.keys() ?? []];
|
|
32
|
+
}
|
|
33
|
+
listAddons() {
|
|
34
|
+
return [...this.byAddon.keys()];
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { CustomActionRegistry as t };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/kernel/custom-action-registry.ts
|
|
2
|
+
/**
|
|
3
|
+
* CustomActionRegistry — per-process registry of addon custom actions.
|
|
4
|
+
*
|
|
5
|
+
* Populated at boot from each addon's `AddonInitResult.customActions` +
|
|
6
|
+
* `handleCustomAction` handler. Rejects actions declared with scope other
|
|
7
|
+
* than 'system' (today only 'system' is runtime-supported; the descriptor
|
|
8
|
+
* allows future scopes for forward compat).
|
|
9
|
+
*/
|
|
10
|
+
var CustomActionRegistry = class {
|
|
11
|
+
byAddon = /* @__PURE__ */ new Map();
|
|
12
|
+
registerAddon(addonId, catalog, handler) {
|
|
13
|
+
const actions = /* @__PURE__ */ new Map();
|
|
14
|
+
for (const [name, spec] of Object.entries(catalog)) {
|
|
15
|
+
const scope = spec.scope ?? { kind: "system" };
|
|
16
|
+
if (scope.kind !== "system") throw new Error(`custom action '${addonId}.${name}' declared scope '${scope.kind}' — not yet implemented`);
|
|
17
|
+
actions.set(name, {
|
|
18
|
+
spec,
|
|
19
|
+
handler: (input) => handler(name, input)
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
this.byAddon.set(addonId, actions);
|
|
23
|
+
}
|
|
24
|
+
unregisterAddon(addonId) {
|
|
25
|
+
this.byAddon.delete(addonId);
|
|
26
|
+
}
|
|
27
|
+
resolve(addonId, action) {
|
|
28
|
+
return this.byAddon.get(addonId)?.get(action) ?? null;
|
|
29
|
+
}
|
|
30
|
+
listActions(addonId) {
|
|
31
|
+
return [...this.byAddon.get(addonId)?.keys() ?? []];
|
|
32
|
+
}
|
|
33
|
+
listAddons() {
|
|
34
|
+
return [...this.byAddon.keys()];
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
Object.defineProperty(exports, "CustomActionRegistry", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get: function() {
|
|
41
|
+
return CustomActionRegistry;
|
|
42
|
+
}
|
|
43
|
+
});
|