@camstack/addon-pipeline-orchestrator 1.1.57 → 1.1.58
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/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-Q4YOYkCV.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-BiQO4cUV.mjs} +1 -1
- package/dist/{hostInit-DMxJQAUo.mjs → hostInit-BtAIIIL1.mjs} +1 -1
- package/dist/index.js +89 -7
- package/dist/index.mjs +89 -7
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11929,8 +11929,18 @@ var pipelineExecutorCapability = {
|
|
|
11929
11929
|
* - `runtime` — main camera-serving engine (no idle TTL).
|
|
11930
11930
|
* - `warm-override` — benchmark/test override held in the warm
|
|
11931
11931
|
* cache; auto-disposed after the idle TTL.
|
|
11932
|
+
* - `device-pool` — a concurrent per-device pool (Phase 2
|
|
11933
|
+
* multi-device, keyed by `deviceKey`) resolved
|
|
11934
|
+
* via `resolveDeviceFactory`. Runs alongside the
|
|
11935
|
+
* `runtime` engine on a DIFFERENT accelerator
|
|
11936
|
+
* (NPU / iGPU / Coral) — this is how the
|
|
11937
|
+
* Engines tab shows all pools running at once.
|
|
11932
11938
|
*/
|
|
11933
|
-
kind: _enum([
|
|
11939
|
+
kind: _enum([
|
|
11940
|
+
"runtime",
|
|
11941
|
+
"warm-override",
|
|
11942
|
+
"device-pool"
|
|
11943
|
+
]),
|
|
11934
11944
|
/** Native pid of the underlying Python pool (null when no pool). */
|
|
11935
11945
|
poolPid: number().nullable(),
|
|
11936
11946
|
/** ms since this factory was last used (null when not warm-tracked). */
|
|
@@ -17764,7 +17774,17 @@ var AgentPipelineSettingsSchema = object({
|
|
|
17764
17774
|
* it already uses to reach the hub). Set this only when the auto-detected
|
|
17765
17775
|
* address is wrong (multi-homed host, NAT, custom interface).
|
|
17766
17776
|
*/
|
|
17767
|
-
reachableHost: string().optional()
|
|
17777
|
+
reachableHost: string().optional(),
|
|
17778
|
+
/**
|
|
17779
|
+
* Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
|
|
17780
|
+
* weight}. Absent / all-disabled ⇒ the node's single default accelerator
|
|
17781
|
+
* (safe default); two+ enabled ⇒ the dispatcher balances detection sessions
|
|
17782
|
+
* across them so they run CONCURRENTLY.
|
|
17783
|
+
*/
|
|
17784
|
+
inferenceDevices: record(string(), object({
|
|
17785
|
+
enabled: boolean(),
|
|
17786
|
+
weight: number().positive().optional()
|
|
17787
|
+
})).optional()
|
|
17768
17788
|
});
|
|
17769
17789
|
var CameraPipelineForAgentSchema = object({
|
|
17770
17790
|
steps: array(PipelineStepInputSchema).readonly(),
|
|
@@ -31437,7 +31457,11 @@ var StoredAgentPipelineSettingsSchema = object({
|
|
|
31437
31457
|
decode: boolean().optional(),
|
|
31438
31458
|
audio: boolean().optional(),
|
|
31439
31459
|
ingest: boolean().optional(),
|
|
31440
|
-
reachableHost: string().optional()
|
|
31460
|
+
reachableHost: string().optional(),
|
|
31461
|
+
inferenceDevices: record(string(), object({
|
|
31462
|
+
enabled: boolean(),
|
|
31463
|
+
weight: number().positive().optional()
|
|
31464
|
+
})).optional()
|
|
31441
31465
|
});
|
|
31442
31466
|
var AgentSettingsMapSchema = record(string(), StoredAgentPipelineSettingsSchema);
|
|
31443
31467
|
var StoredCameraStepOverridePatchSchema = object({
|
|
@@ -32001,6 +32025,20 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
32001
32025
|
return weights;
|
|
32002
32026
|
}
|
|
32003
32027
|
/**
|
|
32028
|
+
* The ENABLED inference devices for a node (Phase 4 multi-device opt-in),
|
|
32029
|
+
* as a `deviceKey → weight` map. Only entries with `enabled:true` are
|
|
32030
|
+
* returned; `weight` defaults to 1. Empty when the node opted into nothing
|
|
32031
|
+
* (or fewer than one device is enabled) — the caller then leaves `deviceKey`
|
|
32032
|
+
* unset and the runner uses its single default accelerator.
|
|
32033
|
+
*/
|
|
32034
|
+
async buildNodeInferenceDevices(nodeId) {
|
|
32035
|
+
const cfg = (await this.agentSettingsState.get())[nodeId]?.inferenceDevices;
|
|
32036
|
+
const out = {};
|
|
32037
|
+
if (!cfg) return out;
|
|
32038
|
+
for (const [deviceKey, entry] of Object.entries(cfg)) if (entry.enabled) out[deviceKey] = entry.weight && entry.weight > 0 ? entry.weight : 1;
|
|
32039
|
+
return out;
|
|
32040
|
+
}
|
|
32041
|
+
/**
|
|
32004
32042
|
* Structural runtime check for `CameraPipelineConfig`. Persisted JSON
|
|
32005
32043
|
* may be stale or partial across upgrades, so we gate reads on shape
|
|
32006
32044
|
* rather than trusting the raw object. False = silently ignore that
|
|
@@ -32516,14 +32554,15 @@ var ReconcileController = class ReconcileController {
|
|
|
32516
32554
|
//#region src/detection-session-registry.ts
|
|
32517
32555
|
var DetectionSessionRegistry = class {
|
|
32518
32556
|
sessions = /* @__PURE__ */ new Map();
|
|
32519
|
-
register(deviceId, nodeId, startedAt) {
|
|
32557
|
+
register(deviceId, nodeId, startedAt, deviceKey) {
|
|
32520
32558
|
const existing = this.sessions.get(deviceId);
|
|
32521
32559
|
if (existing?.teardownTimer) clearTimeout(existing.teardownTimer);
|
|
32522
32560
|
const session = {
|
|
32523
32561
|
deviceId,
|
|
32524
32562
|
nodeId,
|
|
32525
32563
|
startedAt,
|
|
32526
|
-
teardownTimer: null
|
|
32564
|
+
teardownTimer: null,
|
|
32565
|
+
...deviceKey ? { deviceKey } : {}
|
|
32527
32566
|
};
|
|
32528
32567
|
this.sessions.set(deviceId, session);
|
|
32529
32568
|
return session;
|
|
@@ -32565,6 +32604,41 @@ var DetectionSessionRegistry = class {
|
|
|
32565
32604
|
}
|
|
32566
32605
|
};
|
|
32567
32606
|
//#endregion
|
|
32607
|
+
//#region src/device-balancer.ts
|
|
32608
|
+
/**
|
|
32609
|
+
* Per-node inference-device balancer (Phase 4 multi-device).
|
|
32610
|
+
*
|
|
32611
|
+
* Given the ENABLED device pools for a node (deviceKey → relative weight) and
|
|
32612
|
+
* the count of ACTIVE detection sessions currently on each pool, pick the pool
|
|
32613
|
+
* a NEW session should land on so concurrent sessions spread across the node's
|
|
32614
|
+
* accelerators (NPU / iGPU / Coral / additional Corals) proportionally to
|
|
32615
|
+
* weight — the classic weighted least-loaded choice used by the node balancer,
|
|
32616
|
+
* applied one level down at the device granularity.
|
|
32617
|
+
*
|
|
32618
|
+
* Returns `null` when fewer than TWO devices are enabled: with zero the caller
|
|
32619
|
+
* leaves `deviceKey` unset (the runner's single default accelerator); with
|
|
32620
|
+
* exactly one there is nothing to balance and pinning every session to that one
|
|
32621
|
+
* key is redundant (the default already serves it). Balancing only earns its
|
|
32622
|
+
* keep when there's a real choice.
|
|
32623
|
+
*/
|
|
32624
|
+
function pickInferenceDevice(enabled, activeCountByDevice) {
|
|
32625
|
+
const keys = Object.keys(enabled);
|
|
32626
|
+
if (keys.length < 2) return null;
|
|
32627
|
+
let best = null;
|
|
32628
|
+
let bestLoad = Number.POSITIVE_INFINITY;
|
|
32629
|
+
let bestWeight = -1;
|
|
32630
|
+
for (const key of keys) {
|
|
32631
|
+
const weight = enabled[key] > 0 ? enabled[key] : 1;
|
|
32632
|
+
const load = (activeCountByDevice[key] ?? 0) / weight;
|
|
32633
|
+
if (load < bestLoad || load === bestLoad && weight > bestWeight) {
|
|
32634
|
+
best = key;
|
|
32635
|
+
bestLoad = load;
|
|
32636
|
+
bestWeight = weight;
|
|
32637
|
+
}
|
|
32638
|
+
}
|
|
32639
|
+
return best;
|
|
32640
|
+
}
|
|
32641
|
+
//#endregion
|
|
32568
32642
|
//#region src/session-dispatch-controller.ts
|
|
32569
32643
|
var SessionDispatchController = class {
|
|
32570
32644
|
deps;
|
|
@@ -32782,10 +32856,15 @@ var SessionDispatchController = class {
|
|
|
32782
32856
|
return;
|
|
32783
32857
|
}
|
|
32784
32858
|
const targetNodeId = decision.agentNodeId;
|
|
32859
|
+
const enabledDevices = await this.deps.settingsStore.buildNodeInferenceDevices(targetNodeId);
|
|
32860
|
+
const activeByDevice = {};
|
|
32861
|
+
for (const s of this.sessionRegistry.list()) if (s.nodeId === targetNodeId && s.deviceKey) activeByDevice[s.deviceKey] = (activeByDevice[s.deviceKey] ?? 0) + 1;
|
|
32862
|
+
const deviceKey = pickInferenceDevice(enabledDevices, activeByDevice) ?? void 0;
|
|
32785
32863
|
const pipelineConfig = await this.deps.settingsStore.resolvePipelineForDevice(deviceId, targetNodeId);
|
|
32786
32864
|
const zones = await this.deps.listZones(deviceId);
|
|
32787
32865
|
const sessionConfig = {
|
|
32788
32866
|
deviceId,
|
|
32867
|
+
...deviceKey ? { deviceKey } : {},
|
|
32789
32868
|
motionCooldownMs: config.motionCooldownMs,
|
|
32790
32869
|
motionFps: config.motionFps,
|
|
32791
32870
|
detectionFps: config.detectionFps,
|
|
@@ -32807,10 +32886,13 @@ var SessionDispatchController = class {
|
|
|
32807
32886
|
try {
|
|
32808
32887
|
await this.deps.attachOn(targetNodeId, sessionConfig);
|
|
32809
32888
|
this.deps.recordAssignment(deviceId, targetNodeId, decision.reason, decision.reason === "manual");
|
|
32810
|
-
this.sessionRegistry.register(deviceId, targetNodeId, Date.now());
|
|
32889
|
+
this.sessionRegistry.register(deviceId, targetNodeId, Date.now(), deviceKey);
|
|
32811
32890
|
log.info("detection session: dispatched on motion", {
|
|
32812
32891
|
tags: { nodeId: targetNodeId },
|
|
32813
|
-
meta: {
|
|
32892
|
+
meta: {
|
|
32893
|
+
reason: decision.reason,
|
|
32894
|
+
...deviceKey ? { deviceKey } : {}
|
|
32895
|
+
}
|
|
32814
32896
|
});
|
|
32815
32897
|
} finally {
|
|
32816
32898
|
dispatchGuard.dispose();
|
package/dist/index.mjs
CHANGED
|
@@ -11901,8 +11901,18 @@ var pipelineExecutorCapability = {
|
|
|
11901
11901
|
* - `runtime` — main camera-serving engine (no idle TTL).
|
|
11902
11902
|
* - `warm-override` — benchmark/test override held in the warm
|
|
11903
11903
|
* cache; auto-disposed after the idle TTL.
|
|
11904
|
+
* - `device-pool` — a concurrent per-device pool (Phase 2
|
|
11905
|
+
* multi-device, keyed by `deviceKey`) resolved
|
|
11906
|
+
* via `resolveDeviceFactory`. Runs alongside the
|
|
11907
|
+
* `runtime` engine on a DIFFERENT accelerator
|
|
11908
|
+
* (NPU / iGPU / Coral) — this is how the
|
|
11909
|
+
* Engines tab shows all pools running at once.
|
|
11904
11910
|
*/
|
|
11905
|
-
kind: _enum([
|
|
11911
|
+
kind: _enum([
|
|
11912
|
+
"runtime",
|
|
11913
|
+
"warm-override",
|
|
11914
|
+
"device-pool"
|
|
11915
|
+
]),
|
|
11906
11916
|
/** Native pid of the underlying Python pool (null when no pool). */
|
|
11907
11917
|
poolPid: number().nullable(),
|
|
11908
11918
|
/** ms since this factory was last used (null when not warm-tracked). */
|
|
@@ -17736,7 +17746,17 @@ var AgentPipelineSettingsSchema = object({
|
|
|
17736
17746
|
* it already uses to reach the hub). Set this only when the auto-detected
|
|
17737
17747
|
* address is wrong (multi-homed host, NAT, custom interface).
|
|
17738
17748
|
*/
|
|
17739
|
-
reachableHost: string().optional()
|
|
17749
|
+
reachableHost: string().optional(),
|
|
17750
|
+
/**
|
|
17751
|
+
* Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
|
|
17752
|
+
* weight}. Absent / all-disabled ⇒ the node's single default accelerator
|
|
17753
|
+
* (safe default); two+ enabled ⇒ the dispatcher balances detection sessions
|
|
17754
|
+
* across them so they run CONCURRENTLY.
|
|
17755
|
+
*/
|
|
17756
|
+
inferenceDevices: record(string(), object({
|
|
17757
|
+
enabled: boolean(),
|
|
17758
|
+
weight: number().positive().optional()
|
|
17759
|
+
})).optional()
|
|
17740
17760
|
});
|
|
17741
17761
|
var CameraPipelineForAgentSchema = object({
|
|
17742
17762
|
steps: array(PipelineStepInputSchema).readonly(),
|
|
@@ -31409,7 +31429,11 @@ var StoredAgentPipelineSettingsSchema = object({
|
|
|
31409
31429
|
decode: boolean().optional(),
|
|
31410
31430
|
audio: boolean().optional(),
|
|
31411
31431
|
ingest: boolean().optional(),
|
|
31412
|
-
reachableHost: string().optional()
|
|
31432
|
+
reachableHost: string().optional(),
|
|
31433
|
+
inferenceDevices: record(string(), object({
|
|
31434
|
+
enabled: boolean(),
|
|
31435
|
+
weight: number().positive().optional()
|
|
31436
|
+
})).optional()
|
|
31413
31437
|
});
|
|
31414
31438
|
var AgentSettingsMapSchema = record(string(), StoredAgentPipelineSettingsSchema);
|
|
31415
31439
|
var StoredCameraStepOverridePatchSchema = object({
|
|
@@ -31973,6 +31997,20 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
31973
31997
|
return weights;
|
|
31974
31998
|
}
|
|
31975
31999
|
/**
|
|
32000
|
+
* The ENABLED inference devices for a node (Phase 4 multi-device opt-in),
|
|
32001
|
+
* as a `deviceKey → weight` map. Only entries with `enabled:true` are
|
|
32002
|
+
* returned; `weight` defaults to 1. Empty when the node opted into nothing
|
|
32003
|
+
* (or fewer than one device is enabled) — the caller then leaves `deviceKey`
|
|
32004
|
+
* unset and the runner uses its single default accelerator.
|
|
32005
|
+
*/
|
|
32006
|
+
async buildNodeInferenceDevices(nodeId) {
|
|
32007
|
+
const cfg = (await this.agentSettingsState.get())[nodeId]?.inferenceDevices;
|
|
32008
|
+
const out = {};
|
|
32009
|
+
if (!cfg) return out;
|
|
32010
|
+
for (const [deviceKey, entry] of Object.entries(cfg)) if (entry.enabled) out[deviceKey] = entry.weight && entry.weight > 0 ? entry.weight : 1;
|
|
32011
|
+
return out;
|
|
32012
|
+
}
|
|
32013
|
+
/**
|
|
31976
32014
|
* Structural runtime check for `CameraPipelineConfig`. Persisted JSON
|
|
31977
32015
|
* may be stale or partial across upgrades, so we gate reads on shape
|
|
31978
32016
|
* rather than trusting the raw object. False = silently ignore that
|
|
@@ -32488,14 +32526,15 @@ var ReconcileController = class ReconcileController {
|
|
|
32488
32526
|
//#region src/detection-session-registry.ts
|
|
32489
32527
|
var DetectionSessionRegistry = class {
|
|
32490
32528
|
sessions = /* @__PURE__ */ new Map();
|
|
32491
|
-
register(deviceId, nodeId, startedAt) {
|
|
32529
|
+
register(deviceId, nodeId, startedAt, deviceKey) {
|
|
32492
32530
|
const existing = this.sessions.get(deviceId);
|
|
32493
32531
|
if (existing?.teardownTimer) clearTimeout(existing.teardownTimer);
|
|
32494
32532
|
const session = {
|
|
32495
32533
|
deviceId,
|
|
32496
32534
|
nodeId,
|
|
32497
32535
|
startedAt,
|
|
32498
|
-
teardownTimer: null
|
|
32536
|
+
teardownTimer: null,
|
|
32537
|
+
...deviceKey ? { deviceKey } : {}
|
|
32499
32538
|
};
|
|
32500
32539
|
this.sessions.set(deviceId, session);
|
|
32501
32540
|
return session;
|
|
@@ -32537,6 +32576,41 @@ var DetectionSessionRegistry = class {
|
|
|
32537
32576
|
}
|
|
32538
32577
|
};
|
|
32539
32578
|
//#endregion
|
|
32579
|
+
//#region src/device-balancer.ts
|
|
32580
|
+
/**
|
|
32581
|
+
* Per-node inference-device balancer (Phase 4 multi-device).
|
|
32582
|
+
*
|
|
32583
|
+
* Given the ENABLED device pools for a node (deviceKey → relative weight) and
|
|
32584
|
+
* the count of ACTIVE detection sessions currently on each pool, pick the pool
|
|
32585
|
+
* a NEW session should land on so concurrent sessions spread across the node's
|
|
32586
|
+
* accelerators (NPU / iGPU / Coral / additional Corals) proportionally to
|
|
32587
|
+
* weight — the classic weighted least-loaded choice used by the node balancer,
|
|
32588
|
+
* applied one level down at the device granularity.
|
|
32589
|
+
*
|
|
32590
|
+
* Returns `null` when fewer than TWO devices are enabled: with zero the caller
|
|
32591
|
+
* leaves `deviceKey` unset (the runner's single default accelerator); with
|
|
32592
|
+
* exactly one there is nothing to balance and pinning every session to that one
|
|
32593
|
+
* key is redundant (the default already serves it). Balancing only earns its
|
|
32594
|
+
* keep when there's a real choice.
|
|
32595
|
+
*/
|
|
32596
|
+
function pickInferenceDevice(enabled, activeCountByDevice) {
|
|
32597
|
+
const keys = Object.keys(enabled);
|
|
32598
|
+
if (keys.length < 2) return null;
|
|
32599
|
+
let best = null;
|
|
32600
|
+
let bestLoad = Number.POSITIVE_INFINITY;
|
|
32601
|
+
let bestWeight = -1;
|
|
32602
|
+
for (const key of keys) {
|
|
32603
|
+
const weight = enabled[key] > 0 ? enabled[key] : 1;
|
|
32604
|
+
const load = (activeCountByDevice[key] ?? 0) / weight;
|
|
32605
|
+
if (load < bestLoad || load === bestLoad && weight > bestWeight) {
|
|
32606
|
+
best = key;
|
|
32607
|
+
bestLoad = load;
|
|
32608
|
+
bestWeight = weight;
|
|
32609
|
+
}
|
|
32610
|
+
}
|
|
32611
|
+
return best;
|
|
32612
|
+
}
|
|
32613
|
+
//#endregion
|
|
32540
32614
|
//#region src/session-dispatch-controller.ts
|
|
32541
32615
|
var SessionDispatchController = class {
|
|
32542
32616
|
deps;
|
|
@@ -32754,10 +32828,15 @@ var SessionDispatchController = class {
|
|
|
32754
32828
|
return;
|
|
32755
32829
|
}
|
|
32756
32830
|
const targetNodeId = decision.agentNodeId;
|
|
32831
|
+
const enabledDevices = await this.deps.settingsStore.buildNodeInferenceDevices(targetNodeId);
|
|
32832
|
+
const activeByDevice = {};
|
|
32833
|
+
for (const s of this.sessionRegistry.list()) if (s.nodeId === targetNodeId && s.deviceKey) activeByDevice[s.deviceKey] = (activeByDevice[s.deviceKey] ?? 0) + 1;
|
|
32834
|
+
const deviceKey = pickInferenceDevice(enabledDevices, activeByDevice) ?? void 0;
|
|
32757
32835
|
const pipelineConfig = await this.deps.settingsStore.resolvePipelineForDevice(deviceId, targetNodeId);
|
|
32758
32836
|
const zones = await this.deps.listZones(deviceId);
|
|
32759
32837
|
const sessionConfig = {
|
|
32760
32838
|
deviceId,
|
|
32839
|
+
...deviceKey ? { deviceKey } : {},
|
|
32761
32840
|
motionCooldownMs: config.motionCooldownMs,
|
|
32762
32841
|
motionFps: config.motionFps,
|
|
32763
32842
|
detectionFps: config.detectionFps,
|
|
@@ -32779,10 +32858,13 @@ var SessionDispatchController = class {
|
|
|
32779
32858
|
try {
|
|
32780
32859
|
await this.deps.attachOn(targetNodeId, sessionConfig);
|
|
32781
32860
|
this.deps.recordAssignment(deviceId, targetNodeId, decision.reason, decision.reason === "manual");
|
|
32782
|
-
this.sessionRegistry.register(deviceId, targetNodeId, Date.now());
|
|
32861
|
+
this.sessionRegistry.register(deviceId, targetNodeId, Date.now(), deviceKey);
|
|
32783
32862
|
log.info("detection session: dispatched on motion", {
|
|
32784
32863
|
tags: { nodeId: targetNodeId },
|
|
32785
|
-
meta: {
|
|
32864
|
+
meta: {
|
|
32865
|
+
reason: decision.reason,
|
|
32866
|
+
...deviceKey ? { deviceKey } : {}
|
|
32867
|
+
}
|
|
32786
32868
|
});
|
|
32787
32869
|
} finally {
|
|
32788
32870
|
dispatchGuard.dispose();
|
package/dist/remoteEntry.js
CHANGED
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-BiQO4cUV.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline-orchestrator",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.58",
|
|
4
4
|
"description": "Hub-side camera-to-agent load balancer — tracks runner capacity and dispatches attachCamera calls to the optimal pipeline-runner instance",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|