@camstack/addon-pipeline-orchestrator 1.2.1 → 1.2.3
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/assets/viewer/.viewer-version +1 -1
- package/assets/viewer/_expo/static/js/web/{index-655abd000018be9c9945796877aa67b3.js → index-674098cd81fe50a10c3120d3111dfd45.js} +17 -17
- package/assets/viewer/index.html +1 -1
- package/dist/_stub.js +1 -1
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-D2fJDIWd.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-TbyFsMw6.mjs} +3 -3
- package/dist/{_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-fPhxTvHY.mjs → _virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Cyg3CeHQ.mjs} +1 -1
- package/dist/{hostInit-B4KGWuTo.mjs → hostInit-D6JRMTiQ.mjs} +3 -3
- package/dist/index.js +128 -12
- package/dist/index.mjs +128 -12
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -11581,7 +11581,8 @@ var PipelineStepInputSchema = lazy(() => object({
|
|
|
11581
11581
|
modelId: string().optional(),
|
|
11582
11582
|
enabled: boolean().default(true),
|
|
11583
11583
|
children: array(PipelineStepInputSchema).optional(),
|
|
11584
|
-
settings: record(string(), unknown()).optional()
|
|
11584
|
+
settings: record(string(), unknown()).optional(),
|
|
11585
|
+
jumpDeviceKey: string().optional()
|
|
11585
11586
|
}));
|
|
11586
11587
|
var ModelSubstitutionSchema = object({
|
|
11587
11588
|
addonId: string(),
|
|
@@ -12072,8 +12073,24 @@ var NativeCropBboxSchema = object({
|
|
|
12072
12073
|
});
|
|
12073
12074
|
/** Result of a best-effort native-resolution crop (`getNativeCrop`). */
|
|
12074
12075
|
var NativeCropResultSchema = object({
|
|
12075
|
-
/**
|
|
12076
|
-
|
|
12076
|
+
/**
|
|
12077
|
+
* Packed rgb (24-bit) pixels of the crop. Present on the DEFAULT (raw) path —
|
|
12078
|
+
* same-node (in-process / UDS) callers get zero-copy RGB and encode locally.
|
|
12079
|
+
* OMITTED when the caller requested `encodeJpeg` (the cross-node compressed
|
|
12080
|
+
* path below), where shipping raw RGB is both an invariant violation and, for
|
|
12081
|
+
* a native full frame (~26 MB at 4K), larger than Moleculer's 10 MB
|
|
12082
|
+
* `maxPacketSize` → the packet is dropped and the call 60s-times-out. That is
|
|
12083
|
+
* the cross-node native-media miss: `bytes` is replaced by `jpeg`.
|
|
12084
|
+
*/
|
|
12085
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
12086
|
+
/**
|
|
12087
|
+
* Base64 JPEG of the crop — the COMPRESSED cross-node payload. The OWNING node
|
|
12088
|
+
* (which holds the native surface) encodes it in-process, so a native 4K frame
|
|
12089
|
+
* ships as ~0.5–2 MB (well under `maxPacketSize`) and NO raw pixels ever cross
|
|
12090
|
+
* a process boundary (CLAUDE.md invariant #21). Present ONLY when the request
|
|
12091
|
+
* set `encodeJpeg: true`; `bytes` is then absent.
|
|
12092
|
+
*/
|
|
12093
|
+
jpeg: string().optional(),
|
|
12077
12094
|
width: number().int().positive(),
|
|
12078
12095
|
height: number().int().positive(),
|
|
12079
12096
|
/**
|
|
@@ -12219,6 +12236,20 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
|
|
|
12219
12236
|
hubHostnameOverride: string().optional()
|
|
12220
12237
|
})]).describe("Per-camera frame-source mode for the runner (P2c)");
|
|
12221
12238
|
/**
|
|
12239
|
+
* One ENABLED inference device on the runner's node, as the step-tree
|
|
12240
|
+
* device-jump resolver sees it (phase 1). The orchestrator populates this
|
|
12241
|
+
* roster on the attach payload whenever the camera is elected onto a specific
|
|
12242
|
+
* `deviceKey` and the node has ≥2 enabled devices — it is the candidate set the
|
|
12243
|
+
* runner auto-jumps an enrichment step to when the elected device's format
|
|
12244
|
+
* cannot run that step's model. `weight`/`maxSessions` mirror the balancer's
|
|
12245
|
+
* per-device knobs so the auto choice is weighted-least-loaded.
|
|
12246
|
+
*/
|
|
12247
|
+
var RunnerInferenceDeviceSchema = object({
|
|
12248
|
+
deviceKey: string(),
|
|
12249
|
+
weight: number().positive().default(1),
|
|
12250
|
+
maxSessions: number().int().positive().nullable().default(null)
|
|
12251
|
+
});
|
|
12252
|
+
/**
|
|
12222
12253
|
* Camera assignment payload sent by `addon-pipeline-orchestrator` to a
|
|
12223
12254
|
* specific runner instance via `attachCamera`. Carries everything the
|
|
12224
12255
|
* runner needs to subscribe to the local broker and execute inference.
|
|
@@ -12336,7 +12367,16 @@ var RunnerCameraConfigSchema = object({
|
|
|
12336
12367
|
* omitted ⇒ the runner's default device. The engine itself stays node-local —
|
|
12337
12368
|
* this only selects WHICH device pool of that node runs the session.
|
|
12338
12369
|
*/
|
|
12339
|
-
deviceKey: string().optional()
|
|
12370
|
+
deviceKey: string().optional(),
|
|
12371
|
+
/**
|
|
12372
|
+
* Step-tree device-jump roster (phase 1): the node's ENABLED inference
|
|
12373
|
+
* devices, populated by the orchestrator ONLY when `deviceKey` is set and the
|
|
12374
|
+
* node has ≥2 enabled devices. The runner uses it to AUTO-jump an enrichment
|
|
12375
|
+
* step whose model has no build for the elected device's format onto another
|
|
12376
|
+
* enabled device of the SAME node (weighted-least-loaded). Absent/single-entry
|
|
12377
|
+
* ⇒ no jump possible; the step runs on `deviceKey`.
|
|
12378
|
+
*/
|
|
12379
|
+
inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
|
|
12340
12380
|
});
|
|
12341
12381
|
/**
|
|
12342
12382
|
* Per-device settings UI fields the runner cap owns. Co-located with
|
|
@@ -12516,7 +12556,18 @@ var RunnerLocalMetricsSchema = object({
|
|
|
12516
12556
|
method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
|
|
12517
12557
|
handle: FrameHandleSchema,
|
|
12518
12558
|
bbox: NativeCropBboxSchema,
|
|
12519
|
-
maxWidth: number().int().positive().optional()
|
|
12559
|
+
maxWidth: number().int().positive().optional(),
|
|
12560
|
+
/**
|
|
12561
|
+
* When `true`, the runner encodes the resolved crop to JPEG ON THE
|
|
12562
|
+
* OWNING NODE and returns it in `jpeg` (base64) INSTEAD of raw `bytes`.
|
|
12563
|
+
* Callers set this for CROSS-NODE fetches (`handle.nodeId` is a remote
|
|
12564
|
+
* agent) so the compressed payload fits Moleculer's `maxPacketSize` and
|
|
12565
|
+
* no raw pixels cross the process boundary. Same-node callers omit it
|
|
12566
|
+
* and keep the zero-copy raw `bytes` path. Additive + optional: a
|
|
12567
|
+
* pre-encode runner (version skew) ignores it and returns `bytes`, so
|
|
12568
|
+
* the caller falls back to encoding locally.
|
|
12569
|
+
*/
|
|
12570
|
+
encodeJpeg: boolean().optional()
|
|
12520
12571
|
}), NativeCropResultSchema.nullable()), method(object({
|
|
12521
12572
|
deviceId: number(),
|
|
12522
12573
|
frameHandle: FrameHandleSchema.optional(),
|
|
@@ -17428,7 +17479,8 @@ var MediaFileKindEnum = _enum([
|
|
|
17428
17479
|
"faceCrop",
|
|
17429
17480
|
"plateCrop",
|
|
17430
17481
|
"keyFrame",
|
|
17431
|
-
"keyFrameSmall"
|
|
17482
|
+
"keyFrameSmall",
|
|
17483
|
+
"thumbnailSmall"
|
|
17432
17484
|
]);
|
|
17433
17485
|
var MediaFileSchema = object({
|
|
17434
17486
|
key: string(),
|
|
@@ -17759,7 +17811,16 @@ var PipelineTemplateSchema = object({
|
|
|
17759
17811
|
});
|
|
17760
17812
|
var DeviceStepConfigSchema = object({
|
|
17761
17813
|
modelId: string().optional(),
|
|
17762
|
-
settings: record(string(), unknown()).optional()
|
|
17814
|
+
settings: record(string(), unknown()).optional(),
|
|
17815
|
+
/**
|
|
17816
|
+
* Step-tree device jump (same-node, phase 1) — OPTIONAL manual override.
|
|
17817
|
+
* When set, this step runs on the named enabled device of the SAME node
|
|
17818
|
+
* (`<backend>:<device>`) instead of the camera's elected device, bypassing
|
|
17819
|
+
* the runtime AUTO-jump. The orchestrator rejects a `jumpDeviceKey` pointing
|
|
17820
|
+
* at a disabled/absent device on that node at save time. Absent ⇒ the runner
|
|
17821
|
+
* auto-jumps only when the effective device's format cannot run the step.
|
|
17822
|
+
*/
|
|
17823
|
+
jumpDeviceKey: string().optional()
|
|
17763
17824
|
});
|
|
17764
17825
|
var AgentPipelineSettingsSchema = object({
|
|
17765
17826
|
maxCameras: number().int().nonnegative().nullable().default(null),
|
|
@@ -26572,6 +26633,25 @@ function defaultModelIdForDevice(deviceKey) {
|
|
|
26572
26633
|
return "yolo26n";
|
|
26573
26634
|
}
|
|
26574
26635
|
/**
|
|
26636
|
+
* Step-tree device jump (phase 1): validate every `steps[step].jumpDeviceKey`
|
|
26637
|
+
* manual override in a to-be-saved `inferenceDevices` map. A jump target MUST be
|
|
26638
|
+
* an enabled∧available device on the SAME node and DIFFERENT from the owning
|
|
26639
|
+
* device. `enabledAvailableKeys` is the effective enabled∧available set (from
|
|
26640
|
+
* `mergeInferenceDevices(probe, submitted)`) so an absent/unplugged/disabled
|
|
26641
|
+
* target is rejected honestly (an operator can't route a step onto a dead pool).
|
|
26642
|
+
* Returns the FIRST human-readable error, or `null` when every override is
|
|
26643
|
+
* valid. Pure + deterministic.
|
|
26644
|
+
*/
|
|
26645
|
+
function validateJumpTargets(inferenceDevices, enabledAvailableKeys) {
|
|
26646
|
+
for (const [deviceKey, entry] of Object.entries(inferenceDevices)) for (const [stepId, step] of Object.entries(entry.steps ?? {})) {
|
|
26647
|
+
const target = step.jumpDeviceKey;
|
|
26648
|
+
if (target === void 0) continue;
|
|
26649
|
+
if (target === deviceKey) return `step "${stepId}" on "${deviceKey}" has a jumpDeviceKey pointing at its own device`;
|
|
26650
|
+
if (!enabledAvailableKeys.has(target)) return `step "${stepId}" on "${deviceKey}" has a jumpDeviceKey "${target}" that is not an enabled, available device on this node`;
|
|
26651
|
+
}
|
|
26652
|
+
return null;
|
|
26653
|
+
}
|
|
26654
|
+
/**
|
|
26575
26655
|
* Merge a node's live-probed inference devices with its stored per-device map.
|
|
26576
26656
|
*
|
|
26577
26657
|
* The default is **AUTO = all discovered ACCELERATORS enabled** (spec C2,
|
|
@@ -26640,6 +26720,23 @@ function eligibleInferenceDeviceWeights(probed, stored) {
|
|
|
26640
26720
|
for (const d of mergeInferenceDevices(probed, stored)) if (d.enabled && d.available) out[d.key] = d.weight;
|
|
26641
26721
|
return out;
|
|
26642
26722
|
}
|
|
26723
|
+
/**
|
|
26724
|
+
* Step-tree device jump (phase 1): the attach-payload roster of a node's
|
|
26725
|
+
* enabled∧available inference devices with the balancer knobs (`weight`,
|
|
26726
|
+
* `maxSessions`) the runner uses to AUTO-jump an enrichment step off a device
|
|
26727
|
+
* whose format can't run it. Built from the SAME `eligible` (deviceKey→weight)
|
|
26728
|
+
* and `caps` (deviceKey→maxSessions) the dispatcher already computes, so the
|
|
26729
|
+
* roster the runner sees exactly matches the balancer's candidate set. Sorted
|
|
26730
|
+
* by key for determinism. Populated onto `RunnerCameraConfig.inferenceDevices`
|
|
26731
|
+
* ONLY when a `deviceKey` is elected and there are ≥2 entries.
|
|
26732
|
+
*/
|
|
26733
|
+
function buildInferenceDeviceRoster(eligible, caps) {
|
|
26734
|
+
return Object.entries(eligible).map(([deviceKey, weight]) => ({
|
|
26735
|
+
deviceKey,
|
|
26736
|
+
weight: weight > 0 ? weight : 1,
|
|
26737
|
+
maxSessions: caps[deviceKey] ?? null
|
|
26738
|
+
})).toSorted((a, b) => a.deviceKey < b.deviceKey ? -1 : a.deviceKey > b.deviceKey ? 1 : 0);
|
|
26739
|
+
}
|
|
26643
26740
|
//#endregion
|
|
26644
26741
|
//#region src/orchestrator-types.ts
|
|
26645
26742
|
var PHASE_MODE_VALUES = new Set([
|
|
@@ -28801,7 +28898,12 @@ function buildRunnerConfig(base, overrides) {
|
|
|
28801
28898
|
}
|
|
28802
28899
|
//#endregion
|
|
28803
28900
|
//#region src/apply-device-provisioning.ts
|
|
28804
|
-
/**
|
|
28901
|
+
/**
|
|
28902
|
+
* Merge base then override for one step. Override keys win; undefined = inherit.
|
|
28903
|
+
* `jumpDeviceKey` (step-tree device jump, phase 1) is a per-(node,device) BASE
|
|
28904
|
+
* concept ONLY — the per-camera override never carries it (spec: per-camera jump
|
|
28905
|
+
* is out of scope), so it is taken from `base` verbatim.
|
|
28906
|
+
*/
|
|
28805
28907
|
function resolveStep(base, override) {
|
|
28806
28908
|
if (!base && !override) return void 0;
|
|
28807
28909
|
const modelId = override?.modelId ?? base?.modelId;
|
|
@@ -28809,9 +28911,11 @@ function resolveStep(base, override) {
|
|
|
28809
28911
|
...base?.settings,
|
|
28810
28912
|
...override?.settings
|
|
28811
28913
|
} : void 0;
|
|
28914
|
+
const jumpDeviceKey = base?.jumpDeviceKey;
|
|
28812
28915
|
return {
|
|
28813
28916
|
...modelId !== void 0 ? { modelId } : {},
|
|
28814
|
-
...settings ? { settings } : {}
|
|
28917
|
+
...settings ? { settings } : {},
|
|
28918
|
+
...jumpDeviceKey !== void 0 ? { jumpDeviceKey } : {}
|
|
28815
28919
|
};
|
|
28816
28920
|
}
|
|
28817
28921
|
/**
|
|
@@ -28829,6 +28933,7 @@ function applyDeviceProvisioning(steps, base, override) {
|
|
|
28829
28933
|
...step,
|
|
28830
28934
|
...cfg?.modelId !== void 0 ? { modelId: cfg.modelId } : {},
|
|
28831
28935
|
...cfg?.settings ? { settings: cfg.settings } : {},
|
|
28936
|
+
...cfg?.jumpDeviceKey !== void 0 ? { jumpDeviceKey: cfg.jumpDeviceKey } : {},
|
|
28832
28937
|
...children !== void 0 ? { children } : {}
|
|
28833
28938
|
};
|
|
28834
28939
|
});
|
|
@@ -29813,17 +29918,21 @@ var PlacementService = class {
|
|
|
29813
29918
|
dispatchScore = decision.score;
|
|
29814
29919
|
}
|
|
29815
29920
|
const targetLoad = loads.find((load) => load.nodeId === targetNodeId);
|
|
29921
|
+
const eligibleDevices = await this.deps.getEligibleInferenceDevices(targetNodeId);
|
|
29922
|
+
const deviceCaps = await this.deps.getInferenceDeviceCaps(targetNodeId);
|
|
29816
29923
|
const deviceKey = pickInferenceDevice({
|
|
29817
|
-
eligible:
|
|
29924
|
+
eligible: eligibleDevices,
|
|
29818
29925
|
loads: targetLoad?.devices ?? [],
|
|
29819
|
-
caps:
|
|
29926
|
+
caps: deviceCaps,
|
|
29820
29927
|
pinnedDeviceKey: await this.deps.readPipelineDevicePin(runnerConfig.deviceId)
|
|
29821
29928
|
}) ?? void 0;
|
|
29822
29929
|
const deviceBase = deviceKey ? await this.deps.settingsStore.buildNodeInferenceDeviceSteps(targetNodeId, deviceKey) : {};
|
|
29823
29930
|
const deviceOverride = deviceKey ? await this.deps.settingsStore.readCameraDeviceOverride(runnerConfig.deviceId, targetNodeId, deviceKey) : {};
|
|
29931
|
+
const inferenceDevices = deviceKey && Object.keys(eligibleDevices).length >= 2 ? buildInferenceDeviceRoster(eligibleDevices, deviceCaps) : void 0;
|
|
29824
29932
|
const attachConfig = {
|
|
29825
29933
|
...runnerConfig,
|
|
29826
29934
|
...deviceKey ? { deviceKey } : {},
|
|
29935
|
+
...inferenceDevices ? { inferenceDevices } : {},
|
|
29827
29936
|
...runnerConfig.steps ? { steps: applyDeviceProvisioning(runnerConfig.steps, deviceBase, deviceOverride) } : {}
|
|
29828
29937
|
};
|
|
29829
29938
|
const dispatchGuard = this.deps.ledger.markDispatching(runnerConfig.deviceId);
|
|
@@ -31805,7 +31914,8 @@ var StoredAgentAddonConfigSchema = object({
|
|
|
31805
31914
|
});
|
|
31806
31915
|
var StoredDeviceStepConfigSchema = object({
|
|
31807
31916
|
modelId: string().optional(),
|
|
31808
|
-
settings: record(string(), unknown()).readonly().optional()
|
|
31917
|
+
settings: record(string(), unknown()).readonly().optional(),
|
|
31918
|
+
jumpDeviceKey: string().optional()
|
|
31809
31919
|
});
|
|
31810
31920
|
var StoredAgentPipelineSettingsSchema = object({
|
|
31811
31921
|
addonDefaults: record(string(), StoredAgentAddonConfigSchema).optional(),
|
|
@@ -33266,10 +33376,12 @@ var SessionDispatchController = class {
|
|
|
33266
33376
|
const deviceBase = deviceKey ? await this.deps.settingsStore.buildNodeInferenceDeviceSteps(targetNodeId, deviceKey) : {};
|
|
33267
33377
|
const deviceOverride = deviceKey ? await this.deps.settingsStore.readCameraDeviceOverride(deviceId, targetNodeId, deviceKey) : {};
|
|
33268
33378
|
const steps = applyDeviceProvisioning(pipelineConfig.steps, deviceBase, deviceOverride);
|
|
33379
|
+
const inferenceDevices = deviceKey && Object.keys(enabledDevices).length >= 2 ? buildInferenceDeviceRoster(enabledDevices, deviceCaps) : void 0;
|
|
33269
33380
|
const zones = await this.deps.listZones(deviceId);
|
|
33270
33381
|
const sessionConfig = {
|
|
33271
33382
|
deviceId,
|
|
33272
33383
|
...deviceKey ? { deviceKey } : {},
|
|
33384
|
+
...inferenceDevices ? { inferenceDevices } : {},
|
|
33273
33385
|
motionCooldownMs: config.motionCooldownMs,
|
|
33274
33386
|
motionFps: config.motionFps,
|
|
33275
33387
|
detectionFps: config.detectionFps,
|
|
@@ -35316,6 +35428,10 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
35316
35428
|
* pending so a node newly given more capacity picks up stranded cameras.
|
|
35317
35429
|
*/
|
|
35318
35430
|
async setAgentInferenceDevices(input) {
|
|
35431
|
+
const { probed } = await this.probeNodeInferenceDevices(input.agentNodeId);
|
|
35432
|
+
const enabledAvailableKeys = new Set(mergeInferenceDevices(probed, input.inferenceDevices).filter((d) => d.enabled && d.available).map((d) => d.key));
|
|
35433
|
+
const jumpError = validateJumpTargets(input.inferenceDevices, enabledAvailableKeys);
|
|
35434
|
+
if (jumpError) throw new Error(`setAgentInferenceDevices: ${jumpError}`);
|
|
35319
35435
|
const existing = (await this.settingsStore.readAgentSettingsMap())[input.agentNodeId];
|
|
35320
35436
|
const next = existing ? {
|
|
35321
35437
|
...existing,
|
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-TbyFsMw6.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.2.
|
|
3
|
+
"version": "1.2.3",
|
|
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",
|