@camstack/addon-pipeline-orchestrator 1.2.289 → 1.2.290
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/_stub.js +2 -2
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-B17_Um7q.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-DHY2k_nC.mjs} +2 -2
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DWLgJB4I.mjs +26 -0
- package/dist/{_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CvQcqdZe.mjs → _virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CdkZH5kw.mjs} +1 -1
- package/dist/{hostInit-Bd-NWr7j.mjs → hostInit-CRtAczui.mjs} +2 -2
- package/dist/index.js +2000 -128
- package/dist/index.mjs +2001 -129
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-3MI4PP9B.mjs +0 -26
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -8048,7 +8048,7 @@ import { errMsg } from '@camstack/types'
|
|
|
8048
8048
|
* Extract a human-readable message from an unknown error value.
|
|
8049
8049
|
* Replaces the ubiquitous `errMsg(err)` pattern.
|
|
8050
8050
|
*/
|
|
8051
|
-
function errMsg$
|
|
8051
|
+
function errMsg$2(err) {
|
|
8052
8052
|
if (err instanceof Error) return err.message;
|
|
8053
8053
|
if (typeof err === "string") return err;
|
|
8054
8054
|
return String(err);
|
|
@@ -11551,7 +11551,53 @@ var ModelFormatEntrySchema = object({
|
|
|
11551
11551
|
/** Multi-file payload (directory members or sibling files). */
|
|
11552
11552
|
files: array(string()).readonly().optional(),
|
|
11553
11553
|
/** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
|
|
11554
|
-
runtimes: array(_enum(["python"])).readonly().optional()
|
|
11554
|
+
runtimes: array(_enum(["python"])).readonly().optional(),
|
|
11555
|
+
/**
|
|
11556
|
+
* Content revision of the artefact, for one hosted OFF the CamStack mirror.
|
|
11557
|
+
* A format on the mirror leaves it unset: its revision (the sha256 of its
|
|
11558
|
+
* weight file) comes from the downloader's generated table, kept in step
|
|
11559
|
+
* with the mirror by `scripts/check-hosted-model-revisions.ts` (D744). A
|
|
11560
|
+
* node records the revision it downloaded next to the file
|
|
11561
|
+
* (`<file>.revision`); a copy with no record or a different one is stale
|
|
11562
|
+
* unless its weight file hashes to the revision, and a stale copy is
|
|
11563
|
+
* downloaded again, the old one serving until the new one is complete. No
|
|
11564
|
+
* revision at all ⇒ a file on disk is current, whatever it holds (D743).
|
|
11565
|
+
*/
|
|
11566
|
+
revision: string().min(1).optional()
|
|
11567
|
+
});
|
|
11568
|
+
/**
|
|
11569
|
+
* What a node holds for one (model, format) (D746). `current` is a complete
|
|
11570
|
+
* copy at its catalog revision — exactly what `downloaded: true` means.
|
|
11571
|
+
* `stale` is bytes on disk that are not (yet) proven current under D743/D744:
|
|
11572
|
+
* no or a different revision record, or a missing sibling. `missing` is no
|
|
11573
|
+
* copy. `downloaded` alone folds `stale` into "not downloaded", which hid
|
|
11574
|
+
* reclaimable bytes and a copy about to be replaced.
|
|
11575
|
+
*/
|
|
11576
|
+
var ModelOnDiskStateSchema = _enum([
|
|
11577
|
+
"current",
|
|
11578
|
+
"stale",
|
|
11579
|
+
"missing"
|
|
11580
|
+
]);
|
|
11581
|
+
/**
|
|
11582
|
+
* The numeric precision of one format's BUILD (D746) — reported only when the
|
|
11583
|
+
* build states it (file-name token, an INT8 variant, an EdgeTPU compile, a
|
|
11584
|
+
* CamStack Core ML export). Absent means "not stated", never "fp32".
|
|
11585
|
+
*/
|
|
11586
|
+
var ModelFormatPrecisionSchema = _enum([
|
|
11587
|
+
"fp32",
|
|
11588
|
+
"fp16",
|
|
11589
|
+
"int8"
|
|
11590
|
+
]);
|
|
11591
|
+
/**
|
|
11592
|
+
* One format of one model as a node's `pipelineExecutor.getSchema` reports it.
|
|
11593
|
+
* `onDisk` / `precision` are optional for version skew: a runner that predates
|
|
11594
|
+
* them omits them, and a reader falls back to `downloaded`.
|
|
11595
|
+
*/
|
|
11596
|
+
var PipelineModelFormatStatusSchema = object({
|
|
11597
|
+
downloaded: boolean(),
|
|
11598
|
+
sizeMB: number(),
|
|
11599
|
+
onDisk: ModelOnDiskStateSchema.optional(),
|
|
11600
|
+
precision: ModelFormatPrecisionSchema.optional()
|
|
11555
11601
|
});
|
|
11556
11602
|
/**
|
|
11557
11603
|
* Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
|
|
@@ -24297,7 +24343,7 @@ var FrameRefSchema = object({
|
|
|
24297
24343
|
timestamp: number(),
|
|
24298
24344
|
capturedAt: number().optional()
|
|
24299
24345
|
});
|
|
24300
|
-
var ModelFormatSchema$
|
|
24346
|
+
var ModelFormatSchema$2 = _enum([
|
|
24301
24347
|
"onnx",
|
|
24302
24348
|
"coreml",
|
|
24303
24349
|
"openvino",
|
|
@@ -24315,7 +24361,7 @@ var PipelineSlotSchema = _enum([
|
|
|
24315
24361
|
var PipelineEngineChoiceSchema = object({
|
|
24316
24362
|
runtime: literal("python"),
|
|
24317
24363
|
backend: string(),
|
|
24318
|
-
format: ModelFormatSchema$
|
|
24364
|
+
format: ModelFormatSchema$2,
|
|
24319
24365
|
device: string().optional()
|
|
24320
24366
|
});
|
|
24321
24367
|
/**
|
|
@@ -24372,10 +24418,7 @@ var PipelineTemplateSchema$1 = object({
|
|
|
24372
24418
|
var PipelineModelOptionSchema = object({
|
|
24373
24419
|
id: string(),
|
|
24374
24420
|
name: string(),
|
|
24375
|
-
formats: record(string(),
|
|
24376
|
-
downloaded: boolean(),
|
|
24377
|
-
sizeMB: number()
|
|
24378
|
-
})),
|
|
24421
|
+
formats: record(string(), PipelineModelFormatStatusSchema),
|
|
24379
24422
|
group: ModelVariantGroupSchema.optional(),
|
|
24380
24423
|
legacy: boolean().optional(),
|
|
24381
24424
|
provider: ModelProviderIdSchema.optional(),
|
|
@@ -24590,6 +24633,32 @@ var pipelineExecutorCapability = {
|
|
|
24590
24633
|
auth: "admin"
|
|
24591
24634
|
}),
|
|
24592
24635
|
/**
|
|
24636
|
+
* Best settings (D745): the MODEL-BOUND thresholds (`bindsToModel` keys of
|
|
24637
|
+
* the object-detection root, and their binding keys) stored in THIS node's
|
|
24638
|
+
* per-camera detector settings — and, unless `dryRun`, remove them so the
|
|
24639
|
+
* running model's own defaults apply. Every other key of a camera's store
|
|
24640
|
+
* is kept. `nodeId` is the ROUTING key. `deviceIds` absent ⇒ every camera
|
|
24641
|
+
* with a store. A camera whose store could not be read is `refused`, never
|
|
24642
|
+
* reported as clean.
|
|
24643
|
+
*/
|
|
24644
|
+
clearModelBoundDeviceOverrides: method(object({
|
|
24645
|
+
nodeId: string(),
|
|
24646
|
+
deviceIds: array(number()).readonly().optional(),
|
|
24647
|
+
dryRun: boolean()
|
|
24648
|
+
}), object({
|
|
24649
|
+
entries: array(object({
|
|
24650
|
+
deviceId: number(),
|
|
24651
|
+
keys: array(string()).readonly()
|
|
24652
|
+
})).readonly(),
|
|
24653
|
+
refused: array(object({
|
|
24654
|
+
deviceId: number(),
|
|
24655
|
+
reason: string()
|
|
24656
|
+
})).readonly()
|
|
24657
|
+
}), {
|
|
24658
|
+
kind: "mutation",
|
|
24659
|
+
auth: "admin"
|
|
24660
|
+
}),
|
|
24661
|
+
/**
|
|
24593
24662
|
* Which of THIS node's inference devices the executor currently refuses,
|
|
24594
24663
|
* and why. `nodeId` is the ROUTING key (stripped by the generated router).
|
|
24595
24664
|
*
|
|
@@ -24684,12 +24753,12 @@ var pipelineExecutorCapability = {
|
|
|
24684
24753
|
downloadModel: method(object({
|
|
24685
24754
|
addonId: string(),
|
|
24686
24755
|
modelId: string(),
|
|
24687
|
-
format: ModelFormatSchema$
|
|
24756
|
+
format: ModelFormatSchema$2
|
|
24688
24757
|
}), DownloadModelResultSchema, { kind: "mutation" }),
|
|
24689
24758
|
deleteModel: method(object({
|
|
24690
24759
|
addonId: string(),
|
|
24691
24760
|
modelId: string(),
|
|
24692
|
-
format: ModelFormatSchema$
|
|
24761
|
+
format: ModelFormatSchema$2
|
|
24693
24762
|
}), object({ success: literal(true) }), { kind: "mutation" }),
|
|
24694
24763
|
/**
|
|
24695
24764
|
* Stateless single-frame execution. Callers (runner, benchmark) pass
|
|
@@ -24935,6 +25004,204 @@ var pipelineExecutorCapability = {
|
|
|
24935
25004
|
getDetectionConfigSchema: method(_void(), ConfigUISchemaNullableBridge)
|
|
24936
25005
|
}
|
|
24937
25006
|
};
|
|
25007
|
+
/**
|
|
25008
|
+
* The wire shapes of the Best settings plan and apply (D745), used by the
|
|
25009
|
+
* `pipeline-orchestrator` cap. The recommender's own types
|
|
25010
|
+
* (`recommend.ts`, `demand.ts`) are the TS face of the same data; these
|
|
25011
|
+
* schemas are what crosses the cap boundary.
|
|
25012
|
+
*/
|
|
25013
|
+
var ModelFormatSchema$1 = _enum(MODEL_FORMATS);
|
|
25014
|
+
var HardwareClassIdSchema = _enum([
|
|
25015
|
+
"intel-core-ultra",
|
|
25016
|
+
"intel-n-series",
|
|
25017
|
+
"apple-m-base",
|
|
25018
|
+
"apple-m-pro",
|
|
25019
|
+
"unknown"
|
|
25020
|
+
]);
|
|
25021
|
+
var AcceleratorKindSchema = _enum([
|
|
25022
|
+
"intel-npu",
|
|
25023
|
+
"intel-igpu",
|
|
25024
|
+
"x86-cpu",
|
|
25025
|
+
"arm-cpu",
|
|
25026
|
+
"apple-ane",
|
|
25027
|
+
"coral"
|
|
25028
|
+
]);
|
|
25029
|
+
var RecommendationReasonSchema = discriminatedUnion("kind", [
|
|
25030
|
+
object({
|
|
25031
|
+
kind: literal("fits"),
|
|
25032
|
+
utilisation: number()
|
|
25033
|
+
}),
|
|
25034
|
+
object({
|
|
25035
|
+
kind: literal("tie-faster"),
|
|
25036
|
+
over: string(),
|
|
25037
|
+
recallDelta: number()
|
|
25038
|
+
}),
|
|
25039
|
+
object({
|
|
25040
|
+
kind: literal("stepped-down"),
|
|
25041
|
+
from: string(),
|
|
25042
|
+
to: string(),
|
|
25043
|
+
limitedBy: string()
|
|
25044
|
+
}),
|
|
25045
|
+
object({
|
|
25046
|
+
kind: literal("no-fit-lightest"),
|
|
25047
|
+
utilisation: number()
|
|
25048
|
+
}),
|
|
25049
|
+
object({
|
|
25050
|
+
kind: literal("derived-latency"),
|
|
25051
|
+
modelId: string()
|
|
25052
|
+
}),
|
|
25053
|
+
object({
|
|
25054
|
+
kind: literal("estimated-load"),
|
|
25055
|
+
observedForMs: number().nullable()
|
|
25056
|
+
}),
|
|
25057
|
+
object({ kind: literal("cpu-device") }),
|
|
25058
|
+
object({ kind: literal("coral-untouched") }),
|
|
25059
|
+
object({ kind: literal("device-disabled") }),
|
|
25060
|
+
object({ kind: literal("device-unavailable") }),
|
|
25061
|
+
object({ kind: literal("node-offline") }),
|
|
25062
|
+
object({ kind: literal("no-hardware-facts") }),
|
|
25063
|
+
object({
|
|
25064
|
+
kind: literal("no-measured-data"),
|
|
25065
|
+
hardwareClass: HardwareClassIdSchema
|
|
25066
|
+
}),
|
|
25067
|
+
object({
|
|
25068
|
+
kind: literal("no-family-build"),
|
|
25069
|
+
format: ModelFormatSchema$1
|
|
25070
|
+
})
|
|
25071
|
+
]);
|
|
25072
|
+
var DeviceDemandSchema = object({
|
|
25073
|
+
nodeId: string(),
|
|
25074
|
+
deviceKey: string(),
|
|
25075
|
+
demandFps: number(),
|
|
25076
|
+
maxCameraFps: number(),
|
|
25077
|
+
alwaysOnCameras: number(),
|
|
25078
|
+
onMotionSessions: number(),
|
|
25079
|
+
onMotionSource: _enum([
|
|
25080
|
+
"observed-24h",
|
|
25081
|
+
"estimate",
|
|
25082
|
+
"none"
|
|
25083
|
+
]),
|
|
25084
|
+
observedForMs: number().nullable()
|
|
25085
|
+
});
|
|
25086
|
+
var DeviceModelChoiceSchema = object({
|
|
25087
|
+
modelId: string(),
|
|
25088
|
+
p50Ms: number(),
|
|
25089
|
+
fps: number(),
|
|
25090
|
+
measured: boolean(),
|
|
25091
|
+
utilisation: number(),
|
|
25092
|
+
personRecall: number()
|
|
25093
|
+
});
|
|
25094
|
+
var DeviceRecommendationSchema = object({
|
|
25095
|
+
deviceKey: string(),
|
|
25096
|
+
accelerator: AcceleratorKindSchema.nullable(),
|
|
25097
|
+
enabled: boolean(),
|
|
25098
|
+
available: boolean(),
|
|
25099
|
+
demand: DeviceDemandSchema.nullable(),
|
|
25100
|
+
chosen: DeviceModelChoiceSchema.nullable(),
|
|
25101
|
+
reasons: array(RecommendationReasonSchema).readonly()
|
|
25102
|
+
});
|
|
25103
|
+
var NodeRecommendationSchema = object({
|
|
25104
|
+
nodeId: string(),
|
|
25105
|
+
reachable: boolean(),
|
|
25106
|
+
hardwareClass: HardwareClassIdSchema.nullable(),
|
|
25107
|
+
cpuModel: string().nullable(),
|
|
25108
|
+
skipped: boolean(),
|
|
25109
|
+
acceleratorModelId: string().nullable(),
|
|
25110
|
+
devices: array(DeviceRecommendationSchema).readonly(),
|
|
25111
|
+
reasons: array(RecommendationReasonSchema).readonly()
|
|
25112
|
+
});
|
|
25113
|
+
var ModelPinChangeSchema = object({
|
|
25114
|
+
stepId: string(),
|
|
25115
|
+
before: string().nullable(),
|
|
25116
|
+
after: string().nullable()
|
|
25117
|
+
});
|
|
25118
|
+
var NodeDeviceChangeSchema = object({
|
|
25119
|
+
nodeId: string(),
|
|
25120
|
+
deviceKey: string(),
|
|
25121
|
+
madeExplicit: boolean(),
|
|
25122
|
+
modelPins: array(ModelPinChangeSchema).readonly(),
|
|
25123
|
+
thresholds: array(object({
|
|
25124
|
+
stepId: string(),
|
|
25125
|
+
keys: array(string()).readonly()
|
|
25126
|
+
})).readonly()
|
|
25127
|
+
});
|
|
25128
|
+
var CameraOverrideClearSchema = object({
|
|
25129
|
+
deviceId: number(),
|
|
25130
|
+
source: _enum(["step-override", "wholesale"]),
|
|
25131
|
+
nodeId: string(),
|
|
25132
|
+
deviceKey: string().nullable(),
|
|
25133
|
+
stepId: string(),
|
|
25134
|
+
modelIdBefore: string().nullable(),
|
|
25135
|
+
keys: array(string()).readonly()
|
|
25136
|
+
});
|
|
25137
|
+
var DetectorStoreClearSchema = object({
|
|
25138
|
+
deviceId: number(),
|
|
25139
|
+
keys: array(string()).readonly()
|
|
25140
|
+
});
|
|
25141
|
+
var BestSettingsWarningSchema = discriminatedUnion("kind", [
|
|
25142
|
+
object({
|
|
25143
|
+
kind: literal("detector-store-cluster-wide"),
|
|
25144
|
+
skippedNodes: array(string()).readonly()
|
|
25145
|
+
}),
|
|
25146
|
+
object({
|
|
25147
|
+
kind: literal("detector-store-unreadable"),
|
|
25148
|
+
error: string()
|
|
25149
|
+
}),
|
|
25150
|
+
object({
|
|
25151
|
+
kind: literal("coral-enabled"),
|
|
25152
|
+
nodeId: string(),
|
|
25153
|
+
deviceKey: string()
|
|
25154
|
+
})
|
|
25155
|
+
]);
|
|
25156
|
+
var BestSettingsOptionsSchema = object({
|
|
25157
|
+
/** Share of each accelerator's throughput kept free (0.3–0.5). */
|
|
25158
|
+
headroom: number().min(.3).max(.5).default(.4) });
|
|
25159
|
+
var BestSettingsPlanSchema = object({
|
|
25160
|
+
/** Hash of everything the apply will write — the apply refuses a different one. */
|
|
25161
|
+
planHash: string(),
|
|
25162
|
+
family: literal("yolov9"),
|
|
25163
|
+
headroom: number(),
|
|
25164
|
+
generatedAt: number(),
|
|
25165
|
+
nodes: array(NodeRecommendationSchema).readonly(),
|
|
25166
|
+
nodeChanges: array(NodeDeviceChangeSchema).readonly(),
|
|
25167
|
+
cameraClears: array(CameraOverrideClearSchema).readonly(),
|
|
25168
|
+
detectorStoreClears: array(DetectorStoreClearSchema).readonly(),
|
|
25169
|
+
warnings: array(BestSettingsWarningSchema).readonly()
|
|
25170
|
+
});
|
|
25171
|
+
var BestSettingsApplyInputSchema = object({
|
|
25172
|
+
planHash: string(),
|
|
25173
|
+
headroom: number().min(.3).max(.5).default(.4)
|
|
25174
|
+
});
|
|
25175
|
+
var BestSettingsPhaseResultSchema = object({
|
|
25176
|
+
phase: _enum([
|
|
25177
|
+
"download",
|
|
25178
|
+
"camera-overrides",
|
|
25179
|
+
"detector-thresholds",
|
|
25180
|
+
"node-models",
|
|
25181
|
+
"redispatch"
|
|
25182
|
+
]),
|
|
25183
|
+
ok: boolean(),
|
|
25184
|
+
detail: string()
|
|
25185
|
+
});
|
|
25186
|
+
var BestSettingsApplyResultSchema = discriminatedUnion("kind", [
|
|
25187
|
+
object({
|
|
25188
|
+
kind: literal("applied"),
|
|
25189
|
+
planHash: string(),
|
|
25190
|
+
phases: array(BestSettingsPhaseResultSchema).readonly()
|
|
25191
|
+
}),
|
|
25192
|
+
object({
|
|
25193
|
+
kind: literal("aborted"),
|
|
25194
|
+
phases: array(BestSettingsPhaseResultSchema).readonly()
|
|
25195
|
+
}),
|
|
25196
|
+
object({
|
|
25197
|
+
kind: literal("stale"),
|
|
25198
|
+
plan: BestSettingsPlanSchema
|
|
25199
|
+
}),
|
|
25200
|
+
object({
|
|
25201
|
+
kind: literal("refused"),
|
|
25202
|
+
reason: _enum(["apply-in-progress"])
|
|
25203
|
+
})
|
|
25204
|
+
]);
|
|
24938
25205
|
object({
|
|
24939
25206
|
activeCameras: number(),
|
|
24940
25207
|
throttledCameras: number(),
|
|
@@ -27121,6 +27388,27 @@ var pipelineOrchestratorCapability = {
|
|
|
27121
27388
|
kind: "mutation",
|
|
27122
27389
|
auth: "admin"
|
|
27123
27390
|
}),
|
|
27391
|
+
/**
|
|
27392
|
+
* Best settings (D745) — DRY RUN. Recommends ONE detector family (YOLOv9)
|
|
27393
|
+
* for the whole cluster from each node's static hardware class, the
|
|
27394
|
+
* measured latency table and the detection demand, and lists every write
|
|
27395
|
+
* the apply would make: the node model pins set, the enrichment pins and
|
|
27396
|
+
* model-bound thresholds removed (node level and per camera), and the
|
|
27397
|
+
* per-camera detector thresholds cleared. Writes nothing. Step
|
|
27398
|
+
* enablement, device enablement, pins, recording, notifications, zones and
|
|
27399
|
+
* rules are never part of it. `planHash` names exactly this diff.
|
|
27400
|
+
*/
|
|
27401
|
+
planBestSettings: method(BestSettingsOptionsSchema, BestSettingsPlanSchema, { auth: "admin" }),
|
|
27402
|
+
/**
|
|
27403
|
+
* Best settings (D745) — APPLY the plan the operator confirmed. Recomputes
|
|
27404
|
+
* the plan and refuses (`stale`, with the fresh plan) when its hash is not
|
|
27405
|
+
* `planHash`, so what is written is exactly the confirmed diff. Offline
|
|
27406
|
+
* nodes are skipped; there is no undo.
|
|
27407
|
+
*/
|
|
27408
|
+
applyBestSettings: method(BestSettingsApplyInputSchema, BestSettingsApplyResultSchema, {
|
|
27409
|
+
kind: "mutation",
|
|
27410
|
+
auth: "admin"
|
|
27411
|
+
}),
|
|
27124
27412
|
/** Read one camera's settings. Null when never touched (inherits agent defaults fully). */
|
|
27125
27413
|
getCameraSettings: method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()),
|
|
27126
27414
|
/** Set or clear the 3-state toggle for one (camera, addonId). Pass `enabled: null` to clear and revert to agent default. */
|
|
@@ -42922,6 +43210,12 @@ Object.freeze({
|
|
|
42922
43210
|
addonId: null,
|
|
42923
43211
|
access: "delete"
|
|
42924
43212
|
},
|
|
43213
|
+
"pipelineExecutor.clearModelBoundDeviceOverrides": {
|
|
43214
|
+
capName: "pipeline-executor",
|
|
43215
|
+
capScope: "system",
|
|
43216
|
+
addonId: null,
|
|
43217
|
+
access: "delete"
|
|
43218
|
+
},
|
|
42925
43219
|
"pipelineExecutor.deleteModel": {
|
|
42926
43220
|
capName: "pipeline-executor",
|
|
42927
43221
|
capScope: "system",
|
|
@@ -43138,6 +43432,12 @@ Object.freeze({
|
|
|
43138
43432
|
addonId: null,
|
|
43139
43433
|
access: "view"
|
|
43140
43434
|
},
|
|
43435
|
+
"pipelineOrchestrator.applyBestSettings": {
|
|
43436
|
+
capName: "pipeline-orchestrator",
|
|
43437
|
+
capScope: "system",
|
|
43438
|
+
addonId: null,
|
|
43439
|
+
access: "create"
|
|
43440
|
+
},
|
|
43141
43441
|
"pipelineOrchestrator.applyDeviceSettingsPatch": {
|
|
43142
43442
|
capName: "pipeline-orchestrator",
|
|
43143
43443
|
capScope: "system",
|
|
@@ -43312,6 +43612,12 @@ Object.freeze({
|
|
|
43312
43612
|
addonId: null,
|
|
43313
43613
|
access: "create"
|
|
43314
43614
|
},
|
|
43615
|
+
"pipelineOrchestrator.planBestSettings": {
|
|
43616
|
+
capName: "pipeline-orchestrator",
|
|
43617
|
+
capScope: "system",
|
|
43618
|
+
addonId: null,
|
|
43619
|
+
access: "view"
|
|
43620
|
+
},
|
|
43315
43621
|
"pipelineOrchestrator.rebalance": {
|
|
43316
43622
|
capName: "pipeline-orchestrator",
|
|
43317
43623
|
capScope: "system",
|
|
@@ -48762,6 +49068,11 @@ Object.freeze({
|
|
|
48762
49068
|
form: "single",
|
|
48763
49069
|
optional: false
|
|
48764
49070
|
}],
|
|
49071
|
+
"pipelineExecutor.clearModelBoundDeviceOverrides": [{
|
|
49072
|
+
name: "deviceIds",
|
|
49073
|
+
form: "array",
|
|
49074
|
+
optional: true
|
|
49075
|
+
}],
|
|
48765
49076
|
"pipelineExecutor.runPipeline": [{
|
|
48766
49077
|
name: "deviceId",
|
|
48767
49078
|
form: "single",
|
|
@@ -50656,6 +50967,830 @@ var REMOVED_MODEL_IDS = {
|
|
|
50656
50967
|
function isRemovedModelId(stepId, modelId) {
|
|
50657
50968
|
return REMOVED_MODEL_IDS[stepId]?.includes(modelId) ?? false;
|
|
50658
50969
|
}
|
|
50970
|
+
/** Suffix of the sibling key that names the model a stored value was made for. */
|
|
50971
|
+
var MODEL_BINDING_SUFFIX = "ModelId";
|
|
50972
|
+
/** `minConfidencePerson` → `minConfidencePersonModelId`. */
|
|
50973
|
+
function modelBindingKey(key) {
|
|
50974
|
+
return `${key}${MODEL_BINDING_SUFFIX}`;
|
|
50975
|
+
}
|
|
50976
|
+
/**
|
|
50977
|
+
* The keys of every `bindsToModel` field of a step's config schema, groups
|
|
50978
|
+
* flattened (D738). One definition for the runner (which applies the binding)
|
|
50979
|
+
* and the orchestrator's Best settings (which clears those overrides, D745).
|
|
50980
|
+
*/
|
|
50981
|
+
function collectModelBoundKeys(fields) {
|
|
50982
|
+
return fields.flatMap((field) => {
|
|
50983
|
+
if (field.type === "group") return collectModelBoundKeys(field.fields);
|
|
50984
|
+
return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
|
|
50985
|
+
});
|
|
50986
|
+
}
|
|
50987
|
+
var APPLE_CHIP = /^Apple M(\d+)(?:\s+(Pro|Max|Ultra))?\b/i;
|
|
50988
|
+
var INTEL_CORE_ULTRA = /Core\(TM\)\s+Ultra|Core Ultra/i;
|
|
50989
|
+
var INTEL_N_SERIES = /Intel\(R\)\s+N\d{2,3}\b|\bIntel\s+N\d{2,3}\b/i;
|
|
50990
|
+
function parseAppleChip(cpuModel) {
|
|
50991
|
+
const match = APPLE_CHIP.exec(cpuModel.trim());
|
|
50992
|
+
if (match === null) return null;
|
|
50993
|
+
const generation = Number(match[1]);
|
|
50994
|
+
const variantRaw = (match[2] ?? "base").toLowerCase();
|
|
50995
|
+
return {
|
|
50996
|
+
generation,
|
|
50997
|
+
variant: variantRaw === "pro" || variantRaw === "max" || variantRaw === "ultra" ? variantRaw : "base"
|
|
50998
|
+
};
|
|
50999
|
+
}
|
|
51000
|
+
/**
|
|
51001
|
+
* Classify a node. Apple base chips (M1–M4) map to the measured M2 column, the
|
|
51002
|
+
* Pro/Max/Ultra chips to the measured M3 Pro column — the M2 was measured at
|
|
51003
|
+
* 1.0–1.2× the M3 Pro on the Neural Engine, so the generation inside a variant
|
|
51004
|
+
* matters less than the column's own error.
|
|
51005
|
+
*/
|
|
51006
|
+
function classifyInferenceHardware(hw) {
|
|
51007
|
+
const cpuModel = hw.cpuModel;
|
|
51008
|
+
const apple = parseAppleChip(cpuModel);
|
|
51009
|
+
if (apple !== null) return {
|
|
51010
|
+
hardwareClass: apple.variant === "base" ? "apple-m-base" : "apple-m-pro",
|
|
51011
|
+
cpuModel,
|
|
51012
|
+
apple
|
|
51013
|
+
};
|
|
51014
|
+
if (INTEL_CORE_ULTRA.test(cpuModel)) return {
|
|
51015
|
+
hardwareClass: "intel-core-ultra",
|
|
51016
|
+
cpuModel
|
|
51017
|
+
};
|
|
51018
|
+
if (INTEL_N_SERIES.test(cpuModel)) return {
|
|
51019
|
+
hardwareClass: "intel-n-series",
|
|
51020
|
+
cpuModel
|
|
51021
|
+
};
|
|
51022
|
+
return {
|
|
51023
|
+
hardwareClass: "unknown",
|
|
51024
|
+
cpuModel
|
|
51025
|
+
};
|
|
51026
|
+
}
|
|
51027
|
+
/**
|
|
51028
|
+
* The accelerator kind of one of a node's device keys (`openvino:npu`,
|
|
51029
|
+
* `openvino:gpu`, `coreml:all`, `edgetpu:usb`, `cpu`, `onnx:cpu`, …). `null`
|
|
51030
|
+
* for a key this table has no column for.
|
|
51031
|
+
*/
|
|
51032
|
+
function acceleratorKindOf(deviceKey, hw) {
|
|
51033
|
+
const [backend = "", device = ""] = deviceKey.split(":");
|
|
51034
|
+
if (backend === "edgetpu") return "coral";
|
|
51035
|
+
if (backend === "coreml") return device === "cpu" ? "arm-cpu" : "apple-ane";
|
|
51036
|
+
if (backend === "openvino" && device === "npu") return "intel-npu";
|
|
51037
|
+
if (backend === "openvino" && device === "gpu") return "intel-igpu";
|
|
51038
|
+
if (backend === "cpu" || device === "cpu") return hw.arch === "arm64" ? "arm-cpu" : "x86-cpu";
|
|
51039
|
+
return null;
|
|
51040
|
+
}
|
|
51041
|
+
var DETECTOR_PERF = {
|
|
51042
|
+
"intel-core-ultra": {
|
|
51043
|
+
"intel-npu": {
|
|
51044
|
+
"yolov9t-320": {
|
|
51045
|
+
p50Ms: 2.9,
|
|
51046
|
+
fps: 383,
|
|
51047
|
+
measured: true
|
|
51048
|
+
},
|
|
51049
|
+
"yolov9t-320-int8": {
|
|
51050
|
+
p50Ms: 3.8,
|
|
51051
|
+
fps: 333,
|
|
51052
|
+
measured: true
|
|
51053
|
+
},
|
|
51054
|
+
"yolov9t-640": {
|
|
51055
|
+
p50Ms: 7.4,
|
|
51056
|
+
fps: 139,
|
|
51057
|
+
measured: true
|
|
51058
|
+
},
|
|
51059
|
+
"yolov9t-640-int8": {
|
|
51060
|
+
p50Ms: 7.5,
|
|
51061
|
+
fps: 144,
|
|
51062
|
+
measured: true
|
|
51063
|
+
},
|
|
51064
|
+
"yolov9s-320": {
|
|
51065
|
+
p50Ms: 4.4,
|
|
51066
|
+
fps: 241,
|
|
51067
|
+
measured: true
|
|
51068
|
+
},
|
|
51069
|
+
"yolov9s-320-int8": {
|
|
51070
|
+
p50Ms: 4.1,
|
|
51071
|
+
fps: 264,
|
|
51072
|
+
measured: true
|
|
51073
|
+
},
|
|
51074
|
+
"yolov9s-640": {
|
|
51075
|
+
p50Ms: 12.7,
|
|
51076
|
+
fps: 82,
|
|
51077
|
+
measured: true
|
|
51078
|
+
},
|
|
51079
|
+
"yolov9s-640-int8": {
|
|
51080
|
+
p50Ms: 10.7,
|
|
51081
|
+
fps: 99,
|
|
51082
|
+
measured: true
|
|
51083
|
+
},
|
|
51084
|
+
"yolov9m-320": {
|
|
51085
|
+
p50Ms: 8.4,
|
|
51086
|
+
fps: 123,
|
|
51087
|
+
measured: true
|
|
51088
|
+
},
|
|
51089
|
+
"yolov9m-320-int8": {
|
|
51090
|
+
p50Ms: 6.5,
|
|
51091
|
+
fps: 165,
|
|
51092
|
+
measured: true
|
|
51093
|
+
},
|
|
51094
|
+
"yolov9m-640": {
|
|
51095
|
+
p50Ms: 29.5,
|
|
51096
|
+
fps: 32,
|
|
51097
|
+
measured: true
|
|
51098
|
+
},
|
|
51099
|
+
"yolov9m-640-int8": {
|
|
51100
|
+
p50Ms: 19.5,
|
|
51101
|
+
fps: 52,
|
|
51102
|
+
measured: true
|
|
51103
|
+
}
|
|
51104
|
+
},
|
|
51105
|
+
"intel-igpu": {
|
|
51106
|
+
"yolov9t-320": {
|
|
51107
|
+
p50Ms: 2.6,
|
|
51108
|
+
fps: 405,
|
|
51109
|
+
measured: true
|
|
51110
|
+
},
|
|
51111
|
+
"yolov9t-320-int8": {
|
|
51112
|
+
p50Ms: 2.8,
|
|
51113
|
+
fps: 324,
|
|
51114
|
+
measured: true
|
|
51115
|
+
},
|
|
51116
|
+
"yolov9t-640": {
|
|
51117
|
+
p50Ms: 6.5,
|
|
51118
|
+
fps: 157,
|
|
51119
|
+
measured: true
|
|
51120
|
+
},
|
|
51121
|
+
"yolov9t-640-int8": {
|
|
51122
|
+
p50Ms: 5.6,
|
|
51123
|
+
fps: 185,
|
|
51124
|
+
measured: true
|
|
51125
|
+
},
|
|
51126
|
+
"yolov9s-320": {
|
|
51127
|
+
p50Ms: 4.6,
|
|
51128
|
+
fps: 200,
|
|
51129
|
+
measured: true
|
|
51130
|
+
},
|
|
51131
|
+
"yolov9s-320-int8": {
|
|
51132
|
+
p50Ms: 4.1,
|
|
51133
|
+
fps: 239,
|
|
51134
|
+
measured: true
|
|
51135
|
+
},
|
|
51136
|
+
"yolov9s-640": {
|
|
51137
|
+
p50Ms: 15.2,
|
|
51138
|
+
fps: 69,
|
|
51139
|
+
measured: true
|
|
51140
|
+
},
|
|
51141
|
+
"yolov9s-640-int8": {
|
|
51142
|
+
p50Ms: 10.5,
|
|
51143
|
+
fps: 99,
|
|
51144
|
+
measured: true
|
|
51145
|
+
},
|
|
51146
|
+
"yolov9m-320": {
|
|
51147
|
+
p50Ms: 11.4,
|
|
51148
|
+
fps: 88,
|
|
51149
|
+
measured: true
|
|
51150
|
+
},
|
|
51151
|
+
"yolov9m-320-int8": {
|
|
51152
|
+
p50Ms: 7,
|
|
51153
|
+
fps: 127,
|
|
51154
|
+
measured: true
|
|
51155
|
+
},
|
|
51156
|
+
"yolov9m-640": {
|
|
51157
|
+
p50Ms: 40,
|
|
51158
|
+
fps: 25,
|
|
51159
|
+
measured: true
|
|
51160
|
+
},
|
|
51161
|
+
"yolov9m-640-int8": {
|
|
51162
|
+
p50Ms: 23.5,
|
|
51163
|
+
fps: 44,
|
|
51164
|
+
measured: true
|
|
51165
|
+
}
|
|
51166
|
+
},
|
|
51167
|
+
"x86-cpu": {
|
|
51168
|
+
"yolov9t-320": {
|
|
51169
|
+
p50Ms: 6.9,
|
|
51170
|
+
fps: 145,
|
|
51171
|
+
measured: true
|
|
51172
|
+
},
|
|
51173
|
+
"yolov9t-320-int8": {
|
|
51174
|
+
p50Ms: 2.7,
|
|
51175
|
+
fps: 405,
|
|
51176
|
+
measured: true
|
|
51177
|
+
},
|
|
51178
|
+
"yolov9t-640": {
|
|
51179
|
+
p50Ms: 23,
|
|
51180
|
+
fps: 44,
|
|
51181
|
+
measured: true
|
|
51182
|
+
},
|
|
51183
|
+
"yolov9t-640-int8": {
|
|
51184
|
+
p50Ms: 8.8,
|
|
51185
|
+
fps: 124,
|
|
51186
|
+
measured: true
|
|
51187
|
+
},
|
|
51188
|
+
"yolov9s-320": {
|
|
51189
|
+
p50Ms: 15.8,
|
|
51190
|
+
fps: 63,
|
|
51191
|
+
measured: true
|
|
51192
|
+
},
|
|
51193
|
+
"yolov9s-320-int8": {
|
|
51194
|
+
p50Ms: 6.5,
|
|
51195
|
+
fps: 180,
|
|
51196
|
+
measured: true
|
|
51197
|
+
},
|
|
51198
|
+
"yolov9s-640": {
|
|
51199
|
+
p50Ms: 58.2,
|
|
51200
|
+
fps: 17,
|
|
51201
|
+
measured: true
|
|
51202
|
+
},
|
|
51203
|
+
"yolov9s-640-int8": {
|
|
51204
|
+
p50Ms: 20.1,
|
|
51205
|
+
fps: 52,
|
|
51206
|
+
measured: true
|
|
51207
|
+
},
|
|
51208
|
+
"yolov9m-320": {
|
|
51209
|
+
p50Ms: 40,
|
|
51210
|
+
fps: 25,
|
|
51211
|
+
measured: true
|
|
51212
|
+
},
|
|
51213
|
+
"yolov9m-320-int8": {
|
|
51214
|
+
p50Ms: 14.2,
|
|
51215
|
+
fps: 74,
|
|
51216
|
+
measured: true
|
|
51217
|
+
},
|
|
51218
|
+
"yolov9m-640": {
|
|
51219
|
+
p50Ms: 155.8,
|
|
51220
|
+
fps: 6,
|
|
51221
|
+
measured: true
|
|
51222
|
+
},
|
|
51223
|
+
"yolov9m-640-int8": {
|
|
51224
|
+
p50Ms: 52.4,
|
|
51225
|
+
fps: 19,
|
|
51226
|
+
measured: true
|
|
51227
|
+
}
|
|
51228
|
+
}
|
|
51229
|
+
},
|
|
51230
|
+
"intel-n-series": {
|
|
51231
|
+
"intel-igpu": {
|
|
51232
|
+
"yolov9t-320": {
|
|
51233
|
+
p50Ms: 15,
|
|
51234
|
+
fps: 48,
|
|
51235
|
+
measured: true
|
|
51236
|
+
},
|
|
51237
|
+
"yolov9t-320-int8": {
|
|
51238
|
+
p50Ms: 14.1,
|
|
51239
|
+
fps: 67,
|
|
51240
|
+
measured: true
|
|
51241
|
+
},
|
|
51242
|
+
"yolov9t-640": {
|
|
51243
|
+
p50Ms: 38.5,
|
|
51244
|
+
fps: 26,
|
|
51245
|
+
measured: true
|
|
51246
|
+
},
|
|
51247
|
+
"yolov9t-640-int8": {
|
|
51248
|
+
p50Ms: 33.1,
|
|
51249
|
+
fps: 33,
|
|
51250
|
+
measured: true
|
|
51251
|
+
},
|
|
51252
|
+
"yolov9s-320": {
|
|
51253
|
+
p50Ms: 41.4,
|
|
51254
|
+
fps: 24,
|
|
51255
|
+
measured: true
|
|
51256
|
+
},
|
|
51257
|
+
"yolov9s-320-int8": {
|
|
51258
|
+
p50Ms: 23.3,
|
|
51259
|
+
fps: 34,
|
|
51260
|
+
measured: true
|
|
51261
|
+
},
|
|
51262
|
+
"yolov9s-640": {
|
|
51263
|
+
p50Ms: 127.4,
|
|
51264
|
+
fps: 8,
|
|
51265
|
+
measured: true
|
|
51266
|
+
},
|
|
51267
|
+
"yolov9s-640-int8": {
|
|
51268
|
+
p50Ms: 92.9,
|
|
51269
|
+
fps: 11,
|
|
51270
|
+
measured: true
|
|
51271
|
+
},
|
|
51272
|
+
"yolov9m-320": {
|
|
51273
|
+
p50Ms: 63.5,
|
|
51274
|
+
fps: 16,
|
|
51275
|
+
measured: true
|
|
51276
|
+
},
|
|
51277
|
+
"yolov9m-320-int8": {
|
|
51278
|
+
p50Ms: 39.4,
|
|
51279
|
+
fps: 26,
|
|
51280
|
+
measured: true
|
|
51281
|
+
},
|
|
51282
|
+
"yolov9m-640": {
|
|
51283
|
+
p50Ms: 215.5,
|
|
51284
|
+
fps: 5,
|
|
51285
|
+
measured: true
|
|
51286
|
+
},
|
|
51287
|
+
"yolov9m-640-int8": {
|
|
51288
|
+
p50Ms: 139.1,
|
|
51289
|
+
fps: 7,
|
|
51290
|
+
measured: true
|
|
51291
|
+
}
|
|
51292
|
+
},
|
|
51293
|
+
"x86-cpu": {
|
|
51294
|
+
"yolov9t-320": {
|
|
51295
|
+
p50Ms: 86.2,
|
|
51296
|
+
fps: 12,
|
|
51297
|
+
measured: true
|
|
51298
|
+
},
|
|
51299
|
+
"yolov9t-320-int8": {
|
|
51300
|
+
p50Ms: 52.7,
|
|
51301
|
+
fps: 20,
|
|
51302
|
+
measured: true
|
|
51303
|
+
},
|
|
51304
|
+
"yolov9t-640": {
|
|
51305
|
+
p50Ms: 322.1,
|
|
51306
|
+
fps: 3,
|
|
51307
|
+
measured: true
|
|
51308
|
+
},
|
|
51309
|
+
"yolov9t-640-int8": {
|
|
51310
|
+
p50Ms: 205.6,
|
|
51311
|
+
fps: 5,
|
|
51312
|
+
measured: true
|
|
51313
|
+
},
|
|
51314
|
+
"yolov9s-320": {
|
|
51315
|
+
p50Ms: 164.4,
|
|
51316
|
+
fps: 6,
|
|
51317
|
+
measured: true
|
|
51318
|
+
},
|
|
51319
|
+
"yolov9s-320-int8": {
|
|
51320
|
+
p50Ms: 86.9,
|
|
51321
|
+
fps: 12,
|
|
51322
|
+
measured: true
|
|
51323
|
+
},
|
|
51324
|
+
"yolov9s-640": {
|
|
51325
|
+
p50Ms: 341.3,
|
|
51326
|
+
fps: 3,
|
|
51327
|
+
measured: true
|
|
51328
|
+
},
|
|
51329
|
+
"yolov9s-640-int8": {
|
|
51330
|
+
p50Ms: 185.3,
|
|
51331
|
+
fps: 5,
|
|
51332
|
+
measured: true
|
|
51333
|
+
},
|
|
51334
|
+
"yolov9m-320": {
|
|
51335
|
+
p50Ms: 166.8,
|
|
51336
|
+
fps: 6,
|
|
51337
|
+
measured: true
|
|
51338
|
+
},
|
|
51339
|
+
"yolov9m-320-int8": {
|
|
51340
|
+
p50Ms: 226.3,
|
|
51341
|
+
fps: 4,
|
|
51342
|
+
measured: true
|
|
51343
|
+
},
|
|
51344
|
+
"yolov9m-640": {
|
|
51345
|
+
p50Ms: 663.5,
|
|
51346
|
+
fps: 2,
|
|
51347
|
+
measured: true
|
|
51348
|
+
},
|
|
51349
|
+
"yolov9m-640-int8": {
|
|
51350
|
+
p50Ms: 324.4,
|
|
51351
|
+
fps: 3,
|
|
51352
|
+
measured: true
|
|
51353
|
+
}
|
|
51354
|
+
}
|
|
51355
|
+
},
|
|
51356
|
+
"apple-m-pro": { "apple-ane": {
|
|
51357
|
+
"yolov9t-320": {
|
|
51358
|
+
p50Ms: 1.1,
|
|
51359
|
+
fps: 952,
|
|
51360
|
+
measured: true
|
|
51361
|
+
},
|
|
51362
|
+
"yolov9t-640": {
|
|
51363
|
+
p50Ms: 3,
|
|
51364
|
+
fps: 333,
|
|
51365
|
+
measured: true
|
|
51366
|
+
},
|
|
51367
|
+
"yolov9s-320": {
|
|
51368
|
+
p50Ms: 1.5,
|
|
51369
|
+
fps: 667,
|
|
51370
|
+
measured: true
|
|
51371
|
+
},
|
|
51372
|
+
"yolov9s-640": {
|
|
51373
|
+
p50Ms: 5.1,
|
|
51374
|
+
fps: 196,
|
|
51375
|
+
measured: true
|
|
51376
|
+
},
|
|
51377
|
+
"yolov9m-320": {
|
|
51378
|
+
p50Ms: 3.2,
|
|
51379
|
+
fps: 312,
|
|
51380
|
+
measured: true
|
|
51381
|
+
},
|
|
51382
|
+
"yolov9m-640": {
|
|
51383
|
+
p50Ms: 10.2,
|
|
51384
|
+
fps: 98,
|
|
51385
|
+
measured: true
|
|
51386
|
+
}
|
|
51387
|
+
} },
|
|
51388
|
+
"apple-m-base": { "apple-ane": {
|
|
51389
|
+
"yolov9t-320": {
|
|
51390
|
+
p50Ms: 1.32,
|
|
51391
|
+
fps: 758,
|
|
51392
|
+
measured: false
|
|
51393
|
+
},
|
|
51394
|
+
"yolov9t-640": {
|
|
51395
|
+
p50Ms: 3.45,
|
|
51396
|
+
fps: 290,
|
|
51397
|
+
measured: true
|
|
51398
|
+
},
|
|
51399
|
+
"yolov9s-320": {
|
|
51400
|
+
p50Ms: 1.8,
|
|
51401
|
+
fps: 556,
|
|
51402
|
+
measured: false
|
|
51403
|
+
},
|
|
51404
|
+
"yolov9s-640": {
|
|
51405
|
+
p50Ms: 5.46,
|
|
51406
|
+
fps: 183,
|
|
51407
|
+
measured: true
|
|
51408
|
+
},
|
|
51409
|
+
"yolov9m-320": {
|
|
51410
|
+
p50Ms: 3.79,
|
|
51411
|
+
fps: 264,
|
|
51412
|
+
measured: true
|
|
51413
|
+
},
|
|
51414
|
+
"yolov9m-640": {
|
|
51415
|
+
p50Ms: 10.34,
|
|
51416
|
+
fps: 97,
|
|
51417
|
+
measured: true
|
|
51418
|
+
}
|
|
51419
|
+
} }
|
|
51420
|
+
};
|
|
51421
|
+
var DETECTOR_ACCURACY = {
|
|
51422
|
+
"yolov9m-320": {
|
|
51423
|
+
personPrecision: .979,
|
|
51424
|
+
personRecall: .649
|
|
51425
|
+
},
|
|
51426
|
+
"yolov9m-320-int8": {
|
|
51427
|
+
personPrecision: .974,
|
|
51428
|
+
personRecall: .684
|
|
51429
|
+
},
|
|
51430
|
+
"yolov9m-640": {
|
|
51431
|
+
personPrecision: .976,
|
|
51432
|
+
personRecall: .854
|
|
51433
|
+
},
|
|
51434
|
+
"yolov9m-640-int8": {
|
|
51435
|
+
personPrecision: .976,
|
|
51436
|
+
personRecall: .847
|
|
51437
|
+
},
|
|
51438
|
+
"yolov9s-320": {
|
|
51439
|
+
personPrecision: .958,
|
|
51440
|
+
personRecall: .479
|
|
51441
|
+
},
|
|
51442
|
+
"yolov9s-320-int8": {
|
|
51443
|
+
personPrecision: .969,
|
|
51444
|
+
personRecall: .438
|
|
51445
|
+
},
|
|
51446
|
+
"yolov9s-640": {
|
|
51447
|
+
personPrecision: .986,
|
|
51448
|
+
personRecall: .753
|
|
51449
|
+
},
|
|
51450
|
+
"yolov9s-640-int8": {
|
|
51451
|
+
personPrecision: .963,
|
|
51452
|
+
personRecall: .823
|
|
51453
|
+
},
|
|
51454
|
+
"yolov9t-320": {
|
|
51455
|
+
personPrecision: .946,
|
|
51456
|
+
personRecall: .368
|
|
51457
|
+
},
|
|
51458
|
+
"yolov9t-320-int8": {
|
|
51459
|
+
personPrecision: .943,
|
|
51460
|
+
personRecall: .347
|
|
51461
|
+
},
|
|
51462
|
+
"yolov9t-640": {
|
|
51463
|
+
personPrecision: .979,
|
|
51464
|
+
personRecall: .646
|
|
51465
|
+
},
|
|
51466
|
+
"yolov9t-640-int8": {
|
|
51467
|
+
personPrecision: .979,
|
|
51468
|
+
personRecall: .646
|
|
51469
|
+
}
|
|
51470
|
+
};
|
|
51471
|
+
var DEFAULT_DEMAND_OPTIONS = {
|
|
51472
|
+
onMotionConcurrency: .25,
|
|
51473
|
+
onMotionMinConcurrent: 2
|
|
51474
|
+
};
|
|
51475
|
+
function deviceId(nodeId, deviceKey) {
|
|
51476
|
+
return `${nodeId}\u0000${deviceKey}`;
|
|
51477
|
+
}
|
|
51478
|
+
function charges(load) {
|
|
51479
|
+
return load.mode !== "disabled" && load.runsPipelineRoot && load.fps > 0;
|
|
51480
|
+
}
|
|
51481
|
+
/** The highest `count` rates, summed. */
|
|
51482
|
+
function topRatesSum(rates, count) {
|
|
51483
|
+
return rates.toSorted((a, b) => b - a).slice(0, Math.max(0, count)).reduce((sum, r) => sum + r, 0);
|
|
51484
|
+
}
|
|
51485
|
+
/** Concurrent on-motion sessions assumed cluster-wide without an observation. */
|
|
51486
|
+
function estimatedConcurrentSessions(cameraCount, options) {
|
|
51487
|
+
if (cameraCount <= 0) return 0;
|
|
51488
|
+
const share = Math.ceil(options.onMotionConcurrency * cameraCount);
|
|
51489
|
+
return Math.min(cameraCount, Math.max(share, Math.min(cameraCount, options.onMotionMinConcurrent)));
|
|
51490
|
+
}
|
|
51491
|
+
/**
|
|
51492
|
+
* Demand on every eligible device of the cluster.
|
|
51493
|
+
*/
|
|
51494
|
+
function estimateDeviceDemand(input) {
|
|
51495
|
+
const options = input.options ?? DEFAULT_DEMAND_OPTIONS;
|
|
51496
|
+
const eligibleById = new Map(input.eligible.map((d) => [deviceId(d.nodeId, d.deviceKey), d]));
|
|
51497
|
+
const peakById = new Map(input.peaks.map((p) => [deviceId(p.nodeId, p.deviceKey), p]));
|
|
51498
|
+
const weightByNode = /* @__PURE__ */ new Map();
|
|
51499
|
+
const eligibleCountByNode = /* @__PURE__ */ new Map();
|
|
51500
|
+
for (const d of input.eligible) {
|
|
51501
|
+
weightByNode.set(d.nodeId, (weightByNode.get(d.nodeId) ?? 0) + Math.max(d.weight, 0));
|
|
51502
|
+
eligibleCountByNode.set(d.nodeId, (eligibleCountByNode.get(d.nodeId) ?? 0) + 1);
|
|
51503
|
+
}
|
|
51504
|
+
const pinnedFps = /* @__PURE__ */ new Map();
|
|
51505
|
+
const alwaysOnFps = /* @__PURE__ */ new Map();
|
|
51506
|
+
const alwaysOnCount = /* @__PURE__ */ new Map();
|
|
51507
|
+
const maxFps = /* @__PURE__ */ new Map();
|
|
51508
|
+
const onMotionRates = [];
|
|
51509
|
+
const bump = (map, key, by) => {
|
|
51510
|
+
map.set(key, (map.get(key) ?? 0) + by);
|
|
51511
|
+
};
|
|
51512
|
+
const noteMax = (key, fps) => {
|
|
51513
|
+
maxFps.set(key, Math.max(maxFps.get(key) ?? 0, fps));
|
|
51514
|
+
};
|
|
51515
|
+
for (const cam of input.cameras) {
|
|
51516
|
+
if (!charges(cam)) continue;
|
|
51517
|
+
if (cam.devicePin !== null) {
|
|
51518
|
+
const key = deviceId(cam.devicePin.nodeId, cam.devicePin.deviceKey);
|
|
51519
|
+
if (!eligibleById.has(key)) continue;
|
|
51520
|
+
bump(pinnedFps, key, cam.fps);
|
|
51521
|
+
noteMax(key, cam.fps);
|
|
51522
|
+
continue;
|
|
51523
|
+
}
|
|
51524
|
+
if (cam.mode === "always-on" && cam.standingNodeId !== null) {
|
|
51525
|
+
const nodeWeight = weightByNode.get(cam.standingNodeId) ?? 0;
|
|
51526
|
+
for (const d of input.eligible) {
|
|
51527
|
+
if (d.nodeId !== cam.standingNodeId || nodeWeight <= 0) continue;
|
|
51528
|
+
const key = deviceId(d.nodeId, d.deviceKey);
|
|
51529
|
+
const share = Math.max(d.weight, 0) / nodeWeight;
|
|
51530
|
+
bump(alwaysOnFps, key, cam.fps * share);
|
|
51531
|
+
bump(alwaysOnCount, key, share);
|
|
51532
|
+
noteMax(key, cam.fps);
|
|
51533
|
+
}
|
|
51534
|
+
continue;
|
|
51535
|
+
}
|
|
51536
|
+
onMotionRates.push(cam.fps);
|
|
51537
|
+
}
|
|
51538
|
+
const fastestOnMotion = onMotionRates.reduce((m, r) => Math.max(m, r), 0);
|
|
51539
|
+
const nodeCount = weightByNode.size;
|
|
51540
|
+
const estimatedTotal = estimatedConcurrentSessions(onMotionRates.length, options);
|
|
51541
|
+
return input.eligible.map((d) => {
|
|
51542
|
+
const key = deviceId(d.nodeId, d.deviceKey);
|
|
51543
|
+
const peak = peakById.get(key) ?? (eligibleCountByNode.get(d.nodeId) === 1 ? peakById.get(deviceId(d.nodeId, "")) : void 0);
|
|
51544
|
+
const alwaysOn = alwaysOnCount.get(key) ?? 0;
|
|
51545
|
+
let onMotionSessions = 0;
|
|
51546
|
+
let onMotionSource = "none";
|
|
51547
|
+
if (onMotionRates.length > 0) if (peak !== void 0 && peak.observedForMs >= 864e5) {
|
|
51548
|
+
onMotionSessions = Math.max(0, Math.round(peak.peakSessions - alwaysOn));
|
|
51549
|
+
onMotionSource = "observed-24h";
|
|
51550
|
+
} else {
|
|
51551
|
+
const nodeWeight = weightByNode.get(d.nodeId) ?? 0;
|
|
51552
|
+
const share = nodeCount > 0 && nodeWeight > 0 ? Math.max(d.weight, 0) / nodeWeight / nodeCount : 0;
|
|
51553
|
+
onMotionSessions = Math.ceil(estimatedTotal * share);
|
|
51554
|
+
onMotionSource = "estimate";
|
|
51555
|
+
}
|
|
51556
|
+
const demandFps = (pinnedFps.get(key) ?? 0) + (alwaysOnFps.get(key) ?? 0) + topRatesSum(onMotionRates, onMotionSessions);
|
|
51557
|
+
return {
|
|
51558
|
+
nodeId: d.nodeId,
|
|
51559
|
+
deviceKey: d.deviceKey,
|
|
51560
|
+
demandFps: Math.round(demandFps * 100) / 100,
|
|
51561
|
+
maxCameraFps: Math.max(maxFps.get(key) ?? 0, fastestOnMotion),
|
|
51562
|
+
alwaysOnCameras: Math.round(alwaysOn * 100) / 100,
|
|
51563
|
+
onMotionSessions,
|
|
51564
|
+
onMotionSource,
|
|
51565
|
+
observedForMs: peak?.observedForMs ?? null
|
|
51566
|
+
};
|
|
51567
|
+
}).toSorted((a, b) => a.nodeId === b.nodeId ? a.deviceKey.localeCompare(b.deviceKey) : a.nodeId.localeCompare(b.nodeId));
|
|
51568
|
+
}
|
|
51569
|
+
/** The family the cluster runs. */
|
|
51570
|
+
var BEST_SETTINGS_FAMILY = "yolov9";
|
|
51571
|
+
function round(value, digits = 3) {
|
|
51572
|
+
const f = 10 ** digits;
|
|
51573
|
+
return Math.round(value * f) / f;
|
|
51574
|
+
}
|
|
51575
|
+
function utilisationOf(cell, demand) {
|
|
51576
|
+
return cell.fps > 0 ? round((demand?.demandFps ?? 0) / cell.fps) : Number.POSITIVE_INFINITY;
|
|
51577
|
+
}
|
|
51578
|
+
function fitsOn(cell, demand, headroom) {
|
|
51579
|
+
const budget = cell.fps * (1 - headroom);
|
|
51580
|
+
return budget >= (demand?.demandFps ?? 0) && budget >= (demand?.maxCameraFps ?? 0);
|
|
51581
|
+
}
|
|
51582
|
+
function candidatesFor(contexts, formatsByModel) {
|
|
51583
|
+
return Object.keys(DETECTOR_ACCURACY).filter((id) => id.startsWith(BEST_SETTINGS_FAMILY)).filter((id) => contexts.every((c) => c.column[id] !== void 0 && (formatsByModel[id] ?? []).includes(c.device.format))).map((id) => ({
|
|
51584
|
+
modelId: id,
|
|
51585
|
+
personRecall: DETECTOR_ACCURACY[id]?.personRecall ?? 0
|
|
51586
|
+
})).toSorted((a, b) => b.personRecall - a.personRecall || a.modelId.localeCompare(b.modelId));
|
|
51587
|
+
}
|
|
51588
|
+
function totalLatency(contexts, modelId) {
|
|
51589
|
+
return contexts.reduce((sum, c) => sum + (c.column[modelId]?.p50Ms ?? Number.POSITIVE_INFINITY), 0);
|
|
51590
|
+
}
|
|
51591
|
+
function worstUtilisation(contexts, modelId) {
|
|
51592
|
+
return contexts.reduce((worst, c) => {
|
|
51593
|
+
const cell = c.column[modelId];
|
|
51594
|
+
return Math.max(worst, cell ? utilisationOf(cell, c.demand) : Number.POSITIVE_INFINITY);
|
|
51595
|
+
}, 0);
|
|
51596
|
+
}
|
|
51597
|
+
/** One model for a group of devices that must agree (step 2–4 of the header). */
|
|
51598
|
+
function pickForGroup(contexts, formatsByModel, headroom) {
|
|
51599
|
+
const candidates = candidatesFor(contexts, formatsByModel);
|
|
51600
|
+
if (candidates.length === 0) return null;
|
|
51601
|
+
const fitting = candidates.filter((c) => contexts.every((ctx) => {
|
|
51602
|
+
const cell = ctx.column[c.modelId];
|
|
51603
|
+
return cell !== void 0 && fitsOn(cell, ctx.demand, headroom);
|
|
51604
|
+
}));
|
|
51605
|
+
if (fitting.length === 0) {
|
|
51606
|
+
const lightest = candidates.toSorted((a, b) => worstUtilisation(contexts, a.modelId) - worstUtilisation(contexts, b.modelId))[0];
|
|
51607
|
+
if (lightest === void 0) return null;
|
|
51608
|
+
return {
|
|
51609
|
+
modelId: lightest.modelId,
|
|
51610
|
+
reasons: [{
|
|
51611
|
+
kind: "no-fit-lightest",
|
|
51612
|
+
utilisation: worstUtilisation(contexts, lightest.modelId)
|
|
51613
|
+
}]
|
|
51614
|
+
};
|
|
51615
|
+
}
|
|
51616
|
+
const best = fitting[0];
|
|
51617
|
+
if (best === void 0) return null;
|
|
51618
|
+
const chosen = fitting.filter((c) => best.personRecall - c.personRecall <= .02).toSorted((a, b) => totalLatency(contexts, a.modelId) - totalLatency(contexts, b.modelId) || b.personRecall - a.personRecall)[0] ?? best;
|
|
51619
|
+
const reasons = [];
|
|
51620
|
+
const mostAccurate = candidates[0];
|
|
51621
|
+
if (mostAccurate !== void 0 && mostAccurate.modelId !== best.modelId) {
|
|
51622
|
+
const limitedBy = contexts.find((ctx) => {
|
|
51623
|
+
const cell = ctx.column[mostAccurate.modelId];
|
|
51624
|
+
return cell === void 0 || !fitsOn(cell, ctx.demand, headroom);
|
|
51625
|
+
})?.device.key ?? "";
|
|
51626
|
+
reasons.push({
|
|
51627
|
+
kind: "stepped-down",
|
|
51628
|
+
from: mostAccurate.modelId,
|
|
51629
|
+
to: best.modelId,
|
|
51630
|
+
limitedBy
|
|
51631
|
+
});
|
|
51632
|
+
}
|
|
51633
|
+
if (chosen.modelId !== best.modelId) reasons.push({
|
|
51634
|
+
kind: "tie-faster",
|
|
51635
|
+
over: best.modelId,
|
|
51636
|
+
recallDelta: round(best.personRecall - chosen.personRecall)
|
|
51637
|
+
});
|
|
51638
|
+
return {
|
|
51639
|
+
modelId: chosen.modelId,
|
|
51640
|
+
reasons
|
|
51641
|
+
};
|
|
51642
|
+
}
|
|
51643
|
+
function choiceOn(ctx, modelId) {
|
|
51644
|
+
const cell = ctx.column[modelId];
|
|
51645
|
+
if (cell === void 0) return null;
|
|
51646
|
+
return {
|
|
51647
|
+
modelId,
|
|
51648
|
+
p50Ms: cell.p50Ms,
|
|
51649
|
+
fps: cell.fps,
|
|
51650
|
+
measured: cell.measured,
|
|
51651
|
+
utilisation: utilisationOf(cell, ctx.demand),
|
|
51652
|
+
personRecall: DETECTOR_ACCURACY[modelId]?.personRecall ?? 0
|
|
51653
|
+
};
|
|
51654
|
+
}
|
|
51655
|
+
function deviceReasonsFor(ctx, choice) {
|
|
51656
|
+
const out = [{
|
|
51657
|
+
kind: "fits",
|
|
51658
|
+
utilisation: choice.utilisation
|
|
51659
|
+
}];
|
|
51660
|
+
if (!choice.measured) out.push({
|
|
51661
|
+
kind: "derived-latency",
|
|
51662
|
+
modelId: choice.modelId
|
|
51663
|
+
});
|
|
51664
|
+
if (ctx.demand?.onMotionSource === "estimate") out.push({
|
|
51665
|
+
kind: "estimated-load",
|
|
51666
|
+
observedForMs: ctx.demand.observedForMs
|
|
51667
|
+
});
|
|
51668
|
+
return out;
|
|
51669
|
+
}
|
|
51670
|
+
function skippedNode(node, reason, hardwareClass) {
|
|
51671
|
+
return {
|
|
51672
|
+
nodeId: node.nodeId,
|
|
51673
|
+
reachable: node.reachable,
|
|
51674
|
+
hardwareClass,
|
|
51675
|
+
cpuModel: node.hardware?.cpuModel ?? null,
|
|
51676
|
+
skipped: true,
|
|
51677
|
+
acceleratorModelId: null,
|
|
51678
|
+
devices: node.devices.map((d) => ({
|
|
51679
|
+
deviceKey: d.key,
|
|
51680
|
+
accelerator: node.hardware ? acceleratorKindOf(d.key, node.hardware) : null,
|
|
51681
|
+
enabled: d.enabled,
|
|
51682
|
+
available: d.available,
|
|
51683
|
+
demand: null,
|
|
51684
|
+
chosen: null,
|
|
51685
|
+
reasons: []
|
|
51686
|
+
})),
|
|
51687
|
+
reasons: [reason]
|
|
51688
|
+
};
|
|
51689
|
+
}
|
|
51690
|
+
/** Recommend one node. See the module header. */
|
|
51691
|
+
function recommendNode(input) {
|
|
51692
|
+
const { node } = input;
|
|
51693
|
+
const headroom = input.headroom ?? .4;
|
|
51694
|
+
const perf = input.perf ?? DETECTOR_PERF;
|
|
51695
|
+
if (!node.reachable) return skippedNode(node, { kind: "node-offline" }, null);
|
|
51696
|
+
if (node.hardware === null) return skippedNode(node, { kind: "no-hardware-facts" }, null);
|
|
51697
|
+
const hardware = node.hardware;
|
|
51698
|
+
const { hardwareClass, cpuModel } = classifyInferenceHardware(hardware);
|
|
51699
|
+
const classTable = perf[hardwareClass];
|
|
51700
|
+
if (hardwareClass === "unknown" || classTable === void 0) return skippedNode(node, {
|
|
51701
|
+
kind: "no-measured-data",
|
|
51702
|
+
hardwareClass
|
|
51703
|
+
}, hardwareClass);
|
|
51704
|
+
const demandByKey = new Map(input.demand.filter((d) => d.nodeId === node.nodeId).map((d) => [d.deviceKey, d]));
|
|
51705
|
+
const devices = node.devices.toSorted((a, b) => a.key.localeCompare(b.key));
|
|
51706
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
51707
|
+
const early = /* @__PURE__ */ new Map();
|
|
51708
|
+
for (const device of devices) {
|
|
51709
|
+
const accelerator = acceleratorKindOf(device.key, hardware);
|
|
51710
|
+
if (accelerator === "coral") {
|
|
51711
|
+
early.set(device.key, device.enabled ? [{ kind: "coral-untouched" }] : []);
|
|
51712
|
+
continue;
|
|
51713
|
+
}
|
|
51714
|
+
if (!device.enabled) {
|
|
51715
|
+
early.set(device.key, [{ kind: "device-disabled" }]);
|
|
51716
|
+
continue;
|
|
51717
|
+
}
|
|
51718
|
+
if (!device.available) {
|
|
51719
|
+
early.set(device.key, [{ kind: "device-unavailable" }]);
|
|
51720
|
+
continue;
|
|
51721
|
+
}
|
|
51722
|
+
const column = accelerator === null ? void 0 : classTable[accelerator];
|
|
51723
|
+
if (accelerator === null || column === void 0) {
|
|
51724
|
+
early.set(device.key, [{
|
|
51725
|
+
kind: "no-family-build",
|
|
51726
|
+
format: device.format
|
|
51727
|
+
}]);
|
|
51728
|
+
continue;
|
|
51729
|
+
}
|
|
51730
|
+
contexts.set(device.key, {
|
|
51731
|
+
device,
|
|
51732
|
+
accelerator,
|
|
51733
|
+
column,
|
|
51734
|
+
demand: demandByKey.get(device.key) ?? null
|
|
51735
|
+
});
|
|
51736
|
+
}
|
|
51737
|
+
const isCpu = (a) => a === "x86-cpu" || a === "arm-cpu";
|
|
51738
|
+
const accelerators = [...contexts.values()].filter((c) => !isCpu(c.accelerator));
|
|
51739
|
+
const cpus = [...contexts.values()].filter((c) => isCpu(c.accelerator));
|
|
51740
|
+
const chosenByKey = /* @__PURE__ */ new Map();
|
|
51741
|
+
const reasonsByKey = /* @__PURE__ */ new Map();
|
|
51742
|
+
const nodeReasons = [];
|
|
51743
|
+
let acceleratorModelId = null;
|
|
51744
|
+
const groupPick = accelerators.length > 0 ? pickForGroup(accelerators, node.formatsByModel, headroom) : null;
|
|
51745
|
+
if (groupPick !== null) {
|
|
51746
|
+
acceleratorModelId = groupPick.modelId;
|
|
51747
|
+
nodeReasons.push(...groupPick.reasons);
|
|
51748
|
+
for (const ctx of accelerators) {
|
|
51749
|
+
const choice = choiceOn(ctx, groupPick.modelId);
|
|
51750
|
+
if (choice === null) continue;
|
|
51751
|
+
chosenByKey.set(ctx.device.key, choice);
|
|
51752
|
+
reasonsByKey.set(ctx.device.key, deviceReasonsFor(ctx, choice));
|
|
51753
|
+
}
|
|
51754
|
+
} else for (const ctx of accelerators) reasonsByKey.set(ctx.device.key, [{
|
|
51755
|
+
kind: "no-family-build",
|
|
51756
|
+
format: ctx.device.format
|
|
51757
|
+
}]);
|
|
51758
|
+
for (const ctx of cpus) {
|
|
51759
|
+
const pick = pickForGroup([ctx], node.formatsByModel, headroom);
|
|
51760
|
+
const choice = pick === null ? null : choiceOn(ctx, pick.modelId);
|
|
51761
|
+
if (pick === null || choice === null) {
|
|
51762
|
+
reasonsByKey.set(ctx.device.key, [{
|
|
51763
|
+
kind: "no-family-build",
|
|
51764
|
+
format: ctx.device.format
|
|
51765
|
+
}]);
|
|
51766
|
+
continue;
|
|
51767
|
+
}
|
|
51768
|
+
chosenByKey.set(ctx.device.key, choice);
|
|
51769
|
+
reasonsByKey.set(ctx.device.key, [
|
|
51770
|
+
{ kind: "cpu-device" },
|
|
51771
|
+
...pick.reasons,
|
|
51772
|
+
...deviceReasonsFor(ctx, choice)
|
|
51773
|
+
]);
|
|
51774
|
+
}
|
|
51775
|
+
return {
|
|
51776
|
+
nodeId: node.nodeId,
|
|
51777
|
+
reachable: true,
|
|
51778
|
+
hardwareClass,
|
|
51779
|
+
cpuModel,
|
|
51780
|
+
skipped: chosenByKey.size === 0,
|
|
51781
|
+
acceleratorModelId,
|
|
51782
|
+
devices: devices.map((device) => ({
|
|
51783
|
+
deviceKey: device.key,
|
|
51784
|
+
accelerator: acceleratorKindOf(device.key, hardware),
|
|
51785
|
+
enabled: device.enabled,
|
|
51786
|
+
available: device.available,
|
|
51787
|
+
demand: demandByKey.get(device.key) ?? null,
|
|
51788
|
+
chosen: chosenByKey.get(device.key) ?? null,
|
|
51789
|
+
reasons: early.get(device.key) ?? reasonsByKey.get(device.key) ?? []
|
|
51790
|
+
})),
|
|
51791
|
+
reasons: nodeReasons
|
|
51792
|
+
};
|
|
51793
|
+
}
|
|
50659
51794
|
/**
|
|
50660
51795
|
* THE detail-crop convention — the single derivation of the rectangle a
|
|
50661
51796
|
* detail/enrichment step (clip-embedding, face-detection, plate-detection…)
|
|
@@ -51430,7 +52565,7 @@ var AudioSubscriptionController = class {
|
|
|
51430
52565
|
} catch (err) {
|
|
51431
52566
|
this.deps.logger.error("audio re-attach failed", {
|
|
51432
52567
|
tags: { deviceId },
|
|
51433
|
-
meta: { error: errMsg$
|
|
52568
|
+
meta: { error: errMsg$2(err) }
|
|
51434
52569
|
});
|
|
51435
52570
|
}
|
|
51436
52571
|
}
|
|
@@ -51541,7 +52676,7 @@ var AudioSubscriptionController = class {
|
|
|
51541
52676
|
this.closeMotionAudioWindow(deviceId, "motion-cooldown-elapsed").catch((err) => {
|
|
51542
52677
|
this.deps.logger.warn("lazy audio: cooldown teardown failed", {
|
|
51543
52678
|
tags: { deviceId },
|
|
51544
|
-
meta: { error: errMsg$
|
|
52679
|
+
meta: { error: errMsg$2(err) }
|
|
51545
52680
|
});
|
|
51546
52681
|
});
|
|
51547
52682
|
}, cooldownMs);
|
|
@@ -51560,7 +52695,7 @@ var AudioSubscriptionController = class {
|
|
|
51560
52695
|
this.closeMotionAudioWindow(deviceId, "quiet-window-elapsed").catch((err) => {
|
|
51561
52696
|
this.deps.logger.warn("lazy audio: quiet-window teardown failed", {
|
|
51562
52697
|
tags: { deviceId },
|
|
51563
|
-
meta: { error: errMsg$
|
|
52698
|
+
meta: { error: errMsg$2(err) }
|
|
51564
52699
|
});
|
|
51565
52700
|
});
|
|
51566
52701
|
}, windowMs);
|
|
@@ -51762,7 +52897,7 @@ var AudioSubscriptionController = class {
|
|
|
51762
52897
|
meta: {
|
|
51763
52898
|
audioNodeId,
|
|
51764
52899
|
brokerId: audioBrokerId,
|
|
51765
|
-
error: errMsg$
|
|
52900
|
+
error: errMsg$2(err)
|
|
51766
52901
|
}
|
|
51767
52902
|
});
|
|
51768
52903
|
});
|
|
@@ -51837,7 +52972,7 @@ async function readOrRefuse(deps, deviceId) {
|
|
|
51837
52972
|
try {
|
|
51838
52973
|
device = await deps.readDevice(deviceId);
|
|
51839
52974
|
} catch (err) {
|
|
51840
|
-
return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$
|
|
52975
|
+
return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$2(err)}`);
|
|
51841
52976
|
}
|
|
51842
52977
|
if (device === null) return refuse(deps, deviceId, `no device ${deviceId}`);
|
|
51843
52978
|
return device;
|
|
@@ -53167,7 +54302,7 @@ var LoadShedController = class LoadShedController {
|
|
|
53167
54302
|
if (await this.tryClusterRelocate(deviceId).catch((err) => {
|
|
53168
54303
|
this.deps.logger.warn("Load management: cluster relocate failed", {
|
|
53169
54304
|
tags: { deviceId },
|
|
53170
|
-
meta: { error: errMsg$
|
|
54305
|
+
meta: { error: errMsg$2(err) }
|
|
53171
54306
|
});
|
|
53172
54307
|
return false;
|
|
53173
54308
|
})) {
|
|
@@ -53181,7 +54316,7 @@ var LoadShedController = class LoadShedController {
|
|
|
53181
54316
|
this.deps.api()?.pipelineRunner.detachCamera.mutate({ deviceId }).catch((err) => {
|
|
53182
54317
|
this.deps.logger.warn("Load management: detachCamera failed", {
|
|
53183
54318
|
tags: { deviceId },
|
|
53184
|
-
meta: { error: errMsg$
|
|
54319
|
+
meta: { error: errMsg$2(err) }
|
|
53185
54320
|
});
|
|
53186
54321
|
});
|
|
53187
54322
|
this.ensureLoadShedResumeTimer();
|
|
@@ -53213,7 +54348,7 @@ var LoadShedController = class LoadShedController {
|
|
|
53213
54348
|
tags: { deviceId },
|
|
53214
54349
|
meta: {
|
|
53215
54350
|
from: currentNode,
|
|
53216
|
-
error: errMsg$
|
|
54351
|
+
error: errMsg$2(err)
|
|
53217
54352
|
}
|
|
53218
54353
|
});
|
|
53219
54354
|
});
|
|
@@ -53256,7 +54391,7 @@ var LoadShedController = class LoadShedController {
|
|
|
53256
54391
|
this.deps.redispatchSingleCamera(deviceId).catch((err) => {
|
|
53257
54392
|
this.deps.logger.warn("Load management: auto-resume dispatch failed", {
|
|
53258
54393
|
tags: { deviceId },
|
|
53259
|
-
meta: { error: errMsg$
|
|
54394
|
+
meta: { error: errMsg$2(err) }
|
|
53260
54395
|
});
|
|
53261
54396
|
});
|
|
53262
54397
|
}
|
|
@@ -53421,7 +54556,7 @@ var AgentLoadService = class AgentLoadService {
|
|
|
53421
54556
|
ms,
|
|
53422
54557
|
outcome: "failed",
|
|
53423
54558
|
load: null,
|
|
53424
|
-
error: errMsg$
|
|
54559
|
+
error: errMsg$2(outcome.error)
|
|
53425
54560
|
};
|
|
53426
54561
|
return {
|
|
53427
54562
|
nodeId,
|
|
@@ -53888,7 +55023,7 @@ function buildCameraSensorsPorts(api, logger) {
|
|
|
53888
55023
|
} catch (err) {
|
|
53889
55024
|
return {
|
|
53890
55025
|
ok: false,
|
|
53891
|
-
error: errMsg$
|
|
55026
|
+
error: errMsg$2(err)
|
|
53892
55027
|
};
|
|
53893
55028
|
}
|
|
53894
55029
|
};
|
|
@@ -54152,7 +55287,7 @@ var CameraSensorsController = class {
|
|
|
54152
55287
|
async #readFleet() {
|
|
54153
55288
|
const read = await this.#deps.listCameraIds().catch((err) => ({
|
|
54154
55289
|
kind: "unreadable",
|
|
54155
|
-
error: errMsg$
|
|
55290
|
+
error: errMsg$2(err)
|
|
54156
55291
|
}));
|
|
54157
55292
|
if (read.kind === "unreadable") {
|
|
54158
55293
|
this.#deps.logger.warn("camera-sensors: camera list unreadable — mappings stay unloaded", { meta: { error: read.error } });
|
|
@@ -54179,7 +55314,7 @@ var CameraSensorsController = class {
|
|
|
54179
55314
|
} catch (err) {
|
|
54180
55315
|
this.#deps.logger.error("camera-sensors: refresh failed — nothing applied", {
|
|
54181
55316
|
tags: { deviceId: cameraId },
|
|
54182
|
-
meta: { error: errMsg$
|
|
55317
|
+
meta: { error: errMsg$2(err) }
|
|
54183
55318
|
});
|
|
54184
55319
|
}
|
|
54185
55320
|
}
|
|
@@ -54272,7 +55407,7 @@ async function readCameraSensorsSetting(read) {
|
|
|
54272
55407
|
} catch (err) {
|
|
54273
55408
|
return {
|
|
54274
55409
|
state: "unreadable",
|
|
54275
|
-
reason: `settings read failed — ${errMsg$
|
|
55410
|
+
reason: `settings read failed — ${errMsg$2(err)}`
|
|
54276
55411
|
};
|
|
54277
55412
|
}
|
|
54278
55413
|
}
|
|
@@ -54582,7 +55717,7 @@ var CameraMotionSlice = class {
|
|
|
54582
55717
|
st.chain = st.chain.then(job).catch((err) => {
|
|
54583
55718
|
this.#deps.logger.error("camera motion slice: write job failed", {
|
|
54584
55719
|
tags: { deviceId },
|
|
54585
|
-
meta: { error: errMsg$
|
|
55720
|
+
meta: { error: errMsg$2(err) }
|
|
54586
55721
|
});
|
|
54587
55722
|
});
|
|
54588
55723
|
}
|
|
@@ -54646,7 +55781,7 @@ var CameraMotionSlice = class {
|
|
|
54646
55781
|
meta: {
|
|
54647
55782
|
detected: slice.detected,
|
|
54648
55783
|
reason,
|
|
54649
|
-
error: errMsg$
|
|
55784
|
+
error: errMsg$2(err)
|
|
54650
55785
|
}
|
|
54651
55786
|
});
|
|
54652
55787
|
return;
|
|
@@ -54676,7 +55811,7 @@ var CameraMotionSlice = class {
|
|
|
54676
55811
|
async #republishAll() {
|
|
54677
55812
|
const listed = await this.#deps.listCameraIds().catch((err) => ({
|
|
54678
55813
|
kind: "unreadable",
|
|
54679
|
-
error: errMsg$
|
|
55814
|
+
error: errMsg$2(err)
|
|
54680
55815
|
}));
|
|
54681
55816
|
if (!this.#open || this.#stopped) return;
|
|
54682
55817
|
if (listed.kind === "unreadable") this.#deps.logger.warn("camera motion slice: camera list unreadable — only cameras with motion since boot are re-published", { meta: { error: listed.error } });
|
|
@@ -54798,7 +55933,7 @@ function hubMotionSliceStore(api) {
|
|
|
54798
55933
|
} catch (err) {
|
|
54799
55934
|
return {
|
|
54800
55935
|
kind: "unreadable",
|
|
54801
|
-
error: errMsg$
|
|
55936
|
+
error: errMsg$2(err)
|
|
54802
55937
|
};
|
|
54803
55938
|
}
|
|
54804
55939
|
}
|
|
@@ -56670,7 +57805,7 @@ function warnSampled(deps, deviceId, source, message, meta) {
|
|
|
56670
57805
|
function warnUnreachable(deps, deviceId, source, err) {
|
|
56671
57806
|
warnSampled(deps, deviceId, source, UNREACHABLE_MESSAGE, {
|
|
56672
57807
|
source,
|
|
56673
|
-
error: errMsg$
|
|
57808
|
+
error: errMsg$2(err)
|
|
56674
57809
|
});
|
|
56675
57810
|
}
|
|
56676
57811
|
/**
|
|
@@ -57944,7 +59079,7 @@ var ONBOARD_DETECTION_SOURCE = "onboard";
|
|
|
57944
59079
|
* configs, and it reads them the same way the runner does.
|
|
57945
59080
|
*/
|
|
57946
59081
|
var DETECTION_SOURCES_WHEN_ABSENT = ["pipeline"];
|
|
57947
|
-
function errMsg(err) {
|
|
59082
|
+
function errMsg$1(err) {
|
|
57948
59083
|
return err instanceof Error ? err.message : String(err);
|
|
57949
59084
|
}
|
|
57950
59085
|
/**
|
|
@@ -57963,7 +59098,7 @@ async function reconcileOnboardForwarding(input, deps) {
|
|
|
57963
59098
|
log.warn("onboard forwarding not reconciled — could not tell whether the camera has the cap", { meta: {
|
|
57964
59099
|
wantsOnboard,
|
|
57965
59100
|
detectionSources: [...detectionSources],
|
|
57966
|
-
error: errMsg(err)
|
|
59101
|
+
error: errMsg$1(err)
|
|
57967
59102
|
} });
|
|
57968
59103
|
return {
|
|
57969
59104
|
kind: "read-failed",
|
|
@@ -57981,7 +59116,7 @@ async function reconcileOnboardForwarding(input, deps) {
|
|
|
57981
59116
|
log.warn("onboard forwarding not reconciled — the vendor flag could not be read", { meta: {
|
|
57982
59117
|
wantsOnboard,
|
|
57983
59118
|
detectionSources: [...detectionSources],
|
|
57984
|
-
error: errMsg(err)
|
|
59119
|
+
error: errMsg$1(err)
|
|
57985
59120
|
} });
|
|
57986
59121
|
return {
|
|
57987
59122
|
kind: "read-failed",
|
|
@@ -57999,7 +59134,7 @@ async function reconcileOnboardForwarding(input, deps) {
|
|
|
57999
59134
|
was: current,
|
|
58000
59135
|
wanted: wantsOnboard,
|
|
58001
59136
|
detectionSources: [...detectionSources],
|
|
58002
|
-
error: errMsg(err)
|
|
59137
|
+
error: errMsg$1(err)
|
|
58003
59138
|
} });
|
|
58004
59139
|
return {
|
|
58005
59140
|
kind: "write-failed",
|
|
@@ -58195,7 +59330,7 @@ var DetectionWiringController = class {
|
|
|
58195
59330
|
try {
|
|
58196
59331
|
raw = await this.deps.ctx().settings.readDeviceStore(deviceId);
|
|
58197
59332
|
} catch (err) {
|
|
58198
|
-
const msg = errMsg$
|
|
59333
|
+
const msg = errMsg$2(err);
|
|
58199
59334
|
this.deps.logger.error("resolveDeviceDetectionSettings failed", {
|
|
58200
59335
|
tags: { deviceId },
|
|
58201
59336
|
meta: { error: msg }
|
|
@@ -58226,7 +59361,7 @@ var DetectionWiringController = class {
|
|
|
58226
59361
|
motionDetectionEnabled
|
|
58227
59362
|
});
|
|
58228
59363
|
} catch (err) {
|
|
58229
|
-
const msg = errMsg$
|
|
59364
|
+
const msg = errMsg$2(err);
|
|
58230
59365
|
this.deps.logger.error("resolveDeviceDetectionSettings narrowing failed", {
|
|
58231
59366
|
tags: { deviceId },
|
|
58232
59367
|
meta: { error: msg }
|
|
@@ -58261,7 +59396,7 @@ var DetectionWiringController = class {
|
|
|
58261
59396
|
} catch (err) {
|
|
58262
59397
|
this.deps.logger.debug("fetchAssignedProfiles: slot read failed (transient) — treating as unknown", {
|
|
58263
59398
|
tags: { deviceId },
|
|
58264
|
-
meta: { error: errMsg$
|
|
59399
|
+
meta: { error: errMsg$2(err) }
|
|
58265
59400
|
});
|
|
58266
59401
|
return null;
|
|
58267
59402
|
}
|
|
@@ -58337,7 +59472,7 @@ var DetectionWiringController = class {
|
|
|
58337
59472
|
if (config.motionSources.includes("analyzer")) try {
|
|
58338
59473
|
await this.deps.session.ensureMotionWatch(deviceId, config);
|
|
58339
59474
|
} catch (err) {
|
|
58340
|
-
log.error("ensureMotionWatch failed", { meta: { error: errMsg$
|
|
59475
|
+
log.error("ensureMotionWatch failed", { meta: { error: errMsg$2(err) } });
|
|
58341
59476
|
}
|
|
58342
59477
|
log.info("detection armed (session-time) — watching for motion", { meta: { motion: config.motionSources.join("+") } });
|
|
58343
59478
|
} else {
|
|
@@ -58358,14 +59493,14 @@ var DetectionWiringController = class {
|
|
|
58358
59493
|
try {
|
|
58359
59494
|
await this.deps.placement.dispatchCamera(runnerConfig);
|
|
58360
59495
|
} catch (err) {
|
|
58361
|
-
const msg = errMsg$
|
|
59496
|
+
const msg = errMsg$2(err);
|
|
58362
59497
|
log.error("dispatchCamera failed", { meta: { error: msg } });
|
|
58363
59498
|
}
|
|
58364
59499
|
}
|
|
58365
59500
|
try {
|
|
58366
59501
|
await this.deps.audio.resubscribe(deviceId, config);
|
|
58367
59502
|
} catch (err) {
|
|
58368
|
-
const msg = errMsg$
|
|
59503
|
+
const msg = errMsg$2(err);
|
|
58369
59504
|
log.error("subscribeAudioStream failed", { meta: { error: msg } });
|
|
58370
59505
|
}
|
|
58371
59506
|
if (config.detectionMode !== "on-motion") log.info("Detection dispatched", { meta: {
|
|
@@ -58381,7 +59516,7 @@ var DetectionWiringController = class {
|
|
|
58381
59516
|
try {
|
|
58382
59517
|
await this.deps.placement.releaseCamera({ deviceId });
|
|
58383
59518
|
} catch (err) {
|
|
58384
|
-
const msg = errMsg$
|
|
59519
|
+
const msg = errMsg$2(err);
|
|
58385
59520
|
this.deps.logger.error("releaseCamera failed", {
|
|
58386
59521
|
tags: { deviceId },
|
|
58387
59522
|
meta: { error: msg }
|
|
@@ -58407,7 +59542,7 @@ var DetectionWiringController = class {
|
|
|
58407
59542
|
} catch (err) {
|
|
58408
59543
|
this.deps.logger.warn("failed to clear per-device orchestrator settings on removal", {
|
|
58409
59544
|
tags: { deviceId },
|
|
58410
|
-
meta: { error: errMsg$
|
|
59545
|
+
meta: { error: errMsg$2(err) }
|
|
58411
59546
|
});
|
|
58412
59547
|
}
|
|
58413
59548
|
this.deps.logger.info("Purged pipeline config for removed device", { tags: { deviceId } });
|
|
@@ -58431,7 +59566,7 @@ var DetectionWiringController = class {
|
|
|
58431
59566
|
} catch (err) {
|
|
58432
59567
|
this.deps.logger.debug("device existence check failed — proceeding as before", {
|
|
58433
59568
|
tags: { deviceId },
|
|
58434
|
-
meta: { error: errMsg$
|
|
59569
|
+
meta: { error: errMsg$2(err) }
|
|
58435
59570
|
});
|
|
58436
59571
|
return false;
|
|
58437
59572
|
}
|
|
@@ -58548,7 +59683,7 @@ var DetectionWiringController = class {
|
|
|
58548
59683
|
await this.deps.attachOn(assignment.agentNodeId, nextConfig);
|
|
58549
59684
|
log.info("pipeline.camera-updated: hot-reloaded", { tags: { nodeId: assignment.agentNodeId } });
|
|
58550
59685
|
} catch (err) {
|
|
58551
|
-
const msg = errMsg$
|
|
59686
|
+
const msg = errMsg$2(err);
|
|
58552
59687
|
log.error("pipeline.camera-updated: attachOn failed", {
|
|
58553
59688
|
tags: { nodeId: assignment.agentNodeId },
|
|
58554
59689
|
meta: { error: msg }
|
|
@@ -58624,7 +59759,7 @@ var DetectionWiringController = class {
|
|
|
58624
59759
|
this.deps.audio.resubscribe(deviceId, audioCfg).catch((err) => {
|
|
58625
59760
|
this.deps.logger.warn("watchdog audio re-subscribe failed", {
|
|
58626
59761
|
tags: { deviceId },
|
|
58627
|
-
meta: { error: errMsg$
|
|
59762
|
+
meta: { error: errMsg$2(err) }
|
|
58628
59763
|
});
|
|
58629
59764
|
});
|
|
58630
59765
|
}
|
|
@@ -58823,7 +59958,7 @@ var DeviceConfigContributions = class {
|
|
|
58823
59958
|
issue: configIssue
|
|
58824
59959
|
} });
|
|
58825
59960
|
this.deps.topology.refreshNodeCapabilities().then(() => this.deps.redispatchAllActiveCameras("ingest-owner-changed")).catch((err) => {
|
|
58826
|
-
this.deps.ctx().logger.warn("ingest-owner-changed re-dispatch failed", { meta: { error: errMsg$
|
|
59961
|
+
this.deps.ctx().logger.warn("ingest-owner-changed re-dispatch failed", { meta: { error: errMsg$2(err) } });
|
|
58827
59962
|
});
|
|
58828
59963
|
}
|
|
58829
59964
|
this.deps.clearOnSettingsChange();
|
|
@@ -59277,10 +60412,25 @@ function rootRefusal(input, droppedRoots) {
|
|
|
59277
60412
|
droppedRoots
|
|
59278
60413
|
};
|
|
59279
60414
|
}
|
|
60415
|
+
/**
|
|
60416
|
+
* Whose provisioning an attach applies. The elected device when there is one.
|
|
60417
|
+
* With NO elected device the runner's default pool serves the camera; when the
|
|
60418
|
+
* node has exactly one eligible device the balancer returns
|
|
60419
|
+
* `single-eligible-device` and that default pool IS that device (a Mac's
|
|
60420
|
+
* `coreml:all`, an N100 with only its iGPU on), so its model pin and the
|
|
60421
|
+
* camera's override stored under its key apply (D745 — before, the empty key
|
|
60422
|
+
* was read, which no store writes, and the pin was silently never run).
|
|
60423
|
+
* Otherwise `''`: the node's folded settings with no hardware fields.
|
|
60424
|
+
*/
|
|
60425
|
+
function provisioningKeyFor(input) {
|
|
60426
|
+
if (input.deviceKey !== void 0) return input.deviceKey;
|
|
60427
|
+
const eligibleKeys = Object.keys(input.eligible);
|
|
60428
|
+
return eligibleKeys.length === 1 ? eligibleKeys[0] ?? "" : "";
|
|
60429
|
+
}
|
|
59280
60430
|
/** Steps 3 of the module header: provision the resolved structure. */
|
|
59281
60431
|
async function provisionAttachPipeline(input, roster, resolved, store) {
|
|
59282
60432
|
const { deviceId, nodeId, deviceKey } = input;
|
|
59283
|
-
const electedKey =
|
|
60433
|
+
const electedKey = provisioningKeyFor(input);
|
|
59284
60434
|
const rosterKeys = (roster ?? []).map((d) => d.deviceKey);
|
|
59285
60435
|
const sources = await store.readDeviceProvisioning(deviceId, nodeId, [...new Set([electedKey, ...rosterKeys])]);
|
|
59286
60436
|
const elected = sources.get(electedKey);
|
|
@@ -59888,7 +61038,7 @@ var PlacementService = class {
|
|
|
59888
61038
|
async releaseCamera(input) {
|
|
59889
61039
|
const current = this.deps.ledger.getAssignment(input.deviceId);
|
|
59890
61040
|
if (current) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
|
|
59891
|
-
const msg = errMsg$
|
|
61041
|
+
const msg = errMsg$2(err);
|
|
59892
61042
|
this.deps.ctx().logger.debug("releaseCamera detach failed", {
|
|
59893
61043
|
tags: { deviceId: input.deviceId },
|
|
59894
61044
|
meta: { error: msg }
|
|
@@ -59916,7 +61066,7 @@ var PlacementService = class {
|
|
|
59916
61066
|
pipelineNodeId: input.agentNodeId,
|
|
59917
61067
|
[PREFERRED_AGENT_SETTING]: null
|
|
59918
61068
|
}).catch((err) => {
|
|
59919
|
-
const msg = errMsg$
|
|
61069
|
+
const msg = errMsg$2(err);
|
|
59920
61070
|
ctx.logger.warn("assignPipeline: failed to persist pin", {
|
|
59921
61071
|
tags: { deviceId: input.deviceId },
|
|
59922
61072
|
meta: { error: msg }
|
|
@@ -59948,7 +61098,7 @@ var PlacementService = class {
|
|
|
59948
61098
|
}
|
|
59949
61099
|
const current = this.deps.ledger.getAssignment(input.deviceId);
|
|
59950
61100
|
if (current && current.agentNodeId !== input.agentNodeId) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
|
|
59951
|
-
const msg = errMsg$
|
|
61101
|
+
const msg = errMsg$2(err);
|
|
59952
61102
|
ctx.logger.debug("assignPipeline detach-old failed", {
|
|
59953
61103
|
tags: { deviceId: input.deviceId },
|
|
59954
61104
|
meta: { error: msg }
|
|
@@ -59973,7 +61123,7 @@ var PlacementService = class {
|
|
|
59973
61123
|
await ctx.settings?.writeDeviceStore(input.deviceId, { pipelineDeviceKey: clearing ? "auto" : input.deviceKey }).catch((err) => {
|
|
59974
61124
|
ctx.logger.warn("setPipelineDevicePin: failed to persist device pin", {
|
|
59975
61125
|
tags: { deviceId: input.deviceId },
|
|
59976
|
-
meta: { error: errMsg$
|
|
61126
|
+
meta: { error: errMsg$2(err) }
|
|
59977
61127
|
});
|
|
59978
61128
|
});
|
|
59979
61129
|
const sessionNode = this.deps.activeSessionNode(input.deviceId);
|
|
@@ -59996,7 +61146,7 @@ var PlacementService = class {
|
|
|
59996
61146
|
[PREFERRED_AGENT_SETTING]: null,
|
|
59997
61147
|
pipelineNodeId: "auto"
|
|
59998
61148
|
}).catch((err) => {
|
|
59999
|
-
const msg = errMsg$
|
|
61149
|
+
const msg = errMsg$2(err);
|
|
60000
61150
|
ctx.logger.warn("unassignPipeline: failed to clear pin", {
|
|
60001
61151
|
tags: { deviceId: input.deviceId },
|
|
60002
61152
|
meta: { error: msg }
|
|
@@ -60017,7 +61167,7 @@ var PlacementService = class {
|
|
|
60017
61167
|
else {
|
|
60018
61168
|
const targetNodeId = decision.agentNodeId;
|
|
60019
61169
|
if (current && current.agentNodeId !== targetNodeId) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
|
|
60020
|
-
const msg = errMsg$
|
|
61170
|
+
const msg = errMsg$2(err);
|
|
60021
61171
|
ctx.logger.debug("unassignPipeline detach-old failed", {
|
|
60022
61172
|
tags: { deviceId: input.deviceId },
|
|
60023
61173
|
meta: { error: msg }
|
|
@@ -60037,7 +61187,7 @@ var PlacementService = class {
|
|
|
60037
61187
|
}
|
|
60038
61188
|
}
|
|
60039
61189
|
if (current) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
|
|
60040
|
-
const msg = errMsg$
|
|
61190
|
+
const msg = errMsg$2(err);
|
|
60041
61191
|
ctx.logger.debug("unassignPipeline detach failed", {
|
|
60042
61192
|
tags: { deviceId: input.deviceId },
|
|
60043
61193
|
meta: { error: msg }
|
|
@@ -60056,7 +61206,7 @@ var PlacementService = class {
|
|
|
60056
61206
|
async runAutoRebalance() {
|
|
60057
61207
|
if (!this.deps.ctxIfReady()) return;
|
|
60058
61208
|
await this.deps.reconcilePlacementFromRunners().catch((err) => {
|
|
60059
|
-
this.deps.ctx().logger.debug("auto-rebalance: attachment reconcile failed", { meta: { error: errMsg$
|
|
61209
|
+
this.deps.ctx().logger.debug("auto-rebalance: attachment reconcile failed", { meta: { error: errMsg$2(err) } });
|
|
60060
61210
|
});
|
|
60061
61211
|
if (this.autoRebalanceInFlight) return;
|
|
60062
61212
|
if (this.deps.topology.enabledNodes.length < 2) return;
|
|
@@ -60065,7 +61215,7 @@ var PlacementService = class {
|
|
|
60065
61215
|
const { migrated } = await this.rebalance({ minImprovement: AUTO_REBALANCE_MIN_IMPROVEMENT });
|
|
60066
61216
|
if (migrated > 0) this.deps.ctx().logger.info("auto-rebalance: corrected placement drift", { meta: { migrated } });
|
|
60067
61217
|
} catch (err) {
|
|
60068
|
-
this.deps.ctx().logger.debug("auto-rebalance pass failed", { meta: { error: errMsg$
|
|
61218
|
+
this.deps.ctx().logger.debug("auto-rebalance pass failed", { meta: { error: errMsg$2(err) } });
|
|
60069
61219
|
} finally {
|
|
60070
61220
|
this.autoRebalanceInFlight = false;
|
|
60071
61221
|
}
|
|
@@ -60137,7 +61287,7 @@ var PlacementService = class {
|
|
|
60137
61287
|
const targetConfig = withAttachPipeline(config, placement.pipeline);
|
|
60138
61288
|
if (current) {
|
|
60139
61289
|
await this.detachOn(current.agentNodeId, deviceId).catch((err) => {
|
|
60140
|
-
const msg = errMsg$
|
|
61290
|
+
const msg = errMsg$2(err);
|
|
60141
61291
|
ctx.logger.debug("rebalance detach-old failed", {
|
|
60142
61292
|
tags: { deviceId },
|
|
60143
61293
|
meta: { error: msg }
|
|
@@ -60542,7 +61692,7 @@ var NodeLifecycleHandler = class {
|
|
|
60542
61692
|
nodeId: decision.agentNodeId
|
|
60543
61693
|
} });
|
|
60544
61694
|
} catch (err) {
|
|
60545
|
-
const msg = errMsg$
|
|
61695
|
+
const msg = errMsg$2(err);
|
|
60546
61696
|
this.deps.logger.error("Failover: attach failed", {
|
|
60547
61697
|
tags: {
|
|
60548
61698
|
deviceId,
|
|
@@ -60572,7 +61722,7 @@ var NodeLifecycleHandler = class {
|
|
|
60572
61722
|
this.deps.schedulePendingRetry();
|
|
60573
61723
|
if (failoverPolicy.onReconnect === "rebalance") {
|
|
60574
61724
|
await this.deps.rebalance().catch((err) => {
|
|
60575
|
-
const msg = errMsg$
|
|
61725
|
+
const msg = errMsg$2(err);
|
|
60576
61726
|
this.deps.logger.error("rebalance after reconnect failed", { meta: { error: msg } });
|
|
60577
61727
|
});
|
|
60578
61728
|
return;
|
|
@@ -60598,7 +61748,7 @@ var NodeLifecycleHandler = class {
|
|
|
60598
61748
|
this.deps.ledger.recordAssignment(deviceId, nodeId, "failover", false);
|
|
60599
61749
|
restored++;
|
|
60600
61750
|
} catch (err) {
|
|
60601
|
-
const msg = errMsg$
|
|
61751
|
+
const msg = errMsg$2(err);
|
|
60602
61752
|
this.deps.logger.error("restore attach failed", {
|
|
60603
61753
|
tags: {
|
|
60604
61754
|
deviceId,
|
|
@@ -60692,7 +61842,7 @@ var NodeLifecycleHandler = class {
|
|
|
60692
61842
|
deviceId,
|
|
60693
61843
|
nodeId
|
|
60694
61844
|
},
|
|
60695
|
-
meta: { error: errMsg$
|
|
61845
|
+
meta: { error: errMsg$2(err) }
|
|
60696
61846
|
});
|
|
60697
61847
|
}
|
|
60698
61848
|
this.deps.logger.info("readiness: detection-pipeline reseeded agent defaults + redispatched cameras", {
|
|
@@ -60706,7 +61856,7 @@ var NodeLifecycleHandler = class {
|
|
|
60706
61856
|
this.reattachStaleEmpties(nodeId).catch((err) => {
|
|
60707
61857
|
this.deps.logger.debug("readiness deferred sweep failed", {
|
|
60708
61858
|
tags: { nodeId },
|
|
60709
|
-
meta: { error: errMsg$
|
|
61859
|
+
meta: { error: errMsg$2(err) }
|
|
60710
61860
|
});
|
|
60711
61861
|
});
|
|
60712
61862
|
}, 3e3);
|
|
@@ -60717,7 +61867,7 @@ var NodeLifecycleHandler = class {
|
|
|
60717
61867
|
tags: { nodeId },
|
|
60718
61868
|
meta: {
|
|
60719
61869
|
epoch: t.epoch,
|
|
60720
|
-
error: errMsg$
|
|
61870
|
+
error: errMsg$2(err)
|
|
60721
61871
|
}
|
|
60722
61872
|
});
|
|
60723
61873
|
}
|
|
@@ -60796,7 +61946,7 @@ var NodeLifecycleHandler = class {
|
|
|
60796
61946
|
deviceId,
|
|
60797
61947
|
nodeId
|
|
60798
61948
|
},
|
|
60799
|
-
meta: { error: errMsg$
|
|
61949
|
+
meta: { error: errMsg$2(err) }
|
|
60800
61950
|
});
|
|
60801
61951
|
}
|
|
60802
61952
|
}
|
|
@@ -61118,7 +62268,7 @@ var NodeStressController = class NodeStressController {
|
|
|
61118
62268
|
if (this.timer) return;
|
|
61119
62269
|
this.timer = setInterval(() => {
|
|
61120
62270
|
this.sweep().catch((err) => {
|
|
61121
|
-
this.deps.logger.warn("node-stress sweep failed", { meta: { error: errMsg$
|
|
62271
|
+
this.deps.logger.warn("node-stress sweep failed", { meta: { error: errMsg$2(err) } });
|
|
61122
62272
|
});
|
|
61123
62273
|
}, NodeStressController.SWEEP_INTERVAL_MS);
|
|
61124
62274
|
}
|
|
@@ -61291,7 +62441,7 @@ var NodeStressController = class NodeStressController {
|
|
|
61291
62441
|
meta: {
|
|
61292
62442
|
from: plan.fromNodeId,
|
|
61293
62443
|
to: plan.toNodeId,
|
|
61294
|
-
error: errMsg$
|
|
62444
|
+
error: errMsg$2(err)
|
|
61295
62445
|
}
|
|
61296
62446
|
});
|
|
61297
62447
|
} finally {
|
|
@@ -61988,7 +63138,7 @@ var NodeTopologyService = class {
|
|
|
61988
63138
|
try {
|
|
61989
63139
|
blob = await this.deps.readAgentSettingsMap();
|
|
61990
63140
|
} catch (err) {
|
|
61991
|
-
this.deps.logger.warn("refreshNodeCapabilities: agent settings read failed", { meta: { error: errMsg$
|
|
63141
|
+
this.deps.logger.warn("refreshNodeCapabilities: agent settings read failed", { meta: { error: errMsg$2(err) } });
|
|
61992
63142
|
}
|
|
61993
63143
|
const nodeIds = new Set(["hub"]);
|
|
61994
63144
|
for (const nodeId of Object.keys(blob)) if (typeof nodeId === "string" && nodeId.length > 0 && !nodeId.includes("/")) nodeIds.add(nodeId);
|
|
@@ -62162,7 +63312,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62162
63312
|
const unsubInferenceResult = deps.eventBus.subscribe({ category: EventCategory.PipelineInferenceResult }, (event) => {
|
|
62163
63313
|
if (!isEvent(event, EventCategory.PipelineInferenceResult)) return;
|
|
62164
63314
|
deps.handleInferenceResult(event.data).catch((err) => {
|
|
62165
|
-
const msg = errMsg$
|
|
63315
|
+
const msg = errMsg$2(err);
|
|
62166
63316
|
deps.logger.error("PipelineInferenceResult post-processing failed", {
|
|
62167
63317
|
tags: { deviceId: event.data.deviceId },
|
|
62168
63318
|
meta: { error: msg }
|
|
@@ -62181,7 +63331,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62181
63331
|
deps.logger.info("received onProfileSlotsChanged", { tags: { deviceId } });
|
|
62182
63332
|
deps.handleDeviceRegistered(deviceId).catch((err) => {
|
|
62183
63333
|
if (isTornDown) return;
|
|
62184
|
-
const msg = errMsg$
|
|
63334
|
+
const msg = errMsg$2(err);
|
|
62185
63335
|
deps.logger.error("handleDeviceRegistered failed", {
|
|
62186
63336
|
tags: { deviceId },
|
|
62187
63337
|
meta: { error: msg }
|
|
@@ -62193,7 +63343,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62193
63343
|
const unsubDeviceUnregistered = deps.eventBus.subscribe({ category: EventCategory.DeviceUnregistered }, (event) => {
|
|
62194
63344
|
const { deviceId } = event.data;
|
|
62195
63345
|
deps.handleDeviceUnregistered(deviceId).catch((err) => {
|
|
62196
|
-
const msg = errMsg$
|
|
63346
|
+
const msg = errMsg$2(err);
|
|
62197
63347
|
deps.logger.error("handleDeviceUnregistered failed", {
|
|
62198
63348
|
tags: { deviceId },
|
|
62199
63349
|
meta: { error: msg }
|
|
@@ -62206,7 +63356,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62206
63356
|
deps.cameraSensors.requestCamera(deviceId, "settings-updated");
|
|
62207
63357
|
deps.handleSettingsChanged(deviceId).catch((err) => {
|
|
62208
63358
|
if (isTornDown) return;
|
|
62209
|
-
const msg = errMsg$
|
|
63359
|
+
const msg = errMsg$2(err);
|
|
62210
63360
|
deps.logger.error("handleSettingsChanged failed", {
|
|
62211
63361
|
tags: { deviceId },
|
|
62212
63362
|
meta: { error: msg }
|
|
@@ -62216,7 +63366,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62216
63366
|
const unsubCameraUpdated = deps.eventBus.subscribe({ category: EventCategory.PipelineCameraUpdated }, (event) => {
|
|
62217
63367
|
if (!isEvent(event, EventCategory.PipelineCameraUpdated)) return;
|
|
62218
63368
|
deps.handlePipelineCameraUpdated(event.data.deviceId, event.data.config).catch((err) => {
|
|
62219
|
-
const msg = errMsg$
|
|
63369
|
+
const msg = errMsg$2(err);
|
|
62220
63370
|
deps.logger.error("handlePipelineCameraUpdated failed", {
|
|
62221
63371
|
tags: { deviceId: event.data.deviceId },
|
|
62222
63372
|
meta: { error: msg }
|
|
@@ -62229,7 +63379,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62229
63379
|
if (!(reason === "wrapper-activated" || reason === "wrapper-deactivated") || !(capName === "detection-pipeline" || capName === "audio-analysis" || capName === "motion-detection")) return;
|
|
62230
63380
|
deps.handleSettingsChanged(deviceId).catch((err) => {
|
|
62231
63381
|
if (isTornDown) return;
|
|
62232
|
-
const msg = errMsg$
|
|
63382
|
+
const msg = errMsg$2(err);
|
|
62233
63383
|
deps.logger.error("binding-changed re-dispatch failed", {
|
|
62234
63384
|
tags: { deviceId },
|
|
62235
63385
|
meta: {
|
|
@@ -62257,7 +63407,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62257
63407
|
tags: { deviceId },
|
|
62258
63408
|
meta: {
|
|
62259
63409
|
detected,
|
|
62260
|
-
error: errMsg$
|
|
63410
|
+
error: errMsg$2(err)
|
|
62261
63411
|
}
|
|
62262
63412
|
});
|
|
62263
63413
|
});
|
|
@@ -62304,7 +63454,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62304
63454
|
tags: { deviceId },
|
|
62305
63455
|
meta: {
|
|
62306
63456
|
detected,
|
|
62307
|
-
error: errMsg$
|
|
63457
|
+
error: errMsg$2(err)
|
|
62308
63458
|
}
|
|
62309
63459
|
});
|
|
62310
63460
|
});
|
|
@@ -62570,7 +63720,7 @@ async function reattachAfterPackageZoneChange(deps, deviceId) {
|
|
|
62570
63720
|
deviceId,
|
|
62571
63721
|
nodeId
|
|
62572
63722
|
},
|
|
62573
|
-
meta: { error: errMsg$
|
|
63723
|
+
meta: { error: errMsg$2(err) }
|
|
62574
63724
|
});
|
|
62575
63725
|
}
|
|
62576
63726
|
}
|
|
@@ -62851,7 +64001,7 @@ async function resetRemovedModelPins(deps, trigger) {
|
|
|
62851
64001
|
} catch (err) {
|
|
62852
64002
|
deps.logger.warn("removed-model pin reset skipped — custom model registry unreadable", { meta: {
|
|
62853
64003
|
trigger,
|
|
62854
|
-
error: errMsg$
|
|
64004
|
+
error: errMsg$2(err)
|
|
62855
64005
|
} });
|
|
62856
64006
|
return [];
|
|
62857
64007
|
}
|
|
@@ -62870,7 +64020,7 @@ async function resetRemovedModelPins(deps, trigger) {
|
|
|
62870
64020
|
} catch (err) {
|
|
62871
64021
|
deps.logger.warn("removed-model pin reset failed", { meta: {
|
|
62872
64022
|
trigger,
|
|
62873
|
-
error: errMsg$
|
|
64023
|
+
error: errMsg$2(err)
|
|
62874
64024
|
} });
|
|
62875
64025
|
return [];
|
|
62876
64026
|
}
|
|
@@ -63229,6 +64379,21 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
63229
64379
|
});
|
|
63230
64380
|
}
|
|
63231
64381
|
/**
|
|
64382
|
+
* Rewrite the whole agent-settings map in ONE read-modify-write under
|
|
64383
|
+
* {@link blobWriteLock} — atomic across every node of the blob (Best
|
|
64384
|
+
* settings, D745, writes several nodes' devices at once). `transform` must
|
|
64385
|
+
* be pure: it may run on a fresh read.
|
|
64386
|
+
*/
|
|
64387
|
+
async rewriteAgentSettingsMap(transform) {
|
|
64388
|
+
await this.blobWriteLock.run(PipelineSettingsStore.AGENT_SETTINGS_LOCK_KEY, async () => {
|
|
64389
|
+
await this.agentSettingsState.set(transform(await this.readAgentSettingsMap()));
|
|
64390
|
+
});
|
|
64391
|
+
}
|
|
64392
|
+
/** Rewrite the whole camera-settings map in one durable `update()` (D745). */
|
|
64393
|
+
async rewriteCameraSettingsMap(transform) {
|
|
64394
|
+
await this.cameraSettingsState.update(transform);
|
|
64395
|
+
}
|
|
64396
|
+
/**
|
|
63232
64397
|
* Drop one agent's persisted settings entry. Returns whether an entry
|
|
63233
64398
|
* existed (`false` also when the store is already disposed — the locked
|
|
63234
64399
|
* section becomes a no-op then). Serialized via {@link blobWriteLock} for
|
|
@@ -63485,6 +64650,10 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
63485
64650
|
* epochs and operator resets — a bound, not a budget.
|
|
63486
64651
|
*/
|
|
63487
64652
|
static CATALOG_FENCE_REREADS = 3;
|
|
64653
|
+
/** The node's catalog, read through the cache (awaits the node when cold). */
|
|
64654
|
+
async readCatalogForNode(nodeId) {
|
|
64655
|
+
return this.getCatalogForAgent(nodeId);
|
|
64656
|
+
}
|
|
63488
64657
|
async getCatalogForAgent(nodeId) {
|
|
63489
64658
|
const api = this.deps.api();
|
|
63490
64659
|
if (!api) return null;
|
|
@@ -63502,7 +64671,7 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
63502
64671
|
this.catalogReadFailing.add(nodeId);
|
|
63503
64672
|
this.deps.logger.warn("node catalog read failed — placements on it wait or stay pending", {
|
|
63504
64673
|
tags: { nodeId },
|
|
63505
|
-
meta: { error: errMsg$
|
|
64674
|
+
meta: { error: errMsg$2(err) }
|
|
63506
64675
|
});
|
|
63507
64676
|
}
|
|
63508
64677
|
return null;
|
|
@@ -63589,7 +64758,7 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
63589
64758
|
tags: { deviceId },
|
|
63590
64759
|
meta: {
|
|
63591
64760
|
capName: "detection-pipeline",
|
|
63592
|
-
error: errMsg$
|
|
64761
|
+
error: errMsg$2(err)
|
|
63593
64762
|
}
|
|
63594
64763
|
});
|
|
63595
64764
|
return true;
|
|
@@ -64422,7 +65591,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64422
65591
|
try {
|
|
64423
65592
|
slots = await api.streamBroker.listAllProfileSlots.query();
|
|
64424
65593
|
} catch (err) {
|
|
64425
|
-
this.deps.logger.debug("dispatch reconcile skipped — stream-broker not ready", { meta: { error: errMsg$
|
|
65594
|
+
this.deps.logger.debug("dispatch reconcile skipped — stream-broker not ready", { meta: { error: errMsg$2(err) } });
|
|
64426
65595
|
return;
|
|
64427
65596
|
}
|
|
64428
65597
|
const desired = desiredDeviceIdsFromSlots(slots);
|
|
@@ -64449,7 +65618,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64449
65618
|
} catch (err) {
|
|
64450
65619
|
this.deps.logger.warn("dispatch reconcile: handleDeviceRegistered failed", {
|
|
64451
65620
|
tags: { deviceId },
|
|
64452
|
-
meta: { error: errMsg$
|
|
65621
|
+
meta: { error: errMsg$2(err) }
|
|
64453
65622
|
});
|
|
64454
65623
|
}
|
|
64455
65624
|
await this.reconcilePlacementFromRunners();
|
|
@@ -64477,7 +65646,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64477
65646
|
const devices = await api.deviceManager.listAll.query({ projection: "slim" });
|
|
64478
65647
|
return new Set(devices.map((device) => device.id));
|
|
64479
65648
|
} catch (err) {
|
|
64480
|
-
this.deps.logger.debug("dispatch reconcile: deviceManager.listAll failed", { meta: { error: errMsg$
|
|
65649
|
+
this.deps.logger.debug("dispatch reconcile: deviceManager.listAll failed", { meta: { error: errMsg$2(err) } });
|
|
64481
65650
|
return null;
|
|
64482
65651
|
}
|
|
64483
65652
|
}
|
|
@@ -64503,7 +65672,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64503
65672
|
} catch (err) {
|
|
64504
65673
|
this.deps.logger.warn("dispatch reconcile: teardown of vanished device failed", {
|
|
64505
65674
|
tags: { deviceId },
|
|
64506
|
-
meta: { error: errMsg$
|
|
65675
|
+
meta: { error: errMsg$2(err) }
|
|
64507
65676
|
});
|
|
64508
65677
|
}
|
|
64509
65678
|
}
|
|
@@ -64560,7 +65729,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64560
65729
|
this.deps.logger.debug("placement reconcile skipped node — getLocalCameras failed", {
|
|
64561
65730
|
tags: { nodeId },
|
|
64562
65731
|
meta: {
|
|
64563
|
-
error: outcome.kind === "rejected" ? errMsg$
|
|
65732
|
+
error: outcome.kind === "rejected" ? errMsg$2(outcome.error) : "budget exceeded",
|
|
64564
65733
|
ms
|
|
64565
65734
|
}
|
|
64566
65735
|
});
|
|
@@ -64590,7 +65759,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64590
65759
|
nodeId,
|
|
64591
65760
|
deviceId
|
|
64592
65761
|
},
|
|
64593
|
-
meta: { error: errMsg$
|
|
65762
|
+
meta: { error: errMsg$2(err) }
|
|
64594
65763
|
});
|
|
64595
65764
|
})));
|
|
64596
65765
|
}
|
|
@@ -64632,7 +65801,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64632
65801
|
this.deps.handleDeviceRegistered(deviceId).catch((err) => {
|
|
64633
65802
|
this.deps.logger.debug("slot-read retry: handleDeviceRegistered failed", {
|
|
64634
65803
|
tags: { deviceId },
|
|
64635
|
-
meta: { error: errMsg$
|
|
65804
|
+
meta: { error: errMsg$2(err) }
|
|
64636
65805
|
});
|
|
64637
65806
|
});
|
|
64638
65807
|
}, delay);
|
|
@@ -64710,7 +65879,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64710
65879
|
} catch (err) {
|
|
64711
65880
|
this.deps.logger.warn("pending retry: dispatchCamera failed", {
|
|
64712
65881
|
tags: { deviceId },
|
|
64713
|
-
meta: { error: errMsg$
|
|
65882
|
+
meta: { error: errMsg$2(err) }
|
|
64714
65883
|
});
|
|
64715
65884
|
stillPending++;
|
|
64716
65885
|
}
|
|
@@ -64788,7 +65957,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64788
65957
|
await this.deps.detachOn(assignment.agentNodeId, deviceId).catch((err) => {
|
|
64789
65958
|
this.deps.logger.debug("remote-health exhausted-detach failed", {
|
|
64790
65959
|
tags: { deviceId },
|
|
64791
|
-
meta: { error: errMsg$
|
|
65960
|
+
meta: { error: errMsg$2(err) }
|
|
64792
65961
|
});
|
|
64793
65962
|
});
|
|
64794
65963
|
return;
|
|
@@ -64811,14 +65980,14 @@ var ReconcileController = class ReconcileController {
|
|
|
64811
65980
|
await this.deps.detachOn(assignment.agentNodeId, deviceId).catch((err) => {
|
|
64812
65981
|
this.deps.logger.warn("remote-health detach failed", {
|
|
64813
65982
|
tags: { deviceId },
|
|
64814
|
-
meta: { error: errMsg$
|
|
65983
|
+
meta: { error: errMsg$2(err) }
|
|
64815
65984
|
});
|
|
64816
65985
|
});
|
|
64817
65986
|
this.deps.ledger.dropAssignment(deviceId, "silent");
|
|
64818
65987
|
await this.deps.redispatchCamera(cfg).catch((err) => {
|
|
64819
65988
|
this.deps.logger.warn("remote-health re-dispatch failed", {
|
|
64820
65989
|
tags: { deviceId },
|
|
64821
|
-
meta: { error: errMsg$
|
|
65990
|
+
meta: { error: errMsg$2(err) }
|
|
64822
65991
|
});
|
|
64823
65992
|
});
|
|
64824
65993
|
}
|
|
@@ -64993,7 +66162,7 @@ var OccupancyRecheckController = class {
|
|
|
64993
66162
|
tags: { deviceId },
|
|
64994
66163
|
meta: {
|
|
64995
66164
|
deviceId,
|
|
64996
|
-
error: errMsg$
|
|
66165
|
+
error: errMsg$2(err)
|
|
64997
66166
|
}
|
|
64998
66167
|
});
|
|
64999
66168
|
});
|
|
@@ -65127,7 +66296,7 @@ var OccupancyRecheckController = class {
|
|
|
65127
66296
|
tags: { deviceId },
|
|
65128
66297
|
meta: {
|
|
65129
66298
|
deviceId,
|
|
65130
|
-
error: errMsg$
|
|
66299
|
+
error: errMsg$2(err)
|
|
65131
66300
|
}
|
|
65132
66301
|
});
|
|
65133
66302
|
});
|
|
@@ -65182,7 +66351,7 @@ var OccupancyRecheckController = class {
|
|
|
65182
66351
|
meta: {
|
|
65183
66352
|
deviceId,
|
|
65184
66353
|
nodeId,
|
|
65185
|
-
error: errMsg$
|
|
66354
|
+
error: errMsg$2(err),
|
|
65186
66355
|
kind
|
|
65187
66356
|
}
|
|
65188
66357
|
});
|
|
@@ -65191,7 +66360,7 @@ var OccupancyRecheckController = class {
|
|
|
65191
66360
|
}
|
|
65192
66361
|
const answer = await this.deps.requestBurstOn(nodeId, deviceId, frames, reasonTag).catch((err) => ({
|
|
65193
66362
|
started: false,
|
|
65194
|
-
error: errMsg$
|
|
66363
|
+
error: errMsg$2(err)
|
|
65195
66364
|
}));
|
|
65196
66365
|
if (!answer.started) {
|
|
65197
66366
|
const refusedBecause = "refusedBecause" in answer ? answer.refusedBecause : void 0;
|
|
@@ -65239,7 +66408,7 @@ var OccupancyRecheckController = class {
|
|
|
65239
66408
|
meta: {
|
|
65240
66409
|
deviceId,
|
|
65241
66410
|
nodeId,
|
|
65242
|
-
error: errMsg$
|
|
66411
|
+
error: errMsg$2(err)
|
|
65243
66412
|
}
|
|
65244
66413
|
});
|
|
65245
66414
|
});
|
|
@@ -65296,7 +66465,7 @@ var OccupancyRecheckController = class {
|
|
|
65296
66465
|
meta: {
|
|
65297
66466
|
deviceId,
|
|
65298
66467
|
nodeId: burst.nodeId,
|
|
65299
|
-
error: errMsg$
|
|
66468
|
+
error: errMsg$2(err)
|
|
65300
66469
|
}
|
|
65301
66470
|
});
|
|
65302
66471
|
});
|
|
@@ -65953,7 +67122,7 @@ var SessionDispatchController = class {
|
|
|
65953
67122
|
deviceId,
|
|
65954
67123
|
nodeId: session.nodeId
|
|
65955
67124
|
},
|
|
65956
|
-
meta: { error: errMsg$
|
|
67125
|
+
meta: { error: errMsg$2(err) }
|
|
65957
67126
|
});
|
|
65958
67127
|
});
|
|
65959
67128
|
this.deps.dropAssignment(deviceId, "silent");
|
|
@@ -66170,7 +67339,7 @@ var SessionDispatchController = class {
|
|
|
66170
67339
|
};
|
|
66171
67340
|
if (reconcileBudgetExceeded) log.warn("detection session: placement census overran its budget — placing without it", { meta: { budgetMs: 500 } });
|
|
66172
67341
|
else if (censusOutcome.kind === "rejected") log.warn("detection session: placement census failed — placing on the ledger as it is", { meta: {
|
|
66173
|
-
error: errMsg$
|
|
67342
|
+
error: errMsg$2(censusOutcome.error),
|
|
66174
67343
|
reconcileMs
|
|
66175
67344
|
} });
|
|
66176
67345
|
const balanceStartedAt = Date.now();
|
|
@@ -66364,7 +67533,7 @@ var SessionDispatchController = class {
|
|
|
66364
67533
|
await this.resolveSessionLifecycleLocked(deviceId, reason);
|
|
66365
67534
|
}).catch((err) => {
|
|
66366
67535
|
log.warn("detection session: lifecycle re-check failed", { meta: {
|
|
66367
|
-
error: errMsg$
|
|
67536
|
+
error: errMsg$2(err),
|
|
66368
67537
|
reason
|
|
66369
67538
|
} });
|
|
66370
67539
|
});
|
|
@@ -66469,7 +67638,7 @@ var SessionDispatchController = class {
|
|
|
66469
67638
|
log.warn("detection session: teardown detach failed", {
|
|
66470
67639
|
tags: { nodeId: session.nodeId },
|
|
66471
67640
|
meta: {
|
|
66472
|
-
error: errMsg$
|
|
67641
|
+
error: errMsg$2(err),
|
|
66473
67642
|
reason
|
|
66474
67643
|
}
|
|
66475
67644
|
});
|
|
@@ -66576,7 +67745,7 @@ var SessionDispatchController = class {
|
|
|
66576
67745
|
deviceId,
|
|
66577
67746
|
nodeId: session.nodeId
|
|
66578
67747
|
},
|
|
66579
|
-
meta: { error: errMsg$
|
|
67748
|
+
meta: { error: errMsg$2(err) }
|
|
66580
67749
|
});
|
|
66581
67750
|
});
|
|
66582
67751
|
this.sessionRegistry.deregister(deviceId);
|
|
@@ -66597,7 +67766,7 @@ var SessionDispatchController = class {
|
|
|
66597
67766
|
deviceId,
|
|
66598
67767
|
nodeId: watchNodeId
|
|
66599
67768
|
},
|
|
66600
|
-
meta: { error: errMsg$
|
|
67769
|
+
meta: { error: errMsg$2(err) }
|
|
66601
67770
|
});
|
|
66602
67771
|
});
|
|
66603
67772
|
this.motionWatchByDevice.delete(deviceId);
|
|
@@ -66780,7 +67949,7 @@ async function sweepMirror(deps, hydrator, cameraIds) {
|
|
|
66780
67949
|
tags: { deviceId },
|
|
66781
67950
|
meta: {
|
|
66782
67951
|
mirror: hydrator.mirror,
|
|
66783
|
-
error: errMsg$
|
|
67952
|
+
error: errMsg$2(err)
|
|
66784
67953
|
}
|
|
66785
67954
|
});
|
|
66786
67955
|
return "threw";
|
|
@@ -66820,7 +67989,7 @@ async function readCameraIds(deps) {
|
|
|
66820
67989
|
lastError = err;
|
|
66821
67990
|
}
|
|
66822
67991
|
if (Date.now() >= deadline) {
|
|
66823
|
-
deps.logger.warn("zones mirror boot hydration SKIPPED — camera list never became readable; mirror-only consumers keep whatever the last write left", { meta: { error: lastError === null ? "ctx.api unavailable" : errMsg$
|
|
67992
|
+
deps.logger.warn("zones mirror boot hydration SKIPPED — camera list never became readable; mirror-only consumers keep whatever the last write left", { meta: { error: lastError === null ? "ctx.api unavailable" : errMsg$2(lastError) } });
|
|
66824
67993
|
return null;
|
|
66825
67994
|
}
|
|
66826
67995
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
@@ -67441,6 +68610,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67441
68610
|
readGlobalSettings: () => globalSettings,
|
|
67442
68611
|
onCensus: (entries) => {
|
|
67443
68612
|
motionSliceGate?.noteCensus(entries);
|
|
68613
|
+
deps.onRunnerCensus(entries.flatMap((e) => e.load === null ? [] : [e.load]));
|
|
67444
68614
|
providerSliceGate?.refreshIfStale("runner-census");
|
|
67445
68615
|
}
|
|
67446
68616
|
});
|
|
@@ -67467,7 +68637,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67467
68637
|
} catch (err) {
|
|
67468
68638
|
return {
|
|
67469
68639
|
kind: "unknown",
|
|
67470
|
-
reason: errMsg$
|
|
68640
|
+
reason: errMsg$2(err)
|
|
67471
68641
|
};
|
|
67472
68642
|
}
|
|
67473
68643
|
};
|
|
@@ -67552,14 +68722,14 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67552
68722
|
readGlobalSettings: () => globalSettings
|
|
67553
68723
|
});
|
|
67554
68724
|
await NodeStressMoveLedger.declare(deps.ctx().api.settingsStore).then(() => nodeStress.hydrate()).catch((err) => {
|
|
67555
|
-
deps.ctx().logger.warn("node-stress move history unavailable — this boot starts cold", { meta: { error: errMsg$
|
|
68725
|
+
deps.ctx().logger.warn("node-stress move history unavailable — this boot starts cold", { meta: { error: errMsg$2(err) } });
|
|
67556
68726
|
});
|
|
67557
68727
|
await NodeStressLts.declare(deps.ctx().api.settingsStore).catch((err) => {
|
|
67558
|
-
deps.ctx().logger.warn("node-stress statistics unavailable — no baseline this boot", { meta: { error: errMsg$
|
|
68728
|
+
deps.ctx().logger.warn("node-stress statistics unavailable — no baseline this boot", { meta: { error: errMsg$2(err) } });
|
|
67559
68729
|
});
|
|
67560
68730
|
const nodeStressLtsTimer = setInterval(() => {
|
|
67561
68731
|
nodeStressLts.flushDue().catch((err) => {
|
|
67562
|
-
deps.ctx().logger.warn("node-stress statistics flush failed", { meta: { error: errMsg$
|
|
68732
|
+
deps.ctx().logger.warn("node-stress statistics flush failed", { meta: { error: errMsg$2(err) } });
|
|
67563
68733
|
});
|
|
67564
68734
|
}, 6e4);
|
|
67565
68735
|
nodeStress.start();
|
|
@@ -67709,7 +68879,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67709
68879
|
} catch (err) {
|
|
67710
68880
|
return {
|
|
67711
68881
|
kind: "unreadable",
|
|
67712
|
-
error: errMsg$
|
|
68882
|
+
error: errMsg$2(err)
|
|
67713
68883
|
};
|
|
67714
68884
|
}
|
|
67715
68885
|
};
|
|
@@ -67743,7 +68913,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67743
68913
|
} catch (err) {
|
|
67744
68914
|
return {
|
|
67745
68915
|
kind: "unreadable",
|
|
67746
|
-
error: errMsg$
|
|
68916
|
+
error: errMsg$2(err)
|
|
67747
68917
|
};
|
|
67748
68918
|
}
|
|
67749
68919
|
},
|
|
@@ -67774,7 +68944,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67774
68944
|
} catch (err) {
|
|
67775
68945
|
return {
|
|
67776
68946
|
kind: "unreadable",
|
|
67777
|
-
error: errMsg$
|
|
68947
|
+
error: errMsg$2(err)
|
|
67778
68948
|
};
|
|
67779
68949
|
}
|
|
67780
68950
|
},
|
|
@@ -67787,7 +68957,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67787
68957
|
} catch (err) {
|
|
67788
68958
|
return {
|
|
67789
68959
|
kind: "unreadable",
|
|
67790
|
-
error: errMsg$
|
|
68960
|
+
error: errMsg$2(err)
|
|
67791
68961
|
};
|
|
67792
68962
|
}
|
|
67793
68963
|
},
|
|
@@ -67858,7 +69028,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67858
69028
|
deps.applyRuntimeSettings(globalSettings);
|
|
67859
69029
|
await topology.refreshNodeCapabilities();
|
|
67860
69030
|
} catch (err) {
|
|
67861
|
-
const msg = errMsg$
|
|
69031
|
+
const msg = errMsg$2(err);
|
|
67862
69032
|
deps.ctx().logger.warn("orchestrator settings load failed — using defaults", { meta: { error: msg } });
|
|
67863
69033
|
}
|
|
67864
69034
|
const rawNodeId = deps.ctx().kernel.localNodeId ?? "hub";
|
|
@@ -67899,7 +69069,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67899
69069
|
audio.markNodeOffline(nodeId);
|
|
67900
69070
|
deps.forgetInferenceDeviceProbe(nodeId);
|
|
67901
69071
|
nodeLifecycle.handleNodeDisconnect(nodeId).catch((err) => {
|
|
67902
|
-
const msg = errMsg$
|
|
69072
|
+
const msg = errMsg$2(err);
|
|
67903
69073
|
deps.ctx().logger.error("failover after disconnect failed", {
|
|
67904
69074
|
tags: { nodeId },
|
|
67905
69075
|
meta: { error: msg }
|
|
@@ -67934,7 +69104,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67934
69104
|
deps.subscribeToAudioAnalyzerReadiness(nodeId);
|
|
67935
69105
|
}
|
|
67936
69106
|
nodeLifecycle.handleNodeConnect(nodeId).catch((err) => {
|
|
67937
|
-
const msg = errMsg$
|
|
69107
|
+
const msg = errMsg$2(err);
|
|
67938
69108
|
deps.ctx().logger.error("reconnect handling failed", {
|
|
67939
69109
|
tags: { nodeId },
|
|
67940
69110
|
meta: { error: msg }
|
|
@@ -67953,7 +69123,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67953
69123
|
meta: { capName }
|
|
67954
69124
|
});
|
|
67955
69125
|
nodeLifecycle.handleNodeDisconnect(nodeId).catch((err) => {
|
|
67956
|
-
const msg = errMsg$
|
|
69126
|
+
const msg = errMsg$2(err);
|
|
67957
69127
|
deps.ctx().logger.error("failover after cap-down failed", {
|
|
67958
69128
|
tags: { nodeId },
|
|
67959
69129
|
meta: {
|
|
@@ -68016,7 +69186,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
68016
69186
|
doneFlag: settingsStore.bindingsMigrationDoneState,
|
|
68017
69187
|
logger: deps.ctx().logger
|
|
68018
69188
|
}).catch((err) => {
|
|
68019
|
-
deps.ctxIfReady()?.logger.warn("bindings migration failed — will retry on next boot", { meta: { error: errMsg$
|
|
69189
|
+
deps.ctxIfReady()?.logger.warn("bindings migration failed — will retry on next boot", { meta: { error: errMsg$2(err) } });
|
|
68020
69190
|
});
|
|
68021
69191
|
migrateStepGating({
|
|
68022
69192
|
readCameraSettings: () => settingsStore.readCameraSettingsMap(),
|
|
@@ -68029,7 +69199,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
68029
69199
|
for (let i = 0; deps.ctxIfReady()?.api == null && i < 30; i++) await new Promise((r) => setTimeout(r, 200));
|
|
68030
69200
|
if (deps.ctxIfReady() !== null) await deps.resetRemovedModelPins("boot");
|
|
68031
69201
|
}).catch((err) => {
|
|
68032
|
-
deps.ctxIfReady()?.logger.warn("step-gating / per-node-device migration failed — retry next boot", { meta: { error: errMsg$
|
|
69202
|
+
deps.ctxIfReady()?.logger.warn("step-gating / per-node-device migration failed — retry next boot", { meta: { error: errMsg$2(err) } });
|
|
68033
69203
|
});
|
|
68034
69204
|
const zoneRulesProvider = new ZoneRulesProvider({
|
|
68035
69205
|
logger: deps.ctx().logger.child("zone-rules"),
|
|
@@ -68071,7 +69241,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
68071
69241
|
deviceId,
|
|
68072
69242
|
nodeId: assignment.agentNodeId
|
|
68073
69243
|
},
|
|
68074
|
-
meta: { error: errMsg$
|
|
69244
|
+
meta: { error: errMsg$2(err) }
|
|
68075
69245
|
});
|
|
68076
69246
|
});
|
|
68077
69247
|
}
|
|
@@ -68087,7 +69257,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
68087
69257
|
}],
|
|
68088
69258
|
logger: deps.ctx().logger.child("zones")
|
|
68089
69259
|
}).catch((err) => {
|
|
68090
|
-
deps.ctxIfReady()?.logger.warn("zones mirror boot hydration failed", { meta: { error: errMsg$
|
|
69260
|
+
deps.ctxIfReady()?.logger.warn("zones mirror boot hydration failed", { meta: { error: errMsg$2(err) } });
|
|
68091
69261
|
});
|
|
68092
69262
|
const unsubOrchestratorSubscriptions = wireOrchestratorSubscriptions({
|
|
68093
69263
|
eventBus: deps.ctx().eventBus,
|
|
@@ -68148,10 +69318,10 @@ async function buildOrchestratorControllers(deps) {
|
|
|
68148
69318
|
deviceSettingsSchema: () => deps.deviceSettingsSchema()
|
|
68149
69319
|
});
|
|
68150
69320
|
await detectionWiring.hydrateFeaturesMirror().catch((err) => {
|
|
68151
|
-
deps.ctx().logger.warn("device-features mirror not restored — this boot starts cold", { meta: { error: errMsg$
|
|
69321
|
+
deps.ctx().logger.warn("device-features mirror not restored — this boot starts cold", { meta: { error: errMsg$2(err) } });
|
|
68152
69322
|
});
|
|
68153
69323
|
cameraSensors.start().catch((err) => {
|
|
68154
|
-
deps.ctx().logger.warn("camera sensors: boot refresh failed — waiting for the next lifecycle event", { meta: { error: errMsg$
|
|
69324
|
+
deps.ctx().logger.warn("camera sensors: boot refresh failed — waiting for the next lifecycle event", { meta: { error: errMsg$2(err) } });
|
|
68155
69325
|
});
|
|
68156
69326
|
return {
|
|
68157
69327
|
ledger,
|
|
@@ -68783,6 +69953,633 @@ var pipelineOrchestratorWidgets = [{
|
|
|
68783
69953
|
defaultRows: 4
|
|
68784
69954
|
}];
|
|
68785
69955
|
//#endregion
|
|
69956
|
+
//#region src/best-settings/plan-diff.ts
|
|
69957
|
+
/**
|
|
69958
|
+
* The Best settings writes, as pure transforms of the orchestrator's two
|
|
69959
|
+
* settings blobs (D745). The plan runs them on the current blobs to SHOW the
|
|
69960
|
+
* diff; the apply runs the same functions inside each blob's read-modify-write.
|
|
69961
|
+
*
|
|
69962
|
+
* What changes — and nothing else:
|
|
69963
|
+
* - node level (`agentSettings[node].inferenceDevices[deviceKey].steps`): the
|
|
69964
|
+
* object-detection `modelId` of every recommended device is set; the
|
|
69965
|
+
* `modelId` pin of every other in-scope step is removed (each runs its
|
|
69966
|
+
* step default again); every model-bound threshold (and its binding key) is
|
|
69967
|
+
* removed from every device's step `settings`, so the running model's own
|
|
69968
|
+
* defaults apply (D738–D741). `enabled`, `weight`, `maxSessions`,
|
|
69969
|
+
* `jumpDeviceKey` and every other setting are kept.
|
|
69970
|
+
* - camera level, scoped to the touched nodes: the same `modelId` pins and
|
|
69971
|
+
* model-bound thresholds are removed from `stepOverridesByDevice[node]` and
|
|
69972
|
+
* from the wholesale `pipelineByAgent[node]` snapshot (recursively). A
|
|
69973
|
+
* wholesale step keeps `enabled`, `children`, `jumpDeviceKey` and its other
|
|
69974
|
+
* settings: the attach provisions the node pin over the snapshot
|
|
69975
|
+
* (`applyDeviceProvisioning`: override ?? base), so once the snapshot pin
|
|
69976
|
+
* is gone the node's recommended model runs.
|
|
69977
|
+
* - never: `stepToggles`, `pinnedAgentNodeId`, devices' enablement, pins of
|
|
69978
|
+
* embedding steps (cluster-scoped, changing one forces a re-index).
|
|
69979
|
+
*/
|
|
69980
|
+
/** The root detector step. */
|
|
69981
|
+
var DETECTOR_STEP_ID = "object-detection";
|
|
69982
|
+
/** Steps whose `modelId` pin Best settings never touches. */
|
|
69983
|
+
function isModelPinKept(stepId) {
|
|
69984
|
+
return stepId.endsWith("-embedding");
|
|
69985
|
+
}
|
|
69986
|
+
function boundKeysFor(input, stepId) {
|
|
69987
|
+
return input.boundKeysByStep[stepId] ?? [];
|
|
69988
|
+
}
|
|
69989
|
+
/** The model-bound keys present in `settings` (values or bindings), and the rest. */
|
|
69990
|
+
function stripBound(settings, boundKeys) {
|
|
69991
|
+
if (settings === void 0) return {
|
|
69992
|
+
kept: void 0,
|
|
69993
|
+
removed: []
|
|
69994
|
+
};
|
|
69995
|
+
const kept = { ...settings };
|
|
69996
|
+
const removed = [];
|
|
69997
|
+
for (const key of boundKeys) for (const k of [key, modelBindingKey(key)]) if (k in kept) {
|
|
69998
|
+
delete kept[k];
|
|
69999
|
+
removed.push(k);
|
|
70000
|
+
}
|
|
70001
|
+
return {
|
|
70002
|
+
kept,
|
|
70003
|
+
removed: removed.toSorted()
|
|
70004
|
+
};
|
|
70005
|
+
}
|
|
70006
|
+
function pinInScope(stepId) {
|
|
70007
|
+
return !isModelPinKept(stepId);
|
|
70008
|
+
}
|
|
70009
|
+
/** Rebuild one stored step without the in-scope pin and bound keys. */
|
|
70010
|
+
function scrubStep(stepId, step, boundKeys, targetModel) {
|
|
70011
|
+
const before = step.modelId ?? null;
|
|
70012
|
+
let modelId = step.modelId;
|
|
70013
|
+
if (stepId === "object-detection") {
|
|
70014
|
+
if (targetModel !== void 0) modelId = targetModel;
|
|
70015
|
+
} else if (pinInScope(stepId)) modelId = void 0;
|
|
70016
|
+
const { kept, removed } = stripBound(step.settings, boundKeys);
|
|
70017
|
+
const after = modelId ?? null;
|
|
70018
|
+
const next = {
|
|
70019
|
+
...modelId !== void 0 ? { modelId } : {},
|
|
70020
|
+
...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {},
|
|
70021
|
+
...step.jumpDeviceKey !== void 0 ? { jumpDeviceKey: step.jumpDeviceKey } : {}
|
|
70022
|
+
};
|
|
70023
|
+
return {
|
|
70024
|
+
next: Object.keys(next).length > 0 ? next : null,
|
|
70025
|
+
pin: before !== after ? {
|
|
70026
|
+
stepId,
|
|
70027
|
+
before,
|
|
70028
|
+
after
|
|
70029
|
+
} : null,
|
|
70030
|
+
cleared: removed
|
|
70031
|
+
};
|
|
70032
|
+
}
|
|
70033
|
+
/** The node-level transform. Returns the next blob and every change it made. */
|
|
70034
|
+
function applyNodeBestSettings(blob, input) {
|
|
70035
|
+
const next = { ...blob };
|
|
70036
|
+
const changes = [];
|
|
70037
|
+
for (const nodeId of [...input.touchedNodes].toSorted()) {
|
|
70038
|
+
const entry = blob[nodeId];
|
|
70039
|
+
const targets = new Map((input.targets.get(nodeId) ?? []).map((t) => [t.deviceKey, t]));
|
|
70040
|
+
const stored = entry?.inferenceDevices ?? {};
|
|
70041
|
+
const deviceKeys = [...new Set([...Object.keys(stored), ...targets.keys()])].toSorted();
|
|
70042
|
+
const nextDevices = {};
|
|
70043
|
+
for (const deviceKey of deviceKeys) {
|
|
70044
|
+
const device = stored[deviceKey];
|
|
70045
|
+
const target = targets.get(deviceKey);
|
|
70046
|
+
const steps = { ...device?.steps };
|
|
70047
|
+
if (target !== void 0 && steps["object-detection"] === void 0) steps[DETECTOR_STEP_ID] = {};
|
|
70048
|
+
const nextSteps = {};
|
|
70049
|
+
const pins = [];
|
|
70050
|
+
const thresholds = [];
|
|
70051
|
+
for (const stepId of Object.keys(steps).toSorted()) {
|
|
70052
|
+
const step = steps[stepId];
|
|
70053
|
+
if (step === void 0) continue;
|
|
70054
|
+
const scrubbed = scrubStep(stepId, step, boundKeysFor(input, stepId), target?.modelId);
|
|
70055
|
+
if (scrubbed.next !== null) nextSteps[stepId] = scrubbed.next;
|
|
70056
|
+
if (scrubbed.pin !== null) pins.push(scrubbed.pin);
|
|
70057
|
+
if (scrubbed.cleared.length > 0) thresholds.push({
|
|
70058
|
+
stepId,
|
|
70059
|
+
keys: scrubbed.cleared
|
|
70060
|
+
});
|
|
70061
|
+
}
|
|
70062
|
+
const madeExplicit = device === void 0;
|
|
70063
|
+
nextDevices[deviceKey] = {
|
|
70064
|
+
...device !== void 0 ? (({ steps: _steps, ...rest }) => rest)(device) : { enabled: target?.effectiveEnabled ?? false },
|
|
70065
|
+
...Object.keys(nextSteps).length > 0 ? { steps: nextSteps } : {}
|
|
70066
|
+
};
|
|
70067
|
+
if (pins.length > 0 || thresholds.length > 0) changes.push({
|
|
70068
|
+
nodeId,
|
|
70069
|
+
deviceKey,
|
|
70070
|
+
madeExplicit,
|
|
70071
|
+
modelPins: pins,
|
|
70072
|
+
thresholds
|
|
70073
|
+
});
|
|
70074
|
+
}
|
|
70075
|
+
if (entry === void 0 && deviceKeys.length === 0) continue;
|
|
70076
|
+
next[nodeId] = {
|
|
70077
|
+
...entry ?? { maxCameras: null },
|
|
70078
|
+
...deviceKeys.length > 0 ? { inferenceDevices: nextDevices } : {}
|
|
70079
|
+
};
|
|
70080
|
+
}
|
|
70081
|
+
return {
|
|
70082
|
+
next,
|
|
70083
|
+
changes
|
|
70084
|
+
};
|
|
70085
|
+
}
|
|
70086
|
+
/** Scrub a wholesale snapshot tree; collects one clear per changed step. */
|
|
70087
|
+
function scrubTree(steps, input, onClear) {
|
|
70088
|
+
return steps.map((step) => {
|
|
70089
|
+
const children = step.children ? scrubTree(step.children, input, onClear) : void 0;
|
|
70090
|
+
const inScope = step.addonId === "object-detection" || pinInScope(step.addonId);
|
|
70091
|
+
const { kept, removed } = stripBound(step.settings, boundKeysFor(input, step.addonId));
|
|
70092
|
+
const dropPin = inScope && step.modelId !== void 0;
|
|
70093
|
+
if (dropPin || removed.length > 0) onClear(step.addonId, dropPin ? step.modelId ?? null : null, removed);
|
|
70094
|
+
const { modelId: _modelId, settings: _settings, children: _children, ...rest } = step;
|
|
70095
|
+
return {
|
|
70096
|
+
...rest,
|
|
70097
|
+
...!dropPin && step.modelId !== void 0 ? { modelId: step.modelId } : {},
|
|
70098
|
+
...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {},
|
|
70099
|
+
...children !== void 0 ? { children } : {}
|
|
70100
|
+
};
|
|
70101
|
+
});
|
|
70102
|
+
}
|
|
70103
|
+
/** The camera-level transform. Returns the next blob and every override it removed. */
|
|
70104
|
+
function applyCameraBestSettings(blob, input) {
|
|
70105
|
+
const next = {};
|
|
70106
|
+
const clears = [];
|
|
70107
|
+
for (const key of Object.keys(blob).toSorted((a, b) => Number(a) - Number(b))) {
|
|
70108
|
+
const camera = blob[key];
|
|
70109
|
+
if (camera === void 0) continue;
|
|
70110
|
+
const deviceId = Number(key);
|
|
70111
|
+
let nextCamera = camera;
|
|
70112
|
+
const byNode = camera.stepOverridesByDevice;
|
|
70113
|
+
if (byNode !== void 0) {
|
|
70114
|
+
const nextByNode = {};
|
|
70115
|
+
for (const [nodeId, byDevice] of Object.entries(byNode)) {
|
|
70116
|
+
if (!input.touchedNodes.has(nodeId)) {
|
|
70117
|
+
nextByNode[nodeId] = byDevice;
|
|
70118
|
+
continue;
|
|
70119
|
+
}
|
|
70120
|
+
const nextByDevice = {};
|
|
70121
|
+
for (const [deviceKey, byStep] of Object.entries(byDevice)) {
|
|
70122
|
+
const nextByStep = {};
|
|
70123
|
+
for (const [stepId, patch] of Object.entries(byStep)) {
|
|
70124
|
+
const inScope = stepId === "object-detection" || pinInScope(stepId);
|
|
70125
|
+
const { kept, removed } = stripBound(patch.settings, boundKeysFor(input, stepId));
|
|
70126
|
+
const dropPin = inScope && patch.modelId !== void 0;
|
|
70127
|
+
if (dropPin || removed.length > 0) clears.push({
|
|
70128
|
+
deviceId,
|
|
70129
|
+
source: "step-override",
|
|
70130
|
+
nodeId,
|
|
70131
|
+
deviceKey,
|
|
70132
|
+
stepId,
|
|
70133
|
+
modelIdBefore: dropPin ? patch.modelId ?? null : null,
|
|
70134
|
+
keys: removed
|
|
70135
|
+
});
|
|
70136
|
+
const rebuilt = {
|
|
70137
|
+
...!dropPin && patch.modelId !== void 0 ? { modelId: patch.modelId } : {},
|
|
70138
|
+
...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {}
|
|
70139
|
+
};
|
|
70140
|
+
if (Object.keys(rebuilt).length > 0) nextByStep[stepId] = rebuilt;
|
|
70141
|
+
}
|
|
70142
|
+
if (Object.keys(nextByStep).length > 0) nextByDevice[deviceKey] = nextByStep;
|
|
70143
|
+
}
|
|
70144
|
+
if (Object.keys(nextByDevice).length > 0) nextByNode[nodeId] = nextByDevice;
|
|
70145
|
+
}
|
|
70146
|
+
const { stepOverridesByDevice: _drop, ...rest } = nextCamera;
|
|
70147
|
+
nextCamera = {
|
|
70148
|
+
...rest,
|
|
70149
|
+
...Object.keys(nextByNode).length > 0 ? { stepOverridesByDevice: nextByNode } : {}
|
|
70150
|
+
};
|
|
70151
|
+
}
|
|
70152
|
+
const wholesale = camera.pipelineByAgent;
|
|
70153
|
+
if (wholesale !== void 0) {
|
|
70154
|
+
const nextWholesale = {};
|
|
70155
|
+
for (const [nodeId, snapshot] of Object.entries(wholesale)) {
|
|
70156
|
+
if (!input.touchedNodes.has(nodeId)) {
|
|
70157
|
+
nextWholesale[nodeId] = snapshot;
|
|
70158
|
+
continue;
|
|
70159
|
+
}
|
|
70160
|
+
nextWholesale[nodeId] = {
|
|
70161
|
+
...snapshot,
|
|
70162
|
+
steps: scrubTree(snapshot.steps, input, (stepId, modelIdBefore, keys) => {
|
|
70163
|
+
clears.push({
|
|
70164
|
+
deviceId,
|
|
70165
|
+
source: "wholesale",
|
|
70166
|
+
nodeId,
|
|
70167
|
+
deviceKey: null,
|
|
70168
|
+
stepId,
|
|
70169
|
+
modelIdBefore,
|
|
70170
|
+
keys
|
|
70171
|
+
});
|
|
70172
|
+
})
|
|
70173
|
+
};
|
|
70174
|
+
}
|
|
70175
|
+
nextCamera = {
|
|
70176
|
+
...nextCamera,
|
|
70177
|
+
pipelineByAgent: nextWholesale
|
|
70178
|
+
};
|
|
70179
|
+
}
|
|
70180
|
+
next[key] = nextCamera;
|
|
70181
|
+
}
|
|
70182
|
+
return {
|
|
70183
|
+
next,
|
|
70184
|
+
clears
|
|
70185
|
+
};
|
|
70186
|
+
}
|
|
70187
|
+
//#endregion
|
|
70188
|
+
//#region src/best-settings/best-settings-service.ts
|
|
70189
|
+
/**
|
|
70190
|
+
* Best settings (D745): gather the inputs, compute the plan, and apply exactly
|
|
70191
|
+
* the plan the operator confirmed.
|
|
70192
|
+
*
|
|
70193
|
+
* The plan is the recommender (`@camstack/types` best-settings) over every
|
|
70194
|
+
* runner node, plus the diff of the writes (`plan-diff.ts`) and the
|
|
70195
|
+
* per-camera detector thresholds the hub's executor would clear. Its
|
|
70196
|
+
* `planHash` names everything the apply writes; the apply recomputes the plan
|
|
70197
|
+
* and refuses a different hash, so a configuration change between the two is
|
|
70198
|
+
* shown to the operator again instead of being written over.
|
|
70199
|
+
*
|
|
70200
|
+
* Apply order, each phase reported:
|
|
70201
|
+
* 1. download every newly picked detector on its node FIRST — a node never
|
|
70202
|
+
* switches to a model it does not have (an N100 compile takes 5–25 s, a
|
|
70203
|
+
* Core ML package 40 MB); any failure stops the apply before a write;
|
|
70204
|
+
* 2. camera overrides (one camera-blob read-modify-write);
|
|
70205
|
+
* 3. per-camera detector thresholds (the hub executor's store);
|
|
70206
|
+
* 4. node model pins (one agent-blob read-modify-write);
|
|
70207
|
+
* 5. re-dispatch the cameras of every touched node.
|
|
70208
|
+
* A failure after phase 2 leaves "overrides cleared, previous node model",
|
|
70209
|
+
* which is a valid configuration (the defaults run); re-running converges.
|
|
70210
|
+
*/
|
|
70211
|
+
function errMsg(err) {
|
|
70212
|
+
return err instanceof Error ? err.message : String(err);
|
|
70213
|
+
}
|
|
70214
|
+
/** Canonical JSON (sorted keys) — the plan hash must not depend on key order. */
|
|
70215
|
+
function canonical(value) {
|
|
70216
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
70217
|
+
if (typeof value === "object" && value !== null) return `{${Object.entries(value).filter(([, v]) => v !== void 0).toSorted(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
|
|
70218
|
+
return JSON.stringify(value);
|
|
70219
|
+
}
|
|
70220
|
+
function formatsByModelOf(catalog) {
|
|
70221
|
+
const out = {};
|
|
70222
|
+
for (const slot of catalog?.slots ?? []) for (const addon of slot.addons) {
|
|
70223
|
+
if (addon.id !== "object-detection") continue;
|
|
70224
|
+
for (const model of addon.models) out[model.id] = MODEL_FORMATS.filter((f) => f in model.formats);
|
|
70225
|
+
}
|
|
70226
|
+
return out;
|
|
70227
|
+
}
|
|
70228
|
+
function boundKeysOf(catalogs) {
|
|
70229
|
+
const byStep = /* @__PURE__ */ new Map();
|
|
70230
|
+
for (const catalog of catalogs) for (const slot of catalog?.slots ?? []) for (const addon of slot.addons) {
|
|
70231
|
+
const keys = collectModelBoundKeys(addon.configSchema ?? []);
|
|
70232
|
+
if (keys.length === 0) continue;
|
|
70233
|
+
const set = byStep.get(addon.id) ?? /* @__PURE__ */ new Set();
|
|
70234
|
+
for (const k of keys) set.add(k);
|
|
70235
|
+
byStep.set(addon.id, set);
|
|
70236
|
+
}
|
|
70237
|
+
return Object.fromEntries([...byStep].map(([step, set]) => [step, [...set].toSorted()]));
|
|
70238
|
+
}
|
|
70239
|
+
var BestSettingsService = class {
|
|
70240
|
+
deps;
|
|
70241
|
+
applying = false;
|
|
70242
|
+
constructor(deps) {
|
|
70243
|
+
this.deps = deps;
|
|
70244
|
+
}
|
|
70245
|
+
async plan(input) {
|
|
70246
|
+
return (await this.computePlan(input.headroom)).plan;
|
|
70247
|
+
}
|
|
70248
|
+
async computePlan(headroom) {
|
|
70249
|
+
const nodeIds = [...new Set(["hub", ...this.deps.runnerNodeIds()])].toSorted();
|
|
70250
|
+
const gathered = await Promise.all(nodeIds.map(async (nodeId) => {
|
|
70251
|
+
const [devices, hardware, catalog] = await Promise.all([
|
|
70252
|
+
this.deps.readNodeDevices(nodeId),
|
|
70253
|
+
this.deps.readHardware(nodeId).catch(() => null),
|
|
70254
|
+
this.deps.readCatalog(nodeId).catch(() => null)
|
|
70255
|
+
]);
|
|
70256
|
+
return {
|
|
70257
|
+
nodeId,
|
|
70258
|
+
devices,
|
|
70259
|
+
hardware,
|
|
70260
|
+
catalog
|
|
70261
|
+
};
|
|
70262
|
+
}));
|
|
70263
|
+
const nodes = gathered.map((g) => ({
|
|
70264
|
+
nodeId: g.nodeId,
|
|
70265
|
+
reachable: g.devices.reachable && g.catalog !== null,
|
|
70266
|
+
hardware: g.hardware,
|
|
70267
|
+
devices: g.devices.devices.map((d) => ({
|
|
70268
|
+
key: d.key,
|
|
70269
|
+
format: d.format,
|
|
70270
|
+
enabled: d.enabled,
|
|
70271
|
+
available: d.available
|
|
70272
|
+
})),
|
|
70273
|
+
formatsByModel: formatsByModelOf(g.catalog)
|
|
70274
|
+
}));
|
|
70275
|
+
const eligible = gathered.flatMap((g) => g.devices.reachable ? g.devices.devices.filter((d) => d.enabled && d.available).map((d) => ({
|
|
70276
|
+
nodeId: g.nodeId,
|
|
70277
|
+
deviceKey: d.key,
|
|
70278
|
+
weight: d.weight
|
|
70279
|
+
})) : []);
|
|
70280
|
+
const demand = estimateDeviceDemand({
|
|
70281
|
+
cameras: await this.deps.cameraLoads(),
|
|
70282
|
+
eligible,
|
|
70283
|
+
peaks: this.deps.peaks()
|
|
70284
|
+
});
|
|
70285
|
+
const recommendations = nodes.map((node) => recommendNode({
|
|
70286
|
+
node,
|
|
70287
|
+
demand,
|
|
70288
|
+
headroom
|
|
70289
|
+
}));
|
|
70290
|
+
const touchedNodes = new Set(recommendations.filter((r) => !r.skipped).map((r) => r.nodeId));
|
|
70291
|
+
const targets = /* @__PURE__ */ new Map();
|
|
70292
|
+
const formats = /* @__PURE__ */ new Map();
|
|
70293
|
+
for (const rec of recommendations) {
|
|
70294
|
+
if (rec.skipped) continue;
|
|
70295
|
+
const node = nodes.find((n) => n.nodeId === rec.nodeId);
|
|
70296
|
+
const list = [];
|
|
70297
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
70298
|
+
for (const d of rec.devices) {
|
|
70299
|
+
if (d.chosen === null) continue;
|
|
70300
|
+
list.push({
|
|
70301
|
+
deviceKey: d.deviceKey,
|
|
70302
|
+
modelId: d.chosen.modelId,
|
|
70303
|
+
effectiveEnabled: d.enabled
|
|
70304
|
+
});
|
|
70305
|
+
const format = node?.devices.find((x) => x.key === d.deviceKey)?.format;
|
|
70306
|
+
if (format !== void 0) byKey.set(d.deviceKey, format);
|
|
70307
|
+
}
|
|
70308
|
+
targets.set(rec.nodeId, list);
|
|
70309
|
+
formats.set(rec.nodeId, byKey);
|
|
70310
|
+
}
|
|
70311
|
+
const diffInput = {
|
|
70312
|
+
touchedNodes,
|
|
70313
|
+
targets,
|
|
70314
|
+
boundKeysByStep: boundKeysOf(gathered.map((g) => g.catalog))
|
|
70315
|
+
};
|
|
70316
|
+
const nodeChanges = applyNodeBestSettings(await this.deps.readAgentSettings(), diffInput).changes;
|
|
70317
|
+
const cameraClears = applyCameraBestSettings(await this.deps.readCameraSettings(), diffInput).clears;
|
|
70318
|
+
const warnings = [];
|
|
70319
|
+
let detectorStoreClears = [];
|
|
70320
|
+
if (touchedNodes.size > 0) try {
|
|
70321
|
+
const answer = await this.deps.detectorStore({ dryRun: true });
|
|
70322
|
+
detectorStoreClears = answer.entries.map((e) => ({
|
|
70323
|
+
deviceId: e.deviceId,
|
|
70324
|
+
keys: [...e.keys]
|
|
70325
|
+
}));
|
|
70326
|
+
if (answer.refused.length > 0) warnings.push({
|
|
70327
|
+
kind: "detector-store-unreadable",
|
|
70328
|
+
error: `${answer.refused.length} camera store(s) unreadable`
|
|
70329
|
+
});
|
|
70330
|
+
} catch (err) {
|
|
70331
|
+
warnings.push({
|
|
70332
|
+
kind: "detector-store-unreadable",
|
|
70333
|
+
error: errMsg(err)
|
|
70334
|
+
});
|
|
70335
|
+
}
|
|
70336
|
+
const skippedNodes = recommendations.filter((r) => r.skipped).map((r) => r.nodeId);
|
|
70337
|
+
if (detectorStoreClears.length > 0 && skippedNodes.length > 0) warnings.push({
|
|
70338
|
+
kind: "detector-store-cluster-wide",
|
|
70339
|
+
skippedNodes
|
|
70340
|
+
});
|
|
70341
|
+
for (const rec of recommendations) for (const d of rec.devices) if (d.accelerator === "coral" && d.enabled) warnings.push({
|
|
70342
|
+
kind: "coral-enabled",
|
|
70343
|
+
nodeId: rec.nodeId,
|
|
70344
|
+
deviceKey: d.deviceKey
|
|
70345
|
+
});
|
|
70346
|
+
return {
|
|
70347
|
+
plan: {
|
|
70348
|
+
planHash: createHash("sha256").update(canonical({
|
|
70349
|
+
version: 1,
|
|
70350
|
+
headroom,
|
|
70351
|
+
picks: recommendations.map((r) => ({
|
|
70352
|
+
nodeId: r.nodeId,
|
|
70353
|
+
skipped: r.skipped,
|
|
70354
|
+
devices: r.devices.map((d) => [d.deviceKey, d.chosen?.modelId ?? null])
|
|
70355
|
+
})),
|
|
70356
|
+
nodeChanges,
|
|
70357
|
+
cameraClears,
|
|
70358
|
+
detectorStoreClears
|
|
70359
|
+
})).digest("hex"),
|
|
70360
|
+
family: "yolov9",
|
|
70361
|
+
headroom,
|
|
70362
|
+
generatedAt: this.deps.now(),
|
|
70363
|
+
nodes: recommendations,
|
|
70364
|
+
nodeChanges: [...nodeChanges],
|
|
70365
|
+
cameraClears: [...cameraClears],
|
|
70366
|
+
detectorStoreClears,
|
|
70367
|
+
warnings
|
|
70368
|
+
},
|
|
70369
|
+
diffInput,
|
|
70370
|
+
formats
|
|
70371
|
+
};
|
|
70372
|
+
}
|
|
70373
|
+
async apply(input) {
|
|
70374
|
+
if (this.applying) return {
|
|
70375
|
+
kind: "refused",
|
|
70376
|
+
reason: "apply-in-progress"
|
|
70377
|
+
};
|
|
70378
|
+
this.applying = true;
|
|
70379
|
+
try {
|
|
70380
|
+
return await this.applyLocked(input);
|
|
70381
|
+
} finally {
|
|
70382
|
+
this.applying = false;
|
|
70383
|
+
}
|
|
70384
|
+
}
|
|
70385
|
+
async applyLocked(input) {
|
|
70386
|
+
const { plan, diffInput, formats } = await this.computePlan(input.headroom);
|
|
70387
|
+
if (plan.planHash !== input.planHash) {
|
|
70388
|
+
this.deps.logger.warn("best settings: stale plan refused", { meta: {
|
|
70389
|
+
confirmed: input.planHash,
|
|
70390
|
+
current: plan.planHash
|
|
70391
|
+
} });
|
|
70392
|
+
return {
|
|
70393
|
+
kind: "stale",
|
|
70394
|
+
plan
|
|
70395
|
+
};
|
|
70396
|
+
}
|
|
70397
|
+
const phases = [];
|
|
70398
|
+
const downloads = plan.nodeChanges.flatMap((c) => c.modelPins.filter((p) => p.stepId === "object-detection" && p.after !== null && p.after !== p.before).map((p) => ({
|
|
70399
|
+
nodeId: c.nodeId,
|
|
70400
|
+
modelId: p.after ?? "",
|
|
70401
|
+
format: formats.get(c.nodeId)?.get(c.deviceKey)
|
|
70402
|
+
})));
|
|
70403
|
+
const unique = new Map(downloads.map((d) => [`${d.nodeId}|${d.modelId}|${d.format}`, d]));
|
|
70404
|
+
try {
|
|
70405
|
+
for (const d of unique.values()) {
|
|
70406
|
+
if (d.format === void 0) continue;
|
|
70407
|
+
await this.deps.downloadModel(d.nodeId, d.modelId, d.format);
|
|
70408
|
+
}
|
|
70409
|
+
phases.push({
|
|
70410
|
+
phase: "download",
|
|
70411
|
+
ok: true,
|
|
70412
|
+
detail: `${unique.size} model(s) present on their nodes`
|
|
70413
|
+
});
|
|
70414
|
+
} catch (err) {
|
|
70415
|
+
phases.push({
|
|
70416
|
+
phase: "download",
|
|
70417
|
+
ok: false,
|
|
70418
|
+
detail: errMsg(err)
|
|
70419
|
+
});
|
|
70420
|
+
this.deps.logger.warn("best settings: a model download failed — nothing was written", { meta: { error: errMsg(err) } });
|
|
70421
|
+
return {
|
|
70422
|
+
kind: "aborted",
|
|
70423
|
+
phases
|
|
70424
|
+
};
|
|
70425
|
+
}
|
|
70426
|
+
try {
|
|
70427
|
+
await this.deps.rewriteCameraSettings((prev) => applyCameraBestSettings(prev, diffInput).next);
|
|
70428
|
+
for (const c of plan.cameraClears) this.deps.logger.info("best settings: camera override cleared", {
|
|
70429
|
+
tags: {
|
|
70430
|
+
deviceId: c.deviceId,
|
|
70431
|
+
nodeId: c.nodeId
|
|
70432
|
+
},
|
|
70433
|
+
meta: {
|
|
70434
|
+
deviceId: c.deviceId,
|
|
70435
|
+
source: c.source,
|
|
70436
|
+
deviceKey: c.deviceKey,
|
|
70437
|
+
stepId: c.stepId,
|
|
70438
|
+
modelIdBefore: c.modelIdBefore,
|
|
70439
|
+
keys: c.keys
|
|
70440
|
+
}
|
|
70441
|
+
});
|
|
70442
|
+
phases.push({
|
|
70443
|
+
phase: "camera-overrides",
|
|
70444
|
+
ok: true,
|
|
70445
|
+
detail: `${plan.cameraClears.length} cleared`
|
|
70446
|
+
});
|
|
70447
|
+
} catch (err) {
|
|
70448
|
+
phases.push({
|
|
70449
|
+
phase: "camera-overrides",
|
|
70450
|
+
ok: false,
|
|
70451
|
+
detail: errMsg(err)
|
|
70452
|
+
});
|
|
70453
|
+
}
|
|
70454
|
+
if (plan.detectorStoreClears.length > 0) try {
|
|
70455
|
+
const answer = await this.deps.detectorStore({
|
|
70456
|
+
dryRun: false,
|
|
70457
|
+
deviceIds: plan.detectorStoreClears.map((c) => c.deviceId)
|
|
70458
|
+
});
|
|
70459
|
+
phases.push({
|
|
70460
|
+
phase: "detector-thresholds",
|
|
70461
|
+
ok: answer.refused.length === 0,
|
|
70462
|
+
detail: `${answer.entries.length} cleared, ${answer.refused.length} refused`
|
|
70463
|
+
});
|
|
70464
|
+
} catch (err) {
|
|
70465
|
+
phases.push({
|
|
70466
|
+
phase: "detector-thresholds",
|
|
70467
|
+
ok: false,
|
|
70468
|
+
detail: errMsg(err)
|
|
70469
|
+
});
|
|
70470
|
+
}
|
|
70471
|
+
try {
|
|
70472
|
+
await this.deps.rewriteAgentSettings((prev) => applyNodeBestSettings(prev, diffInput).next);
|
|
70473
|
+
for (const c of plan.nodeChanges) for (const pin of c.modelPins) this.deps.logger.info("best settings: node model set", {
|
|
70474
|
+
tags: { nodeId: c.nodeId },
|
|
70475
|
+
meta: {
|
|
70476
|
+
nodeId: c.nodeId,
|
|
70477
|
+
deviceKey: c.deviceKey,
|
|
70478
|
+
stepId: pin.stepId,
|
|
70479
|
+
from: pin.before,
|
|
70480
|
+
to: pin.after
|
|
70481
|
+
}
|
|
70482
|
+
});
|
|
70483
|
+
phases.push({
|
|
70484
|
+
phase: "node-models",
|
|
70485
|
+
ok: true,
|
|
70486
|
+
detail: `${plan.nodeChanges.length} device(s) updated`
|
|
70487
|
+
});
|
|
70488
|
+
} catch (err) {
|
|
70489
|
+
phases.push({
|
|
70490
|
+
phase: "node-models",
|
|
70491
|
+
ok: false,
|
|
70492
|
+
detail: errMsg(err)
|
|
70493
|
+
});
|
|
70494
|
+
}
|
|
70495
|
+
this.deps.invalidateCatalog();
|
|
70496
|
+
const failed = [];
|
|
70497
|
+
for (const nodeId of [...diffInput.touchedNodes].toSorted()) try {
|
|
70498
|
+
await this.deps.redispatch(nodeId);
|
|
70499
|
+
} catch (err) {
|
|
70500
|
+
failed.push(`${nodeId}: ${errMsg(err)}`);
|
|
70501
|
+
}
|
|
70502
|
+
phases.push({
|
|
70503
|
+
phase: "redispatch",
|
|
70504
|
+
ok: failed.length === 0,
|
|
70505
|
+
detail: failed.length === 0 ? `${diffInput.touchedNodes.size} node(s)` : failed.join("; ")
|
|
70506
|
+
});
|
|
70507
|
+
for (const rec of plan.nodes.filter((r) => r.skipped)) this.deps.logger.info("best settings: node skipped", {
|
|
70508
|
+
tags: { nodeId: rec.nodeId },
|
|
70509
|
+
meta: {
|
|
70510
|
+
nodeId: rec.nodeId,
|
|
70511
|
+
reasons: rec.reasons
|
|
70512
|
+
}
|
|
70513
|
+
});
|
|
70514
|
+
this.deps.logger.info("best settings: applied", { meta: {
|
|
70515
|
+
planHash: plan.planHash,
|
|
70516
|
+
nodeChanges: plan.nodeChanges.length,
|
|
70517
|
+
cameraClears: plan.cameraClears.length,
|
|
70518
|
+
detectorStoreClears: plan.detectorStoreClears.length,
|
|
70519
|
+
phases
|
|
70520
|
+
} });
|
|
70521
|
+
return {
|
|
70522
|
+
kind: "applied",
|
|
70523
|
+
planHash: plan.planHash,
|
|
70524
|
+
phases
|
|
70525
|
+
};
|
|
70526
|
+
}
|
|
70527
|
+
};
|
|
70528
|
+
//#endregion
|
|
70529
|
+
//#region src/best-settings/device-peak-tracker.ts
|
|
70530
|
+
var BUCKET_MS = 3600 * 1e3;
|
|
70531
|
+
var DevicePeakTracker = class DevicePeakTracker {
|
|
70532
|
+
now;
|
|
70533
|
+
series = /* @__PURE__ */ new Map();
|
|
70534
|
+
constructor(now = () => Date.now()) {
|
|
70535
|
+
this.now = now;
|
|
70536
|
+
}
|
|
70537
|
+
static key(nodeId, deviceKey) {
|
|
70538
|
+
return `${nodeId}\u0000${deviceKey}`;
|
|
70539
|
+
}
|
|
70540
|
+
/** Fold one runner census in. A runner that did not answer is simply absent. */
|
|
70541
|
+
noteCensus(loads) {
|
|
70542
|
+
const now = this.now();
|
|
70543
|
+
const bucket = now - now % BUCKET_MS;
|
|
70544
|
+
for (const load of loads) {
|
|
70545
|
+
const rows = load.devices.length > 0 ? load.devices : [{
|
|
70546
|
+
deviceKey: "",
|
|
70547
|
+
attachedCameras: load.attachedCameras
|
|
70548
|
+
}];
|
|
70549
|
+
for (const device of rows) {
|
|
70550
|
+
const key = DevicePeakTracker.key(load.nodeId, device.deviceKey);
|
|
70551
|
+
let s = this.series.get(key);
|
|
70552
|
+
if (s === void 0) {
|
|
70553
|
+
s = {
|
|
70554
|
+
firstSeenAt: now,
|
|
70555
|
+
buckets: /* @__PURE__ */ new Map()
|
|
70556
|
+
};
|
|
70557
|
+
this.series.set(key, s);
|
|
70558
|
+
}
|
|
70559
|
+
s.buckets.set(bucket, Math.max(s.buckets.get(bucket) ?? 0, device.attachedCameras));
|
|
70560
|
+
for (const start of s.buckets.keys()) if (start + BUCKET_MS <= now - 864e5) s.buckets.delete(start);
|
|
70561
|
+
}
|
|
70562
|
+
}
|
|
70563
|
+
}
|
|
70564
|
+
/** Every observed device's peak over the window and how long it has been watched. */
|
|
70565
|
+
peaks() {
|
|
70566
|
+
const now = this.now();
|
|
70567
|
+
const out = [];
|
|
70568
|
+
for (const [key, s] of this.series) {
|
|
70569
|
+
const [nodeId = "", deviceKey = ""] = key.split("\0");
|
|
70570
|
+
let peak = 0;
|
|
70571
|
+
for (const [start, value] of s.buckets) if (start + BUCKET_MS > now - 864e5) peak = Math.max(peak, value);
|
|
70572
|
+
out.push({
|
|
70573
|
+
nodeId,
|
|
70574
|
+
deviceKey,
|
|
70575
|
+
peakSessions: peak,
|
|
70576
|
+
observedForMs: now - s.firstSeenAt
|
|
70577
|
+
});
|
|
70578
|
+
}
|
|
70579
|
+
return out.toSorted((a, b) => a.nodeId === b.nodeId ? a.deviceKey.localeCompare(b.deviceKey) : a.nodeId.localeCompare(b.nodeId));
|
|
70580
|
+
}
|
|
70581
|
+
};
|
|
70582
|
+
//#endregion
|
|
68786
70583
|
//#region src/index.ts
|
|
68787
70584
|
/**
|
|
68788
70585
|
* addon-pipeline-orchestrator — hub-side camera-to-agent load balancer.
|
|
@@ -68850,6 +70647,9 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
68850
70647
|
* not a claim this flag makes.
|
|
68851
70648
|
*/
|
|
68852
70649
|
clusterModelChoiceReady = false;
|
|
70650
|
+
/** The per-device 24 h peak of concurrent sessions, fed by every runner census (D745). */
|
|
70651
|
+
devicePeaks = new DevicePeakTracker();
|
|
70652
|
+
bestSettingsService = null;
|
|
68853
70653
|
/**
|
|
68854
70654
|
* Single owner of the shared placement state (`assignments`/`cameraConfigs`/
|
|
68855
70655
|
* `pendingReasons`/`dispatchingDevices`) — see `placement-ledger.ts` (S6
|
|
@@ -69126,6 +70926,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
69126
70926
|
rebalance: (opts) => this.rebalance(opts),
|
|
69127
70927
|
setCameraPipelineForAgent: (input) => this.setCameraPipelineForAgent(input),
|
|
69128
70928
|
maintenancePaused: () => this.storageMigrationLeaseId !== null,
|
|
70929
|
+
onRunnerCensus: (loads) => this.devicePeaks.noteCensus(loads),
|
|
69129
70930
|
setTopology: (topology) => {
|
|
69130
70931
|
this.topology = topology;
|
|
69131
70932
|
},
|
|
@@ -69267,7 +71068,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
69267
71068
|
for (const deviceId of this.detectionWiring.activeDetectionDeviceIds()) try {
|
|
69268
71069
|
await this.releaseCamera({ deviceId });
|
|
69269
71070
|
} catch (err) {
|
|
69270
|
-
const msg = errMsg$
|
|
71071
|
+
const msg = errMsg$2(err);
|
|
69271
71072
|
this.ctx.logger.debug("releaseCamera failed during shutdown", {
|
|
69272
71073
|
tags: { deviceId },
|
|
69273
71074
|
meta: { error: msg }
|
|
@@ -70062,7 +71863,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70062
71863
|
tags: { nodeId },
|
|
70063
71864
|
meta: {
|
|
70064
71865
|
nodeId,
|
|
70065
|
-
error: errMsg$
|
|
71866
|
+
error: errMsg$2(err)
|
|
70066
71867
|
}
|
|
70067
71868
|
});
|
|
70068
71869
|
return null;
|
|
@@ -70168,7 +71969,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70168
71969
|
tags: { nodeId },
|
|
70169
71970
|
meta: {
|
|
70170
71971
|
nodeId,
|
|
70171
|
-
error: errMsg$
|
|
71972
|
+
error: errMsg$2(err)
|
|
70172
71973
|
}
|
|
70173
71974
|
});
|
|
70174
71975
|
return {
|
|
@@ -70215,7 +72016,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70215
72016
|
} catch (err) {
|
|
70216
72017
|
this.ctx.logger.warn("resetNodePipelineDefaults: executor clearDeviceOverrides failed (node offline?)", {
|
|
70217
72018
|
tags: { nodeId: node },
|
|
70218
|
-
meta: { error: errMsg$
|
|
72019
|
+
meta: { error: errMsg$2(err) }
|
|
70219
72020
|
});
|
|
70220
72021
|
}
|
|
70221
72022
|
this.settingsStore.invalidateCatalog();
|
|
@@ -70226,7 +72027,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70226
72027
|
} catch (err) {
|
|
70227
72028
|
this.ctx.logger.warn("resetNodePipelineDefaults: effective-model probe failed (node offline?)", {
|
|
70228
72029
|
tags: { nodeId: node },
|
|
70229
|
-
meta: { error: errMsg$
|
|
72030
|
+
meta: { error: errMsg$2(err) }
|
|
70230
72031
|
});
|
|
70231
72032
|
}
|
|
70232
72033
|
this.ctx.logger.info("node pipeline reset to hardware-aware defaults", {
|
|
@@ -70242,6 +72043,77 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70242
72043
|
clearedCameraOverrides
|
|
70243
72044
|
};
|
|
70244
72045
|
}
|
|
72046
|
+
bestSettings() {
|
|
72047
|
+
if (this.bestSettingsService !== null) return this.bestSettingsService;
|
|
72048
|
+
const store = this.settingsStore;
|
|
72049
|
+
this.bestSettingsService = new BestSettingsService({
|
|
72050
|
+
logger: this.ctx.logger,
|
|
72051
|
+
runnerNodeIds: () => this.topology.collectRunnerNodeIds(),
|
|
72052
|
+
readNodeDevices: (nodeId) => this.getNodeInferenceDevices({ nodeId }),
|
|
72053
|
+
readHardware: async (nodeId) => {
|
|
72054
|
+
const api = this.ctx.api;
|
|
72055
|
+
if (!api) return null;
|
|
72056
|
+
return nodeId === this.localNodeId ? api.platformProbe.getHardware.query() : api.platformProbe.getHardware.query({ nodeId });
|
|
72057
|
+
},
|
|
72058
|
+
readCatalog: (nodeId) => store.readCatalogForNode(nodeId),
|
|
72059
|
+
readAgentSettings: () => store.readAgentSettingsMap(),
|
|
72060
|
+
readCameraSettings: () => store.readCameraSettingsMap(),
|
|
72061
|
+
rewriteAgentSettings: (transform) => store.rewriteAgentSettingsMap(transform),
|
|
72062
|
+
rewriteCameraSettings: (transform) => store.rewriteCameraSettingsMap(transform),
|
|
72063
|
+
cameraLoads: () => this.bestSettingsCameraLoads(),
|
|
72064
|
+
peaks: () => this.devicePeaks.peaks(),
|
|
72065
|
+
detectorStore: async (input) => {
|
|
72066
|
+
const api = this.ctx.api;
|
|
72067
|
+
if (!api) throw new Error("ctx.api unavailable");
|
|
72068
|
+
return api.pipelineExecutor.clearModelBoundDeviceOverrides.mutate({
|
|
72069
|
+
nodeId: this.localNodeId,
|
|
72070
|
+
dryRun: input.dryRun,
|
|
72071
|
+
...input.deviceIds !== void 0 ? { deviceIds: input.deviceIds } : {}
|
|
72072
|
+
});
|
|
72073
|
+
},
|
|
72074
|
+
downloadModel: async (nodeId, modelId, format) => {
|
|
72075
|
+
const api = this.ctx.api;
|
|
72076
|
+
if (!api) throw new Error("ctx.api unavailable");
|
|
72077
|
+
await api.pipelineExecutor.downloadModel.mutate({
|
|
72078
|
+
addonId: "object-detection",
|
|
72079
|
+
modelId,
|
|
72080
|
+
format
|
|
72081
|
+
}, nodePin(nodeId));
|
|
72082
|
+
},
|
|
72083
|
+
invalidateCatalog: () => store.invalidateCatalog(),
|
|
72084
|
+
redispatch: (nodeId) => this.placement.redispatchCamerasOnAgent(nodeId),
|
|
72085
|
+
now: () => Date.now()
|
|
72086
|
+
});
|
|
72087
|
+
return this.bestSettingsService;
|
|
72088
|
+
}
|
|
72089
|
+
/** Every camera with detection started, as the recommender charges it (D745). */
|
|
72090
|
+
async bestSettingsCameraLoads() {
|
|
72091
|
+
const standing = new Map(this.getPipelineAssignments().map((a) => [a.deviceId, a.agentNodeId]));
|
|
72092
|
+
const cameraMap = await this.settingsStore.readCameraSettingsMap();
|
|
72093
|
+
const loads = [];
|
|
72094
|
+
for (const [deviceId, config] of this.detectionWiring.activeDetections) {
|
|
72095
|
+
const pinnedNode = cameraMap[String(deviceId)]?.pinnedAgentNodeId ?? standing.get(deviceId);
|
|
72096
|
+
const pinnedKey = await this.readPipelineDevicePin(deviceId);
|
|
72097
|
+
loads.push({
|
|
72098
|
+
deviceId,
|
|
72099
|
+
mode: config.detectionMode,
|
|
72100
|
+
fps: config.detectionFps,
|
|
72101
|
+
runsPipelineRoot: (config.detectionSources ?? ["pipeline"]).includes("pipeline"),
|
|
72102
|
+
standingNodeId: config.detectionMode === "always-on" ? standing.get(deviceId) ?? null : null,
|
|
72103
|
+
devicePin: pinnedKey !== null && pinnedNode !== void 0 ? {
|
|
72104
|
+
nodeId: pinnedNode,
|
|
72105
|
+
deviceKey: pinnedKey
|
|
72106
|
+
} : null
|
|
72107
|
+
});
|
|
72108
|
+
}
|
|
72109
|
+
return loads;
|
|
72110
|
+
}
|
|
72111
|
+
async planBestSettings(input) {
|
|
72112
|
+
return this.bestSettings().plan(input);
|
|
72113
|
+
}
|
|
72114
|
+
async applyBestSettings(input) {
|
|
72115
|
+
return this.bestSettings().apply(input);
|
|
72116
|
+
}
|
|
70245
72117
|
async getCameraSettings(input) {
|
|
70246
72118
|
return (await this.settingsStore.readCameraSettingsMap())[String(input.deviceId)] ?? null;
|
|
70247
72119
|
}
|
|
@@ -70498,7 +72370,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70498
72370
|
tags: { deviceId },
|
|
70499
72371
|
meta: {
|
|
70500
72372
|
capName,
|
|
70501
|
-
error: errMsg$
|
|
72373
|
+
error: errMsg$2(err)
|
|
70502
72374
|
}
|
|
70503
72375
|
});
|
|
70504
72376
|
return true;
|