@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.js
CHANGED
|
@@ -8076,7 +8076,7 @@ import { errMsg } from '@camstack/types'
|
|
|
8076
8076
|
* Extract a human-readable message from an unknown error value.
|
|
8077
8077
|
* Replaces the ubiquitous `errMsg(err)` pattern.
|
|
8078
8078
|
*/
|
|
8079
|
-
function errMsg$
|
|
8079
|
+
function errMsg$2(err) {
|
|
8080
8080
|
if (err instanceof Error) return err.message;
|
|
8081
8081
|
if (typeof err === "string") return err;
|
|
8082
8082
|
return String(err);
|
|
@@ -11579,7 +11579,53 @@ var ModelFormatEntrySchema = object({
|
|
|
11579
11579
|
/** Multi-file payload (directory members or sibling files). */
|
|
11580
11580
|
files: array(string()).readonly().optional(),
|
|
11581
11581
|
/** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
|
|
11582
|
-
runtimes: array(_enum(["python"])).readonly().optional()
|
|
11582
|
+
runtimes: array(_enum(["python"])).readonly().optional(),
|
|
11583
|
+
/**
|
|
11584
|
+
* Content revision of the artefact, for one hosted OFF the CamStack mirror.
|
|
11585
|
+
* A format on the mirror leaves it unset: its revision (the sha256 of its
|
|
11586
|
+
* weight file) comes from the downloader's generated table, kept in step
|
|
11587
|
+
* with the mirror by `scripts/check-hosted-model-revisions.ts` (D744). A
|
|
11588
|
+
* node records the revision it downloaded next to the file
|
|
11589
|
+
* (`<file>.revision`); a copy with no record or a different one is stale
|
|
11590
|
+
* unless its weight file hashes to the revision, and a stale copy is
|
|
11591
|
+
* downloaded again, the old one serving until the new one is complete. No
|
|
11592
|
+
* revision at all ⇒ a file on disk is current, whatever it holds (D743).
|
|
11593
|
+
*/
|
|
11594
|
+
revision: string().min(1).optional()
|
|
11595
|
+
});
|
|
11596
|
+
/**
|
|
11597
|
+
* What a node holds for one (model, format) (D746). `current` is a complete
|
|
11598
|
+
* copy at its catalog revision — exactly what `downloaded: true` means.
|
|
11599
|
+
* `stale` is bytes on disk that are not (yet) proven current under D743/D744:
|
|
11600
|
+
* no or a different revision record, or a missing sibling. `missing` is no
|
|
11601
|
+
* copy. `downloaded` alone folds `stale` into "not downloaded", which hid
|
|
11602
|
+
* reclaimable bytes and a copy about to be replaced.
|
|
11603
|
+
*/
|
|
11604
|
+
var ModelOnDiskStateSchema = _enum([
|
|
11605
|
+
"current",
|
|
11606
|
+
"stale",
|
|
11607
|
+
"missing"
|
|
11608
|
+
]);
|
|
11609
|
+
/**
|
|
11610
|
+
* The numeric precision of one format's BUILD (D746) — reported only when the
|
|
11611
|
+
* build states it (file-name token, an INT8 variant, an EdgeTPU compile, a
|
|
11612
|
+
* CamStack Core ML export). Absent means "not stated", never "fp32".
|
|
11613
|
+
*/
|
|
11614
|
+
var ModelFormatPrecisionSchema = _enum([
|
|
11615
|
+
"fp32",
|
|
11616
|
+
"fp16",
|
|
11617
|
+
"int8"
|
|
11618
|
+
]);
|
|
11619
|
+
/**
|
|
11620
|
+
* One format of one model as a node's `pipelineExecutor.getSchema` reports it.
|
|
11621
|
+
* `onDisk` / `precision` are optional for version skew: a runner that predates
|
|
11622
|
+
* them omits them, and a reader falls back to `downloaded`.
|
|
11623
|
+
*/
|
|
11624
|
+
var PipelineModelFormatStatusSchema = object({
|
|
11625
|
+
downloaded: boolean(),
|
|
11626
|
+
sizeMB: number(),
|
|
11627
|
+
onDisk: ModelOnDiskStateSchema.optional(),
|
|
11628
|
+
precision: ModelFormatPrecisionSchema.optional()
|
|
11583
11629
|
});
|
|
11584
11630
|
/**
|
|
11585
11631
|
* Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
|
|
@@ -24325,7 +24371,7 @@ var FrameRefSchema = object({
|
|
|
24325
24371
|
timestamp: number(),
|
|
24326
24372
|
capturedAt: number().optional()
|
|
24327
24373
|
});
|
|
24328
|
-
var ModelFormatSchema$
|
|
24374
|
+
var ModelFormatSchema$2 = _enum([
|
|
24329
24375
|
"onnx",
|
|
24330
24376
|
"coreml",
|
|
24331
24377
|
"openvino",
|
|
@@ -24343,7 +24389,7 @@ var PipelineSlotSchema = _enum([
|
|
|
24343
24389
|
var PipelineEngineChoiceSchema = object({
|
|
24344
24390
|
runtime: literal("python"),
|
|
24345
24391
|
backend: string(),
|
|
24346
|
-
format: ModelFormatSchema$
|
|
24392
|
+
format: ModelFormatSchema$2,
|
|
24347
24393
|
device: string().optional()
|
|
24348
24394
|
});
|
|
24349
24395
|
/**
|
|
@@ -24400,10 +24446,7 @@ var PipelineTemplateSchema$1 = object({
|
|
|
24400
24446
|
var PipelineModelOptionSchema = object({
|
|
24401
24447
|
id: string(),
|
|
24402
24448
|
name: string(),
|
|
24403
|
-
formats: record(string(),
|
|
24404
|
-
downloaded: boolean(),
|
|
24405
|
-
sizeMB: number()
|
|
24406
|
-
})),
|
|
24449
|
+
formats: record(string(), PipelineModelFormatStatusSchema),
|
|
24407
24450
|
group: ModelVariantGroupSchema.optional(),
|
|
24408
24451
|
legacy: boolean().optional(),
|
|
24409
24452
|
provider: ModelProviderIdSchema.optional(),
|
|
@@ -24618,6 +24661,32 @@ var pipelineExecutorCapability = {
|
|
|
24618
24661
|
auth: "admin"
|
|
24619
24662
|
}),
|
|
24620
24663
|
/**
|
|
24664
|
+
* Best settings (D745): the MODEL-BOUND thresholds (`bindsToModel` keys of
|
|
24665
|
+
* the object-detection root, and their binding keys) stored in THIS node's
|
|
24666
|
+
* per-camera detector settings — and, unless `dryRun`, remove them so the
|
|
24667
|
+
* running model's own defaults apply. Every other key of a camera's store
|
|
24668
|
+
* is kept. `nodeId` is the ROUTING key. `deviceIds` absent ⇒ every camera
|
|
24669
|
+
* with a store. A camera whose store could not be read is `refused`, never
|
|
24670
|
+
* reported as clean.
|
|
24671
|
+
*/
|
|
24672
|
+
clearModelBoundDeviceOverrides: method(object({
|
|
24673
|
+
nodeId: string(),
|
|
24674
|
+
deviceIds: array(number()).readonly().optional(),
|
|
24675
|
+
dryRun: boolean()
|
|
24676
|
+
}), object({
|
|
24677
|
+
entries: array(object({
|
|
24678
|
+
deviceId: number(),
|
|
24679
|
+
keys: array(string()).readonly()
|
|
24680
|
+
})).readonly(),
|
|
24681
|
+
refused: array(object({
|
|
24682
|
+
deviceId: number(),
|
|
24683
|
+
reason: string()
|
|
24684
|
+
})).readonly()
|
|
24685
|
+
}), {
|
|
24686
|
+
kind: "mutation",
|
|
24687
|
+
auth: "admin"
|
|
24688
|
+
}),
|
|
24689
|
+
/**
|
|
24621
24690
|
* Which of THIS node's inference devices the executor currently refuses,
|
|
24622
24691
|
* and why. `nodeId` is the ROUTING key (stripped by the generated router).
|
|
24623
24692
|
*
|
|
@@ -24712,12 +24781,12 @@ var pipelineExecutorCapability = {
|
|
|
24712
24781
|
downloadModel: method(object({
|
|
24713
24782
|
addonId: string(),
|
|
24714
24783
|
modelId: string(),
|
|
24715
|
-
format: ModelFormatSchema$
|
|
24784
|
+
format: ModelFormatSchema$2
|
|
24716
24785
|
}), DownloadModelResultSchema, { kind: "mutation" }),
|
|
24717
24786
|
deleteModel: method(object({
|
|
24718
24787
|
addonId: string(),
|
|
24719
24788
|
modelId: string(),
|
|
24720
|
-
format: ModelFormatSchema$
|
|
24789
|
+
format: ModelFormatSchema$2
|
|
24721
24790
|
}), object({ success: literal(true) }), { kind: "mutation" }),
|
|
24722
24791
|
/**
|
|
24723
24792
|
* Stateless single-frame execution. Callers (runner, benchmark) pass
|
|
@@ -24963,6 +25032,204 @@ var pipelineExecutorCapability = {
|
|
|
24963
25032
|
getDetectionConfigSchema: method(_void(), ConfigUISchemaNullableBridge)
|
|
24964
25033
|
}
|
|
24965
25034
|
};
|
|
25035
|
+
/**
|
|
25036
|
+
* The wire shapes of the Best settings plan and apply (D745), used by the
|
|
25037
|
+
* `pipeline-orchestrator` cap. The recommender's own types
|
|
25038
|
+
* (`recommend.ts`, `demand.ts`) are the TS face of the same data; these
|
|
25039
|
+
* schemas are what crosses the cap boundary.
|
|
25040
|
+
*/
|
|
25041
|
+
var ModelFormatSchema$1 = _enum(MODEL_FORMATS);
|
|
25042
|
+
var HardwareClassIdSchema = _enum([
|
|
25043
|
+
"intel-core-ultra",
|
|
25044
|
+
"intel-n-series",
|
|
25045
|
+
"apple-m-base",
|
|
25046
|
+
"apple-m-pro",
|
|
25047
|
+
"unknown"
|
|
25048
|
+
]);
|
|
25049
|
+
var AcceleratorKindSchema = _enum([
|
|
25050
|
+
"intel-npu",
|
|
25051
|
+
"intel-igpu",
|
|
25052
|
+
"x86-cpu",
|
|
25053
|
+
"arm-cpu",
|
|
25054
|
+
"apple-ane",
|
|
25055
|
+
"coral"
|
|
25056
|
+
]);
|
|
25057
|
+
var RecommendationReasonSchema = discriminatedUnion("kind", [
|
|
25058
|
+
object({
|
|
25059
|
+
kind: literal("fits"),
|
|
25060
|
+
utilisation: number()
|
|
25061
|
+
}),
|
|
25062
|
+
object({
|
|
25063
|
+
kind: literal("tie-faster"),
|
|
25064
|
+
over: string(),
|
|
25065
|
+
recallDelta: number()
|
|
25066
|
+
}),
|
|
25067
|
+
object({
|
|
25068
|
+
kind: literal("stepped-down"),
|
|
25069
|
+
from: string(),
|
|
25070
|
+
to: string(),
|
|
25071
|
+
limitedBy: string()
|
|
25072
|
+
}),
|
|
25073
|
+
object({
|
|
25074
|
+
kind: literal("no-fit-lightest"),
|
|
25075
|
+
utilisation: number()
|
|
25076
|
+
}),
|
|
25077
|
+
object({
|
|
25078
|
+
kind: literal("derived-latency"),
|
|
25079
|
+
modelId: string()
|
|
25080
|
+
}),
|
|
25081
|
+
object({
|
|
25082
|
+
kind: literal("estimated-load"),
|
|
25083
|
+
observedForMs: number().nullable()
|
|
25084
|
+
}),
|
|
25085
|
+
object({ kind: literal("cpu-device") }),
|
|
25086
|
+
object({ kind: literal("coral-untouched") }),
|
|
25087
|
+
object({ kind: literal("device-disabled") }),
|
|
25088
|
+
object({ kind: literal("device-unavailable") }),
|
|
25089
|
+
object({ kind: literal("node-offline") }),
|
|
25090
|
+
object({ kind: literal("no-hardware-facts") }),
|
|
25091
|
+
object({
|
|
25092
|
+
kind: literal("no-measured-data"),
|
|
25093
|
+
hardwareClass: HardwareClassIdSchema
|
|
25094
|
+
}),
|
|
25095
|
+
object({
|
|
25096
|
+
kind: literal("no-family-build"),
|
|
25097
|
+
format: ModelFormatSchema$1
|
|
25098
|
+
})
|
|
25099
|
+
]);
|
|
25100
|
+
var DeviceDemandSchema = object({
|
|
25101
|
+
nodeId: string(),
|
|
25102
|
+
deviceKey: string(),
|
|
25103
|
+
demandFps: number(),
|
|
25104
|
+
maxCameraFps: number(),
|
|
25105
|
+
alwaysOnCameras: number(),
|
|
25106
|
+
onMotionSessions: number(),
|
|
25107
|
+
onMotionSource: _enum([
|
|
25108
|
+
"observed-24h",
|
|
25109
|
+
"estimate",
|
|
25110
|
+
"none"
|
|
25111
|
+
]),
|
|
25112
|
+
observedForMs: number().nullable()
|
|
25113
|
+
});
|
|
25114
|
+
var DeviceModelChoiceSchema = object({
|
|
25115
|
+
modelId: string(),
|
|
25116
|
+
p50Ms: number(),
|
|
25117
|
+
fps: number(),
|
|
25118
|
+
measured: boolean(),
|
|
25119
|
+
utilisation: number(),
|
|
25120
|
+
personRecall: number()
|
|
25121
|
+
});
|
|
25122
|
+
var DeviceRecommendationSchema = object({
|
|
25123
|
+
deviceKey: string(),
|
|
25124
|
+
accelerator: AcceleratorKindSchema.nullable(),
|
|
25125
|
+
enabled: boolean(),
|
|
25126
|
+
available: boolean(),
|
|
25127
|
+
demand: DeviceDemandSchema.nullable(),
|
|
25128
|
+
chosen: DeviceModelChoiceSchema.nullable(),
|
|
25129
|
+
reasons: array(RecommendationReasonSchema).readonly()
|
|
25130
|
+
});
|
|
25131
|
+
var NodeRecommendationSchema = object({
|
|
25132
|
+
nodeId: string(),
|
|
25133
|
+
reachable: boolean(),
|
|
25134
|
+
hardwareClass: HardwareClassIdSchema.nullable(),
|
|
25135
|
+
cpuModel: string().nullable(),
|
|
25136
|
+
skipped: boolean(),
|
|
25137
|
+
acceleratorModelId: string().nullable(),
|
|
25138
|
+
devices: array(DeviceRecommendationSchema).readonly(),
|
|
25139
|
+
reasons: array(RecommendationReasonSchema).readonly()
|
|
25140
|
+
});
|
|
25141
|
+
var ModelPinChangeSchema = object({
|
|
25142
|
+
stepId: string(),
|
|
25143
|
+
before: string().nullable(),
|
|
25144
|
+
after: string().nullable()
|
|
25145
|
+
});
|
|
25146
|
+
var NodeDeviceChangeSchema = object({
|
|
25147
|
+
nodeId: string(),
|
|
25148
|
+
deviceKey: string(),
|
|
25149
|
+
madeExplicit: boolean(),
|
|
25150
|
+
modelPins: array(ModelPinChangeSchema).readonly(),
|
|
25151
|
+
thresholds: array(object({
|
|
25152
|
+
stepId: string(),
|
|
25153
|
+
keys: array(string()).readonly()
|
|
25154
|
+
})).readonly()
|
|
25155
|
+
});
|
|
25156
|
+
var CameraOverrideClearSchema = object({
|
|
25157
|
+
deviceId: number(),
|
|
25158
|
+
source: _enum(["step-override", "wholesale"]),
|
|
25159
|
+
nodeId: string(),
|
|
25160
|
+
deviceKey: string().nullable(),
|
|
25161
|
+
stepId: string(),
|
|
25162
|
+
modelIdBefore: string().nullable(),
|
|
25163
|
+
keys: array(string()).readonly()
|
|
25164
|
+
});
|
|
25165
|
+
var DetectorStoreClearSchema = object({
|
|
25166
|
+
deviceId: number(),
|
|
25167
|
+
keys: array(string()).readonly()
|
|
25168
|
+
});
|
|
25169
|
+
var BestSettingsWarningSchema = discriminatedUnion("kind", [
|
|
25170
|
+
object({
|
|
25171
|
+
kind: literal("detector-store-cluster-wide"),
|
|
25172
|
+
skippedNodes: array(string()).readonly()
|
|
25173
|
+
}),
|
|
25174
|
+
object({
|
|
25175
|
+
kind: literal("detector-store-unreadable"),
|
|
25176
|
+
error: string()
|
|
25177
|
+
}),
|
|
25178
|
+
object({
|
|
25179
|
+
kind: literal("coral-enabled"),
|
|
25180
|
+
nodeId: string(),
|
|
25181
|
+
deviceKey: string()
|
|
25182
|
+
})
|
|
25183
|
+
]);
|
|
25184
|
+
var BestSettingsOptionsSchema = object({
|
|
25185
|
+
/** Share of each accelerator's throughput kept free (0.3–0.5). */
|
|
25186
|
+
headroom: number().min(.3).max(.5).default(.4) });
|
|
25187
|
+
var BestSettingsPlanSchema = object({
|
|
25188
|
+
/** Hash of everything the apply will write — the apply refuses a different one. */
|
|
25189
|
+
planHash: string(),
|
|
25190
|
+
family: literal("yolov9"),
|
|
25191
|
+
headroom: number(),
|
|
25192
|
+
generatedAt: number(),
|
|
25193
|
+
nodes: array(NodeRecommendationSchema).readonly(),
|
|
25194
|
+
nodeChanges: array(NodeDeviceChangeSchema).readonly(),
|
|
25195
|
+
cameraClears: array(CameraOverrideClearSchema).readonly(),
|
|
25196
|
+
detectorStoreClears: array(DetectorStoreClearSchema).readonly(),
|
|
25197
|
+
warnings: array(BestSettingsWarningSchema).readonly()
|
|
25198
|
+
});
|
|
25199
|
+
var BestSettingsApplyInputSchema = object({
|
|
25200
|
+
planHash: string(),
|
|
25201
|
+
headroom: number().min(.3).max(.5).default(.4)
|
|
25202
|
+
});
|
|
25203
|
+
var BestSettingsPhaseResultSchema = object({
|
|
25204
|
+
phase: _enum([
|
|
25205
|
+
"download",
|
|
25206
|
+
"camera-overrides",
|
|
25207
|
+
"detector-thresholds",
|
|
25208
|
+
"node-models",
|
|
25209
|
+
"redispatch"
|
|
25210
|
+
]),
|
|
25211
|
+
ok: boolean(),
|
|
25212
|
+
detail: string()
|
|
25213
|
+
});
|
|
25214
|
+
var BestSettingsApplyResultSchema = discriminatedUnion("kind", [
|
|
25215
|
+
object({
|
|
25216
|
+
kind: literal("applied"),
|
|
25217
|
+
planHash: string(),
|
|
25218
|
+
phases: array(BestSettingsPhaseResultSchema).readonly()
|
|
25219
|
+
}),
|
|
25220
|
+
object({
|
|
25221
|
+
kind: literal("aborted"),
|
|
25222
|
+
phases: array(BestSettingsPhaseResultSchema).readonly()
|
|
25223
|
+
}),
|
|
25224
|
+
object({
|
|
25225
|
+
kind: literal("stale"),
|
|
25226
|
+
plan: BestSettingsPlanSchema
|
|
25227
|
+
}),
|
|
25228
|
+
object({
|
|
25229
|
+
kind: literal("refused"),
|
|
25230
|
+
reason: _enum(["apply-in-progress"])
|
|
25231
|
+
})
|
|
25232
|
+
]);
|
|
24966
25233
|
object({
|
|
24967
25234
|
activeCameras: number(),
|
|
24968
25235
|
throttledCameras: number(),
|
|
@@ -27149,6 +27416,27 @@ var pipelineOrchestratorCapability = {
|
|
|
27149
27416
|
kind: "mutation",
|
|
27150
27417
|
auth: "admin"
|
|
27151
27418
|
}),
|
|
27419
|
+
/**
|
|
27420
|
+
* Best settings (D745) — DRY RUN. Recommends ONE detector family (YOLOv9)
|
|
27421
|
+
* for the whole cluster from each node's static hardware class, the
|
|
27422
|
+
* measured latency table and the detection demand, and lists every write
|
|
27423
|
+
* the apply would make: the node model pins set, the enrichment pins and
|
|
27424
|
+
* model-bound thresholds removed (node level and per camera), and the
|
|
27425
|
+
* per-camera detector thresholds cleared. Writes nothing. Step
|
|
27426
|
+
* enablement, device enablement, pins, recording, notifications, zones and
|
|
27427
|
+
* rules are never part of it. `planHash` names exactly this diff.
|
|
27428
|
+
*/
|
|
27429
|
+
planBestSettings: method(BestSettingsOptionsSchema, BestSettingsPlanSchema, { auth: "admin" }),
|
|
27430
|
+
/**
|
|
27431
|
+
* Best settings (D745) — APPLY the plan the operator confirmed. Recomputes
|
|
27432
|
+
* the plan and refuses (`stale`, with the fresh plan) when its hash is not
|
|
27433
|
+
* `planHash`, so what is written is exactly the confirmed diff. Offline
|
|
27434
|
+
* nodes are skipped; there is no undo.
|
|
27435
|
+
*/
|
|
27436
|
+
applyBestSettings: method(BestSettingsApplyInputSchema, BestSettingsApplyResultSchema, {
|
|
27437
|
+
kind: "mutation",
|
|
27438
|
+
auth: "admin"
|
|
27439
|
+
}),
|
|
27152
27440
|
/** Read one camera's settings. Null when never touched (inherits agent defaults fully). */
|
|
27153
27441
|
getCameraSettings: method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()),
|
|
27154
27442
|
/** Set or clear the 3-state toggle for one (camera, addonId). Pass `enabled: null` to clear and revert to agent default. */
|
|
@@ -42950,6 +43238,12 @@ Object.freeze({
|
|
|
42950
43238
|
addonId: null,
|
|
42951
43239
|
access: "delete"
|
|
42952
43240
|
},
|
|
43241
|
+
"pipelineExecutor.clearModelBoundDeviceOverrides": {
|
|
43242
|
+
capName: "pipeline-executor",
|
|
43243
|
+
capScope: "system",
|
|
43244
|
+
addonId: null,
|
|
43245
|
+
access: "delete"
|
|
43246
|
+
},
|
|
42953
43247
|
"pipelineExecutor.deleteModel": {
|
|
42954
43248
|
capName: "pipeline-executor",
|
|
42955
43249
|
capScope: "system",
|
|
@@ -43166,6 +43460,12 @@ Object.freeze({
|
|
|
43166
43460
|
addonId: null,
|
|
43167
43461
|
access: "view"
|
|
43168
43462
|
},
|
|
43463
|
+
"pipelineOrchestrator.applyBestSettings": {
|
|
43464
|
+
capName: "pipeline-orchestrator",
|
|
43465
|
+
capScope: "system",
|
|
43466
|
+
addonId: null,
|
|
43467
|
+
access: "create"
|
|
43468
|
+
},
|
|
43169
43469
|
"pipelineOrchestrator.applyDeviceSettingsPatch": {
|
|
43170
43470
|
capName: "pipeline-orchestrator",
|
|
43171
43471
|
capScope: "system",
|
|
@@ -43340,6 +43640,12 @@ Object.freeze({
|
|
|
43340
43640
|
addonId: null,
|
|
43341
43641
|
access: "create"
|
|
43342
43642
|
},
|
|
43643
|
+
"pipelineOrchestrator.planBestSettings": {
|
|
43644
|
+
capName: "pipeline-orchestrator",
|
|
43645
|
+
capScope: "system",
|
|
43646
|
+
addonId: null,
|
|
43647
|
+
access: "view"
|
|
43648
|
+
},
|
|
43343
43649
|
"pipelineOrchestrator.rebalance": {
|
|
43344
43650
|
capName: "pipeline-orchestrator",
|
|
43345
43651
|
capScope: "system",
|
|
@@ -48790,6 +49096,11 @@ Object.freeze({
|
|
|
48790
49096
|
form: "single",
|
|
48791
49097
|
optional: false
|
|
48792
49098
|
}],
|
|
49099
|
+
"pipelineExecutor.clearModelBoundDeviceOverrides": [{
|
|
49100
|
+
name: "deviceIds",
|
|
49101
|
+
form: "array",
|
|
49102
|
+
optional: true
|
|
49103
|
+
}],
|
|
48793
49104
|
"pipelineExecutor.runPipeline": [{
|
|
48794
49105
|
name: "deviceId",
|
|
48795
49106
|
form: "single",
|
|
@@ -50684,6 +50995,830 @@ var REMOVED_MODEL_IDS = {
|
|
|
50684
50995
|
function isRemovedModelId(stepId, modelId) {
|
|
50685
50996
|
return REMOVED_MODEL_IDS[stepId]?.includes(modelId) ?? false;
|
|
50686
50997
|
}
|
|
50998
|
+
/** Suffix of the sibling key that names the model a stored value was made for. */
|
|
50999
|
+
var MODEL_BINDING_SUFFIX = "ModelId";
|
|
51000
|
+
/** `minConfidencePerson` → `minConfidencePersonModelId`. */
|
|
51001
|
+
function modelBindingKey(key) {
|
|
51002
|
+
return `${key}${MODEL_BINDING_SUFFIX}`;
|
|
51003
|
+
}
|
|
51004
|
+
/**
|
|
51005
|
+
* The keys of every `bindsToModel` field of a step's config schema, groups
|
|
51006
|
+
* flattened (D738). One definition for the runner (which applies the binding)
|
|
51007
|
+
* and the orchestrator's Best settings (which clears those overrides, D745).
|
|
51008
|
+
*/
|
|
51009
|
+
function collectModelBoundKeys(fields) {
|
|
51010
|
+
return fields.flatMap((field) => {
|
|
51011
|
+
if (field.type === "group") return collectModelBoundKeys(field.fields);
|
|
51012
|
+
return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
|
|
51013
|
+
});
|
|
51014
|
+
}
|
|
51015
|
+
var APPLE_CHIP = /^Apple M(\d+)(?:\s+(Pro|Max|Ultra))?\b/i;
|
|
51016
|
+
var INTEL_CORE_ULTRA = /Core\(TM\)\s+Ultra|Core Ultra/i;
|
|
51017
|
+
var INTEL_N_SERIES = /Intel\(R\)\s+N\d{2,3}\b|\bIntel\s+N\d{2,3}\b/i;
|
|
51018
|
+
function parseAppleChip(cpuModel) {
|
|
51019
|
+
const match = APPLE_CHIP.exec(cpuModel.trim());
|
|
51020
|
+
if (match === null) return null;
|
|
51021
|
+
const generation = Number(match[1]);
|
|
51022
|
+
const variantRaw = (match[2] ?? "base").toLowerCase();
|
|
51023
|
+
return {
|
|
51024
|
+
generation,
|
|
51025
|
+
variant: variantRaw === "pro" || variantRaw === "max" || variantRaw === "ultra" ? variantRaw : "base"
|
|
51026
|
+
};
|
|
51027
|
+
}
|
|
51028
|
+
/**
|
|
51029
|
+
* Classify a node. Apple base chips (M1–M4) map to the measured M2 column, the
|
|
51030
|
+
* Pro/Max/Ultra chips to the measured M3 Pro column — the M2 was measured at
|
|
51031
|
+
* 1.0–1.2× the M3 Pro on the Neural Engine, so the generation inside a variant
|
|
51032
|
+
* matters less than the column's own error.
|
|
51033
|
+
*/
|
|
51034
|
+
function classifyInferenceHardware(hw) {
|
|
51035
|
+
const cpuModel = hw.cpuModel;
|
|
51036
|
+
const apple = parseAppleChip(cpuModel);
|
|
51037
|
+
if (apple !== null) return {
|
|
51038
|
+
hardwareClass: apple.variant === "base" ? "apple-m-base" : "apple-m-pro",
|
|
51039
|
+
cpuModel,
|
|
51040
|
+
apple
|
|
51041
|
+
};
|
|
51042
|
+
if (INTEL_CORE_ULTRA.test(cpuModel)) return {
|
|
51043
|
+
hardwareClass: "intel-core-ultra",
|
|
51044
|
+
cpuModel
|
|
51045
|
+
};
|
|
51046
|
+
if (INTEL_N_SERIES.test(cpuModel)) return {
|
|
51047
|
+
hardwareClass: "intel-n-series",
|
|
51048
|
+
cpuModel
|
|
51049
|
+
};
|
|
51050
|
+
return {
|
|
51051
|
+
hardwareClass: "unknown",
|
|
51052
|
+
cpuModel
|
|
51053
|
+
};
|
|
51054
|
+
}
|
|
51055
|
+
/**
|
|
51056
|
+
* The accelerator kind of one of a node's device keys (`openvino:npu`,
|
|
51057
|
+
* `openvino:gpu`, `coreml:all`, `edgetpu:usb`, `cpu`, `onnx:cpu`, …). `null`
|
|
51058
|
+
* for a key this table has no column for.
|
|
51059
|
+
*/
|
|
51060
|
+
function acceleratorKindOf(deviceKey, hw) {
|
|
51061
|
+
const [backend = "", device = ""] = deviceKey.split(":");
|
|
51062
|
+
if (backend === "edgetpu") return "coral";
|
|
51063
|
+
if (backend === "coreml") return device === "cpu" ? "arm-cpu" : "apple-ane";
|
|
51064
|
+
if (backend === "openvino" && device === "npu") return "intel-npu";
|
|
51065
|
+
if (backend === "openvino" && device === "gpu") return "intel-igpu";
|
|
51066
|
+
if (backend === "cpu" || device === "cpu") return hw.arch === "arm64" ? "arm-cpu" : "x86-cpu";
|
|
51067
|
+
return null;
|
|
51068
|
+
}
|
|
51069
|
+
var DETECTOR_PERF = {
|
|
51070
|
+
"intel-core-ultra": {
|
|
51071
|
+
"intel-npu": {
|
|
51072
|
+
"yolov9t-320": {
|
|
51073
|
+
p50Ms: 2.9,
|
|
51074
|
+
fps: 383,
|
|
51075
|
+
measured: true
|
|
51076
|
+
},
|
|
51077
|
+
"yolov9t-320-int8": {
|
|
51078
|
+
p50Ms: 3.8,
|
|
51079
|
+
fps: 333,
|
|
51080
|
+
measured: true
|
|
51081
|
+
},
|
|
51082
|
+
"yolov9t-640": {
|
|
51083
|
+
p50Ms: 7.4,
|
|
51084
|
+
fps: 139,
|
|
51085
|
+
measured: true
|
|
51086
|
+
},
|
|
51087
|
+
"yolov9t-640-int8": {
|
|
51088
|
+
p50Ms: 7.5,
|
|
51089
|
+
fps: 144,
|
|
51090
|
+
measured: true
|
|
51091
|
+
},
|
|
51092
|
+
"yolov9s-320": {
|
|
51093
|
+
p50Ms: 4.4,
|
|
51094
|
+
fps: 241,
|
|
51095
|
+
measured: true
|
|
51096
|
+
},
|
|
51097
|
+
"yolov9s-320-int8": {
|
|
51098
|
+
p50Ms: 4.1,
|
|
51099
|
+
fps: 264,
|
|
51100
|
+
measured: true
|
|
51101
|
+
},
|
|
51102
|
+
"yolov9s-640": {
|
|
51103
|
+
p50Ms: 12.7,
|
|
51104
|
+
fps: 82,
|
|
51105
|
+
measured: true
|
|
51106
|
+
},
|
|
51107
|
+
"yolov9s-640-int8": {
|
|
51108
|
+
p50Ms: 10.7,
|
|
51109
|
+
fps: 99,
|
|
51110
|
+
measured: true
|
|
51111
|
+
},
|
|
51112
|
+
"yolov9m-320": {
|
|
51113
|
+
p50Ms: 8.4,
|
|
51114
|
+
fps: 123,
|
|
51115
|
+
measured: true
|
|
51116
|
+
},
|
|
51117
|
+
"yolov9m-320-int8": {
|
|
51118
|
+
p50Ms: 6.5,
|
|
51119
|
+
fps: 165,
|
|
51120
|
+
measured: true
|
|
51121
|
+
},
|
|
51122
|
+
"yolov9m-640": {
|
|
51123
|
+
p50Ms: 29.5,
|
|
51124
|
+
fps: 32,
|
|
51125
|
+
measured: true
|
|
51126
|
+
},
|
|
51127
|
+
"yolov9m-640-int8": {
|
|
51128
|
+
p50Ms: 19.5,
|
|
51129
|
+
fps: 52,
|
|
51130
|
+
measured: true
|
|
51131
|
+
}
|
|
51132
|
+
},
|
|
51133
|
+
"intel-igpu": {
|
|
51134
|
+
"yolov9t-320": {
|
|
51135
|
+
p50Ms: 2.6,
|
|
51136
|
+
fps: 405,
|
|
51137
|
+
measured: true
|
|
51138
|
+
},
|
|
51139
|
+
"yolov9t-320-int8": {
|
|
51140
|
+
p50Ms: 2.8,
|
|
51141
|
+
fps: 324,
|
|
51142
|
+
measured: true
|
|
51143
|
+
},
|
|
51144
|
+
"yolov9t-640": {
|
|
51145
|
+
p50Ms: 6.5,
|
|
51146
|
+
fps: 157,
|
|
51147
|
+
measured: true
|
|
51148
|
+
},
|
|
51149
|
+
"yolov9t-640-int8": {
|
|
51150
|
+
p50Ms: 5.6,
|
|
51151
|
+
fps: 185,
|
|
51152
|
+
measured: true
|
|
51153
|
+
},
|
|
51154
|
+
"yolov9s-320": {
|
|
51155
|
+
p50Ms: 4.6,
|
|
51156
|
+
fps: 200,
|
|
51157
|
+
measured: true
|
|
51158
|
+
},
|
|
51159
|
+
"yolov9s-320-int8": {
|
|
51160
|
+
p50Ms: 4.1,
|
|
51161
|
+
fps: 239,
|
|
51162
|
+
measured: true
|
|
51163
|
+
},
|
|
51164
|
+
"yolov9s-640": {
|
|
51165
|
+
p50Ms: 15.2,
|
|
51166
|
+
fps: 69,
|
|
51167
|
+
measured: true
|
|
51168
|
+
},
|
|
51169
|
+
"yolov9s-640-int8": {
|
|
51170
|
+
p50Ms: 10.5,
|
|
51171
|
+
fps: 99,
|
|
51172
|
+
measured: true
|
|
51173
|
+
},
|
|
51174
|
+
"yolov9m-320": {
|
|
51175
|
+
p50Ms: 11.4,
|
|
51176
|
+
fps: 88,
|
|
51177
|
+
measured: true
|
|
51178
|
+
},
|
|
51179
|
+
"yolov9m-320-int8": {
|
|
51180
|
+
p50Ms: 7,
|
|
51181
|
+
fps: 127,
|
|
51182
|
+
measured: true
|
|
51183
|
+
},
|
|
51184
|
+
"yolov9m-640": {
|
|
51185
|
+
p50Ms: 40,
|
|
51186
|
+
fps: 25,
|
|
51187
|
+
measured: true
|
|
51188
|
+
},
|
|
51189
|
+
"yolov9m-640-int8": {
|
|
51190
|
+
p50Ms: 23.5,
|
|
51191
|
+
fps: 44,
|
|
51192
|
+
measured: true
|
|
51193
|
+
}
|
|
51194
|
+
},
|
|
51195
|
+
"x86-cpu": {
|
|
51196
|
+
"yolov9t-320": {
|
|
51197
|
+
p50Ms: 6.9,
|
|
51198
|
+
fps: 145,
|
|
51199
|
+
measured: true
|
|
51200
|
+
},
|
|
51201
|
+
"yolov9t-320-int8": {
|
|
51202
|
+
p50Ms: 2.7,
|
|
51203
|
+
fps: 405,
|
|
51204
|
+
measured: true
|
|
51205
|
+
},
|
|
51206
|
+
"yolov9t-640": {
|
|
51207
|
+
p50Ms: 23,
|
|
51208
|
+
fps: 44,
|
|
51209
|
+
measured: true
|
|
51210
|
+
},
|
|
51211
|
+
"yolov9t-640-int8": {
|
|
51212
|
+
p50Ms: 8.8,
|
|
51213
|
+
fps: 124,
|
|
51214
|
+
measured: true
|
|
51215
|
+
},
|
|
51216
|
+
"yolov9s-320": {
|
|
51217
|
+
p50Ms: 15.8,
|
|
51218
|
+
fps: 63,
|
|
51219
|
+
measured: true
|
|
51220
|
+
},
|
|
51221
|
+
"yolov9s-320-int8": {
|
|
51222
|
+
p50Ms: 6.5,
|
|
51223
|
+
fps: 180,
|
|
51224
|
+
measured: true
|
|
51225
|
+
},
|
|
51226
|
+
"yolov9s-640": {
|
|
51227
|
+
p50Ms: 58.2,
|
|
51228
|
+
fps: 17,
|
|
51229
|
+
measured: true
|
|
51230
|
+
},
|
|
51231
|
+
"yolov9s-640-int8": {
|
|
51232
|
+
p50Ms: 20.1,
|
|
51233
|
+
fps: 52,
|
|
51234
|
+
measured: true
|
|
51235
|
+
},
|
|
51236
|
+
"yolov9m-320": {
|
|
51237
|
+
p50Ms: 40,
|
|
51238
|
+
fps: 25,
|
|
51239
|
+
measured: true
|
|
51240
|
+
},
|
|
51241
|
+
"yolov9m-320-int8": {
|
|
51242
|
+
p50Ms: 14.2,
|
|
51243
|
+
fps: 74,
|
|
51244
|
+
measured: true
|
|
51245
|
+
},
|
|
51246
|
+
"yolov9m-640": {
|
|
51247
|
+
p50Ms: 155.8,
|
|
51248
|
+
fps: 6,
|
|
51249
|
+
measured: true
|
|
51250
|
+
},
|
|
51251
|
+
"yolov9m-640-int8": {
|
|
51252
|
+
p50Ms: 52.4,
|
|
51253
|
+
fps: 19,
|
|
51254
|
+
measured: true
|
|
51255
|
+
}
|
|
51256
|
+
}
|
|
51257
|
+
},
|
|
51258
|
+
"intel-n-series": {
|
|
51259
|
+
"intel-igpu": {
|
|
51260
|
+
"yolov9t-320": {
|
|
51261
|
+
p50Ms: 15,
|
|
51262
|
+
fps: 48,
|
|
51263
|
+
measured: true
|
|
51264
|
+
},
|
|
51265
|
+
"yolov9t-320-int8": {
|
|
51266
|
+
p50Ms: 14.1,
|
|
51267
|
+
fps: 67,
|
|
51268
|
+
measured: true
|
|
51269
|
+
},
|
|
51270
|
+
"yolov9t-640": {
|
|
51271
|
+
p50Ms: 38.5,
|
|
51272
|
+
fps: 26,
|
|
51273
|
+
measured: true
|
|
51274
|
+
},
|
|
51275
|
+
"yolov9t-640-int8": {
|
|
51276
|
+
p50Ms: 33.1,
|
|
51277
|
+
fps: 33,
|
|
51278
|
+
measured: true
|
|
51279
|
+
},
|
|
51280
|
+
"yolov9s-320": {
|
|
51281
|
+
p50Ms: 41.4,
|
|
51282
|
+
fps: 24,
|
|
51283
|
+
measured: true
|
|
51284
|
+
},
|
|
51285
|
+
"yolov9s-320-int8": {
|
|
51286
|
+
p50Ms: 23.3,
|
|
51287
|
+
fps: 34,
|
|
51288
|
+
measured: true
|
|
51289
|
+
},
|
|
51290
|
+
"yolov9s-640": {
|
|
51291
|
+
p50Ms: 127.4,
|
|
51292
|
+
fps: 8,
|
|
51293
|
+
measured: true
|
|
51294
|
+
},
|
|
51295
|
+
"yolov9s-640-int8": {
|
|
51296
|
+
p50Ms: 92.9,
|
|
51297
|
+
fps: 11,
|
|
51298
|
+
measured: true
|
|
51299
|
+
},
|
|
51300
|
+
"yolov9m-320": {
|
|
51301
|
+
p50Ms: 63.5,
|
|
51302
|
+
fps: 16,
|
|
51303
|
+
measured: true
|
|
51304
|
+
},
|
|
51305
|
+
"yolov9m-320-int8": {
|
|
51306
|
+
p50Ms: 39.4,
|
|
51307
|
+
fps: 26,
|
|
51308
|
+
measured: true
|
|
51309
|
+
},
|
|
51310
|
+
"yolov9m-640": {
|
|
51311
|
+
p50Ms: 215.5,
|
|
51312
|
+
fps: 5,
|
|
51313
|
+
measured: true
|
|
51314
|
+
},
|
|
51315
|
+
"yolov9m-640-int8": {
|
|
51316
|
+
p50Ms: 139.1,
|
|
51317
|
+
fps: 7,
|
|
51318
|
+
measured: true
|
|
51319
|
+
}
|
|
51320
|
+
},
|
|
51321
|
+
"x86-cpu": {
|
|
51322
|
+
"yolov9t-320": {
|
|
51323
|
+
p50Ms: 86.2,
|
|
51324
|
+
fps: 12,
|
|
51325
|
+
measured: true
|
|
51326
|
+
},
|
|
51327
|
+
"yolov9t-320-int8": {
|
|
51328
|
+
p50Ms: 52.7,
|
|
51329
|
+
fps: 20,
|
|
51330
|
+
measured: true
|
|
51331
|
+
},
|
|
51332
|
+
"yolov9t-640": {
|
|
51333
|
+
p50Ms: 322.1,
|
|
51334
|
+
fps: 3,
|
|
51335
|
+
measured: true
|
|
51336
|
+
},
|
|
51337
|
+
"yolov9t-640-int8": {
|
|
51338
|
+
p50Ms: 205.6,
|
|
51339
|
+
fps: 5,
|
|
51340
|
+
measured: true
|
|
51341
|
+
},
|
|
51342
|
+
"yolov9s-320": {
|
|
51343
|
+
p50Ms: 164.4,
|
|
51344
|
+
fps: 6,
|
|
51345
|
+
measured: true
|
|
51346
|
+
},
|
|
51347
|
+
"yolov9s-320-int8": {
|
|
51348
|
+
p50Ms: 86.9,
|
|
51349
|
+
fps: 12,
|
|
51350
|
+
measured: true
|
|
51351
|
+
},
|
|
51352
|
+
"yolov9s-640": {
|
|
51353
|
+
p50Ms: 341.3,
|
|
51354
|
+
fps: 3,
|
|
51355
|
+
measured: true
|
|
51356
|
+
},
|
|
51357
|
+
"yolov9s-640-int8": {
|
|
51358
|
+
p50Ms: 185.3,
|
|
51359
|
+
fps: 5,
|
|
51360
|
+
measured: true
|
|
51361
|
+
},
|
|
51362
|
+
"yolov9m-320": {
|
|
51363
|
+
p50Ms: 166.8,
|
|
51364
|
+
fps: 6,
|
|
51365
|
+
measured: true
|
|
51366
|
+
},
|
|
51367
|
+
"yolov9m-320-int8": {
|
|
51368
|
+
p50Ms: 226.3,
|
|
51369
|
+
fps: 4,
|
|
51370
|
+
measured: true
|
|
51371
|
+
},
|
|
51372
|
+
"yolov9m-640": {
|
|
51373
|
+
p50Ms: 663.5,
|
|
51374
|
+
fps: 2,
|
|
51375
|
+
measured: true
|
|
51376
|
+
},
|
|
51377
|
+
"yolov9m-640-int8": {
|
|
51378
|
+
p50Ms: 324.4,
|
|
51379
|
+
fps: 3,
|
|
51380
|
+
measured: true
|
|
51381
|
+
}
|
|
51382
|
+
}
|
|
51383
|
+
},
|
|
51384
|
+
"apple-m-pro": { "apple-ane": {
|
|
51385
|
+
"yolov9t-320": {
|
|
51386
|
+
p50Ms: 1.1,
|
|
51387
|
+
fps: 952,
|
|
51388
|
+
measured: true
|
|
51389
|
+
},
|
|
51390
|
+
"yolov9t-640": {
|
|
51391
|
+
p50Ms: 3,
|
|
51392
|
+
fps: 333,
|
|
51393
|
+
measured: true
|
|
51394
|
+
},
|
|
51395
|
+
"yolov9s-320": {
|
|
51396
|
+
p50Ms: 1.5,
|
|
51397
|
+
fps: 667,
|
|
51398
|
+
measured: true
|
|
51399
|
+
},
|
|
51400
|
+
"yolov9s-640": {
|
|
51401
|
+
p50Ms: 5.1,
|
|
51402
|
+
fps: 196,
|
|
51403
|
+
measured: true
|
|
51404
|
+
},
|
|
51405
|
+
"yolov9m-320": {
|
|
51406
|
+
p50Ms: 3.2,
|
|
51407
|
+
fps: 312,
|
|
51408
|
+
measured: true
|
|
51409
|
+
},
|
|
51410
|
+
"yolov9m-640": {
|
|
51411
|
+
p50Ms: 10.2,
|
|
51412
|
+
fps: 98,
|
|
51413
|
+
measured: true
|
|
51414
|
+
}
|
|
51415
|
+
} },
|
|
51416
|
+
"apple-m-base": { "apple-ane": {
|
|
51417
|
+
"yolov9t-320": {
|
|
51418
|
+
p50Ms: 1.32,
|
|
51419
|
+
fps: 758,
|
|
51420
|
+
measured: false
|
|
51421
|
+
},
|
|
51422
|
+
"yolov9t-640": {
|
|
51423
|
+
p50Ms: 3.45,
|
|
51424
|
+
fps: 290,
|
|
51425
|
+
measured: true
|
|
51426
|
+
},
|
|
51427
|
+
"yolov9s-320": {
|
|
51428
|
+
p50Ms: 1.8,
|
|
51429
|
+
fps: 556,
|
|
51430
|
+
measured: false
|
|
51431
|
+
},
|
|
51432
|
+
"yolov9s-640": {
|
|
51433
|
+
p50Ms: 5.46,
|
|
51434
|
+
fps: 183,
|
|
51435
|
+
measured: true
|
|
51436
|
+
},
|
|
51437
|
+
"yolov9m-320": {
|
|
51438
|
+
p50Ms: 3.79,
|
|
51439
|
+
fps: 264,
|
|
51440
|
+
measured: true
|
|
51441
|
+
},
|
|
51442
|
+
"yolov9m-640": {
|
|
51443
|
+
p50Ms: 10.34,
|
|
51444
|
+
fps: 97,
|
|
51445
|
+
measured: true
|
|
51446
|
+
}
|
|
51447
|
+
} }
|
|
51448
|
+
};
|
|
51449
|
+
var DETECTOR_ACCURACY = {
|
|
51450
|
+
"yolov9m-320": {
|
|
51451
|
+
personPrecision: .979,
|
|
51452
|
+
personRecall: .649
|
|
51453
|
+
},
|
|
51454
|
+
"yolov9m-320-int8": {
|
|
51455
|
+
personPrecision: .974,
|
|
51456
|
+
personRecall: .684
|
|
51457
|
+
},
|
|
51458
|
+
"yolov9m-640": {
|
|
51459
|
+
personPrecision: .976,
|
|
51460
|
+
personRecall: .854
|
|
51461
|
+
},
|
|
51462
|
+
"yolov9m-640-int8": {
|
|
51463
|
+
personPrecision: .976,
|
|
51464
|
+
personRecall: .847
|
|
51465
|
+
},
|
|
51466
|
+
"yolov9s-320": {
|
|
51467
|
+
personPrecision: .958,
|
|
51468
|
+
personRecall: .479
|
|
51469
|
+
},
|
|
51470
|
+
"yolov9s-320-int8": {
|
|
51471
|
+
personPrecision: .969,
|
|
51472
|
+
personRecall: .438
|
|
51473
|
+
},
|
|
51474
|
+
"yolov9s-640": {
|
|
51475
|
+
personPrecision: .986,
|
|
51476
|
+
personRecall: .753
|
|
51477
|
+
},
|
|
51478
|
+
"yolov9s-640-int8": {
|
|
51479
|
+
personPrecision: .963,
|
|
51480
|
+
personRecall: .823
|
|
51481
|
+
},
|
|
51482
|
+
"yolov9t-320": {
|
|
51483
|
+
personPrecision: .946,
|
|
51484
|
+
personRecall: .368
|
|
51485
|
+
},
|
|
51486
|
+
"yolov9t-320-int8": {
|
|
51487
|
+
personPrecision: .943,
|
|
51488
|
+
personRecall: .347
|
|
51489
|
+
},
|
|
51490
|
+
"yolov9t-640": {
|
|
51491
|
+
personPrecision: .979,
|
|
51492
|
+
personRecall: .646
|
|
51493
|
+
},
|
|
51494
|
+
"yolov9t-640-int8": {
|
|
51495
|
+
personPrecision: .979,
|
|
51496
|
+
personRecall: .646
|
|
51497
|
+
}
|
|
51498
|
+
};
|
|
51499
|
+
var DEFAULT_DEMAND_OPTIONS = {
|
|
51500
|
+
onMotionConcurrency: .25,
|
|
51501
|
+
onMotionMinConcurrent: 2
|
|
51502
|
+
};
|
|
51503
|
+
function deviceId(nodeId, deviceKey) {
|
|
51504
|
+
return `${nodeId}\u0000${deviceKey}`;
|
|
51505
|
+
}
|
|
51506
|
+
function charges(load) {
|
|
51507
|
+
return load.mode !== "disabled" && load.runsPipelineRoot && load.fps > 0;
|
|
51508
|
+
}
|
|
51509
|
+
/** The highest `count` rates, summed. */
|
|
51510
|
+
function topRatesSum(rates, count) {
|
|
51511
|
+
return rates.toSorted((a, b) => b - a).slice(0, Math.max(0, count)).reduce((sum, r) => sum + r, 0);
|
|
51512
|
+
}
|
|
51513
|
+
/** Concurrent on-motion sessions assumed cluster-wide without an observation. */
|
|
51514
|
+
function estimatedConcurrentSessions(cameraCount, options) {
|
|
51515
|
+
if (cameraCount <= 0) return 0;
|
|
51516
|
+
const share = Math.ceil(options.onMotionConcurrency * cameraCount);
|
|
51517
|
+
return Math.min(cameraCount, Math.max(share, Math.min(cameraCount, options.onMotionMinConcurrent)));
|
|
51518
|
+
}
|
|
51519
|
+
/**
|
|
51520
|
+
* Demand on every eligible device of the cluster.
|
|
51521
|
+
*/
|
|
51522
|
+
function estimateDeviceDemand(input) {
|
|
51523
|
+
const options = input.options ?? DEFAULT_DEMAND_OPTIONS;
|
|
51524
|
+
const eligibleById = new Map(input.eligible.map((d) => [deviceId(d.nodeId, d.deviceKey), d]));
|
|
51525
|
+
const peakById = new Map(input.peaks.map((p) => [deviceId(p.nodeId, p.deviceKey), p]));
|
|
51526
|
+
const weightByNode = /* @__PURE__ */ new Map();
|
|
51527
|
+
const eligibleCountByNode = /* @__PURE__ */ new Map();
|
|
51528
|
+
for (const d of input.eligible) {
|
|
51529
|
+
weightByNode.set(d.nodeId, (weightByNode.get(d.nodeId) ?? 0) + Math.max(d.weight, 0));
|
|
51530
|
+
eligibleCountByNode.set(d.nodeId, (eligibleCountByNode.get(d.nodeId) ?? 0) + 1);
|
|
51531
|
+
}
|
|
51532
|
+
const pinnedFps = /* @__PURE__ */ new Map();
|
|
51533
|
+
const alwaysOnFps = /* @__PURE__ */ new Map();
|
|
51534
|
+
const alwaysOnCount = /* @__PURE__ */ new Map();
|
|
51535
|
+
const maxFps = /* @__PURE__ */ new Map();
|
|
51536
|
+
const onMotionRates = [];
|
|
51537
|
+
const bump = (map, key, by) => {
|
|
51538
|
+
map.set(key, (map.get(key) ?? 0) + by);
|
|
51539
|
+
};
|
|
51540
|
+
const noteMax = (key, fps) => {
|
|
51541
|
+
maxFps.set(key, Math.max(maxFps.get(key) ?? 0, fps));
|
|
51542
|
+
};
|
|
51543
|
+
for (const cam of input.cameras) {
|
|
51544
|
+
if (!charges(cam)) continue;
|
|
51545
|
+
if (cam.devicePin !== null) {
|
|
51546
|
+
const key = deviceId(cam.devicePin.nodeId, cam.devicePin.deviceKey);
|
|
51547
|
+
if (!eligibleById.has(key)) continue;
|
|
51548
|
+
bump(pinnedFps, key, cam.fps);
|
|
51549
|
+
noteMax(key, cam.fps);
|
|
51550
|
+
continue;
|
|
51551
|
+
}
|
|
51552
|
+
if (cam.mode === "always-on" && cam.standingNodeId !== null) {
|
|
51553
|
+
const nodeWeight = weightByNode.get(cam.standingNodeId) ?? 0;
|
|
51554
|
+
for (const d of input.eligible) {
|
|
51555
|
+
if (d.nodeId !== cam.standingNodeId || nodeWeight <= 0) continue;
|
|
51556
|
+
const key = deviceId(d.nodeId, d.deviceKey);
|
|
51557
|
+
const share = Math.max(d.weight, 0) / nodeWeight;
|
|
51558
|
+
bump(alwaysOnFps, key, cam.fps * share);
|
|
51559
|
+
bump(alwaysOnCount, key, share);
|
|
51560
|
+
noteMax(key, cam.fps);
|
|
51561
|
+
}
|
|
51562
|
+
continue;
|
|
51563
|
+
}
|
|
51564
|
+
onMotionRates.push(cam.fps);
|
|
51565
|
+
}
|
|
51566
|
+
const fastestOnMotion = onMotionRates.reduce((m, r) => Math.max(m, r), 0);
|
|
51567
|
+
const nodeCount = weightByNode.size;
|
|
51568
|
+
const estimatedTotal = estimatedConcurrentSessions(onMotionRates.length, options);
|
|
51569
|
+
return input.eligible.map((d) => {
|
|
51570
|
+
const key = deviceId(d.nodeId, d.deviceKey);
|
|
51571
|
+
const peak = peakById.get(key) ?? (eligibleCountByNode.get(d.nodeId) === 1 ? peakById.get(deviceId(d.nodeId, "")) : void 0);
|
|
51572
|
+
const alwaysOn = alwaysOnCount.get(key) ?? 0;
|
|
51573
|
+
let onMotionSessions = 0;
|
|
51574
|
+
let onMotionSource = "none";
|
|
51575
|
+
if (onMotionRates.length > 0) if (peak !== void 0 && peak.observedForMs >= 864e5) {
|
|
51576
|
+
onMotionSessions = Math.max(0, Math.round(peak.peakSessions - alwaysOn));
|
|
51577
|
+
onMotionSource = "observed-24h";
|
|
51578
|
+
} else {
|
|
51579
|
+
const nodeWeight = weightByNode.get(d.nodeId) ?? 0;
|
|
51580
|
+
const share = nodeCount > 0 && nodeWeight > 0 ? Math.max(d.weight, 0) / nodeWeight / nodeCount : 0;
|
|
51581
|
+
onMotionSessions = Math.ceil(estimatedTotal * share);
|
|
51582
|
+
onMotionSource = "estimate";
|
|
51583
|
+
}
|
|
51584
|
+
const demandFps = (pinnedFps.get(key) ?? 0) + (alwaysOnFps.get(key) ?? 0) + topRatesSum(onMotionRates, onMotionSessions);
|
|
51585
|
+
return {
|
|
51586
|
+
nodeId: d.nodeId,
|
|
51587
|
+
deviceKey: d.deviceKey,
|
|
51588
|
+
demandFps: Math.round(demandFps * 100) / 100,
|
|
51589
|
+
maxCameraFps: Math.max(maxFps.get(key) ?? 0, fastestOnMotion),
|
|
51590
|
+
alwaysOnCameras: Math.round(alwaysOn * 100) / 100,
|
|
51591
|
+
onMotionSessions,
|
|
51592
|
+
onMotionSource,
|
|
51593
|
+
observedForMs: peak?.observedForMs ?? null
|
|
51594
|
+
};
|
|
51595
|
+
}).toSorted((a, b) => a.nodeId === b.nodeId ? a.deviceKey.localeCompare(b.deviceKey) : a.nodeId.localeCompare(b.nodeId));
|
|
51596
|
+
}
|
|
51597
|
+
/** The family the cluster runs. */
|
|
51598
|
+
var BEST_SETTINGS_FAMILY = "yolov9";
|
|
51599
|
+
function round(value, digits = 3) {
|
|
51600
|
+
const f = 10 ** digits;
|
|
51601
|
+
return Math.round(value * f) / f;
|
|
51602
|
+
}
|
|
51603
|
+
function utilisationOf(cell, demand) {
|
|
51604
|
+
return cell.fps > 0 ? round((demand?.demandFps ?? 0) / cell.fps) : Number.POSITIVE_INFINITY;
|
|
51605
|
+
}
|
|
51606
|
+
function fitsOn(cell, demand, headroom) {
|
|
51607
|
+
const budget = cell.fps * (1 - headroom);
|
|
51608
|
+
return budget >= (demand?.demandFps ?? 0) && budget >= (demand?.maxCameraFps ?? 0);
|
|
51609
|
+
}
|
|
51610
|
+
function candidatesFor(contexts, formatsByModel) {
|
|
51611
|
+
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) => ({
|
|
51612
|
+
modelId: id,
|
|
51613
|
+
personRecall: DETECTOR_ACCURACY[id]?.personRecall ?? 0
|
|
51614
|
+
})).toSorted((a, b) => b.personRecall - a.personRecall || a.modelId.localeCompare(b.modelId));
|
|
51615
|
+
}
|
|
51616
|
+
function totalLatency(contexts, modelId) {
|
|
51617
|
+
return contexts.reduce((sum, c) => sum + (c.column[modelId]?.p50Ms ?? Number.POSITIVE_INFINITY), 0);
|
|
51618
|
+
}
|
|
51619
|
+
function worstUtilisation(contexts, modelId) {
|
|
51620
|
+
return contexts.reduce((worst, c) => {
|
|
51621
|
+
const cell = c.column[modelId];
|
|
51622
|
+
return Math.max(worst, cell ? utilisationOf(cell, c.demand) : Number.POSITIVE_INFINITY);
|
|
51623
|
+
}, 0);
|
|
51624
|
+
}
|
|
51625
|
+
/** One model for a group of devices that must agree (step 2–4 of the header). */
|
|
51626
|
+
function pickForGroup(contexts, formatsByModel, headroom) {
|
|
51627
|
+
const candidates = candidatesFor(contexts, formatsByModel);
|
|
51628
|
+
if (candidates.length === 0) return null;
|
|
51629
|
+
const fitting = candidates.filter((c) => contexts.every((ctx) => {
|
|
51630
|
+
const cell = ctx.column[c.modelId];
|
|
51631
|
+
return cell !== void 0 && fitsOn(cell, ctx.demand, headroom);
|
|
51632
|
+
}));
|
|
51633
|
+
if (fitting.length === 0) {
|
|
51634
|
+
const lightest = candidates.toSorted((a, b) => worstUtilisation(contexts, a.modelId) - worstUtilisation(contexts, b.modelId))[0];
|
|
51635
|
+
if (lightest === void 0) return null;
|
|
51636
|
+
return {
|
|
51637
|
+
modelId: lightest.modelId,
|
|
51638
|
+
reasons: [{
|
|
51639
|
+
kind: "no-fit-lightest",
|
|
51640
|
+
utilisation: worstUtilisation(contexts, lightest.modelId)
|
|
51641
|
+
}]
|
|
51642
|
+
};
|
|
51643
|
+
}
|
|
51644
|
+
const best = fitting[0];
|
|
51645
|
+
if (best === void 0) return null;
|
|
51646
|
+
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;
|
|
51647
|
+
const reasons = [];
|
|
51648
|
+
const mostAccurate = candidates[0];
|
|
51649
|
+
if (mostAccurate !== void 0 && mostAccurate.modelId !== best.modelId) {
|
|
51650
|
+
const limitedBy = contexts.find((ctx) => {
|
|
51651
|
+
const cell = ctx.column[mostAccurate.modelId];
|
|
51652
|
+
return cell === void 0 || !fitsOn(cell, ctx.demand, headroom);
|
|
51653
|
+
})?.device.key ?? "";
|
|
51654
|
+
reasons.push({
|
|
51655
|
+
kind: "stepped-down",
|
|
51656
|
+
from: mostAccurate.modelId,
|
|
51657
|
+
to: best.modelId,
|
|
51658
|
+
limitedBy
|
|
51659
|
+
});
|
|
51660
|
+
}
|
|
51661
|
+
if (chosen.modelId !== best.modelId) reasons.push({
|
|
51662
|
+
kind: "tie-faster",
|
|
51663
|
+
over: best.modelId,
|
|
51664
|
+
recallDelta: round(best.personRecall - chosen.personRecall)
|
|
51665
|
+
});
|
|
51666
|
+
return {
|
|
51667
|
+
modelId: chosen.modelId,
|
|
51668
|
+
reasons
|
|
51669
|
+
};
|
|
51670
|
+
}
|
|
51671
|
+
function choiceOn(ctx, modelId) {
|
|
51672
|
+
const cell = ctx.column[modelId];
|
|
51673
|
+
if (cell === void 0) return null;
|
|
51674
|
+
return {
|
|
51675
|
+
modelId,
|
|
51676
|
+
p50Ms: cell.p50Ms,
|
|
51677
|
+
fps: cell.fps,
|
|
51678
|
+
measured: cell.measured,
|
|
51679
|
+
utilisation: utilisationOf(cell, ctx.demand),
|
|
51680
|
+
personRecall: DETECTOR_ACCURACY[modelId]?.personRecall ?? 0
|
|
51681
|
+
};
|
|
51682
|
+
}
|
|
51683
|
+
function deviceReasonsFor(ctx, choice) {
|
|
51684
|
+
const out = [{
|
|
51685
|
+
kind: "fits",
|
|
51686
|
+
utilisation: choice.utilisation
|
|
51687
|
+
}];
|
|
51688
|
+
if (!choice.measured) out.push({
|
|
51689
|
+
kind: "derived-latency",
|
|
51690
|
+
modelId: choice.modelId
|
|
51691
|
+
});
|
|
51692
|
+
if (ctx.demand?.onMotionSource === "estimate") out.push({
|
|
51693
|
+
kind: "estimated-load",
|
|
51694
|
+
observedForMs: ctx.demand.observedForMs
|
|
51695
|
+
});
|
|
51696
|
+
return out;
|
|
51697
|
+
}
|
|
51698
|
+
function skippedNode(node, reason, hardwareClass) {
|
|
51699
|
+
return {
|
|
51700
|
+
nodeId: node.nodeId,
|
|
51701
|
+
reachable: node.reachable,
|
|
51702
|
+
hardwareClass,
|
|
51703
|
+
cpuModel: node.hardware?.cpuModel ?? null,
|
|
51704
|
+
skipped: true,
|
|
51705
|
+
acceleratorModelId: null,
|
|
51706
|
+
devices: node.devices.map((d) => ({
|
|
51707
|
+
deviceKey: d.key,
|
|
51708
|
+
accelerator: node.hardware ? acceleratorKindOf(d.key, node.hardware) : null,
|
|
51709
|
+
enabled: d.enabled,
|
|
51710
|
+
available: d.available,
|
|
51711
|
+
demand: null,
|
|
51712
|
+
chosen: null,
|
|
51713
|
+
reasons: []
|
|
51714
|
+
})),
|
|
51715
|
+
reasons: [reason]
|
|
51716
|
+
};
|
|
51717
|
+
}
|
|
51718
|
+
/** Recommend one node. See the module header. */
|
|
51719
|
+
function recommendNode(input) {
|
|
51720
|
+
const { node } = input;
|
|
51721
|
+
const headroom = input.headroom ?? .4;
|
|
51722
|
+
const perf = input.perf ?? DETECTOR_PERF;
|
|
51723
|
+
if (!node.reachable) return skippedNode(node, { kind: "node-offline" }, null);
|
|
51724
|
+
if (node.hardware === null) return skippedNode(node, { kind: "no-hardware-facts" }, null);
|
|
51725
|
+
const hardware = node.hardware;
|
|
51726
|
+
const { hardwareClass, cpuModel } = classifyInferenceHardware(hardware);
|
|
51727
|
+
const classTable = perf[hardwareClass];
|
|
51728
|
+
if (hardwareClass === "unknown" || classTable === void 0) return skippedNode(node, {
|
|
51729
|
+
kind: "no-measured-data",
|
|
51730
|
+
hardwareClass
|
|
51731
|
+
}, hardwareClass);
|
|
51732
|
+
const demandByKey = new Map(input.demand.filter((d) => d.nodeId === node.nodeId).map((d) => [d.deviceKey, d]));
|
|
51733
|
+
const devices = node.devices.toSorted((a, b) => a.key.localeCompare(b.key));
|
|
51734
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
51735
|
+
const early = /* @__PURE__ */ new Map();
|
|
51736
|
+
for (const device of devices) {
|
|
51737
|
+
const accelerator = acceleratorKindOf(device.key, hardware);
|
|
51738
|
+
if (accelerator === "coral") {
|
|
51739
|
+
early.set(device.key, device.enabled ? [{ kind: "coral-untouched" }] : []);
|
|
51740
|
+
continue;
|
|
51741
|
+
}
|
|
51742
|
+
if (!device.enabled) {
|
|
51743
|
+
early.set(device.key, [{ kind: "device-disabled" }]);
|
|
51744
|
+
continue;
|
|
51745
|
+
}
|
|
51746
|
+
if (!device.available) {
|
|
51747
|
+
early.set(device.key, [{ kind: "device-unavailable" }]);
|
|
51748
|
+
continue;
|
|
51749
|
+
}
|
|
51750
|
+
const column = accelerator === null ? void 0 : classTable[accelerator];
|
|
51751
|
+
if (accelerator === null || column === void 0) {
|
|
51752
|
+
early.set(device.key, [{
|
|
51753
|
+
kind: "no-family-build",
|
|
51754
|
+
format: device.format
|
|
51755
|
+
}]);
|
|
51756
|
+
continue;
|
|
51757
|
+
}
|
|
51758
|
+
contexts.set(device.key, {
|
|
51759
|
+
device,
|
|
51760
|
+
accelerator,
|
|
51761
|
+
column,
|
|
51762
|
+
demand: demandByKey.get(device.key) ?? null
|
|
51763
|
+
});
|
|
51764
|
+
}
|
|
51765
|
+
const isCpu = (a) => a === "x86-cpu" || a === "arm-cpu";
|
|
51766
|
+
const accelerators = [...contexts.values()].filter((c) => !isCpu(c.accelerator));
|
|
51767
|
+
const cpus = [...contexts.values()].filter((c) => isCpu(c.accelerator));
|
|
51768
|
+
const chosenByKey = /* @__PURE__ */ new Map();
|
|
51769
|
+
const reasonsByKey = /* @__PURE__ */ new Map();
|
|
51770
|
+
const nodeReasons = [];
|
|
51771
|
+
let acceleratorModelId = null;
|
|
51772
|
+
const groupPick = accelerators.length > 0 ? pickForGroup(accelerators, node.formatsByModel, headroom) : null;
|
|
51773
|
+
if (groupPick !== null) {
|
|
51774
|
+
acceleratorModelId = groupPick.modelId;
|
|
51775
|
+
nodeReasons.push(...groupPick.reasons);
|
|
51776
|
+
for (const ctx of accelerators) {
|
|
51777
|
+
const choice = choiceOn(ctx, groupPick.modelId);
|
|
51778
|
+
if (choice === null) continue;
|
|
51779
|
+
chosenByKey.set(ctx.device.key, choice);
|
|
51780
|
+
reasonsByKey.set(ctx.device.key, deviceReasonsFor(ctx, choice));
|
|
51781
|
+
}
|
|
51782
|
+
} else for (const ctx of accelerators) reasonsByKey.set(ctx.device.key, [{
|
|
51783
|
+
kind: "no-family-build",
|
|
51784
|
+
format: ctx.device.format
|
|
51785
|
+
}]);
|
|
51786
|
+
for (const ctx of cpus) {
|
|
51787
|
+
const pick = pickForGroup([ctx], node.formatsByModel, headroom);
|
|
51788
|
+
const choice = pick === null ? null : choiceOn(ctx, pick.modelId);
|
|
51789
|
+
if (pick === null || choice === null) {
|
|
51790
|
+
reasonsByKey.set(ctx.device.key, [{
|
|
51791
|
+
kind: "no-family-build",
|
|
51792
|
+
format: ctx.device.format
|
|
51793
|
+
}]);
|
|
51794
|
+
continue;
|
|
51795
|
+
}
|
|
51796
|
+
chosenByKey.set(ctx.device.key, choice);
|
|
51797
|
+
reasonsByKey.set(ctx.device.key, [
|
|
51798
|
+
{ kind: "cpu-device" },
|
|
51799
|
+
...pick.reasons,
|
|
51800
|
+
...deviceReasonsFor(ctx, choice)
|
|
51801
|
+
]);
|
|
51802
|
+
}
|
|
51803
|
+
return {
|
|
51804
|
+
nodeId: node.nodeId,
|
|
51805
|
+
reachable: true,
|
|
51806
|
+
hardwareClass,
|
|
51807
|
+
cpuModel,
|
|
51808
|
+
skipped: chosenByKey.size === 0,
|
|
51809
|
+
acceleratorModelId,
|
|
51810
|
+
devices: devices.map((device) => ({
|
|
51811
|
+
deviceKey: device.key,
|
|
51812
|
+
accelerator: acceleratorKindOf(device.key, hardware),
|
|
51813
|
+
enabled: device.enabled,
|
|
51814
|
+
available: device.available,
|
|
51815
|
+
demand: demandByKey.get(device.key) ?? null,
|
|
51816
|
+
chosen: chosenByKey.get(device.key) ?? null,
|
|
51817
|
+
reasons: early.get(device.key) ?? reasonsByKey.get(device.key) ?? []
|
|
51818
|
+
})),
|
|
51819
|
+
reasons: nodeReasons
|
|
51820
|
+
};
|
|
51821
|
+
}
|
|
50687
51822
|
/**
|
|
50688
51823
|
* THE detail-crop convention — the single derivation of the rectangle a
|
|
50689
51824
|
* detail/enrichment step (clip-embedding, face-detection, plate-detection…)
|
|
@@ -51458,7 +52593,7 @@ var AudioSubscriptionController = class {
|
|
|
51458
52593
|
} catch (err) {
|
|
51459
52594
|
this.deps.logger.error("audio re-attach failed", {
|
|
51460
52595
|
tags: { deviceId },
|
|
51461
|
-
meta: { error: errMsg$
|
|
52596
|
+
meta: { error: errMsg$2(err) }
|
|
51462
52597
|
});
|
|
51463
52598
|
}
|
|
51464
52599
|
}
|
|
@@ -51569,7 +52704,7 @@ var AudioSubscriptionController = class {
|
|
|
51569
52704
|
this.closeMotionAudioWindow(deviceId, "motion-cooldown-elapsed").catch((err) => {
|
|
51570
52705
|
this.deps.logger.warn("lazy audio: cooldown teardown failed", {
|
|
51571
52706
|
tags: { deviceId },
|
|
51572
|
-
meta: { error: errMsg$
|
|
52707
|
+
meta: { error: errMsg$2(err) }
|
|
51573
52708
|
});
|
|
51574
52709
|
});
|
|
51575
52710
|
}, cooldownMs);
|
|
@@ -51588,7 +52723,7 @@ var AudioSubscriptionController = class {
|
|
|
51588
52723
|
this.closeMotionAudioWindow(deviceId, "quiet-window-elapsed").catch((err) => {
|
|
51589
52724
|
this.deps.logger.warn("lazy audio: quiet-window teardown failed", {
|
|
51590
52725
|
tags: { deviceId },
|
|
51591
|
-
meta: { error: errMsg$
|
|
52726
|
+
meta: { error: errMsg$2(err) }
|
|
51592
52727
|
});
|
|
51593
52728
|
});
|
|
51594
52729
|
}, windowMs);
|
|
@@ -51790,7 +52925,7 @@ var AudioSubscriptionController = class {
|
|
|
51790
52925
|
meta: {
|
|
51791
52926
|
audioNodeId,
|
|
51792
52927
|
brokerId: audioBrokerId,
|
|
51793
|
-
error: errMsg$
|
|
52928
|
+
error: errMsg$2(err)
|
|
51794
52929
|
}
|
|
51795
52930
|
});
|
|
51796
52931
|
});
|
|
@@ -51865,7 +53000,7 @@ async function readOrRefuse(deps, deviceId) {
|
|
|
51865
53000
|
try {
|
|
51866
53001
|
device = await deps.readDevice(deviceId);
|
|
51867
53002
|
} catch (err) {
|
|
51868
|
-
return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$
|
|
53003
|
+
return refuse(deps, deviceId, `could not read device ${deviceId}: ${errMsg$2(err)}`);
|
|
51869
53004
|
}
|
|
51870
53005
|
if (device === null) return refuse(deps, deviceId, `no device ${deviceId}`);
|
|
51871
53006
|
return device;
|
|
@@ -53195,7 +54330,7 @@ var LoadShedController = class LoadShedController {
|
|
|
53195
54330
|
if (await this.tryClusterRelocate(deviceId).catch((err) => {
|
|
53196
54331
|
this.deps.logger.warn("Load management: cluster relocate failed", {
|
|
53197
54332
|
tags: { deviceId },
|
|
53198
|
-
meta: { error: errMsg$
|
|
54333
|
+
meta: { error: errMsg$2(err) }
|
|
53199
54334
|
});
|
|
53200
54335
|
return false;
|
|
53201
54336
|
})) {
|
|
@@ -53209,7 +54344,7 @@ var LoadShedController = class LoadShedController {
|
|
|
53209
54344
|
this.deps.api()?.pipelineRunner.detachCamera.mutate({ deviceId }).catch((err) => {
|
|
53210
54345
|
this.deps.logger.warn("Load management: detachCamera failed", {
|
|
53211
54346
|
tags: { deviceId },
|
|
53212
|
-
meta: { error: errMsg$
|
|
54347
|
+
meta: { error: errMsg$2(err) }
|
|
53213
54348
|
});
|
|
53214
54349
|
});
|
|
53215
54350
|
this.ensureLoadShedResumeTimer();
|
|
@@ -53241,7 +54376,7 @@ var LoadShedController = class LoadShedController {
|
|
|
53241
54376
|
tags: { deviceId },
|
|
53242
54377
|
meta: {
|
|
53243
54378
|
from: currentNode,
|
|
53244
|
-
error: errMsg$
|
|
54379
|
+
error: errMsg$2(err)
|
|
53245
54380
|
}
|
|
53246
54381
|
});
|
|
53247
54382
|
});
|
|
@@ -53284,7 +54419,7 @@ var LoadShedController = class LoadShedController {
|
|
|
53284
54419
|
this.deps.redispatchSingleCamera(deviceId).catch((err) => {
|
|
53285
54420
|
this.deps.logger.warn("Load management: auto-resume dispatch failed", {
|
|
53286
54421
|
tags: { deviceId },
|
|
53287
|
-
meta: { error: errMsg$
|
|
54422
|
+
meta: { error: errMsg$2(err) }
|
|
53288
54423
|
});
|
|
53289
54424
|
});
|
|
53290
54425
|
}
|
|
@@ -53449,7 +54584,7 @@ var AgentLoadService = class AgentLoadService {
|
|
|
53449
54584
|
ms,
|
|
53450
54585
|
outcome: "failed",
|
|
53451
54586
|
load: null,
|
|
53452
|
-
error: errMsg$
|
|
54587
|
+
error: errMsg$2(outcome.error)
|
|
53453
54588
|
};
|
|
53454
54589
|
return {
|
|
53455
54590
|
nodeId,
|
|
@@ -53916,7 +55051,7 @@ function buildCameraSensorsPorts(api, logger) {
|
|
|
53916
55051
|
} catch (err) {
|
|
53917
55052
|
return {
|
|
53918
55053
|
ok: false,
|
|
53919
|
-
error: errMsg$
|
|
55054
|
+
error: errMsg$2(err)
|
|
53920
55055
|
};
|
|
53921
55056
|
}
|
|
53922
55057
|
};
|
|
@@ -54180,7 +55315,7 @@ var CameraSensorsController = class {
|
|
|
54180
55315
|
async #readFleet() {
|
|
54181
55316
|
const read = await this.#deps.listCameraIds().catch((err) => ({
|
|
54182
55317
|
kind: "unreadable",
|
|
54183
|
-
error: errMsg$
|
|
55318
|
+
error: errMsg$2(err)
|
|
54184
55319
|
}));
|
|
54185
55320
|
if (read.kind === "unreadable") {
|
|
54186
55321
|
this.#deps.logger.warn("camera-sensors: camera list unreadable — mappings stay unloaded", { meta: { error: read.error } });
|
|
@@ -54207,7 +55342,7 @@ var CameraSensorsController = class {
|
|
|
54207
55342
|
} catch (err) {
|
|
54208
55343
|
this.#deps.logger.error("camera-sensors: refresh failed — nothing applied", {
|
|
54209
55344
|
tags: { deviceId: cameraId },
|
|
54210
|
-
meta: { error: errMsg$
|
|
55345
|
+
meta: { error: errMsg$2(err) }
|
|
54211
55346
|
});
|
|
54212
55347
|
}
|
|
54213
55348
|
}
|
|
@@ -54300,7 +55435,7 @@ async function readCameraSensorsSetting(read) {
|
|
|
54300
55435
|
} catch (err) {
|
|
54301
55436
|
return {
|
|
54302
55437
|
state: "unreadable",
|
|
54303
|
-
reason: `settings read failed — ${errMsg$
|
|
55438
|
+
reason: `settings read failed — ${errMsg$2(err)}`
|
|
54304
55439
|
};
|
|
54305
55440
|
}
|
|
54306
55441
|
}
|
|
@@ -54610,7 +55745,7 @@ var CameraMotionSlice = class {
|
|
|
54610
55745
|
st.chain = st.chain.then(job).catch((err) => {
|
|
54611
55746
|
this.#deps.logger.error("camera motion slice: write job failed", {
|
|
54612
55747
|
tags: { deviceId },
|
|
54613
|
-
meta: { error: errMsg$
|
|
55748
|
+
meta: { error: errMsg$2(err) }
|
|
54614
55749
|
});
|
|
54615
55750
|
});
|
|
54616
55751
|
}
|
|
@@ -54674,7 +55809,7 @@ var CameraMotionSlice = class {
|
|
|
54674
55809
|
meta: {
|
|
54675
55810
|
detected: slice.detected,
|
|
54676
55811
|
reason,
|
|
54677
|
-
error: errMsg$
|
|
55812
|
+
error: errMsg$2(err)
|
|
54678
55813
|
}
|
|
54679
55814
|
});
|
|
54680
55815
|
return;
|
|
@@ -54704,7 +55839,7 @@ var CameraMotionSlice = class {
|
|
|
54704
55839
|
async #republishAll() {
|
|
54705
55840
|
const listed = await this.#deps.listCameraIds().catch((err) => ({
|
|
54706
55841
|
kind: "unreadable",
|
|
54707
|
-
error: errMsg$
|
|
55842
|
+
error: errMsg$2(err)
|
|
54708
55843
|
}));
|
|
54709
55844
|
if (!this.#open || this.#stopped) return;
|
|
54710
55845
|
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 } });
|
|
@@ -54826,7 +55961,7 @@ function hubMotionSliceStore(api) {
|
|
|
54826
55961
|
} catch (err) {
|
|
54827
55962
|
return {
|
|
54828
55963
|
kind: "unreadable",
|
|
54829
|
-
error: errMsg$
|
|
55964
|
+
error: errMsg$2(err)
|
|
54830
55965
|
};
|
|
54831
55966
|
}
|
|
54832
55967
|
}
|
|
@@ -56698,7 +57833,7 @@ function warnSampled(deps, deviceId, source, message, meta) {
|
|
|
56698
57833
|
function warnUnreachable(deps, deviceId, source, err) {
|
|
56699
57834
|
warnSampled(deps, deviceId, source, UNREACHABLE_MESSAGE, {
|
|
56700
57835
|
source,
|
|
56701
|
-
error: errMsg$
|
|
57836
|
+
error: errMsg$2(err)
|
|
56702
57837
|
});
|
|
56703
57838
|
}
|
|
56704
57839
|
/**
|
|
@@ -57972,7 +59107,7 @@ var ONBOARD_DETECTION_SOURCE = "onboard";
|
|
|
57972
59107
|
* configs, and it reads them the same way the runner does.
|
|
57973
59108
|
*/
|
|
57974
59109
|
var DETECTION_SOURCES_WHEN_ABSENT = ["pipeline"];
|
|
57975
|
-
function errMsg(err) {
|
|
59110
|
+
function errMsg$1(err) {
|
|
57976
59111
|
return err instanceof Error ? err.message : String(err);
|
|
57977
59112
|
}
|
|
57978
59113
|
/**
|
|
@@ -57991,7 +59126,7 @@ async function reconcileOnboardForwarding(input, deps) {
|
|
|
57991
59126
|
log.warn("onboard forwarding not reconciled — could not tell whether the camera has the cap", { meta: {
|
|
57992
59127
|
wantsOnboard,
|
|
57993
59128
|
detectionSources: [...detectionSources],
|
|
57994
|
-
error: errMsg(err)
|
|
59129
|
+
error: errMsg$1(err)
|
|
57995
59130
|
} });
|
|
57996
59131
|
return {
|
|
57997
59132
|
kind: "read-failed",
|
|
@@ -58009,7 +59144,7 @@ async function reconcileOnboardForwarding(input, deps) {
|
|
|
58009
59144
|
log.warn("onboard forwarding not reconciled — the vendor flag could not be read", { meta: {
|
|
58010
59145
|
wantsOnboard,
|
|
58011
59146
|
detectionSources: [...detectionSources],
|
|
58012
|
-
error: errMsg(err)
|
|
59147
|
+
error: errMsg$1(err)
|
|
58013
59148
|
} });
|
|
58014
59149
|
return {
|
|
58015
59150
|
kind: "read-failed",
|
|
@@ -58027,7 +59162,7 @@ async function reconcileOnboardForwarding(input, deps) {
|
|
|
58027
59162
|
was: current,
|
|
58028
59163
|
wanted: wantsOnboard,
|
|
58029
59164
|
detectionSources: [...detectionSources],
|
|
58030
|
-
error: errMsg(err)
|
|
59165
|
+
error: errMsg$1(err)
|
|
58031
59166
|
} });
|
|
58032
59167
|
return {
|
|
58033
59168
|
kind: "write-failed",
|
|
@@ -58223,7 +59358,7 @@ var DetectionWiringController = class {
|
|
|
58223
59358
|
try {
|
|
58224
59359
|
raw = await this.deps.ctx().settings.readDeviceStore(deviceId);
|
|
58225
59360
|
} catch (err) {
|
|
58226
|
-
const msg = errMsg$
|
|
59361
|
+
const msg = errMsg$2(err);
|
|
58227
59362
|
this.deps.logger.error("resolveDeviceDetectionSettings failed", {
|
|
58228
59363
|
tags: { deviceId },
|
|
58229
59364
|
meta: { error: msg }
|
|
@@ -58254,7 +59389,7 @@ var DetectionWiringController = class {
|
|
|
58254
59389
|
motionDetectionEnabled
|
|
58255
59390
|
});
|
|
58256
59391
|
} catch (err) {
|
|
58257
|
-
const msg = errMsg$
|
|
59392
|
+
const msg = errMsg$2(err);
|
|
58258
59393
|
this.deps.logger.error("resolveDeviceDetectionSettings narrowing failed", {
|
|
58259
59394
|
tags: { deviceId },
|
|
58260
59395
|
meta: { error: msg }
|
|
@@ -58289,7 +59424,7 @@ var DetectionWiringController = class {
|
|
|
58289
59424
|
} catch (err) {
|
|
58290
59425
|
this.deps.logger.debug("fetchAssignedProfiles: slot read failed (transient) — treating as unknown", {
|
|
58291
59426
|
tags: { deviceId },
|
|
58292
|
-
meta: { error: errMsg$
|
|
59427
|
+
meta: { error: errMsg$2(err) }
|
|
58293
59428
|
});
|
|
58294
59429
|
return null;
|
|
58295
59430
|
}
|
|
@@ -58365,7 +59500,7 @@ var DetectionWiringController = class {
|
|
|
58365
59500
|
if (config.motionSources.includes("analyzer")) try {
|
|
58366
59501
|
await this.deps.session.ensureMotionWatch(deviceId, config);
|
|
58367
59502
|
} catch (err) {
|
|
58368
|
-
log.error("ensureMotionWatch failed", { meta: { error: errMsg$
|
|
59503
|
+
log.error("ensureMotionWatch failed", { meta: { error: errMsg$2(err) } });
|
|
58369
59504
|
}
|
|
58370
59505
|
log.info("detection armed (session-time) — watching for motion", { meta: { motion: config.motionSources.join("+") } });
|
|
58371
59506
|
} else {
|
|
@@ -58386,14 +59521,14 @@ var DetectionWiringController = class {
|
|
|
58386
59521
|
try {
|
|
58387
59522
|
await this.deps.placement.dispatchCamera(runnerConfig);
|
|
58388
59523
|
} catch (err) {
|
|
58389
|
-
const msg = errMsg$
|
|
59524
|
+
const msg = errMsg$2(err);
|
|
58390
59525
|
log.error("dispatchCamera failed", { meta: { error: msg } });
|
|
58391
59526
|
}
|
|
58392
59527
|
}
|
|
58393
59528
|
try {
|
|
58394
59529
|
await this.deps.audio.resubscribe(deviceId, config);
|
|
58395
59530
|
} catch (err) {
|
|
58396
|
-
const msg = errMsg$
|
|
59531
|
+
const msg = errMsg$2(err);
|
|
58397
59532
|
log.error("subscribeAudioStream failed", { meta: { error: msg } });
|
|
58398
59533
|
}
|
|
58399
59534
|
if (config.detectionMode !== "on-motion") log.info("Detection dispatched", { meta: {
|
|
@@ -58409,7 +59544,7 @@ var DetectionWiringController = class {
|
|
|
58409
59544
|
try {
|
|
58410
59545
|
await this.deps.placement.releaseCamera({ deviceId });
|
|
58411
59546
|
} catch (err) {
|
|
58412
|
-
const msg = errMsg$
|
|
59547
|
+
const msg = errMsg$2(err);
|
|
58413
59548
|
this.deps.logger.error("releaseCamera failed", {
|
|
58414
59549
|
tags: { deviceId },
|
|
58415
59550
|
meta: { error: msg }
|
|
@@ -58435,7 +59570,7 @@ var DetectionWiringController = class {
|
|
|
58435
59570
|
} catch (err) {
|
|
58436
59571
|
this.deps.logger.warn("failed to clear per-device orchestrator settings on removal", {
|
|
58437
59572
|
tags: { deviceId },
|
|
58438
|
-
meta: { error: errMsg$
|
|
59573
|
+
meta: { error: errMsg$2(err) }
|
|
58439
59574
|
});
|
|
58440
59575
|
}
|
|
58441
59576
|
this.deps.logger.info("Purged pipeline config for removed device", { tags: { deviceId } });
|
|
@@ -58459,7 +59594,7 @@ var DetectionWiringController = class {
|
|
|
58459
59594
|
} catch (err) {
|
|
58460
59595
|
this.deps.logger.debug("device existence check failed — proceeding as before", {
|
|
58461
59596
|
tags: { deviceId },
|
|
58462
|
-
meta: { error: errMsg$
|
|
59597
|
+
meta: { error: errMsg$2(err) }
|
|
58463
59598
|
});
|
|
58464
59599
|
return false;
|
|
58465
59600
|
}
|
|
@@ -58576,7 +59711,7 @@ var DetectionWiringController = class {
|
|
|
58576
59711
|
await this.deps.attachOn(assignment.agentNodeId, nextConfig);
|
|
58577
59712
|
log.info("pipeline.camera-updated: hot-reloaded", { tags: { nodeId: assignment.agentNodeId } });
|
|
58578
59713
|
} catch (err) {
|
|
58579
|
-
const msg = errMsg$
|
|
59714
|
+
const msg = errMsg$2(err);
|
|
58580
59715
|
log.error("pipeline.camera-updated: attachOn failed", {
|
|
58581
59716
|
tags: { nodeId: assignment.agentNodeId },
|
|
58582
59717
|
meta: { error: msg }
|
|
@@ -58652,7 +59787,7 @@ var DetectionWiringController = class {
|
|
|
58652
59787
|
this.deps.audio.resubscribe(deviceId, audioCfg).catch((err) => {
|
|
58653
59788
|
this.deps.logger.warn("watchdog audio re-subscribe failed", {
|
|
58654
59789
|
tags: { deviceId },
|
|
58655
|
-
meta: { error: errMsg$
|
|
59790
|
+
meta: { error: errMsg$2(err) }
|
|
58656
59791
|
});
|
|
58657
59792
|
});
|
|
58658
59793
|
}
|
|
@@ -58851,7 +59986,7 @@ var DeviceConfigContributions = class {
|
|
|
58851
59986
|
issue: configIssue
|
|
58852
59987
|
} });
|
|
58853
59988
|
this.deps.topology.refreshNodeCapabilities().then(() => this.deps.redispatchAllActiveCameras("ingest-owner-changed")).catch((err) => {
|
|
58854
|
-
this.deps.ctx().logger.warn("ingest-owner-changed re-dispatch failed", { meta: { error: errMsg$
|
|
59989
|
+
this.deps.ctx().logger.warn("ingest-owner-changed re-dispatch failed", { meta: { error: errMsg$2(err) } });
|
|
58855
59990
|
});
|
|
58856
59991
|
}
|
|
58857
59992
|
this.deps.clearOnSettingsChange();
|
|
@@ -59305,10 +60440,25 @@ function rootRefusal(input, droppedRoots) {
|
|
|
59305
60440
|
droppedRoots
|
|
59306
60441
|
};
|
|
59307
60442
|
}
|
|
60443
|
+
/**
|
|
60444
|
+
* Whose provisioning an attach applies. The elected device when there is one.
|
|
60445
|
+
* With NO elected device the runner's default pool serves the camera; when the
|
|
60446
|
+
* node has exactly one eligible device the balancer returns
|
|
60447
|
+
* `single-eligible-device` and that default pool IS that device (a Mac's
|
|
60448
|
+
* `coreml:all`, an N100 with only its iGPU on), so its model pin and the
|
|
60449
|
+
* camera's override stored under its key apply (D745 — before, the empty key
|
|
60450
|
+
* was read, which no store writes, and the pin was silently never run).
|
|
60451
|
+
* Otherwise `''`: the node's folded settings with no hardware fields.
|
|
60452
|
+
*/
|
|
60453
|
+
function provisioningKeyFor(input) {
|
|
60454
|
+
if (input.deviceKey !== void 0) return input.deviceKey;
|
|
60455
|
+
const eligibleKeys = Object.keys(input.eligible);
|
|
60456
|
+
return eligibleKeys.length === 1 ? eligibleKeys[0] ?? "" : "";
|
|
60457
|
+
}
|
|
59308
60458
|
/** Steps 3 of the module header: provision the resolved structure. */
|
|
59309
60459
|
async function provisionAttachPipeline(input, roster, resolved, store) {
|
|
59310
60460
|
const { deviceId, nodeId, deviceKey } = input;
|
|
59311
|
-
const electedKey =
|
|
60461
|
+
const electedKey = provisioningKeyFor(input);
|
|
59312
60462
|
const rosterKeys = (roster ?? []).map((d) => d.deviceKey);
|
|
59313
60463
|
const sources = await store.readDeviceProvisioning(deviceId, nodeId, [...new Set([electedKey, ...rosterKeys])]);
|
|
59314
60464
|
const elected = sources.get(electedKey);
|
|
@@ -59916,7 +61066,7 @@ var PlacementService = class {
|
|
|
59916
61066
|
async releaseCamera(input) {
|
|
59917
61067
|
const current = this.deps.ledger.getAssignment(input.deviceId);
|
|
59918
61068
|
if (current) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
|
|
59919
|
-
const msg = errMsg$
|
|
61069
|
+
const msg = errMsg$2(err);
|
|
59920
61070
|
this.deps.ctx().logger.debug("releaseCamera detach failed", {
|
|
59921
61071
|
tags: { deviceId: input.deviceId },
|
|
59922
61072
|
meta: { error: msg }
|
|
@@ -59944,7 +61094,7 @@ var PlacementService = class {
|
|
|
59944
61094
|
pipelineNodeId: input.agentNodeId,
|
|
59945
61095
|
[PREFERRED_AGENT_SETTING]: null
|
|
59946
61096
|
}).catch((err) => {
|
|
59947
|
-
const msg = errMsg$
|
|
61097
|
+
const msg = errMsg$2(err);
|
|
59948
61098
|
ctx.logger.warn("assignPipeline: failed to persist pin", {
|
|
59949
61099
|
tags: { deviceId: input.deviceId },
|
|
59950
61100
|
meta: { error: msg }
|
|
@@ -59976,7 +61126,7 @@ var PlacementService = class {
|
|
|
59976
61126
|
}
|
|
59977
61127
|
const current = this.deps.ledger.getAssignment(input.deviceId);
|
|
59978
61128
|
if (current && current.agentNodeId !== input.agentNodeId) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
|
|
59979
|
-
const msg = errMsg$
|
|
61129
|
+
const msg = errMsg$2(err);
|
|
59980
61130
|
ctx.logger.debug("assignPipeline detach-old failed", {
|
|
59981
61131
|
tags: { deviceId: input.deviceId },
|
|
59982
61132
|
meta: { error: msg }
|
|
@@ -60001,7 +61151,7 @@ var PlacementService = class {
|
|
|
60001
61151
|
await ctx.settings?.writeDeviceStore(input.deviceId, { pipelineDeviceKey: clearing ? "auto" : input.deviceKey }).catch((err) => {
|
|
60002
61152
|
ctx.logger.warn("setPipelineDevicePin: failed to persist device pin", {
|
|
60003
61153
|
tags: { deviceId: input.deviceId },
|
|
60004
|
-
meta: { error: errMsg$
|
|
61154
|
+
meta: { error: errMsg$2(err) }
|
|
60005
61155
|
});
|
|
60006
61156
|
});
|
|
60007
61157
|
const sessionNode = this.deps.activeSessionNode(input.deviceId);
|
|
@@ -60024,7 +61174,7 @@ var PlacementService = class {
|
|
|
60024
61174
|
[PREFERRED_AGENT_SETTING]: null,
|
|
60025
61175
|
pipelineNodeId: "auto"
|
|
60026
61176
|
}).catch((err) => {
|
|
60027
|
-
const msg = errMsg$
|
|
61177
|
+
const msg = errMsg$2(err);
|
|
60028
61178
|
ctx.logger.warn("unassignPipeline: failed to clear pin", {
|
|
60029
61179
|
tags: { deviceId: input.deviceId },
|
|
60030
61180
|
meta: { error: msg }
|
|
@@ -60045,7 +61195,7 @@ var PlacementService = class {
|
|
|
60045
61195
|
else {
|
|
60046
61196
|
const targetNodeId = decision.agentNodeId;
|
|
60047
61197
|
if (current && current.agentNodeId !== targetNodeId) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
|
|
60048
|
-
const msg = errMsg$
|
|
61198
|
+
const msg = errMsg$2(err);
|
|
60049
61199
|
ctx.logger.debug("unassignPipeline detach-old failed", {
|
|
60050
61200
|
tags: { deviceId: input.deviceId },
|
|
60051
61201
|
meta: { error: msg }
|
|
@@ -60065,7 +61215,7 @@ var PlacementService = class {
|
|
|
60065
61215
|
}
|
|
60066
61216
|
}
|
|
60067
61217
|
if (current) await this.detachOn(current.agentNodeId, input.deviceId).catch((err) => {
|
|
60068
|
-
const msg = errMsg$
|
|
61218
|
+
const msg = errMsg$2(err);
|
|
60069
61219
|
ctx.logger.debug("unassignPipeline detach failed", {
|
|
60070
61220
|
tags: { deviceId: input.deviceId },
|
|
60071
61221
|
meta: { error: msg }
|
|
@@ -60084,7 +61234,7 @@ var PlacementService = class {
|
|
|
60084
61234
|
async runAutoRebalance() {
|
|
60085
61235
|
if (!this.deps.ctxIfReady()) return;
|
|
60086
61236
|
await this.deps.reconcilePlacementFromRunners().catch((err) => {
|
|
60087
|
-
this.deps.ctx().logger.debug("auto-rebalance: attachment reconcile failed", { meta: { error: errMsg$
|
|
61237
|
+
this.deps.ctx().logger.debug("auto-rebalance: attachment reconcile failed", { meta: { error: errMsg$2(err) } });
|
|
60088
61238
|
});
|
|
60089
61239
|
if (this.autoRebalanceInFlight) return;
|
|
60090
61240
|
if (this.deps.topology.enabledNodes.length < 2) return;
|
|
@@ -60093,7 +61243,7 @@ var PlacementService = class {
|
|
|
60093
61243
|
const { migrated } = await this.rebalance({ minImprovement: AUTO_REBALANCE_MIN_IMPROVEMENT });
|
|
60094
61244
|
if (migrated > 0) this.deps.ctx().logger.info("auto-rebalance: corrected placement drift", { meta: { migrated } });
|
|
60095
61245
|
} catch (err) {
|
|
60096
|
-
this.deps.ctx().logger.debug("auto-rebalance pass failed", { meta: { error: errMsg$
|
|
61246
|
+
this.deps.ctx().logger.debug("auto-rebalance pass failed", { meta: { error: errMsg$2(err) } });
|
|
60097
61247
|
} finally {
|
|
60098
61248
|
this.autoRebalanceInFlight = false;
|
|
60099
61249
|
}
|
|
@@ -60165,7 +61315,7 @@ var PlacementService = class {
|
|
|
60165
61315
|
const targetConfig = withAttachPipeline(config, placement.pipeline);
|
|
60166
61316
|
if (current) {
|
|
60167
61317
|
await this.detachOn(current.agentNodeId, deviceId).catch((err) => {
|
|
60168
|
-
const msg = errMsg$
|
|
61318
|
+
const msg = errMsg$2(err);
|
|
60169
61319
|
ctx.logger.debug("rebalance detach-old failed", {
|
|
60170
61320
|
tags: { deviceId },
|
|
60171
61321
|
meta: { error: msg }
|
|
@@ -60570,7 +61720,7 @@ var NodeLifecycleHandler = class {
|
|
|
60570
61720
|
nodeId: decision.agentNodeId
|
|
60571
61721
|
} });
|
|
60572
61722
|
} catch (err) {
|
|
60573
|
-
const msg = errMsg$
|
|
61723
|
+
const msg = errMsg$2(err);
|
|
60574
61724
|
this.deps.logger.error("Failover: attach failed", {
|
|
60575
61725
|
tags: {
|
|
60576
61726
|
deviceId,
|
|
@@ -60600,7 +61750,7 @@ var NodeLifecycleHandler = class {
|
|
|
60600
61750
|
this.deps.schedulePendingRetry();
|
|
60601
61751
|
if (failoverPolicy.onReconnect === "rebalance") {
|
|
60602
61752
|
await this.deps.rebalance().catch((err) => {
|
|
60603
|
-
const msg = errMsg$
|
|
61753
|
+
const msg = errMsg$2(err);
|
|
60604
61754
|
this.deps.logger.error("rebalance after reconnect failed", { meta: { error: msg } });
|
|
60605
61755
|
});
|
|
60606
61756
|
return;
|
|
@@ -60626,7 +61776,7 @@ var NodeLifecycleHandler = class {
|
|
|
60626
61776
|
this.deps.ledger.recordAssignment(deviceId, nodeId, "failover", false);
|
|
60627
61777
|
restored++;
|
|
60628
61778
|
} catch (err) {
|
|
60629
|
-
const msg = errMsg$
|
|
61779
|
+
const msg = errMsg$2(err);
|
|
60630
61780
|
this.deps.logger.error("restore attach failed", {
|
|
60631
61781
|
tags: {
|
|
60632
61782
|
deviceId,
|
|
@@ -60720,7 +61870,7 @@ var NodeLifecycleHandler = class {
|
|
|
60720
61870
|
deviceId,
|
|
60721
61871
|
nodeId
|
|
60722
61872
|
},
|
|
60723
|
-
meta: { error: errMsg$
|
|
61873
|
+
meta: { error: errMsg$2(err) }
|
|
60724
61874
|
});
|
|
60725
61875
|
}
|
|
60726
61876
|
this.deps.logger.info("readiness: detection-pipeline reseeded agent defaults + redispatched cameras", {
|
|
@@ -60734,7 +61884,7 @@ var NodeLifecycleHandler = class {
|
|
|
60734
61884
|
this.reattachStaleEmpties(nodeId).catch((err) => {
|
|
60735
61885
|
this.deps.logger.debug("readiness deferred sweep failed", {
|
|
60736
61886
|
tags: { nodeId },
|
|
60737
|
-
meta: { error: errMsg$
|
|
61887
|
+
meta: { error: errMsg$2(err) }
|
|
60738
61888
|
});
|
|
60739
61889
|
});
|
|
60740
61890
|
}, 3e3);
|
|
@@ -60745,7 +61895,7 @@ var NodeLifecycleHandler = class {
|
|
|
60745
61895
|
tags: { nodeId },
|
|
60746
61896
|
meta: {
|
|
60747
61897
|
epoch: t.epoch,
|
|
60748
|
-
error: errMsg$
|
|
61898
|
+
error: errMsg$2(err)
|
|
60749
61899
|
}
|
|
60750
61900
|
});
|
|
60751
61901
|
}
|
|
@@ -60824,7 +61974,7 @@ var NodeLifecycleHandler = class {
|
|
|
60824
61974
|
deviceId,
|
|
60825
61975
|
nodeId
|
|
60826
61976
|
},
|
|
60827
|
-
meta: { error: errMsg$
|
|
61977
|
+
meta: { error: errMsg$2(err) }
|
|
60828
61978
|
});
|
|
60829
61979
|
}
|
|
60830
61980
|
}
|
|
@@ -61146,7 +62296,7 @@ var NodeStressController = class NodeStressController {
|
|
|
61146
62296
|
if (this.timer) return;
|
|
61147
62297
|
this.timer = setInterval(() => {
|
|
61148
62298
|
this.sweep().catch((err) => {
|
|
61149
|
-
this.deps.logger.warn("node-stress sweep failed", { meta: { error: errMsg$
|
|
62299
|
+
this.deps.logger.warn("node-stress sweep failed", { meta: { error: errMsg$2(err) } });
|
|
61150
62300
|
});
|
|
61151
62301
|
}, NodeStressController.SWEEP_INTERVAL_MS);
|
|
61152
62302
|
}
|
|
@@ -61319,7 +62469,7 @@ var NodeStressController = class NodeStressController {
|
|
|
61319
62469
|
meta: {
|
|
61320
62470
|
from: plan.fromNodeId,
|
|
61321
62471
|
to: plan.toNodeId,
|
|
61322
|
-
error: errMsg$
|
|
62472
|
+
error: errMsg$2(err)
|
|
61323
62473
|
}
|
|
61324
62474
|
});
|
|
61325
62475
|
} finally {
|
|
@@ -62016,7 +63166,7 @@ var NodeTopologyService = class {
|
|
|
62016
63166
|
try {
|
|
62017
63167
|
blob = await this.deps.readAgentSettingsMap();
|
|
62018
63168
|
} catch (err) {
|
|
62019
|
-
this.deps.logger.warn("refreshNodeCapabilities: agent settings read failed", { meta: { error: errMsg$
|
|
63169
|
+
this.deps.logger.warn("refreshNodeCapabilities: agent settings read failed", { meta: { error: errMsg$2(err) } });
|
|
62020
63170
|
}
|
|
62021
63171
|
const nodeIds = new Set(["hub"]);
|
|
62022
63172
|
for (const nodeId of Object.keys(blob)) if (typeof nodeId === "string" && nodeId.length > 0 && !nodeId.includes("/")) nodeIds.add(nodeId);
|
|
@@ -62190,7 +63340,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62190
63340
|
const unsubInferenceResult = deps.eventBus.subscribe({ category: EventCategory.PipelineInferenceResult }, (event) => {
|
|
62191
63341
|
if (!isEvent(event, EventCategory.PipelineInferenceResult)) return;
|
|
62192
63342
|
deps.handleInferenceResult(event.data).catch((err) => {
|
|
62193
|
-
const msg = errMsg$
|
|
63343
|
+
const msg = errMsg$2(err);
|
|
62194
63344
|
deps.logger.error("PipelineInferenceResult post-processing failed", {
|
|
62195
63345
|
tags: { deviceId: event.data.deviceId },
|
|
62196
63346
|
meta: { error: msg }
|
|
@@ -62209,7 +63359,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62209
63359
|
deps.logger.info("received onProfileSlotsChanged", { tags: { deviceId } });
|
|
62210
63360
|
deps.handleDeviceRegistered(deviceId).catch((err) => {
|
|
62211
63361
|
if (isTornDown) return;
|
|
62212
|
-
const msg = errMsg$
|
|
63362
|
+
const msg = errMsg$2(err);
|
|
62213
63363
|
deps.logger.error("handleDeviceRegistered failed", {
|
|
62214
63364
|
tags: { deviceId },
|
|
62215
63365
|
meta: { error: msg }
|
|
@@ -62221,7 +63371,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62221
63371
|
const unsubDeviceUnregistered = deps.eventBus.subscribe({ category: EventCategory.DeviceUnregistered }, (event) => {
|
|
62222
63372
|
const { deviceId } = event.data;
|
|
62223
63373
|
deps.handleDeviceUnregistered(deviceId).catch((err) => {
|
|
62224
|
-
const msg = errMsg$
|
|
63374
|
+
const msg = errMsg$2(err);
|
|
62225
63375
|
deps.logger.error("handleDeviceUnregistered failed", {
|
|
62226
63376
|
tags: { deviceId },
|
|
62227
63377
|
meta: { error: msg }
|
|
@@ -62234,7 +63384,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62234
63384
|
deps.cameraSensors.requestCamera(deviceId, "settings-updated");
|
|
62235
63385
|
deps.handleSettingsChanged(deviceId).catch((err) => {
|
|
62236
63386
|
if (isTornDown) return;
|
|
62237
|
-
const msg = errMsg$
|
|
63387
|
+
const msg = errMsg$2(err);
|
|
62238
63388
|
deps.logger.error("handleSettingsChanged failed", {
|
|
62239
63389
|
tags: { deviceId },
|
|
62240
63390
|
meta: { error: msg }
|
|
@@ -62244,7 +63394,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62244
63394
|
const unsubCameraUpdated = deps.eventBus.subscribe({ category: EventCategory.PipelineCameraUpdated }, (event) => {
|
|
62245
63395
|
if (!isEvent(event, EventCategory.PipelineCameraUpdated)) return;
|
|
62246
63396
|
deps.handlePipelineCameraUpdated(event.data.deviceId, event.data.config).catch((err) => {
|
|
62247
|
-
const msg = errMsg$
|
|
63397
|
+
const msg = errMsg$2(err);
|
|
62248
63398
|
deps.logger.error("handlePipelineCameraUpdated failed", {
|
|
62249
63399
|
tags: { deviceId: event.data.deviceId },
|
|
62250
63400
|
meta: { error: msg }
|
|
@@ -62257,7 +63407,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62257
63407
|
if (!(reason === "wrapper-activated" || reason === "wrapper-deactivated") || !(capName === "detection-pipeline" || capName === "audio-analysis" || capName === "motion-detection")) return;
|
|
62258
63408
|
deps.handleSettingsChanged(deviceId).catch((err) => {
|
|
62259
63409
|
if (isTornDown) return;
|
|
62260
|
-
const msg = errMsg$
|
|
63410
|
+
const msg = errMsg$2(err);
|
|
62261
63411
|
deps.logger.error("binding-changed re-dispatch failed", {
|
|
62262
63412
|
tags: { deviceId },
|
|
62263
63413
|
meta: {
|
|
@@ -62285,7 +63435,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62285
63435
|
tags: { deviceId },
|
|
62286
63436
|
meta: {
|
|
62287
63437
|
detected,
|
|
62288
|
-
error: errMsg$
|
|
63438
|
+
error: errMsg$2(err)
|
|
62289
63439
|
}
|
|
62290
63440
|
});
|
|
62291
63441
|
});
|
|
@@ -62332,7 +63482,7 @@ function wireOrchestratorSubscriptions(deps) {
|
|
|
62332
63482
|
tags: { deviceId },
|
|
62333
63483
|
meta: {
|
|
62334
63484
|
detected,
|
|
62335
|
-
error: errMsg$
|
|
63485
|
+
error: errMsg$2(err)
|
|
62336
63486
|
}
|
|
62337
63487
|
});
|
|
62338
63488
|
});
|
|
@@ -62598,7 +63748,7 @@ async function reattachAfterPackageZoneChange(deps, deviceId) {
|
|
|
62598
63748
|
deviceId,
|
|
62599
63749
|
nodeId
|
|
62600
63750
|
},
|
|
62601
|
-
meta: { error: errMsg$
|
|
63751
|
+
meta: { error: errMsg$2(err) }
|
|
62602
63752
|
});
|
|
62603
63753
|
}
|
|
62604
63754
|
}
|
|
@@ -62879,7 +64029,7 @@ async function resetRemovedModelPins(deps, trigger) {
|
|
|
62879
64029
|
} catch (err) {
|
|
62880
64030
|
deps.logger.warn("removed-model pin reset skipped — custom model registry unreadable", { meta: {
|
|
62881
64031
|
trigger,
|
|
62882
|
-
error: errMsg$
|
|
64032
|
+
error: errMsg$2(err)
|
|
62883
64033
|
} });
|
|
62884
64034
|
return [];
|
|
62885
64035
|
}
|
|
@@ -62898,7 +64048,7 @@ async function resetRemovedModelPins(deps, trigger) {
|
|
|
62898
64048
|
} catch (err) {
|
|
62899
64049
|
deps.logger.warn("removed-model pin reset failed", { meta: {
|
|
62900
64050
|
trigger,
|
|
62901
|
-
error: errMsg$
|
|
64051
|
+
error: errMsg$2(err)
|
|
62902
64052
|
} });
|
|
62903
64053
|
return [];
|
|
62904
64054
|
}
|
|
@@ -63257,6 +64407,21 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
63257
64407
|
});
|
|
63258
64408
|
}
|
|
63259
64409
|
/**
|
|
64410
|
+
* Rewrite the whole agent-settings map in ONE read-modify-write under
|
|
64411
|
+
* {@link blobWriteLock} — atomic across every node of the blob (Best
|
|
64412
|
+
* settings, D745, writes several nodes' devices at once). `transform` must
|
|
64413
|
+
* be pure: it may run on a fresh read.
|
|
64414
|
+
*/
|
|
64415
|
+
async rewriteAgentSettingsMap(transform) {
|
|
64416
|
+
await this.blobWriteLock.run(PipelineSettingsStore.AGENT_SETTINGS_LOCK_KEY, async () => {
|
|
64417
|
+
await this.agentSettingsState.set(transform(await this.readAgentSettingsMap()));
|
|
64418
|
+
});
|
|
64419
|
+
}
|
|
64420
|
+
/** Rewrite the whole camera-settings map in one durable `update()` (D745). */
|
|
64421
|
+
async rewriteCameraSettingsMap(transform) {
|
|
64422
|
+
await this.cameraSettingsState.update(transform);
|
|
64423
|
+
}
|
|
64424
|
+
/**
|
|
63260
64425
|
* Drop one agent's persisted settings entry. Returns whether an entry
|
|
63261
64426
|
* existed (`false` also when the store is already disposed — the locked
|
|
63262
64427
|
* section becomes a no-op then). Serialized via {@link blobWriteLock} for
|
|
@@ -63513,6 +64678,10 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
63513
64678
|
* epochs and operator resets — a bound, not a budget.
|
|
63514
64679
|
*/
|
|
63515
64680
|
static CATALOG_FENCE_REREADS = 3;
|
|
64681
|
+
/** The node's catalog, read through the cache (awaits the node when cold). */
|
|
64682
|
+
async readCatalogForNode(nodeId) {
|
|
64683
|
+
return this.getCatalogForAgent(nodeId);
|
|
64684
|
+
}
|
|
63516
64685
|
async getCatalogForAgent(nodeId) {
|
|
63517
64686
|
const api = this.deps.api();
|
|
63518
64687
|
if (!api) return null;
|
|
@@ -63530,7 +64699,7 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
63530
64699
|
this.catalogReadFailing.add(nodeId);
|
|
63531
64700
|
this.deps.logger.warn("node catalog read failed — placements on it wait or stay pending", {
|
|
63532
64701
|
tags: { nodeId },
|
|
63533
|
-
meta: { error: errMsg$
|
|
64702
|
+
meta: { error: errMsg$2(err) }
|
|
63534
64703
|
});
|
|
63535
64704
|
}
|
|
63536
64705
|
return null;
|
|
@@ -63617,7 +64786,7 @@ var PipelineSettingsStore = class PipelineSettingsStore {
|
|
|
63617
64786
|
tags: { deviceId },
|
|
63618
64787
|
meta: {
|
|
63619
64788
|
capName: "detection-pipeline",
|
|
63620
|
-
error: errMsg$
|
|
64789
|
+
error: errMsg$2(err)
|
|
63621
64790
|
}
|
|
63622
64791
|
});
|
|
63623
64792
|
return true;
|
|
@@ -64450,7 +65619,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64450
65619
|
try {
|
|
64451
65620
|
slots = await api.streamBroker.listAllProfileSlots.query();
|
|
64452
65621
|
} catch (err) {
|
|
64453
|
-
this.deps.logger.debug("dispatch reconcile skipped — stream-broker not ready", { meta: { error: errMsg$
|
|
65622
|
+
this.deps.logger.debug("dispatch reconcile skipped — stream-broker not ready", { meta: { error: errMsg$2(err) } });
|
|
64454
65623
|
return;
|
|
64455
65624
|
}
|
|
64456
65625
|
const desired = desiredDeviceIdsFromSlots(slots);
|
|
@@ -64477,7 +65646,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64477
65646
|
} catch (err) {
|
|
64478
65647
|
this.deps.logger.warn("dispatch reconcile: handleDeviceRegistered failed", {
|
|
64479
65648
|
tags: { deviceId },
|
|
64480
|
-
meta: { error: errMsg$
|
|
65649
|
+
meta: { error: errMsg$2(err) }
|
|
64481
65650
|
});
|
|
64482
65651
|
}
|
|
64483
65652
|
await this.reconcilePlacementFromRunners();
|
|
@@ -64505,7 +65674,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64505
65674
|
const devices = await api.deviceManager.listAll.query({ projection: "slim" });
|
|
64506
65675
|
return new Set(devices.map((device) => device.id));
|
|
64507
65676
|
} catch (err) {
|
|
64508
|
-
this.deps.logger.debug("dispatch reconcile: deviceManager.listAll failed", { meta: { error: errMsg$
|
|
65677
|
+
this.deps.logger.debug("dispatch reconcile: deviceManager.listAll failed", { meta: { error: errMsg$2(err) } });
|
|
64509
65678
|
return null;
|
|
64510
65679
|
}
|
|
64511
65680
|
}
|
|
@@ -64531,7 +65700,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64531
65700
|
} catch (err) {
|
|
64532
65701
|
this.deps.logger.warn("dispatch reconcile: teardown of vanished device failed", {
|
|
64533
65702
|
tags: { deviceId },
|
|
64534
|
-
meta: { error: errMsg$
|
|
65703
|
+
meta: { error: errMsg$2(err) }
|
|
64535
65704
|
});
|
|
64536
65705
|
}
|
|
64537
65706
|
}
|
|
@@ -64588,7 +65757,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64588
65757
|
this.deps.logger.debug("placement reconcile skipped node — getLocalCameras failed", {
|
|
64589
65758
|
tags: { nodeId },
|
|
64590
65759
|
meta: {
|
|
64591
|
-
error: outcome.kind === "rejected" ? errMsg$
|
|
65760
|
+
error: outcome.kind === "rejected" ? errMsg$2(outcome.error) : "budget exceeded",
|
|
64592
65761
|
ms
|
|
64593
65762
|
}
|
|
64594
65763
|
});
|
|
@@ -64618,7 +65787,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64618
65787
|
nodeId,
|
|
64619
65788
|
deviceId
|
|
64620
65789
|
},
|
|
64621
|
-
meta: { error: errMsg$
|
|
65790
|
+
meta: { error: errMsg$2(err) }
|
|
64622
65791
|
});
|
|
64623
65792
|
})));
|
|
64624
65793
|
}
|
|
@@ -64660,7 +65829,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64660
65829
|
this.deps.handleDeviceRegistered(deviceId).catch((err) => {
|
|
64661
65830
|
this.deps.logger.debug("slot-read retry: handleDeviceRegistered failed", {
|
|
64662
65831
|
tags: { deviceId },
|
|
64663
|
-
meta: { error: errMsg$
|
|
65832
|
+
meta: { error: errMsg$2(err) }
|
|
64664
65833
|
});
|
|
64665
65834
|
});
|
|
64666
65835
|
}, delay);
|
|
@@ -64738,7 +65907,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64738
65907
|
} catch (err) {
|
|
64739
65908
|
this.deps.logger.warn("pending retry: dispatchCamera failed", {
|
|
64740
65909
|
tags: { deviceId },
|
|
64741
|
-
meta: { error: errMsg$
|
|
65910
|
+
meta: { error: errMsg$2(err) }
|
|
64742
65911
|
});
|
|
64743
65912
|
stillPending++;
|
|
64744
65913
|
}
|
|
@@ -64816,7 +65985,7 @@ var ReconcileController = class ReconcileController {
|
|
|
64816
65985
|
await this.deps.detachOn(assignment.agentNodeId, deviceId).catch((err) => {
|
|
64817
65986
|
this.deps.logger.debug("remote-health exhausted-detach failed", {
|
|
64818
65987
|
tags: { deviceId },
|
|
64819
|
-
meta: { error: errMsg$
|
|
65988
|
+
meta: { error: errMsg$2(err) }
|
|
64820
65989
|
});
|
|
64821
65990
|
});
|
|
64822
65991
|
return;
|
|
@@ -64839,14 +66008,14 @@ var ReconcileController = class ReconcileController {
|
|
|
64839
66008
|
await this.deps.detachOn(assignment.agentNodeId, deviceId).catch((err) => {
|
|
64840
66009
|
this.deps.logger.warn("remote-health detach failed", {
|
|
64841
66010
|
tags: { deviceId },
|
|
64842
|
-
meta: { error: errMsg$
|
|
66011
|
+
meta: { error: errMsg$2(err) }
|
|
64843
66012
|
});
|
|
64844
66013
|
});
|
|
64845
66014
|
this.deps.ledger.dropAssignment(deviceId, "silent");
|
|
64846
66015
|
await this.deps.redispatchCamera(cfg).catch((err) => {
|
|
64847
66016
|
this.deps.logger.warn("remote-health re-dispatch failed", {
|
|
64848
66017
|
tags: { deviceId },
|
|
64849
|
-
meta: { error: errMsg$
|
|
66018
|
+
meta: { error: errMsg$2(err) }
|
|
64850
66019
|
});
|
|
64851
66020
|
});
|
|
64852
66021
|
}
|
|
@@ -65021,7 +66190,7 @@ var OccupancyRecheckController = class {
|
|
|
65021
66190
|
tags: { deviceId },
|
|
65022
66191
|
meta: {
|
|
65023
66192
|
deviceId,
|
|
65024
|
-
error: errMsg$
|
|
66193
|
+
error: errMsg$2(err)
|
|
65025
66194
|
}
|
|
65026
66195
|
});
|
|
65027
66196
|
});
|
|
@@ -65155,7 +66324,7 @@ var OccupancyRecheckController = class {
|
|
|
65155
66324
|
tags: { deviceId },
|
|
65156
66325
|
meta: {
|
|
65157
66326
|
deviceId,
|
|
65158
|
-
error: errMsg$
|
|
66327
|
+
error: errMsg$2(err)
|
|
65159
66328
|
}
|
|
65160
66329
|
});
|
|
65161
66330
|
});
|
|
@@ -65210,7 +66379,7 @@ var OccupancyRecheckController = class {
|
|
|
65210
66379
|
meta: {
|
|
65211
66380
|
deviceId,
|
|
65212
66381
|
nodeId,
|
|
65213
|
-
error: errMsg$
|
|
66382
|
+
error: errMsg$2(err),
|
|
65214
66383
|
kind
|
|
65215
66384
|
}
|
|
65216
66385
|
});
|
|
@@ -65219,7 +66388,7 @@ var OccupancyRecheckController = class {
|
|
|
65219
66388
|
}
|
|
65220
66389
|
const answer = await this.deps.requestBurstOn(nodeId, deviceId, frames, reasonTag).catch((err) => ({
|
|
65221
66390
|
started: false,
|
|
65222
|
-
error: errMsg$
|
|
66391
|
+
error: errMsg$2(err)
|
|
65223
66392
|
}));
|
|
65224
66393
|
if (!answer.started) {
|
|
65225
66394
|
const refusedBecause = "refusedBecause" in answer ? answer.refusedBecause : void 0;
|
|
@@ -65267,7 +66436,7 @@ var OccupancyRecheckController = class {
|
|
|
65267
66436
|
meta: {
|
|
65268
66437
|
deviceId,
|
|
65269
66438
|
nodeId,
|
|
65270
|
-
error: errMsg$
|
|
66439
|
+
error: errMsg$2(err)
|
|
65271
66440
|
}
|
|
65272
66441
|
});
|
|
65273
66442
|
});
|
|
@@ -65324,7 +66493,7 @@ var OccupancyRecheckController = class {
|
|
|
65324
66493
|
meta: {
|
|
65325
66494
|
deviceId,
|
|
65326
66495
|
nodeId: burst.nodeId,
|
|
65327
|
-
error: errMsg$
|
|
66496
|
+
error: errMsg$2(err)
|
|
65328
66497
|
}
|
|
65329
66498
|
});
|
|
65330
66499
|
});
|
|
@@ -65981,7 +67150,7 @@ var SessionDispatchController = class {
|
|
|
65981
67150
|
deviceId,
|
|
65982
67151
|
nodeId: session.nodeId
|
|
65983
67152
|
},
|
|
65984
|
-
meta: { error: errMsg$
|
|
67153
|
+
meta: { error: errMsg$2(err) }
|
|
65985
67154
|
});
|
|
65986
67155
|
});
|
|
65987
67156
|
this.deps.dropAssignment(deviceId, "silent");
|
|
@@ -66198,7 +67367,7 @@ var SessionDispatchController = class {
|
|
|
66198
67367
|
};
|
|
66199
67368
|
if (reconcileBudgetExceeded) log.warn("detection session: placement census overran its budget — placing without it", { meta: { budgetMs: 500 } });
|
|
66200
67369
|
else if (censusOutcome.kind === "rejected") log.warn("detection session: placement census failed — placing on the ledger as it is", { meta: {
|
|
66201
|
-
error: errMsg$
|
|
67370
|
+
error: errMsg$2(censusOutcome.error),
|
|
66202
67371
|
reconcileMs
|
|
66203
67372
|
} });
|
|
66204
67373
|
const balanceStartedAt = Date.now();
|
|
@@ -66392,7 +67561,7 @@ var SessionDispatchController = class {
|
|
|
66392
67561
|
await this.resolveSessionLifecycleLocked(deviceId, reason);
|
|
66393
67562
|
}).catch((err) => {
|
|
66394
67563
|
log.warn("detection session: lifecycle re-check failed", { meta: {
|
|
66395
|
-
error: errMsg$
|
|
67564
|
+
error: errMsg$2(err),
|
|
66396
67565
|
reason
|
|
66397
67566
|
} });
|
|
66398
67567
|
});
|
|
@@ -66497,7 +67666,7 @@ var SessionDispatchController = class {
|
|
|
66497
67666
|
log.warn("detection session: teardown detach failed", {
|
|
66498
67667
|
tags: { nodeId: session.nodeId },
|
|
66499
67668
|
meta: {
|
|
66500
|
-
error: errMsg$
|
|
67669
|
+
error: errMsg$2(err),
|
|
66501
67670
|
reason
|
|
66502
67671
|
}
|
|
66503
67672
|
});
|
|
@@ -66604,7 +67773,7 @@ var SessionDispatchController = class {
|
|
|
66604
67773
|
deviceId,
|
|
66605
67774
|
nodeId: session.nodeId
|
|
66606
67775
|
},
|
|
66607
|
-
meta: { error: errMsg$
|
|
67776
|
+
meta: { error: errMsg$2(err) }
|
|
66608
67777
|
});
|
|
66609
67778
|
});
|
|
66610
67779
|
this.sessionRegistry.deregister(deviceId);
|
|
@@ -66625,7 +67794,7 @@ var SessionDispatchController = class {
|
|
|
66625
67794
|
deviceId,
|
|
66626
67795
|
nodeId: watchNodeId
|
|
66627
67796
|
},
|
|
66628
|
-
meta: { error: errMsg$
|
|
67797
|
+
meta: { error: errMsg$2(err) }
|
|
66629
67798
|
});
|
|
66630
67799
|
});
|
|
66631
67800
|
this.motionWatchByDevice.delete(deviceId);
|
|
@@ -66808,7 +67977,7 @@ async function sweepMirror(deps, hydrator, cameraIds) {
|
|
|
66808
67977
|
tags: { deviceId },
|
|
66809
67978
|
meta: {
|
|
66810
67979
|
mirror: hydrator.mirror,
|
|
66811
|
-
error: errMsg$
|
|
67980
|
+
error: errMsg$2(err)
|
|
66812
67981
|
}
|
|
66813
67982
|
});
|
|
66814
67983
|
return "threw";
|
|
@@ -66848,7 +68017,7 @@ async function readCameraIds(deps) {
|
|
|
66848
68017
|
lastError = err;
|
|
66849
68018
|
}
|
|
66850
68019
|
if (Date.now() >= deadline) {
|
|
66851
|
-
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$
|
|
68020
|
+
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) } });
|
|
66852
68021
|
return null;
|
|
66853
68022
|
}
|
|
66854
68023
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
@@ -67469,6 +68638,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67469
68638
|
readGlobalSettings: () => globalSettings,
|
|
67470
68639
|
onCensus: (entries) => {
|
|
67471
68640
|
motionSliceGate?.noteCensus(entries);
|
|
68641
|
+
deps.onRunnerCensus(entries.flatMap((e) => e.load === null ? [] : [e.load]));
|
|
67472
68642
|
providerSliceGate?.refreshIfStale("runner-census");
|
|
67473
68643
|
}
|
|
67474
68644
|
});
|
|
@@ -67495,7 +68665,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67495
68665
|
} catch (err) {
|
|
67496
68666
|
return {
|
|
67497
68667
|
kind: "unknown",
|
|
67498
|
-
reason: errMsg$
|
|
68668
|
+
reason: errMsg$2(err)
|
|
67499
68669
|
};
|
|
67500
68670
|
}
|
|
67501
68671
|
};
|
|
@@ -67580,14 +68750,14 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67580
68750
|
readGlobalSettings: () => globalSettings
|
|
67581
68751
|
});
|
|
67582
68752
|
await NodeStressMoveLedger.declare(deps.ctx().api.settingsStore).then(() => nodeStress.hydrate()).catch((err) => {
|
|
67583
|
-
deps.ctx().logger.warn("node-stress move history unavailable — this boot starts cold", { meta: { error: errMsg$
|
|
68753
|
+
deps.ctx().logger.warn("node-stress move history unavailable — this boot starts cold", { meta: { error: errMsg$2(err) } });
|
|
67584
68754
|
});
|
|
67585
68755
|
await NodeStressLts.declare(deps.ctx().api.settingsStore).catch((err) => {
|
|
67586
|
-
deps.ctx().logger.warn("node-stress statistics unavailable — no baseline this boot", { meta: { error: errMsg$
|
|
68756
|
+
deps.ctx().logger.warn("node-stress statistics unavailable — no baseline this boot", { meta: { error: errMsg$2(err) } });
|
|
67587
68757
|
});
|
|
67588
68758
|
const nodeStressLtsTimer = setInterval(() => {
|
|
67589
68759
|
nodeStressLts.flushDue().catch((err) => {
|
|
67590
|
-
deps.ctx().logger.warn("node-stress statistics flush failed", { meta: { error: errMsg$
|
|
68760
|
+
deps.ctx().logger.warn("node-stress statistics flush failed", { meta: { error: errMsg$2(err) } });
|
|
67591
68761
|
});
|
|
67592
68762
|
}, 6e4);
|
|
67593
68763
|
nodeStress.start();
|
|
@@ -67737,7 +68907,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67737
68907
|
} catch (err) {
|
|
67738
68908
|
return {
|
|
67739
68909
|
kind: "unreadable",
|
|
67740
|
-
error: errMsg$
|
|
68910
|
+
error: errMsg$2(err)
|
|
67741
68911
|
};
|
|
67742
68912
|
}
|
|
67743
68913
|
};
|
|
@@ -67771,7 +68941,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67771
68941
|
} catch (err) {
|
|
67772
68942
|
return {
|
|
67773
68943
|
kind: "unreadable",
|
|
67774
|
-
error: errMsg$
|
|
68944
|
+
error: errMsg$2(err)
|
|
67775
68945
|
};
|
|
67776
68946
|
}
|
|
67777
68947
|
},
|
|
@@ -67802,7 +68972,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67802
68972
|
} catch (err) {
|
|
67803
68973
|
return {
|
|
67804
68974
|
kind: "unreadable",
|
|
67805
|
-
error: errMsg$
|
|
68975
|
+
error: errMsg$2(err)
|
|
67806
68976
|
};
|
|
67807
68977
|
}
|
|
67808
68978
|
},
|
|
@@ -67815,7 +68985,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67815
68985
|
} catch (err) {
|
|
67816
68986
|
return {
|
|
67817
68987
|
kind: "unreadable",
|
|
67818
|
-
error: errMsg$
|
|
68988
|
+
error: errMsg$2(err)
|
|
67819
68989
|
};
|
|
67820
68990
|
}
|
|
67821
68991
|
},
|
|
@@ -67886,7 +69056,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67886
69056
|
deps.applyRuntimeSettings(globalSettings);
|
|
67887
69057
|
await topology.refreshNodeCapabilities();
|
|
67888
69058
|
} catch (err) {
|
|
67889
|
-
const msg = errMsg$
|
|
69059
|
+
const msg = errMsg$2(err);
|
|
67890
69060
|
deps.ctx().logger.warn("orchestrator settings load failed — using defaults", { meta: { error: msg } });
|
|
67891
69061
|
}
|
|
67892
69062
|
const rawNodeId = deps.ctx().kernel.localNodeId ?? "hub";
|
|
@@ -67927,7 +69097,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67927
69097
|
audio.markNodeOffline(nodeId);
|
|
67928
69098
|
deps.forgetInferenceDeviceProbe(nodeId);
|
|
67929
69099
|
nodeLifecycle.handleNodeDisconnect(nodeId).catch((err) => {
|
|
67930
|
-
const msg = errMsg$
|
|
69100
|
+
const msg = errMsg$2(err);
|
|
67931
69101
|
deps.ctx().logger.error("failover after disconnect failed", {
|
|
67932
69102
|
tags: { nodeId },
|
|
67933
69103
|
meta: { error: msg }
|
|
@@ -67962,7 +69132,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67962
69132
|
deps.subscribeToAudioAnalyzerReadiness(nodeId);
|
|
67963
69133
|
}
|
|
67964
69134
|
nodeLifecycle.handleNodeConnect(nodeId).catch((err) => {
|
|
67965
|
-
const msg = errMsg$
|
|
69135
|
+
const msg = errMsg$2(err);
|
|
67966
69136
|
deps.ctx().logger.error("reconnect handling failed", {
|
|
67967
69137
|
tags: { nodeId },
|
|
67968
69138
|
meta: { error: msg }
|
|
@@ -67981,7 +69151,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
67981
69151
|
meta: { capName }
|
|
67982
69152
|
});
|
|
67983
69153
|
nodeLifecycle.handleNodeDisconnect(nodeId).catch((err) => {
|
|
67984
|
-
const msg = errMsg$
|
|
69154
|
+
const msg = errMsg$2(err);
|
|
67985
69155
|
deps.ctx().logger.error("failover after cap-down failed", {
|
|
67986
69156
|
tags: { nodeId },
|
|
67987
69157
|
meta: {
|
|
@@ -68044,7 +69214,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
68044
69214
|
doneFlag: settingsStore.bindingsMigrationDoneState,
|
|
68045
69215
|
logger: deps.ctx().logger
|
|
68046
69216
|
}).catch((err) => {
|
|
68047
|
-
deps.ctxIfReady()?.logger.warn("bindings migration failed — will retry on next boot", { meta: { error: errMsg$
|
|
69217
|
+
deps.ctxIfReady()?.logger.warn("bindings migration failed — will retry on next boot", { meta: { error: errMsg$2(err) } });
|
|
68048
69218
|
});
|
|
68049
69219
|
migrateStepGating({
|
|
68050
69220
|
readCameraSettings: () => settingsStore.readCameraSettingsMap(),
|
|
@@ -68057,7 +69227,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
68057
69227
|
for (let i = 0; deps.ctxIfReady()?.api == null && i < 30; i++) await new Promise((r) => setTimeout(r, 200));
|
|
68058
69228
|
if (deps.ctxIfReady() !== null) await deps.resetRemovedModelPins("boot");
|
|
68059
69229
|
}).catch((err) => {
|
|
68060
|
-
deps.ctxIfReady()?.logger.warn("step-gating / per-node-device migration failed — retry next boot", { meta: { error: errMsg$
|
|
69230
|
+
deps.ctxIfReady()?.logger.warn("step-gating / per-node-device migration failed — retry next boot", { meta: { error: errMsg$2(err) } });
|
|
68061
69231
|
});
|
|
68062
69232
|
const zoneRulesProvider = new ZoneRulesProvider({
|
|
68063
69233
|
logger: deps.ctx().logger.child("zone-rules"),
|
|
@@ -68099,7 +69269,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
68099
69269
|
deviceId,
|
|
68100
69270
|
nodeId: assignment.agentNodeId
|
|
68101
69271
|
},
|
|
68102
|
-
meta: { error: errMsg$
|
|
69272
|
+
meta: { error: errMsg$2(err) }
|
|
68103
69273
|
});
|
|
68104
69274
|
});
|
|
68105
69275
|
}
|
|
@@ -68115,7 +69285,7 @@ async function buildOrchestratorControllers(deps) {
|
|
|
68115
69285
|
}],
|
|
68116
69286
|
logger: deps.ctx().logger.child("zones")
|
|
68117
69287
|
}).catch((err) => {
|
|
68118
|
-
deps.ctxIfReady()?.logger.warn("zones mirror boot hydration failed", { meta: { error: errMsg$
|
|
69288
|
+
deps.ctxIfReady()?.logger.warn("zones mirror boot hydration failed", { meta: { error: errMsg$2(err) } });
|
|
68119
69289
|
});
|
|
68120
69290
|
const unsubOrchestratorSubscriptions = wireOrchestratorSubscriptions({
|
|
68121
69291
|
eventBus: deps.ctx().eventBus,
|
|
@@ -68176,10 +69346,10 @@ async function buildOrchestratorControllers(deps) {
|
|
|
68176
69346
|
deviceSettingsSchema: () => deps.deviceSettingsSchema()
|
|
68177
69347
|
});
|
|
68178
69348
|
await detectionWiring.hydrateFeaturesMirror().catch((err) => {
|
|
68179
|
-
deps.ctx().logger.warn("device-features mirror not restored — this boot starts cold", { meta: { error: errMsg$
|
|
69349
|
+
deps.ctx().logger.warn("device-features mirror not restored — this boot starts cold", { meta: { error: errMsg$2(err) } });
|
|
68180
69350
|
});
|
|
68181
69351
|
cameraSensors.start().catch((err) => {
|
|
68182
|
-
deps.ctx().logger.warn("camera sensors: boot refresh failed — waiting for the next lifecycle event", { meta: { error: errMsg$
|
|
69352
|
+
deps.ctx().logger.warn("camera sensors: boot refresh failed — waiting for the next lifecycle event", { meta: { error: errMsg$2(err) } });
|
|
68183
69353
|
});
|
|
68184
69354
|
return {
|
|
68185
69355
|
ledger,
|
|
@@ -68811,6 +69981,633 @@ var pipelineOrchestratorWidgets = [{
|
|
|
68811
69981
|
defaultRows: 4
|
|
68812
69982
|
}];
|
|
68813
69983
|
//#endregion
|
|
69984
|
+
//#region src/best-settings/plan-diff.ts
|
|
69985
|
+
/**
|
|
69986
|
+
* The Best settings writes, as pure transforms of the orchestrator's two
|
|
69987
|
+
* settings blobs (D745). The plan runs them on the current blobs to SHOW the
|
|
69988
|
+
* diff; the apply runs the same functions inside each blob's read-modify-write.
|
|
69989
|
+
*
|
|
69990
|
+
* What changes — and nothing else:
|
|
69991
|
+
* - node level (`agentSettings[node].inferenceDevices[deviceKey].steps`): the
|
|
69992
|
+
* object-detection `modelId` of every recommended device is set; the
|
|
69993
|
+
* `modelId` pin of every other in-scope step is removed (each runs its
|
|
69994
|
+
* step default again); every model-bound threshold (and its binding key) is
|
|
69995
|
+
* removed from every device's step `settings`, so the running model's own
|
|
69996
|
+
* defaults apply (D738–D741). `enabled`, `weight`, `maxSessions`,
|
|
69997
|
+
* `jumpDeviceKey` and every other setting are kept.
|
|
69998
|
+
* - camera level, scoped to the touched nodes: the same `modelId` pins and
|
|
69999
|
+
* model-bound thresholds are removed from `stepOverridesByDevice[node]` and
|
|
70000
|
+
* from the wholesale `pipelineByAgent[node]` snapshot (recursively). A
|
|
70001
|
+
* wholesale step keeps `enabled`, `children`, `jumpDeviceKey` and its other
|
|
70002
|
+
* settings: the attach provisions the node pin over the snapshot
|
|
70003
|
+
* (`applyDeviceProvisioning`: override ?? base), so once the snapshot pin
|
|
70004
|
+
* is gone the node's recommended model runs.
|
|
70005
|
+
* - never: `stepToggles`, `pinnedAgentNodeId`, devices' enablement, pins of
|
|
70006
|
+
* embedding steps (cluster-scoped, changing one forces a re-index).
|
|
70007
|
+
*/
|
|
70008
|
+
/** The root detector step. */
|
|
70009
|
+
var DETECTOR_STEP_ID = "object-detection";
|
|
70010
|
+
/** Steps whose `modelId` pin Best settings never touches. */
|
|
70011
|
+
function isModelPinKept(stepId) {
|
|
70012
|
+
return stepId.endsWith("-embedding");
|
|
70013
|
+
}
|
|
70014
|
+
function boundKeysFor(input, stepId) {
|
|
70015
|
+
return input.boundKeysByStep[stepId] ?? [];
|
|
70016
|
+
}
|
|
70017
|
+
/** The model-bound keys present in `settings` (values or bindings), and the rest. */
|
|
70018
|
+
function stripBound(settings, boundKeys) {
|
|
70019
|
+
if (settings === void 0) return {
|
|
70020
|
+
kept: void 0,
|
|
70021
|
+
removed: []
|
|
70022
|
+
};
|
|
70023
|
+
const kept = { ...settings };
|
|
70024
|
+
const removed = [];
|
|
70025
|
+
for (const key of boundKeys) for (const k of [key, modelBindingKey(key)]) if (k in kept) {
|
|
70026
|
+
delete kept[k];
|
|
70027
|
+
removed.push(k);
|
|
70028
|
+
}
|
|
70029
|
+
return {
|
|
70030
|
+
kept,
|
|
70031
|
+
removed: removed.toSorted()
|
|
70032
|
+
};
|
|
70033
|
+
}
|
|
70034
|
+
function pinInScope(stepId) {
|
|
70035
|
+
return !isModelPinKept(stepId);
|
|
70036
|
+
}
|
|
70037
|
+
/** Rebuild one stored step without the in-scope pin and bound keys. */
|
|
70038
|
+
function scrubStep(stepId, step, boundKeys, targetModel) {
|
|
70039
|
+
const before = step.modelId ?? null;
|
|
70040
|
+
let modelId = step.modelId;
|
|
70041
|
+
if (stepId === "object-detection") {
|
|
70042
|
+
if (targetModel !== void 0) modelId = targetModel;
|
|
70043
|
+
} else if (pinInScope(stepId)) modelId = void 0;
|
|
70044
|
+
const { kept, removed } = stripBound(step.settings, boundKeys);
|
|
70045
|
+
const after = modelId ?? null;
|
|
70046
|
+
const next = {
|
|
70047
|
+
...modelId !== void 0 ? { modelId } : {},
|
|
70048
|
+
...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {},
|
|
70049
|
+
...step.jumpDeviceKey !== void 0 ? { jumpDeviceKey: step.jumpDeviceKey } : {}
|
|
70050
|
+
};
|
|
70051
|
+
return {
|
|
70052
|
+
next: Object.keys(next).length > 0 ? next : null,
|
|
70053
|
+
pin: before !== after ? {
|
|
70054
|
+
stepId,
|
|
70055
|
+
before,
|
|
70056
|
+
after
|
|
70057
|
+
} : null,
|
|
70058
|
+
cleared: removed
|
|
70059
|
+
};
|
|
70060
|
+
}
|
|
70061
|
+
/** The node-level transform. Returns the next blob and every change it made. */
|
|
70062
|
+
function applyNodeBestSettings(blob, input) {
|
|
70063
|
+
const next = { ...blob };
|
|
70064
|
+
const changes = [];
|
|
70065
|
+
for (const nodeId of [...input.touchedNodes].toSorted()) {
|
|
70066
|
+
const entry = blob[nodeId];
|
|
70067
|
+
const targets = new Map((input.targets.get(nodeId) ?? []).map((t) => [t.deviceKey, t]));
|
|
70068
|
+
const stored = entry?.inferenceDevices ?? {};
|
|
70069
|
+
const deviceKeys = [...new Set([...Object.keys(stored), ...targets.keys()])].toSorted();
|
|
70070
|
+
const nextDevices = {};
|
|
70071
|
+
for (const deviceKey of deviceKeys) {
|
|
70072
|
+
const device = stored[deviceKey];
|
|
70073
|
+
const target = targets.get(deviceKey);
|
|
70074
|
+
const steps = { ...device?.steps };
|
|
70075
|
+
if (target !== void 0 && steps["object-detection"] === void 0) steps[DETECTOR_STEP_ID] = {};
|
|
70076
|
+
const nextSteps = {};
|
|
70077
|
+
const pins = [];
|
|
70078
|
+
const thresholds = [];
|
|
70079
|
+
for (const stepId of Object.keys(steps).toSorted()) {
|
|
70080
|
+
const step = steps[stepId];
|
|
70081
|
+
if (step === void 0) continue;
|
|
70082
|
+
const scrubbed = scrubStep(stepId, step, boundKeysFor(input, stepId), target?.modelId);
|
|
70083
|
+
if (scrubbed.next !== null) nextSteps[stepId] = scrubbed.next;
|
|
70084
|
+
if (scrubbed.pin !== null) pins.push(scrubbed.pin);
|
|
70085
|
+
if (scrubbed.cleared.length > 0) thresholds.push({
|
|
70086
|
+
stepId,
|
|
70087
|
+
keys: scrubbed.cleared
|
|
70088
|
+
});
|
|
70089
|
+
}
|
|
70090
|
+
const madeExplicit = device === void 0;
|
|
70091
|
+
nextDevices[deviceKey] = {
|
|
70092
|
+
...device !== void 0 ? (({ steps: _steps, ...rest }) => rest)(device) : { enabled: target?.effectiveEnabled ?? false },
|
|
70093
|
+
...Object.keys(nextSteps).length > 0 ? { steps: nextSteps } : {}
|
|
70094
|
+
};
|
|
70095
|
+
if (pins.length > 0 || thresholds.length > 0) changes.push({
|
|
70096
|
+
nodeId,
|
|
70097
|
+
deviceKey,
|
|
70098
|
+
madeExplicit,
|
|
70099
|
+
modelPins: pins,
|
|
70100
|
+
thresholds
|
|
70101
|
+
});
|
|
70102
|
+
}
|
|
70103
|
+
if (entry === void 0 && deviceKeys.length === 0) continue;
|
|
70104
|
+
next[nodeId] = {
|
|
70105
|
+
...entry ?? { maxCameras: null },
|
|
70106
|
+
...deviceKeys.length > 0 ? { inferenceDevices: nextDevices } : {}
|
|
70107
|
+
};
|
|
70108
|
+
}
|
|
70109
|
+
return {
|
|
70110
|
+
next,
|
|
70111
|
+
changes
|
|
70112
|
+
};
|
|
70113
|
+
}
|
|
70114
|
+
/** Scrub a wholesale snapshot tree; collects one clear per changed step. */
|
|
70115
|
+
function scrubTree(steps, input, onClear) {
|
|
70116
|
+
return steps.map((step) => {
|
|
70117
|
+
const children = step.children ? scrubTree(step.children, input, onClear) : void 0;
|
|
70118
|
+
const inScope = step.addonId === "object-detection" || pinInScope(step.addonId);
|
|
70119
|
+
const { kept, removed } = stripBound(step.settings, boundKeysFor(input, step.addonId));
|
|
70120
|
+
const dropPin = inScope && step.modelId !== void 0;
|
|
70121
|
+
if (dropPin || removed.length > 0) onClear(step.addonId, dropPin ? step.modelId ?? null : null, removed);
|
|
70122
|
+
const { modelId: _modelId, settings: _settings, children: _children, ...rest } = step;
|
|
70123
|
+
return {
|
|
70124
|
+
...rest,
|
|
70125
|
+
...!dropPin && step.modelId !== void 0 ? { modelId: step.modelId } : {},
|
|
70126
|
+
...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {},
|
|
70127
|
+
...children !== void 0 ? { children } : {}
|
|
70128
|
+
};
|
|
70129
|
+
});
|
|
70130
|
+
}
|
|
70131
|
+
/** The camera-level transform. Returns the next blob and every override it removed. */
|
|
70132
|
+
function applyCameraBestSettings(blob, input) {
|
|
70133
|
+
const next = {};
|
|
70134
|
+
const clears = [];
|
|
70135
|
+
for (const key of Object.keys(blob).toSorted((a, b) => Number(a) - Number(b))) {
|
|
70136
|
+
const camera = blob[key];
|
|
70137
|
+
if (camera === void 0) continue;
|
|
70138
|
+
const deviceId = Number(key);
|
|
70139
|
+
let nextCamera = camera;
|
|
70140
|
+
const byNode = camera.stepOverridesByDevice;
|
|
70141
|
+
if (byNode !== void 0) {
|
|
70142
|
+
const nextByNode = {};
|
|
70143
|
+
for (const [nodeId, byDevice] of Object.entries(byNode)) {
|
|
70144
|
+
if (!input.touchedNodes.has(nodeId)) {
|
|
70145
|
+
nextByNode[nodeId] = byDevice;
|
|
70146
|
+
continue;
|
|
70147
|
+
}
|
|
70148
|
+
const nextByDevice = {};
|
|
70149
|
+
for (const [deviceKey, byStep] of Object.entries(byDevice)) {
|
|
70150
|
+
const nextByStep = {};
|
|
70151
|
+
for (const [stepId, patch] of Object.entries(byStep)) {
|
|
70152
|
+
const inScope = stepId === "object-detection" || pinInScope(stepId);
|
|
70153
|
+
const { kept, removed } = stripBound(patch.settings, boundKeysFor(input, stepId));
|
|
70154
|
+
const dropPin = inScope && patch.modelId !== void 0;
|
|
70155
|
+
if (dropPin || removed.length > 0) clears.push({
|
|
70156
|
+
deviceId,
|
|
70157
|
+
source: "step-override",
|
|
70158
|
+
nodeId,
|
|
70159
|
+
deviceKey,
|
|
70160
|
+
stepId,
|
|
70161
|
+
modelIdBefore: dropPin ? patch.modelId ?? null : null,
|
|
70162
|
+
keys: removed
|
|
70163
|
+
});
|
|
70164
|
+
const rebuilt = {
|
|
70165
|
+
...!dropPin && patch.modelId !== void 0 ? { modelId: patch.modelId } : {},
|
|
70166
|
+
...kept !== void 0 && Object.keys(kept).length > 0 ? { settings: kept } : {}
|
|
70167
|
+
};
|
|
70168
|
+
if (Object.keys(rebuilt).length > 0) nextByStep[stepId] = rebuilt;
|
|
70169
|
+
}
|
|
70170
|
+
if (Object.keys(nextByStep).length > 0) nextByDevice[deviceKey] = nextByStep;
|
|
70171
|
+
}
|
|
70172
|
+
if (Object.keys(nextByDevice).length > 0) nextByNode[nodeId] = nextByDevice;
|
|
70173
|
+
}
|
|
70174
|
+
const { stepOverridesByDevice: _drop, ...rest } = nextCamera;
|
|
70175
|
+
nextCamera = {
|
|
70176
|
+
...rest,
|
|
70177
|
+
...Object.keys(nextByNode).length > 0 ? { stepOverridesByDevice: nextByNode } : {}
|
|
70178
|
+
};
|
|
70179
|
+
}
|
|
70180
|
+
const wholesale = camera.pipelineByAgent;
|
|
70181
|
+
if (wholesale !== void 0) {
|
|
70182
|
+
const nextWholesale = {};
|
|
70183
|
+
for (const [nodeId, snapshot] of Object.entries(wholesale)) {
|
|
70184
|
+
if (!input.touchedNodes.has(nodeId)) {
|
|
70185
|
+
nextWholesale[nodeId] = snapshot;
|
|
70186
|
+
continue;
|
|
70187
|
+
}
|
|
70188
|
+
nextWholesale[nodeId] = {
|
|
70189
|
+
...snapshot,
|
|
70190
|
+
steps: scrubTree(snapshot.steps, input, (stepId, modelIdBefore, keys) => {
|
|
70191
|
+
clears.push({
|
|
70192
|
+
deviceId,
|
|
70193
|
+
source: "wholesale",
|
|
70194
|
+
nodeId,
|
|
70195
|
+
deviceKey: null,
|
|
70196
|
+
stepId,
|
|
70197
|
+
modelIdBefore,
|
|
70198
|
+
keys
|
|
70199
|
+
});
|
|
70200
|
+
})
|
|
70201
|
+
};
|
|
70202
|
+
}
|
|
70203
|
+
nextCamera = {
|
|
70204
|
+
...nextCamera,
|
|
70205
|
+
pipelineByAgent: nextWholesale
|
|
70206
|
+
};
|
|
70207
|
+
}
|
|
70208
|
+
next[key] = nextCamera;
|
|
70209
|
+
}
|
|
70210
|
+
return {
|
|
70211
|
+
next,
|
|
70212
|
+
clears
|
|
70213
|
+
};
|
|
70214
|
+
}
|
|
70215
|
+
//#endregion
|
|
70216
|
+
//#region src/best-settings/best-settings-service.ts
|
|
70217
|
+
/**
|
|
70218
|
+
* Best settings (D745): gather the inputs, compute the plan, and apply exactly
|
|
70219
|
+
* the plan the operator confirmed.
|
|
70220
|
+
*
|
|
70221
|
+
* The plan is the recommender (`@camstack/types` best-settings) over every
|
|
70222
|
+
* runner node, plus the diff of the writes (`plan-diff.ts`) and the
|
|
70223
|
+
* per-camera detector thresholds the hub's executor would clear. Its
|
|
70224
|
+
* `planHash` names everything the apply writes; the apply recomputes the plan
|
|
70225
|
+
* and refuses a different hash, so a configuration change between the two is
|
|
70226
|
+
* shown to the operator again instead of being written over.
|
|
70227
|
+
*
|
|
70228
|
+
* Apply order, each phase reported:
|
|
70229
|
+
* 1. download every newly picked detector on its node FIRST — a node never
|
|
70230
|
+
* switches to a model it does not have (an N100 compile takes 5–25 s, a
|
|
70231
|
+
* Core ML package 40 MB); any failure stops the apply before a write;
|
|
70232
|
+
* 2. camera overrides (one camera-blob read-modify-write);
|
|
70233
|
+
* 3. per-camera detector thresholds (the hub executor's store);
|
|
70234
|
+
* 4. node model pins (one agent-blob read-modify-write);
|
|
70235
|
+
* 5. re-dispatch the cameras of every touched node.
|
|
70236
|
+
* A failure after phase 2 leaves "overrides cleared, previous node model",
|
|
70237
|
+
* which is a valid configuration (the defaults run); re-running converges.
|
|
70238
|
+
*/
|
|
70239
|
+
function errMsg(err) {
|
|
70240
|
+
return err instanceof Error ? err.message : String(err);
|
|
70241
|
+
}
|
|
70242
|
+
/** Canonical JSON (sorted keys) — the plan hash must not depend on key order. */
|
|
70243
|
+
function canonical(value) {
|
|
70244
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
70245
|
+
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(",")}}`;
|
|
70246
|
+
return JSON.stringify(value);
|
|
70247
|
+
}
|
|
70248
|
+
function formatsByModelOf(catalog) {
|
|
70249
|
+
const out = {};
|
|
70250
|
+
for (const slot of catalog?.slots ?? []) for (const addon of slot.addons) {
|
|
70251
|
+
if (addon.id !== "object-detection") continue;
|
|
70252
|
+
for (const model of addon.models) out[model.id] = MODEL_FORMATS.filter((f) => f in model.formats);
|
|
70253
|
+
}
|
|
70254
|
+
return out;
|
|
70255
|
+
}
|
|
70256
|
+
function boundKeysOf(catalogs) {
|
|
70257
|
+
const byStep = /* @__PURE__ */ new Map();
|
|
70258
|
+
for (const catalog of catalogs) for (const slot of catalog?.slots ?? []) for (const addon of slot.addons) {
|
|
70259
|
+
const keys = collectModelBoundKeys(addon.configSchema ?? []);
|
|
70260
|
+
if (keys.length === 0) continue;
|
|
70261
|
+
const set = byStep.get(addon.id) ?? /* @__PURE__ */ new Set();
|
|
70262
|
+
for (const k of keys) set.add(k);
|
|
70263
|
+
byStep.set(addon.id, set);
|
|
70264
|
+
}
|
|
70265
|
+
return Object.fromEntries([...byStep].map(([step, set]) => [step, [...set].toSorted()]));
|
|
70266
|
+
}
|
|
70267
|
+
var BestSettingsService = class {
|
|
70268
|
+
deps;
|
|
70269
|
+
applying = false;
|
|
70270
|
+
constructor(deps) {
|
|
70271
|
+
this.deps = deps;
|
|
70272
|
+
}
|
|
70273
|
+
async plan(input) {
|
|
70274
|
+
return (await this.computePlan(input.headroom)).plan;
|
|
70275
|
+
}
|
|
70276
|
+
async computePlan(headroom) {
|
|
70277
|
+
const nodeIds = [...new Set(["hub", ...this.deps.runnerNodeIds()])].toSorted();
|
|
70278
|
+
const gathered = await Promise.all(nodeIds.map(async (nodeId) => {
|
|
70279
|
+
const [devices, hardware, catalog] = await Promise.all([
|
|
70280
|
+
this.deps.readNodeDevices(nodeId),
|
|
70281
|
+
this.deps.readHardware(nodeId).catch(() => null),
|
|
70282
|
+
this.deps.readCatalog(nodeId).catch(() => null)
|
|
70283
|
+
]);
|
|
70284
|
+
return {
|
|
70285
|
+
nodeId,
|
|
70286
|
+
devices,
|
|
70287
|
+
hardware,
|
|
70288
|
+
catalog
|
|
70289
|
+
};
|
|
70290
|
+
}));
|
|
70291
|
+
const nodes = gathered.map((g) => ({
|
|
70292
|
+
nodeId: g.nodeId,
|
|
70293
|
+
reachable: g.devices.reachable && g.catalog !== null,
|
|
70294
|
+
hardware: g.hardware,
|
|
70295
|
+
devices: g.devices.devices.map((d) => ({
|
|
70296
|
+
key: d.key,
|
|
70297
|
+
format: d.format,
|
|
70298
|
+
enabled: d.enabled,
|
|
70299
|
+
available: d.available
|
|
70300
|
+
})),
|
|
70301
|
+
formatsByModel: formatsByModelOf(g.catalog)
|
|
70302
|
+
}));
|
|
70303
|
+
const eligible = gathered.flatMap((g) => g.devices.reachable ? g.devices.devices.filter((d) => d.enabled && d.available).map((d) => ({
|
|
70304
|
+
nodeId: g.nodeId,
|
|
70305
|
+
deviceKey: d.key,
|
|
70306
|
+
weight: d.weight
|
|
70307
|
+
})) : []);
|
|
70308
|
+
const demand = estimateDeviceDemand({
|
|
70309
|
+
cameras: await this.deps.cameraLoads(),
|
|
70310
|
+
eligible,
|
|
70311
|
+
peaks: this.deps.peaks()
|
|
70312
|
+
});
|
|
70313
|
+
const recommendations = nodes.map((node) => recommendNode({
|
|
70314
|
+
node,
|
|
70315
|
+
demand,
|
|
70316
|
+
headroom
|
|
70317
|
+
}));
|
|
70318
|
+
const touchedNodes = new Set(recommendations.filter((r) => !r.skipped).map((r) => r.nodeId));
|
|
70319
|
+
const targets = /* @__PURE__ */ new Map();
|
|
70320
|
+
const formats = /* @__PURE__ */ new Map();
|
|
70321
|
+
for (const rec of recommendations) {
|
|
70322
|
+
if (rec.skipped) continue;
|
|
70323
|
+
const node = nodes.find((n) => n.nodeId === rec.nodeId);
|
|
70324
|
+
const list = [];
|
|
70325
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
70326
|
+
for (const d of rec.devices) {
|
|
70327
|
+
if (d.chosen === null) continue;
|
|
70328
|
+
list.push({
|
|
70329
|
+
deviceKey: d.deviceKey,
|
|
70330
|
+
modelId: d.chosen.modelId,
|
|
70331
|
+
effectiveEnabled: d.enabled
|
|
70332
|
+
});
|
|
70333
|
+
const format = node?.devices.find((x) => x.key === d.deviceKey)?.format;
|
|
70334
|
+
if (format !== void 0) byKey.set(d.deviceKey, format);
|
|
70335
|
+
}
|
|
70336
|
+
targets.set(rec.nodeId, list);
|
|
70337
|
+
formats.set(rec.nodeId, byKey);
|
|
70338
|
+
}
|
|
70339
|
+
const diffInput = {
|
|
70340
|
+
touchedNodes,
|
|
70341
|
+
targets,
|
|
70342
|
+
boundKeysByStep: boundKeysOf(gathered.map((g) => g.catalog))
|
|
70343
|
+
};
|
|
70344
|
+
const nodeChanges = applyNodeBestSettings(await this.deps.readAgentSettings(), diffInput).changes;
|
|
70345
|
+
const cameraClears = applyCameraBestSettings(await this.deps.readCameraSettings(), diffInput).clears;
|
|
70346
|
+
const warnings = [];
|
|
70347
|
+
let detectorStoreClears = [];
|
|
70348
|
+
if (touchedNodes.size > 0) try {
|
|
70349
|
+
const answer = await this.deps.detectorStore({ dryRun: true });
|
|
70350
|
+
detectorStoreClears = answer.entries.map((e) => ({
|
|
70351
|
+
deviceId: e.deviceId,
|
|
70352
|
+
keys: [...e.keys]
|
|
70353
|
+
}));
|
|
70354
|
+
if (answer.refused.length > 0) warnings.push({
|
|
70355
|
+
kind: "detector-store-unreadable",
|
|
70356
|
+
error: `${answer.refused.length} camera store(s) unreadable`
|
|
70357
|
+
});
|
|
70358
|
+
} catch (err) {
|
|
70359
|
+
warnings.push({
|
|
70360
|
+
kind: "detector-store-unreadable",
|
|
70361
|
+
error: errMsg(err)
|
|
70362
|
+
});
|
|
70363
|
+
}
|
|
70364
|
+
const skippedNodes = recommendations.filter((r) => r.skipped).map((r) => r.nodeId);
|
|
70365
|
+
if (detectorStoreClears.length > 0 && skippedNodes.length > 0) warnings.push({
|
|
70366
|
+
kind: "detector-store-cluster-wide",
|
|
70367
|
+
skippedNodes
|
|
70368
|
+
});
|
|
70369
|
+
for (const rec of recommendations) for (const d of rec.devices) if (d.accelerator === "coral" && d.enabled) warnings.push({
|
|
70370
|
+
kind: "coral-enabled",
|
|
70371
|
+
nodeId: rec.nodeId,
|
|
70372
|
+
deviceKey: d.deviceKey
|
|
70373
|
+
});
|
|
70374
|
+
return {
|
|
70375
|
+
plan: {
|
|
70376
|
+
planHash: (0, node_crypto.createHash)("sha256").update(canonical({
|
|
70377
|
+
version: 1,
|
|
70378
|
+
headroom,
|
|
70379
|
+
picks: recommendations.map((r) => ({
|
|
70380
|
+
nodeId: r.nodeId,
|
|
70381
|
+
skipped: r.skipped,
|
|
70382
|
+
devices: r.devices.map((d) => [d.deviceKey, d.chosen?.modelId ?? null])
|
|
70383
|
+
})),
|
|
70384
|
+
nodeChanges,
|
|
70385
|
+
cameraClears,
|
|
70386
|
+
detectorStoreClears
|
|
70387
|
+
})).digest("hex"),
|
|
70388
|
+
family: "yolov9",
|
|
70389
|
+
headroom,
|
|
70390
|
+
generatedAt: this.deps.now(),
|
|
70391
|
+
nodes: recommendations,
|
|
70392
|
+
nodeChanges: [...nodeChanges],
|
|
70393
|
+
cameraClears: [...cameraClears],
|
|
70394
|
+
detectorStoreClears,
|
|
70395
|
+
warnings
|
|
70396
|
+
},
|
|
70397
|
+
diffInput,
|
|
70398
|
+
formats
|
|
70399
|
+
};
|
|
70400
|
+
}
|
|
70401
|
+
async apply(input) {
|
|
70402
|
+
if (this.applying) return {
|
|
70403
|
+
kind: "refused",
|
|
70404
|
+
reason: "apply-in-progress"
|
|
70405
|
+
};
|
|
70406
|
+
this.applying = true;
|
|
70407
|
+
try {
|
|
70408
|
+
return await this.applyLocked(input);
|
|
70409
|
+
} finally {
|
|
70410
|
+
this.applying = false;
|
|
70411
|
+
}
|
|
70412
|
+
}
|
|
70413
|
+
async applyLocked(input) {
|
|
70414
|
+
const { plan, diffInput, formats } = await this.computePlan(input.headroom);
|
|
70415
|
+
if (plan.planHash !== input.planHash) {
|
|
70416
|
+
this.deps.logger.warn("best settings: stale plan refused", { meta: {
|
|
70417
|
+
confirmed: input.planHash,
|
|
70418
|
+
current: plan.planHash
|
|
70419
|
+
} });
|
|
70420
|
+
return {
|
|
70421
|
+
kind: "stale",
|
|
70422
|
+
plan
|
|
70423
|
+
};
|
|
70424
|
+
}
|
|
70425
|
+
const phases = [];
|
|
70426
|
+
const downloads = plan.nodeChanges.flatMap((c) => c.modelPins.filter((p) => p.stepId === "object-detection" && p.after !== null && p.after !== p.before).map((p) => ({
|
|
70427
|
+
nodeId: c.nodeId,
|
|
70428
|
+
modelId: p.after ?? "",
|
|
70429
|
+
format: formats.get(c.nodeId)?.get(c.deviceKey)
|
|
70430
|
+
})));
|
|
70431
|
+
const unique = new Map(downloads.map((d) => [`${d.nodeId}|${d.modelId}|${d.format}`, d]));
|
|
70432
|
+
try {
|
|
70433
|
+
for (const d of unique.values()) {
|
|
70434
|
+
if (d.format === void 0) continue;
|
|
70435
|
+
await this.deps.downloadModel(d.nodeId, d.modelId, d.format);
|
|
70436
|
+
}
|
|
70437
|
+
phases.push({
|
|
70438
|
+
phase: "download",
|
|
70439
|
+
ok: true,
|
|
70440
|
+
detail: `${unique.size} model(s) present on their nodes`
|
|
70441
|
+
});
|
|
70442
|
+
} catch (err) {
|
|
70443
|
+
phases.push({
|
|
70444
|
+
phase: "download",
|
|
70445
|
+
ok: false,
|
|
70446
|
+
detail: errMsg(err)
|
|
70447
|
+
});
|
|
70448
|
+
this.deps.logger.warn("best settings: a model download failed — nothing was written", { meta: { error: errMsg(err) } });
|
|
70449
|
+
return {
|
|
70450
|
+
kind: "aborted",
|
|
70451
|
+
phases
|
|
70452
|
+
};
|
|
70453
|
+
}
|
|
70454
|
+
try {
|
|
70455
|
+
await this.deps.rewriteCameraSettings((prev) => applyCameraBestSettings(prev, diffInput).next);
|
|
70456
|
+
for (const c of plan.cameraClears) this.deps.logger.info("best settings: camera override cleared", {
|
|
70457
|
+
tags: {
|
|
70458
|
+
deviceId: c.deviceId,
|
|
70459
|
+
nodeId: c.nodeId
|
|
70460
|
+
},
|
|
70461
|
+
meta: {
|
|
70462
|
+
deviceId: c.deviceId,
|
|
70463
|
+
source: c.source,
|
|
70464
|
+
deviceKey: c.deviceKey,
|
|
70465
|
+
stepId: c.stepId,
|
|
70466
|
+
modelIdBefore: c.modelIdBefore,
|
|
70467
|
+
keys: c.keys
|
|
70468
|
+
}
|
|
70469
|
+
});
|
|
70470
|
+
phases.push({
|
|
70471
|
+
phase: "camera-overrides",
|
|
70472
|
+
ok: true,
|
|
70473
|
+
detail: `${plan.cameraClears.length} cleared`
|
|
70474
|
+
});
|
|
70475
|
+
} catch (err) {
|
|
70476
|
+
phases.push({
|
|
70477
|
+
phase: "camera-overrides",
|
|
70478
|
+
ok: false,
|
|
70479
|
+
detail: errMsg(err)
|
|
70480
|
+
});
|
|
70481
|
+
}
|
|
70482
|
+
if (plan.detectorStoreClears.length > 0) try {
|
|
70483
|
+
const answer = await this.deps.detectorStore({
|
|
70484
|
+
dryRun: false,
|
|
70485
|
+
deviceIds: plan.detectorStoreClears.map((c) => c.deviceId)
|
|
70486
|
+
});
|
|
70487
|
+
phases.push({
|
|
70488
|
+
phase: "detector-thresholds",
|
|
70489
|
+
ok: answer.refused.length === 0,
|
|
70490
|
+
detail: `${answer.entries.length} cleared, ${answer.refused.length} refused`
|
|
70491
|
+
});
|
|
70492
|
+
} catch (err) {
|
|
70493
|
+
phases.push({
|
|
70494
|
+
phase: "detector-thresholds",
|
|
70495
|
+
ok: false,
|
|
70496
|
+
detail: errMsg(err)
|
|
70497
|
+
});
|
|
70498
|
+
}
|
|
70499
|
+
try {
|
|
70500
|
+
await this.deps.rewriteAgentSettings((prev) => applyNodeBestSettings(prev, diffInput).next);
|
|
70501
|
+
for (const c of plan.nodeChanges) for (const pin of c.modelPins) this.deps.logger.info("best settings: node model set", {
|
|
70502
|
+
tags: { nodeId: c.nodeId },
|
|
70503
|
+
meta: {
|
|
70504
|
+
nodeId: c.nodeId,
|
|
70505
|
+
deviceKey: c.deviceKey,
|
|
70506
|
+
stepId: pin.stepId,
|
|
70507
|
+
from: pin.before,
|
|
70508
|
+
to: pin.after
|
|
70509
|
+
}
|
|
70510
|
+
});
|
|
70511
|
+
phases.push({
|
|
70512
|
+
phase: "node-models",
|
|
70513
|
+
ok: true,
|
|
70514
|
+
detail: `${plan.nodeChanges.length} device(s) updated`
|
|
70515
|
+
});
|
|
70516
|
+
} catch (err) {
|
|
70517
|
+
phases.push({
|
|
70518
|
+
phase: "node-models",
|
|
70519
|
+
ok: false,
|
|
70520
|
+
detail: errMsg(err)
|
|
70521
|
+
});
|
|
70522
|
+
}
|
|
70523
|
+
this.deps.invalidateCatalog();
|
|
70524
|
+
const failed = [];
|
|
70525
|
+
for (const nodeId of [...diffInput.touchedNodes].toSorted()) try {
|
|
70526
|
+
await this.deps.redispatch(nodeId);
|
|
70527
|
+
} catch (err) {
|
|
70528
|
+
failed.push(`${nodeId}: ${errMsg(err)}`);
|
|
70529
|
+
}
|
|
70530
|
+
phases.push({
|
|
70531
|
+
phase: "redispatch",
|
|
70532
|
+
ok: failed.length === 0,
|
|
70533
|
+
detail: failed.length === 0 ? `${diffInput.touchedNodes.size} node(s)` : failed.join("; ")
|
|
70534
|
+
});
|
|
70535
|
+
for (const rec of plan.nodes.filter((r) => r.skipped)) this.deps.logger.info("best settings: node skipped", {
|
|
70536
|
+
tags: { nodeId: rec.nodeId },
|
|
70537
|
+
meta: {
|
|
70538
|
+
nodeId: rec.nodeId,
|
|
70539
|
+
reasons: rec.reasons
|
|
70540
|
+
}
|
|
70541
|
+
});
|
|
70542
|
+
this.deps.logger.info("best settings: applied", { meta: {
|
|
70543
|
+
planHash: plan.planHash,
|
|
70544
|
+
nodeChanges: plan.nodeChanges.length,
|
|
70545
|
+
cameraClears: plan.cameraClears.length,
|
|
70546
|
+
detectorStoreClears: plan.detectorStoreClears.length,
|
|
70547
|
+
phases
|
|
70548
|
+
} });
|
|
70549
|
+
return {
|
|
70550
|
+
kind: "applied",
|
|
70551
|
+
planHash: plan.planHash,
|
|
70552
|
+
phases
|
|
70553
|
+
};
|
|
70554
|
+
}
|
|
70555
|
+
};
|
|
70556
|
+
//#endregion
|
|
70557
|
+
//#region src/best-settings/device-peak-tracker.ts
|
|
70558
|
+
var BUCKET_MS = 3600 * 1e3;
|
|
70559
|
+
var DevicePeakTracker = class DevicePeakTracker {
|
|
70560
|
+
now;
|
|
70561
|
+
series = /* @__PURE__ */ new Map();
|
|
70562
|
+
constructor(now = () => Date.now()) {
|
|
70563
|
+
this.now = now;
|
|
70564
|
+
}
|
|
70565
|
+
static key(nodeId, deviceKey) {
|
|
70566
|
+
return `${nodeId}\u0000${deviceKey}`;
|
|
70567
|
+
}
|
|
70568
|
+
/** Fold one runner census in. A runner that did not answer is simply absent. */
|
|
70569
|
+
noteCensus(loads) {
|
|
70570
|
+
const now = this.now();
|
|
70571
|
+
const bucket = now - now % BUCKET_MS;
|
|
70572
|
+
for (const load of loads) {
|
|
70573
|
+
const rows = load.devices.length > 0 ? load.devices : [{
|
|
70574
|
+
deviceKey: "",
|
|
70575
|
+
attachedCameras: load.attachedCameras
|
|
70576
|
+
}];
|
|
70577
|
+
for (const device of rows) {
|
|
70578
|
+
const key = DevicePeakTracker.key(load.nodeId, device.deviceKey);
|
|
70579
|
+
let s = this.series.get(key);
|
|
70580
|
+
if (s === void 0) {
|
|
70581
|
+
s = {
|
|
70582
|
+
firstSeenAt: now,
|
|
70583
|
+
buckets: /* @__PURE__ */ new Map()
|
|
70584
|
+
};
|
|
70585
|
+
this.series.set(key, s);
|
|
70586
|
+
}
|
|
70587
|
+
s.buckets.set(bucket, Math.max(s.buckets.get(bucket) ?? 0, device.attachedCameras));
|
|
70588
|
+
for (const start of s.buckets.keys()) if (start + BUCKET_MS <= now - 864e5) s.buckets.delete(start);
|
|
70589
|
+
}
|
|
70590
|
+
}
|
|
70591
|
+
}
|
|
70592
|
+
/** Every observed device's peak over the window and how long it has been watched. */
|
|
70593
|
+
peaks() {
|
|
70594
|
+
const now = this.now();
|
|
70595
|
+
const out = [];
|
|
70596
|
+
for (const [key, s] of this.series) {
|
|
70597
|
+
const [nodeId = "", deviceKey = ""] = key.split("\0");
|
|
70598
|
+
let peak = 0;
|
|
70599
|
+
for (const [start, value] of s.buckets) if (start + BUCKET_MS > now - 864e5) peak = Math.max(peak, value);
|
|
70600
|
+
out.push({
|
|
70601
|
+
nodeId,
|
|
70602
|
+
deviceKey,
|
|
70603
|
+
peakSessions: peak,
|
|
70604
|
+
observedForMs: now - s.firstSeenAt
|
|
70605
|
+
});
|
|
70606
|
+
}
|
|
70607
|
+
return out.toSorted((a, b) => a.nodeId === b.nodeId ? a.deviceKey.localeCompare(b.deviceKey) : a.nodeId.localeCompare(b.nodeId));
|
|
70608
|
+
}
|
|
70609
|
+
};
|
|
70610
|
+
//#endregion
|
|
68814
70611
|
//#region src/index.ts
|
|
68815
70612
|
/**
|
|
68816
70613
|
* addon-pipeline-orchestrator — hub-side camera-to-agent load balancer.
|
|
@@ -68878,6 +70675,9 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
68878
70675
|
* not a claim this flag makes.
|
|
68879
70676
|
*/
|
|
68880
70677
|
clusterModelChoiceReady = false;
|
|
70678
|
+
/** The per-device 24 h peak of concurrent sessions, fed by every runner census (D745). */
|
|
70679
|
+
devicePeaks = new DevicePeakTracker();
|
|
70680
|
+
bestSettingsService = null;
|
|
68881
70681
|
/**
|
|
68882
70682
|
* Single owner of the shared placement state (`assignments`/`cameraConfigs`/
|
|
68883
70683
|
* `pendingReasons`/`dispatchingDevices`) — see `placement-ledger.ts` (S6
|
|
@@ -69154,6 +70954,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
69154
70954
|
rebalance: (opts) => this.rebalance(opts),
|
|
69155
70955
|
setCameraPipelineForAgent: (input) => this.setCameraPipelineForAgent(input),
|
|
69156
70956
|
maintenancePaused: () => this.storageMigrationLeaseId !== null,
|
|
70957
|
+
onRunnerCensus: (loads) => this.devicePeaks.noteCensus(loads),
|
|
69157
70958
|
setTopology: (topology) => {
|
|
69158
70959
|
this.topology = topology;
|
|
69159
70960
|
},
|
|
@@ -69295,7 +71096,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
69295
71096
|
for (const deviceId of this.detectionWiring.activeDetectionDeviceIds()) try {
|
|
69296
71097
|
await this.releaseCamera({ deviceId });
|
|
69297
71098
|
} catch (err) {
|
|
69298
|
-
const msg = errMsg$
|
|
71099
|
+
const msg = errMsg$2(err);
|
|
69299
71100
|
this.ctx.logger.debug("releaseCamera failed during shutdown", {
|
|
69300
71101
|
tags: { deviceId },
|
|
69301
71102
|
meta: { error: msg }
|
|
@@ -70090,7 +71891,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70090
71891
|
tags: { nodeId },
|
|
70091
71892
|
meta: {
|
|
70092
71893
|
nodeId,
|
|
70093
|
-
error: errMsg$
|
|
71894
|
+
error: errMsg$2(err)
|
|
70094
71895
|
}
|
|
70095
71896
|
});
|
|
70096
71897
|
return null;
|
|
@@ -70196,7 +71997,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70196
71997
|
tags: { nodeId },
|
|
70197
71998
|
meta: {
|
|
70198
71999
|
nodeId,
|
|
70199
|
-
error: errMsg$
|
|
72000
|
+
error: errMsg$2(err)
|
|
70200
72001
|
}
|
|
70201
72002
|
});
|
|
70202
72003
|
return {
|
|
@@ -70243,7 +72044,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70243
72044
|
} catch (err) {
|
|
70244
72045
|
this.ctx.logger.warn("resetNodePipelineDefaults: executor clearDeviceOverrides failed (node offline?)", {
|
|
70245
72046
|
tags: { nodeId: node },
|
|
70246
|
-
meta: { error: errMsg$
|
|
72047
|
+
meta: { error: errMsg$2(err) }
|
|
70247
72048
|
});
|
|
70248
72049
|
}
|
|
70249
72050
|
this.settingsStore.invalidateCatalog();
|
|
@@ -70254,7 +72055,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70254
72055
|
} catch (err) {
|
|
70255
72056
|
this.ctx.logger.warn("resetNodePipelineDefaults: effective-model probe failed (node offline?)", {
|
|
70256
72057
|
tags: { nodeId: node },
|
|
70257
|
-
meta: { error: errMsg$
|
|
72058
|
+
meta: { error: errMsg$2(err) }
|
|
70258
72059
|
});
|
|
70259
72060
|
}
|
|
70260
72061
|
this.ctx.logger.info("node pipeline reset to hardware-aware defaults", {
|
|
@@ -70270,6 +72071,77 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70270
72071
|
clearedCameraOverrides
|
|
70271
72072
|
};
|
|
70272
72073
|
}
|
|
72074
|
+
bestSettings() {
|
|
72075
|
+
if (this.bestSettingsService !== null) return this.bestSettingsService;
|
|
72076
|
+
const store = this.settingsStore;
|
|
72077
|
+
this.bestSettingsService = new BestSettingsService({
|
|
72078
|
+
logger: this.ctx.logger,
|
|
72079
|
+
runnerNodeIds: () => this.topology.collectRunnerNodeIds(),
|
|
72080
|
+
readNodeDevices: (nodeId) => this.getNodeInferenceDevices({ nodeId }),
|
|
72081
|
+
readHardware: async (nodeId) => {
|
|
72082
|
+
const api = this.ctx.api;
|
|
72083
|
+
if (!api) return null;
|
|
72084
|
+
return nodeId === this.localNodeId ? api.platformProbe.getHardware.query() : api.platformProbe.getHardware.query({ nodeId });
|
|
72085
|
+
},
|
|
72086
|
+
readCatalog: (nodeId) => store.readCatalogForNode(nodeId),
|
|
72087
|
+
readAgentSettings: () => store.readAgentSettingsMap(),
|
|
72088
|
+
readCameraSettings: () => store.readCameraSettingsMap(),
|
|
72089
|
+
rewriteAgentSettings: (transform) => store.rewriteAgentSettingsMap(transform),
|
|
72090
|
+
rewriteCameraSettings: (transform) => store.rewriteCameraSettingsMap(transform),
|
|
72091
|
+
cameraLoads: () => this.bestSettingsCameraLoads(),
|
|
72092
|
+
peaks: () => this.devicePeaks.peaks(),
|
|
72093
|
+
detectorStore: async (input) => {
|
|
72094
|
+
const api = this.ctx.api;
|
|
72095
|
+
if (!api) throw new Error("ctx.api unavailable");
|
|
72096
|
+
return api.pipelineExecutor.clearModelBoundDeviceOverrides.mutate({
|
|
72097
|
+
nodeId: this.localNodeId,
|
|
72098
|
+
dryRun: input.dryRun,
|
|
72099
|
+
...input.deviceIds !== void 0 ? { deviceIds: input.deviceIds } : {}
|
|
72100
|
+
});
|
|
72101
|
+
},
|
|
72102
|
+
downloadModel: async (nodeId, modelId, format) => {
|
|
72103
|
+
const api = this.ctx.api;
|
|
72104
|
+
if (!api) throw new Error("ctx.api unavailable");
|
|
72105
|
+
await api.pipelineExecutor.downloadModel.mutate({
|
|
72106
|
+
addonId: "object-detection",
|
|
72107
|
+
modelId,
|
|
72108
|
+
format
|
|
72109
|
+
}, nodePin(nodeId));
|
|
72110
|
+
},
|
|
72111
|
+
invalidateCatalog: () => store.invalidateCatalog(),
|
|
72112
|
+
redispatch: (nodeId) => this.placement.redispatchCamerasOnAgent(nodeId),
|
|
72113
|
+
now: () => Date.now()
|
|
72114
|
+
});
|
|
72115
|
+
return this.bestSettingsService;
|
|
72116
|
+
}
|
|
72117
|
+
/** Every camera with detection started, as the recommender charges it (D745). */
|
|
72118
|
+
async bestSettingsCameraLoads() {
|
|
72119
|
+
const standing = new Map(this.getPipelineAssignments().map((a) => [a.deviceId, a.agentNodeId]));
|
|
72120
|
+
const cameraMap = await this.settingsStore.readCameraSettingsMap();
|
|
72121
|
+
const loads = [];
|
|
72122
|
+
for (const [deviceId, config] of this.detectionWiring.activeDetections) {
|
|
72123
|
+
const pinnedNode = cameraMap[String(deviceId)]?.pinnedAgentNodeId ?? standing.get(deviceId);
|
|
72124
|
+
const pinnedKey = await this.readPipelineDevicePin(deviceId);
|
|
72125
|
+
loads.push({
|
|
72126
|
+
deviceId,
|
|
72127
|
+
mode: config.detectionMode,
|
|
72128
|
+
fps: config.detectionFps,
|
|
72129
|
+
runsPipelineRoot: (config.detectionSources ?? ["pipeline"]).includes("pipeline"),
|
|
72130
|
+
standingNodeId: config.detectionMode === "always-on" ? standing.get(deviceId) ?? null : null,
|
|
72131
|
+
devicePin: pinnedKey !== null && pinnedNode !== void 0 ? {
|
|
72132
|
+
nodeId: pinnedNode,
|
|
72133
|
+
deviceKey: pinnedKey
|
|
72134
|
+
} : null
|
|
72135
|
+
});
|
|
72136
|
+
}
|
|
72137
|
+
return loads;
|
|
72138
|
+
}
|
|
72139
|
+
async planBestSettings(input) {
|
|
72140
|
+
return this.bestSettings().plan(input);
|
|
72141
|
+
}
|
|
72142
|
+
async applyBestSettings(input) {
|
|
72143
|
+
return this.bestSettings().apply(input);
|
|
72144
|
+
}
|
|
70273
72145
|
async getCameraSettings(input) {
|
|
70274
72146
|
return (await this.settingsStore.readCameraSettingsMap())[String(input.deviceId)] ?? null;
|
|
70275
72147
|
}
|
|
@@ -70526,7 +72398,7 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
|
|
|
70526
72398
|
tags: { deviceId },
|
|
70527
72399
|
meta: {
|
|
70528
72400
|
capName,
|
|
70529
|
-
error: errMsg$
|
|
72401
|
+
error: errMsg$2(err)
|
|
70530
72402
|
}
|
|
70531
72403
|
});
|
|
70532
72404
|
return true;
|