@camstack/addon-pipeline 1.2.293 → 1.2.295
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/THIRD_PARTY_MODELS.md +233 -0
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-DCQlW9fx.mjs → default-detection-model-CAUBVbgK.mjs} +53 -19
- package/dist/{default-detection-model-DTKHLLcC.js → default-detection-model-wNEISVA9.js} +58 -18
- package/dist/detection-pipeline/index.js +286 -458
- package/dist/detection-pipeline/index.mjs +286 -458
- package/dist/{dist-Ce8s7XqV.js → dist-BsVcf5wO.js} +1321 -833
- package/dist/{dist-C0Fr4hM5.mjs → dist-CuxSNLKW.mjs} +1316 -834
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-Xm7amG2e.mjs → node-Cqb1QiXk.mjs} +1 -1
- package/dist/{node-DpOrymbW.js → node-UFk6I2f6.js} +1 -1
- package/dist/pipeline-runner/index.js +266 -72
- package/dist/pipeline-runner/index.mjs +266 -72
- package/dist/{process-memory-CBRjuhyg.js → process-memory-CJV29sPf.js} +1 -1
- package/dist/{process-memory-BN5qYGsD.mjs → process-memory-D0Nvs9rr.mjs} +1 -1
- package/dist/recorder/index.js +12 -2
- package/dist/recorder/index.mjs +12 -2
- package/dist/{segment-demux-js-BhMlS_Ow.js → segment-demux-js-DGwmf5hu.js} +1 -1
- package/dist/{segment-demux-js-Bhwpj5Wl.mjs → segment-demux-js-DIDWw1iE.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DKd7oYba.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B7nBFqva.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCIyBvRa.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DoyA71_q.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DeD_UFdb.mjs} +1 -1
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-CCMIHjff.mjs → hostInit-BKlb3qac.mjs} +2 -2
- package/dist/stream-broker/index.js +3 -3
- package/dist/stream-broker/index.mjs +3 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +3 -2
- package/python/postprocessors/__init__.py +2 -0
- package/python/postprocessors/ssd.py +73 -17
- package/python/postprocessors/test_ssd.py +205 -0
- package/python/postprocessors/test_yunet.py +292 -0
- package/python/postprocessors/testdata/ssdlite_mobiledet_outputs.json +1 -0
- package/python/postprocessors/yunet.py +275 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-6IHzlLJ_.mjs +0 -26
|
@@ -5360,7 +5360,7 @@ var ZodIssueCode = {
|
|
|
5360
5360
|
var ZodFirstPartyTypeKind;
|
|
5361
5361
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5362
5362
|
//#endregion
|
|
5363
|
-
//#region ../types/dist/sleep-
|
|
5363
|
+
//#region ../types/dist/sleep-BaRu0bS5.mjs
|
|
5364
5364
|
/**
|
|
5365
5365
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5366
5366
|
* window to float samples (D455).
|
|
@@ -11327,7 +11327,7 @@ var ModelFormatEntrySchema = object({
|
|
|
11327
11327
|
/** Multi-file payload (directory members or sibling files). */
|
|
11328
11328
|
files: array(string()).readonly().optional(),
|
|
11329
11329
|
/** Runtime(s) that can use this format. If omitted, inferred from ModelFormat key */
|
|
11330
|
-
runtimes: array(_enum(["
|
|
11330
|
+
runtimes: array(_enum(["python"])).readonly().optional()
|
|
11331
11331
|
});
|
|
11332
11332
|
/**
|
|
11333
11333
|
* Extra file that must be downloaded alongside the model (e.g., labels JSON, dict.txt).
|
|
@@ -19852,6 +19852,29 @@ var NC_LEGACY_SYSTEM_EVENT_KINDS = new Set([
|
|
|
19852
19852
|
]);
|
|
19853
19853
|
NcSystemEventKindSchema.options.filter((kind) => !NC_LEGACY_SYSTEM_EVENT_KINDS.has(kind));
|
|
19854
19854
|
/**
|
|
19855
|
+
* The panel's transitions, as ONE list.
|
|
19856
|
+
*
|
|
19857
|
+
* Named here rather than spelled out at each of the four sites that need them
|
|
19858
|
+
* (the emitter, the intake, the editor group, the combined-notification gate),
|
|
19859
|
+
* because a transition added to the enum and forgotten at one of them is an
|
|
19860
|
+
* alarm state nobody can be notified about. That is exactly what this list
|
|
19861
|
+
* failed to prevent while it held three: `alarm-arming` was the fourth.
|
|
19862
|
+
*
|
|
19863
|
+
* In LIFECYCLE order, which is the order an editor lists them in and the order
|
|
19864
|
+
* a person experiences them — not the order they were added.
|
|
19865
|
+
*/
|
|
19866
|
+
var NC_ALARM_SYSTEM_EVENT_KINDS = [
|
|
19867
|
+
"alarm-arm-refused",
|
|
19868
|
+
"alarm-check-failed",
|
|
19869
|
+
"alarm-arming",
|
|
19870
|
+
"alarm-armed",
|
|
19871
|
+
"alarm-pending",
|
|
19872
|
+
"alarm-triggered",
|
|
19873
|
+
"alarm-rearmed",
|
|
19874
|
+
"alarm-sensor-bypass",
|
|
19875
|
+
"alarm-disarmed"
|
|
19876
|
+
];
|
|
19877
|
+
/**
|
|
19855
19878
|
* One coherent system-event condition. `kinds` is the required opt-in safety
|
|
19856
19879
|
* gate; the remaining lists are optional narrowing filters relevant to the
|
|
19857
19880
|
* selected kinds.
|
|
@@ -19918,6 +19941,25 @@ var NcScheduleSchema = object({
|
|
|
19918
19941
|
/** Active OUTSIDE the windows (e.g. "only outside business hours"). */
|
|
19919
19942
|
invert: boolean().optional()
|
|
19920
19943
|
});
|
|
19944
|
+
/**
|
|
19945
|
+
* How a system-event rule of the `device-events` or `system` family speaks
|
|
19946
|
+
* (D647): at once, per event (`instant`, the only behaviour before D647), or
|
|
19947
|
+
* as a STATE SUMMARY at the end of each window of `schedule`.
|
|
19948
|
+
*/
|
|
19949
|
+
var NcSystemEventDeliveryModeSchema = _enum(["instant", "summary"]);
|
|
19950
|
+
/**
|
|
19951
|
+
* `summary`: the rule sends NOTHING per event. When a window of `schedule`
|
|
19952
|
+
* CLOSES, it sends one notification with the current state of its kinds
|
|
19953
|
+
* (low batteries, consumables to replace, devices offline, updates
|
|
19954
|
+
* available, ...), after its own filters. Nothing to report sends nothing.
|
|
19955
|
+
* The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
|
|
19956
|
+
* midnight crossing); the send time is each window's END. Refused on the
|
|
19957
|
+
* `alarm` family, which stays instant (`systemEventDeliveryProblems`).
|
|
19958
|
+
*/
|
|
19959
|
+
var NcSystemEventDeliverySchema = discriminatedUnion("mode", [object({ mode: literal("instant") }), object({
|
|
19960
|
+
mode: literal("summary"),
|
|
19961
|
+
schedule: NcScheduleSchema
|
|
19962
|
+
})]);
|
|
19921
19963
|
/** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
|
|
19922
19964
|
var NcPlateMatcherSchema = object({
|
|
19923
19965
|
/**
|
|
@@ -20653,6 +20695,16 @@ var NcRuleInputSchema = object({
|
|
|
20653
20695
|
delivery: NcDeliverySchema,
|
|
20654
20696
|
conditions: NcConditionsSchema.default({}),
|
|
20655
20697
|
schedule: NcScheduleSchema.optional(),
|
|
20698
|
+
/**
|
|
20699
|
+
* A `device-events` / `system` rule's delivery (D647): absent or `instant`
|
|
20700
|
+
* is a notification per event; `summary` is one state summary per closed
|
|
20701
|
+
* window of its schedule. `.optional()` and never defaulted: absent must
|
|
20702
|
+
* keep meaning what every rule authored before D647 meant, with no
|
|
20703
|
+
* migration. Top-level rather than inside `conditions.systemEvent`, so a
|
|
20704
|
+
* client that does not know it (an older viewer re-sending `conditions`
|
|
20705
|
+
* whole) leaves it standing through the store's patch merge.
|
|
20706
|
+
*/
|
|
20707
|
+
systemEventDelivery: NcSystemEventDeliverySchema.optional(),
|
|
20656
20708
|
/** May be empty when `targetUsers` addresses at least one user — the
|
|
20657
20709
|
* "at least one addressee" invariant is enforced by the provider, because
|
|
20658
20710
|
* a cross-field refine here would break `NcRulePatchSchema.partial()`. */
|
|
@@ -20881,6 +20933,28 @@ var NcRuleSchema = NcRuleInputSchema.extend({
|
|
|
20881
20933
|
*/
|
|
20882
20934
|
disabledTargetIds: array(string()).default([])
|
|
20883
20935
|
});
|
|
20936
|
+
/** One section of a previewed state summary. `unavailable` = its state could not be read. */
|
|
20937
|
+
var NcStateSummaryPreviewSectionSchema = object({
|
|
20938
|
+
id: string(),
|
|
20939
|
+
status: _enum(["read", "unavailable"]),
|
|
20940
|
+
items: number().int().nonnegative(),
|
|
20941
|
+
reason: string().optional()
|
|
20942
|
+
});
|
|
20943
|
+
/**
|
|
20944
|
+
* The summary a rule would send now. `empty` = nothing to report and every
|
|
20945
|
+
* section read: such a summary is NOT sent. `dropped` counts the open states
|
|
20946
|
+
* the rule's own filters (or a camera mute) left out, by reason.
|
|
20947
|
+
* `excludedGroups` names the kind groups selected that have no state to report
|
|
20948
|
+
* (backups & exports).
|
|
20949
|
+
*/
|
|
20950
|
+
var NcStateSummaryPreviewSchema = object({
|
|
20951
|
+
empty: boolean(),
|
|
20952
|
+
count: number().int().nonnegative(),
|
|
20953
|
+
text: string(),
|
|
20954
|
+
sections: array(NcStateSummaryPreviewSectionSchema),
|
|
20955
|
+
dropped: record(string(), number().int().nonnegative()),
|
|
20956
|
+
excludedGroups: array(string())
|
|
20957
|
+
});
|
|
20884
20958
|
var NcTestResultSchema = object({
|
|
20885
20959
|
recordId: string(),
|
|
20886
20960
|
recordKind: _enum([
|
|
@@ -20972,6 +21046,13 @@ var NcTemplateVarDescriptorSchema = object({
|
|
|
20972
21046
|
kinds: array(NcTemplateVarKindSchema).optional(),
|
|
20973
21047
|
deliveries: array(NcDeliverySchema).optional(),
|
|
20974
21048
|
systemEventKinds: array(NcSystemEventKindSchema).optional(),
|
|
21049
|
+
/**
|
|
21050
|
+
* Which system-event DELIVERY has a value for it (D647). Absent = an
|
|
21051
|
+
* instant row only when the descriptor names `systemEventKinds` (a
|
|
21052
|
+
* per-event fact: a summary row has no single device), and both otherwise.
|
|
21053
|
+
* `['summary']` marks the state-summary vocabulary (`{{summaryText}}`, ...).
|
|
21054
|
+
*/
|
|
21055
|
+
systemEventDeliveries: array(NcSystemEventDeliveryModeSchema).optional(),
|
|
20975
21056
|
dynamic: object({
|
|
20976
21057
|
prefix: literal("count_"),
|
|
20977
21058
|
from: literal("classes")
|
|
@@ -20984,7 +21065,9 @@ var NcTemplatePreviewInputSchema = object({
|
|
|
20984
21065
|
field: NcTemplateFieldSchema,
|
|
20985
21066
|
kind: NcRuleKindSchema.optional(),
|
|
20986
21067
|
delivery: NcDeliverySchema.optional(),
|
|
20987
|
-
systemEventKinds: array(NcSystemEventKindSchema).optional()
|
|
21068
|
+
systemEventKinds: array(NcSystemEventKindSchema).optional(),
|
|
21069
|
+
/** The rule's system-event delivery (D647); absent = instant. */
|
|
21070
|
+
systemEventDelivery: NcSystemEventDeliveryModeSchema.optional()
|
|
20988
21071
|
}),
|
|
20989
21072
|
template: object({
|
|
20990
21073
|
title: string().max(500).optional(),
|
|
@@ -21518,6 +21601,10 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
21518
21601
|
}), object({ results: array(NcTestResultSchema) }), {
|
|
21519
21602
|
kind: "mutation",
|
|
21520
21603
|
auth: "admin"
|
|
21604
|
+
}), method(object({ rule: NcRuleInputSchema }), NcStateSummaryPreviewSchema, {
|
|
21605
|
+
kind: "mutation",
|
|
21606
|
+
auth: "admin",
|
|
21607
|
+
access: "view"
|
|
21521
21608
|
}), method(object({}), object({
|
|
21522
21609
|
catalog: array(NcConditionDescriptorSchema),
|
|
21523
21610
|
taxonomy: NcTaxonomySchema.optional()
|
|
@@ -21864,790 +21951,6 @@ var OauthIntegrationDescriptorSchema = object({
|
|
|
21864
21951
|
});
|
|
21865
21952
|
method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
|
|
21866
21953
|
/**
|
|
21867
|
-
* Reference to the frame's retained NATIVE surface + the parent crop's placement
|
|
21868
|
-
* within the frame, so the executor can re-cut a leaf child ROI at native
|
|
21869
|
-
* resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
|
|
21870
|
-
*/
|
|
21871
|
-
var NativeCropRefSchema = object({
|
|
21872
|
-
/** Handle keying the retained native surface (node-pinned to its owner). */
|
|
21873
|
-
handle: FrameHandleSchema,
|
|
21874
|
-
/** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
|
|
21875
|
-
cropFrameSpace: object({
|
|
21876
|
-
x: number(),
|
|
21877
|
-
y: number(),
|
|
21878
|
-
w: number(),
|
|
21879
|
-
h: number()
|
|
21880
|
-
})
|
|
21881
|
-
});
|
|
21882
|
-
object({
|
|
21883
|
-
crop: object({
|
|
21884
|
-
left: number(),
|
|
21885
|
-
top: number(),
|
|
21886
|
-
width: number().positive(),
|
|
21887
|
-
height: number().positive()
|
|
21888
|
-
}).optional(),
|
|
21889
|
-
content: object({
|
|
21890
|
-
width: number().int().positive(),
|
|
21891
|
-
height: number().int().positive()
|
|
21892
|
-
}),
|
|
21893
|
-
fit: _enum(["stretch", "contain"]),
|
|
21894
|
-
format: _enum([
|
|
21895
|
-
"rgb",
|
|
21896
|
-
"gray",
|
|
21897
|
-
"jpeg"
|
|
21898
|
-
])
|
|
21899
|
-
});
|
|
21900
|
-
/**
|
|
21901
|
-
* Process-local frame identity. It is serializable so it can ride an in-process
|
|
21902
|
-
* capability call, but `registryId` deliberately prevents resolution in any
|
|
21903
|
-
* other process or execution group.
|
|
21904
|
-
*/
|
|
21905
|
-
var FrameRefSchema = object({
|
|
21906
|
-
registryId: string().min(1),
|
|
21907
|
-
id: string().min(1),
|
|
21908
|
-
width: number().int().positive(),
|
|
21909
|
-
height: number().int().positive(),
|
|
21910
|
-
format: _enum(["rgb", "gray"]),
|
|
21911
|
-
timestamp: number(),
|
|
21912
|
-
capturedAt: number().optional()
|
|
21913
|
-
});
|
|
21914
|
-
var ModelFormatSchema$1 = _enum([
|
|
21915
|
-
"onnx",
|
|
21916
|
-
"coreml",
|
|
21917
|
-
"openvino",
|
|
21918
|
-
"tflite",
|
|
21919
|
-
"pt",
|
|
21920
|
-
"gguf"
|
|
21921
|
-
]);
|
|
21922
|
-
var PipelineSlotSchema = _enum([
|
|
21923
|
-
"detector",
|
|
21924
|
-
"cropper",
|
|
21925
|
-
"classifier",
|
|
21926
|
-
"refiner",
|
|
21927
|
-
"audio-classifier"
|
|
21928
|
-
]);
|
|
21929
|
-
var PipelineEngineChoiceSchema = object({
|
|
21930
|
-
runtime: _enum(["node", "python"]),
|
|
21931
|
-
backend: string(),
|
|
21932
|
-
format: ModelFormatSchema$1,
|
|
21933
|
-
device: string().optional()
|
|
21934
|
-
});
|
|
21935
|
-
var AvailableEngineSchema = object({
|
|
21936
|
-
engine: PipelineEngineChoiceSchema,
|
|
21937
|
-
devices: array(object({
|
|
21938
|
-
id: string(),
|
|
21939
|
-
label: string(),
|
|
21940
|
-
description: string().optional()
|
|
21941
|
-
})).readonly(),
|
|
21942
|
-
defaultDevice: string()
|
|
21943
|
-
});
|
|
21944
|
-
var PipelineDefaultStepSchema = lazy(() => object({
|
|
21945
|
-
addonId: string(),
|
|
21946
|
-
addonName: string(),
|
|
21947
|
-
slot: PipelineSlotSchema,
|
|
21948
|
-
inputClasses: array(string()).readonly(),
|
|
21949
|
-
outputClasses: array(string()).readonly(),
|
|
21950
|
-
enabled: boolean(),
|
|
21951
|
-
modelId: string(),
|
|
21952
|
-
children: array(PipelineDefaultStepSchema).readonly(),
|
|
21953
|
-
group: string().optional(),
|
|
21954
|
-
settings: record(string(), unknown()).optional()
|
|
21955
|
-
}));
|
|
21956
|
-
var PipelineTemplateStepSchema = lazy(() => object({
|
|
21957
|
-
addonId: string(),
|
|
21958
|
-
enabled: boolean(),
|
|
21959
|
-
modelId: string(),
|
|
21960
|
-
children: array(PipelineTemplateStepSchema).readonly(),
|
|
21961
|
-
settings: record(string(), unknown()).optional()
|
|
21962
|
-
}));
|
|
21963
|
-
var PipelineTemplateSchema$1 = object({
|
|
21964
|
-
id: string(),
|
|
21965
|
-
name: string(),
|
|
21966
|
-
createdAt: string(),
|
|
21967
|
-
updatedAt: string(),
|
|
21968
|
-
engine: PipelineEngineChoiceSchema,
|
|
21969
|
-
steps: array(PipelineTemplateStepSchema).readonly()
|
|
21970
|
-
});
|
|
21971
|
-
var PipelineModelOptionSchema = object({
|
|
21972
|
-
id: string(),
|
|
21973
|
-
name: string(),
|
|
21974
|
-
formats: record(string(), object({
|
|
21975
|
-
downloaded: boolean(),
|
|
21976
|
-
sizeMB: number()
|
|
21977
|
-
})),
|
|
21978
|
-
group: ModelVariantGroupSchema.optional(),
|
|
21979
|
-
legacy: boolean().optional(),
|
|
21980
|
-
provider: ModelProviderIdSchema.optional()
|
|
21981
|
-
});
|
|
21982
|
-
var ConfigFieldBridge = custom();
|
|
21983
|
-
var PipelineAddonSchemaSchema = object({
|
|
21984
|
-
id: string(),
|
|
21985
|
-
name: string(),
|
|
21986
|
-
slot: PipelineSlotSchema,
|
|
21987
|
-
inputClasses: array(string()).readonly(),
|
|
21988
|
-
outputClasses: array(string()).readonly(),
|
|
21989
|
-
childSlots: array(PipelineSlotSchema).readonly(),
|
|
21990
|
-
models: array(PipelineModelOptionSchema).readonly(),
|
|
21991
|
-
defaultModelId: string(),
|
|
21992
|
-
defaultModelIdByFormat: record(string(), string()).optional(),
|
|
21993
|
-
enabledByDefault: boolean().optional(),
|
|
21994
|
-
backfillIntoExistingOverrides: boolean().optional(),
|
|
21995
|
-
defaultConfidence: number(),
|
|
21996
|
-
group: string().optional(),
|
|
21997
|
-
configSchema: array(ConfigFieldBridge).readonly().optional()
|
|
21998
|
-
});
|
|
21999
|
-
var PipelineSlotSchemaSchema = object({
|
|
22000
|
-
id: PipelineSlotSchema,
|
|
22001
|
-
label: string(),
|
|
22002
|
-
priority: number(),
|
|
22003
|
-
parentSlot: PipelineSlotSchema.nullable(),
|
|
22004
|
-
addons: array(PipelineAddonSchemaSchema).readonly()
|
|
22005
|
-
});
|
|
22006
|
-
var PipelineSchemaSchema = object({
|
|
22007
|
-
availableEngines: array(AvailableEngineSchema).readonly(),
|
|
22008
|
-
selectedEngine: PipelineEngineChoiceSchema,
|
|
22009
|
-
slots: array(PipelineSlotSchemaSchema).readonly()
|
|
22010
|
-
});
|
|
22011
|
-
var EngineProvisioningSchema = object({
|
|
22012
|
-
runtimeId: _enum([
|
|
22013
|
-
"onnx",
|
|
22014
|
-
"openvino",
|
|
22015
|
-
"coreml",
|
|
22016
|
-
"edgetpu"
|
|
22017
|
-
]).nullable(),
|
|
22018
|
-
device: string().nullable(),
|
|
22019
|
-
state: _enum([
|
|
22020
|
-
"idle",
|
|
22021
|
-
"installing",
|
|
22022
|
-
"verifying",
|
|
22023
|
-
"ready",
|
|
22024
|
-
"failed"
|
|
22025
|
-
]),
|
|
22026
|
-
progress: number().optional(),
|
|
22027
|
-
error: string().optional(),
|
|
22028
|
-
nextRetryAt: number().optional(),
|
|
22029
|
-
/**
|
|
22030
|
-
* Gate A (config-correctness gate at engine change): human-readable
|
|
22031
|
-
* config issues surfaced EAGERLY when the node's engine changes — model
|
|
22032
|
-
* substitutions ("chose X, running Y") and zero-build steps ("no model
|
|
22033
|
-
* has a <format> build"). Additive/optional: informational only, never
|
|
22034
|
-
* enforced here — `assertEngineReady` (readiness) still gates inference.
|
|
22035
|
-
* Absent/empty when the node-default tree resolves cleanly.
|
|
22036
|
-
*/
|
|
22037
|
-
configIssues: array(string()).optional()
|
|
22038
|
-
});
|
|
22039
|
-
var PipelineStepInputSchema = lazy(() => object({
|
|
22040
|
-
addonId: string(),
|
|
22041
|
-
modelId: string().optional(),
|
|
22042
|
-
enabled: boolean().default(true),
|
|
22043
|
-
children: array(PipelineStepInputSchema).optional(),
|
|
22044
|
-
settings: record(string(), unknown()).optional(),
|
|
22045
|
-
jumpDeviceKey: string().optional()
|
|
22046
|
-
}));
|
|
22047
|
-
var ModelSubstitutionSchema = object({
|
|
22048
|
-
addonId: string(),
|
|
22049
|
-
chosen: string(),
|
|
22050
|
-
running: string(),
|
|
22051
|
-
format: string()
|
|
22052
|
-
});
|
|
22053
|
-
var PipelineValidationIssueSchema = object({
|
|
22054
|
-
addonId: string(),
|
|
22055
|
-
kind: _enum(["unknown-addon", "no-format-build"]),
|
|
22056
|
-
detail: string()
|
|
22057
|
-
});
|
|
22058
|
-
var PipelineValidationResultSchema = object({
|
|
22059
|
-
ok: boolean(),
|
|
22060
|
-
issues: array(PipelineValidationIssueSchema).readonly(),
|
|
22061
|
-
substitutions: array(ModelSubstitutionSchema).readonly(),
|
|
22062
|
-
/** The node's `currentEngine.format` this validation ran against. */
|
|
22063
|
-
format: string()
|
|
22064
|
-
});
|
|
22065
|
-
var ReferenceImageEntrySchema = object({
|
|
22066
|
-
filename: string(),
|
|
22067
|
-
stepIds: array(string()).readonly().optional()
|
|
22068
|
-
});
|
|
22069
|
-
var ReferenceImageBodySchema = object({
|
|
22070
|
-
base64: string(),
|
|
22071
|
-
filename: string()
|
|
22072
|
-
});
|
|
22073
|
-
var ReferenceAudioEntrySchema = object({
|
|
22074
|
-
filename: string(),
|
|
22075
|
-
sizeKb: number()
|
|
22076
|
-
});
|
|
22077
|
-
var ReferenceAudioBodySchema = object({ base64: string() });
|
|
22078
|
-
var AudioBackendSchema = object({
|
|
22079
|
-
id: string(),
|
|
22080
|
-
name: string(),
|
|
22081
|
-
description: string(),
|
|
22082
|
-
available: boolean(),
|
|
22083
|
-
/**
|
|
22084
|
-
* Raw classifier labels this backend can emit (e.g. YAMNet's
|
|
22085
|
-
* 521-class set or Apple SoundAnalysis's 303-class set). Used by
|
|
22086
|
-
* the benchmark UI to populate the `enabledMicroClasses` filter
|
|
22087
|
-
* specific to the selected backend without a separate fetch.
|
|
22088
|
-
*/
|
|
22089
|
-
rawLabels: array(string()).readonly().optional()
|
|
22090
|
-
});
|
|
22091
|
-
var AudioCapabilitiesSchema = object({
|
|
22092
|
-
activeBackend: string(),
|
|
22093
|
-
availableBackends: array(AudioBackendSchema).readonly(),
|
|
22094
|
-
sampleRate: number(),
|
|
22095
|
-
chunkDurationMs: number()
|
|
22096
|
-
});
|
|
22097
|
-
var DownloadModelResultSchema = object({
|
|
22098
|
-
filePath: string(),
|
|
22099
|
-
sizeMB: number(),
|
|
22100
|
-
durationMs: number()
|
|
22101
|
-
});
|
|
22102
|
-
/**
|
|
22103
|
-
* Wrapper carrying a single test run's result. Replaces the legacy
|
|
22104
|
-
* ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
|
|
22105
|
-
* canonical `AudioResult` from the Phase 6 output rework: one
|
|
22106
|
-
* `AudioDetection` per class above `minScore`, top-N candidates in
|
|
22107
|
-
* `debug.alternateLabels['audio-classifier']`, per-source timings in
|
|
22108
|
-
* `debug.stepTimings`. The outer `success`/`error` fields stay so the
|
|
22109
|
-
* benchmark UI can still report a clean failure when the classifier
|
|
22110
|
-
* cap isn't available.
|
|
22111
|
-
*/
|
|
22112
|
-
var AudioTestResultSchema = object({
|
|
22113
|
-
success: boolean(),
|
|
22114
|
-
error: string().optional(),
|
|
22115
|
-
frame: custom().optional()
|
|
22116
|
-
});
|
|
22117
|
-
var PipelineConfigBridge = custom();
|
|
22118
|
-
var ConfigUISchemaBridge = custom();
|
|
22119
|
-
var ConfigUISchemaNullableBridge = custom();
|
|
22120
|
-
var InferenceCapabilitiesBridge = custom();
|
|
22121
|
-
var ModelAvailabilityListBridge = custom();
|
|
22122
|
-
var PipelineRunResultBridge = custom();
|
|
22123
|
-
/**
|
|
22124
|
-
* Pipeline executor — detection engine + configuration + inference API.
|
|
22125
|
-
*
|
|
22126
|
-
* Merged from: pipeline-executor, pipeline-config, inference, detection-config.
|
|
22127
|
-
* Implemented by the detection-pipeline addon.
|
|
22128
|
-
*
|
|
22129
|
-
* Per-device surface (DeviceSettingsContribution + the "is detection
|
|
22130
|
-
* enabled for this camera?" toggle) lives on the paired
|
|
22131
|
-
* `detection-pipeline` cap (device-scoped, singleton, wrapper
|
|
22132
|
-
* defaultActive) — same split pattern used by stream-broker /
|
|
22133
|
-
* camera-streams and audio-analyzer / audio-analysis.
|
|
22134
|
-
*/
|
|
22135
|
-
var pipelineExecutorCapability = {
|
|
22136
|
-
name: "pipeline-executor",
|
|
22137
|
-
scope: "system",
|
|
22138
|
-
mode: "singleton",
|
|
22139
|
-
methods: {
|
|
22140
|
-
getAvailableEngines: method(_void(), array(PipelineEngineChoiceSchema)),
|
|
22141
|
-
getSelectedEngine: method(_void(), PipelineEngineChoiceSchema),
|
|
22142
|
-
getDefaultSteps: method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)),
|
|
22143
|
-
/**
|
|
22144
|
-
* Per-node detection-engine provisioning snapshot. Returns the live
|
|
22145
|
-
* state of the lazy runtime-provisioning machine on `nodeId`
|
|
22146
|
-
* (idle / installing / verifying / ready / failed). The UI pairs this
|
|
22147
|
-
* one-shot query with the `pipeline.engine-provisioning` live event
|
|
22148
|
-
* (emitted on every transition) to drive a per-node "engine ready?"
|
|
22149
|
-
* indicator without polling. Phase 2.
|
|
22150
|
-
*/
|
|
22151
|
-
getEngineProvisioning: method(object({ nodeId: string() }), EngineProvisioningSchema),
|
|
22152
|
-
getVideoPipelineSteps: method(_void(), record(string(), object({
|
|
22153
|
-
modelId: string(),
|
|
22154
|
-
settings: record(string(), unknown()).readonly()
|
|
22155
|
-
}))),
|
|
22156
|
-
setVideoPipelineSteps: method(object({ steps: record(string(), object({
|
|
22157
|
-
modelId: string(),
|
|
22158
|
-
settings: record(string(), unknown()).readonly()
|
|
22159
|
-
})) }), object({ success: literal(true) }), {
|
|
22160
|
-
kind: "mutation",
|
|
22161
|
-
auth: "admin"
|
|
22162
|
-
}),
|
|
22163
|
-
/**
|
|
22164
|
-
* Clear THIS node's executor-side PER-DEVICE settings stores (the
|
|
22165
|
-
* per-camera step overrides the object-detection root reads via
|
|
22166
|
-
* `applyDeviceOverridesToTree`). `nodeId` is the ROUTING key — the
|
|
22167
|
-
* generated cap-router strips it (default `nodeIdMode: 'routing'`) and
|
|
22168
|
-
* dispatches to that node, so the provider method runs ON the target
|
|
22169
|
-
* node and receives no `nodeId`.
|
|
22170
|
-
*
|
|
22171
|
-
* This is the slimmed executor leg of the orchestrator's
|
|
22172
|
-
* `resetNodePipelineDefaults` flow (which owns the real reset: node
|
|
22173
|
-
* addonDefaults pins + per-camera orchestrator overrides). The legacy
|
|
22174
|
-
* `resetToDefault` — which reset a persisted global step-tree seed
|
|
22175
|
-
* nothing in the live per-camera path read — was removed together with
|
|
22176
|
-
* that seed.
|
|
22177
|
-
*/
|
|
22178
|
-
clearDeviceOverrides: method(object({ nodeId: string() }), object({
|
|
22179
|
-
success: literal(true),
|
|
22180
|
-
clearedDevices: number()
|
|
22181
|
-
}), {
|
|
22182
|
-
kind: "mutation",
|
|
22183
|
-
auth: "admin"
|
|
22184
|
-
}),
|
|
22185
|
-
/**
|
|
22186
|
-
* Which of THIS node's inference devices the executor currently refuses,
|
|
22187
|
-
* and why. `nodeId` is the ROUTING key (stripped by the generated router).
|
|
22188
|
-
*
|
|
22189
|
-
* The channel that did not exist. Pool health was known only inside the
|
|
22190
|
-
* detection addon and was an input to no routing decision anywhere: the
|
|
22191
|
-
* per-dispatch capability gate is keyed on model FORMAT and so can never
|
|
22192
|
-
* separate `openvino:gpu` from `openvino:npu`, and the orchestrator's live
|
|
22193
|
-
* eligibility probe (`platformProbe.getCapabilities`) answers about
|
|
22194
|
-
* HARDWARE — which was present throughout. So when the hub's `openvino:gpu`
|
|
22195
|
-
* Python worker was SIGABRT'd by the Intel GPU plugin on 2026-08-25, the
|
|
22196
|
-
* balancer went on handing that dead pool cameras by rotation for 31 hours:
|
|
22197
|
-
* ~370 000 `PoolWorker[w0]: not initialized` lines, every frame lost.
|
|
22198
|
-
*
|
|
22199
|
-
* Read semantics the caller depends on, and which the provider guarantees:
|
|
22200
|
-
* this is a synchronous read of in-memory state. It never probes hardware,
|
|
22201
|
-
* never spawns a pool and never throws — an EMPTY `unhealthy` means "asked,
|
|
22202
|
-
* nothing is refused", which is what re-admits a device. A read that FAILS
|
|
22203
|
-
* (node offline, version skew) must therefore be distinguishable from an
|
|
22204
|
-
* empty answer, and it is: it rejects.
|
|
22205
|
-
*/
|
|
22206
|
-
getInferenceDeviceHealth: method(object({ nodeId: string() }), object({ unhealthy: array(object({
|
|
22207
|
-
/** `<backend>:<device>`, e.g. `openvino:gpu`. */
|
|
22208
|
-
deviceKey: string(),
|
|
22209
|
-
/**
|
|
22210
|
-
* `failed` — the per-device restart budget is exhausted; no pool
|
|
22211
|
-
* will be spawned until an operator re-arms it or the runner
|
|
22212
|
-
* respawns. `backoff` — under budget, waiting out the backoff (or
|
|
22213
|
-
* a cached pool observed dead and not yet condemned).
|
|
22214
|
-
*/
|
|
22215
|
-
state: _enum(["failed", "backoff"]),
|
|
22216
|
-
/** Epoch ms of the death that produced this state. */
|
|
22217
|
-
since: number(),
|
|
22218
|
-
/** Pool deaths inside the current window. */
|
|
22219
|
-
deaths: number(),
|
|
22220
|
-
/** The last death's message. */
|
|
22221
|
-
lastError: string()
|
|
22222
|
-
})).readonly() })),
|
|
22223
|
-
/**
|
|
22224
|
-
* Re-arm a terminally `failed` inference device on `nodeId`: forget its
|
|
22225
|
-
* restart budget so the next dispatch builds a fresh pool.
|
|
22226
|
-
*
|
|
22227
|
-
* The terminal state is deliberate (the abort it bounds is deterministic —
|
|
22228
|
-
* an automatic probation would just respawn Python forever, more slowly),
|
|
22229
|
-
* and a terminal state an operator cannot leave is a silent fault. This is
|
|
22230
|
-
* the way out. `rearmed:false` means there was nothing to forget.
|
|
22231
|
-
*/
|
|
22232
|
-
rearmInferenceDevice: method(object({
|
|
22233
|
-
nodeId: string(),
|
|
22234
|
-
deviceKey: string()
|
|
22235
|
-
}), object({ rearmed: boolean() }), {
|
|
22236
|
-
kind: "mutation",
|
|
22237
|
-
auth: "admin"
|
|
22238
|
-
}),
|
|
22239
|
-
getSchema: method(_void(), PipelineSchemaSchema),
|
|
22240
|
-
getGlobalSteps: method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()),
|
|
22241
|
-
getGlobalPipelineConfig: method(_void(), PipelineConfigBridge),
|
|
22242
|
-
getOrchestratorConfigSchema: method(_void(), ConfigUISchemaBridge),
|
|
22243
|
-
/**
|
|
22244
|
-
* Gate B (pre-init config-correctness gate). PURE COMPUTE against this
|
|
22245
|
-
* node's `currentEngine.format` — resolves `steps` the same way the
|
|
22246
|
-
* runtime dispatch path would, and reports what WOULD happen without
|
|
22247
|
-
* touching any node-global state. Called by the orchestrator at attach
|
|
22248
|
-
* time (`attachOn`), node-pinned to the TARGET node, so config problems
|
|
22249
|
-
* surface BEFORE `pipelineRunner.attachCamera` rather than at the first
|
|
22250
|
-
* per-frame resolve. `ok` is false iff `issues` is non-empty (both
|
|
22251
|
-
* `unknown-addon` and `no-format-build` are HARD issues); `substitutions`
|
|
22252
|
-
* is informational (a degraded-but-loadable model swap) and never
|
|
22253
|
-
* affects `ok`. Never throws.
|
|
22254
|
-
*/
|
|
22255
|
-
validatePipeline: method(object({ steps: array(PipelineStepInputSchema) }), PipelineValidationResultSchema),
|
|
22256
|
-
listTemplates: method(_void(), array(PipelineTemplateSchema$1).readonly()),
|
|
22257
|
-
saveTemplate: method(object({
|
|
22258
|
-
name: string(),
|
|
22259
|
-
steps: array(PipelineTemplateStepSchema).readonly(),
|
|
22260
|
-
engine: PipelineEngineChoiceSchema
|
|
22261
|
-
}), PipelineTemplateSchema$1, { kind: "mutation" }),
|
|
22262
|
-
updateTemplate: method(object({
|
|
22263
|
-
id: string(),
|
|
22264
|
-
name: string().optional(),
|
|
22265
|
-
steps: array(PipelineTemplateStepSchema).readonly().optional()
|
|
22266
|
-
}), PipelineTemplateSchema$1, { kind: "mutation" }),
|
|
22267
|
-
deleteTemplate: method(object({ id: string() }), _void(), { kind: "mutation" }),
|
|
22268
|
-
getCapabilities: method(_void(), InferenceCapabilitiesBridge),
|
|
22269
|
-
getAddonModels: method(object({ addonId: string() }), ModelAvailabilityListBridge),
|
|
22270
|
-
downloadModel: method(object({
|
|
22271
|
-
addonId: string(),
|
|
22272
|
-
modelId: string(),
|
|
22273
|
-
format: ModelFormatSchema$1
|
|
22274
|
-
}), DownloadModelResultSchema, { kind: "mutation" }),
|
|
22275
|
-
deleteModel: method(object({
|
|
22276
|
-
addonId: string(),
|
|
22277
|
-
modelId: string(),
|
|
22278
|
-
format: ModelFormatSchema$1
|
|
22279
|
-
}), object({ success: literal(true) }), { kind: "mutation" }),
|
|
22280
|
-
/**
|
|
22281
|
-
* Stateless single-frame execution. Callers (runner, benchmark) pass
|
|
22282
|
-
* the complete `engine` + `steps` tree; the executor holds no state
|
|
22283
|
-
* about cameras or saved pipelines.
|
|
22284
|
-
*
|
|
22285
|
-
* `engine` is optional during the migration window to preserve the
|
|
22286
|
-
* legacy call shape used by existing benchmark code; once all
|
|
22287
|
-
* callers pass it explicitly we make it required.
|
|
22288
|
-
*
|
|
22289
|
-
* Exactly one of `frame`, `frameRef`, `frameHandle`, `imageBase64`,
|
|
22290
|
-
* `referenceImage` must be provided:
|
|
22291
|
-
* - `frame`: runtime dispatch path (runner → decoded broker frame).
|
|
22292
|
-
* Carries the raw buffer, dimensions, and format; the executor
|
|
22293
|
-
* uses it directly without base64 round-tripping.
|
|
22294
|
-
* - `frameHandle` (CB5): a zero-pixel shm `FrameHandle` for the SAME
|
|
22295
|
-
* decoded frame. Both runner and executor are hub-local processes
|
|
22296
|
-
* sharing `/dev/shm`, so the executor maps the named segment and
|
|
22297
|
-
* reads the pixels back zero-copy — eliminating the ~1.2MB
|
|
22298
|
-
* re-serialisation over UDS/MsgPack the `frame` path pays per call.
|
|
22299
|
-
* High-risk: the FrameRing is a latest-wins seqlock with no
|
|
22300
|
-
* refcount, so a recycled slot yields a null read; the executor
|
|
22301
|
-
* then degrades to an empty result and the runner ships pixels via
|
|
22302
|
-
* `frame` as the fallback (queue-depth gated on the runner side).
|
|
22303
|
-
* - `imageBase64`: one-shot test path (benchmark ImageTab).
|
|
22304
|
-
* - `referenceImage`: named file from the reference-image store.
|
|
22305
|
-
*/
|
|
22306
|
-
runPipeline: method(object({
|
|
22307
|
-
engine: PipelineEngineChoiceSchema.optional(),
|
|
22308
|
-
steps: array(PipelineStepInputSchema).min(1),
|
|
22309
|
-
frame: FrameInputSchema.optional(),
|
|
22310
|
-
/**
|
|
22311
|
-
* Process-local lazy frame. Valid only when caller and provider resolve
|
|
22312
|
-
* in the same execution-group process; split/cross-node callers use
|
|
22313
|
-
* `frame`/`image` inline compatibility instead.
|
|
22314
|
-
*/
|
|
22315
|
-
frameRef: FrameRefSchema.optional(),
|
|
22316
|
-
/**
|
|
22317
|
-
* CB5 shm passthrough — a `FrameHandle` naming the same ring slot
|
|
22318
|
-
* the decoded pixels live in. One more member of the one-of
|
|
22319
|
-
* frame/frameHandle/image/imageBase64/referenceImage group.
|
|
22320
|
-
*/
|
|
22321
|
-
frameHandle: FrameHandleSchema.optional(),
|
|
22322
|
-
imageBase64: string().optional(),
|
|
22323
|
-
/**
|
|
22324
|
-
* Binary JPEG bytes — preferred over `imageBase64` on internal
|
|
22325
|
-
* hops (hub → forked worker via Moleculer MsgPack) because it
|
|
22326
|
-
* skips the 33% base64 overhead + the per-call base64 decode on
|
|
22327
|
-
* the detection-pipeline worker. Callers can pass either; exactly
|
|
22328
|
-
* one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
|
|
22329
|
-
*/
|
|
22330
|
-
image: _instanceof(Uint8Array).optional(),
|
|
22331
|
-
referenceImage: string().optional(),
|
|
22332
|
-
deviceId: number().optional(),
|
|
22333
|
-
sessionId: string().optional(),
|
|
22334
|
-
/**
|
|
22335
|
-
* Execution plane. 'full' (default) runs the whole tree — benchmark,
|
|
22336
|
-
* reference-image, and detail-subtree calls. 'frame' is the live
|
|
22337
|
-
* per-frame dispatch: ONLY root-plane steps run; crop children
|
|
22338
|
-
* (inputClasses ≠ null) are skipped and served per-track via
|
|
22339
|
-
* pipelineRunner.runDetailSubtree (two-plane design).
|
|
22340
|
-
*/
|
|
22341
|
-
plane: _enum(["full", "frame"]).optional(),
|
|
22342
|
-
/**
|
|
22343
|
-
* Inference-device selector (Phase 2 multi-device). Format
|
|
22344
|
-
* `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
|
|
22345
|
-
* Omitted ⇒ the runner's default device (current single-engine
|
|
22346
|
-
* behaviour). Selects WHICH device pool of the node runs the call.
|
|
22347
|
-
*/
|
|
22348
|
-
deviceKey: string().optional(),
|
|
22349
|
-
/**
|
|
22350
|
-
* Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
|
|
22351
|
-
* when the parent crop was resolved from the frame's retained NATIVE
|
|
22352
|
-
* surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
|
|
22353
|
-
* child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
|
|
22354
|
-
* resolution from that surface — the SAME quality path faces already
|
|
22355
|
-
* had — instead of the downscaled parent tile. `handle` keys the native
|
|
22356
|
-
* surface (node-pinned to its owner); `cropFrameSpace` is the parent
|
|
22357
|
-
* crop's padded/clamped rectangle in FRAME-space pixels, used to compose
|
|
22358
|
-
* the executor's crop-normalized child ROI back into frame-normalized
|
|
22359
|
-
* coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
|
|
22360
|
-
* of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
|
|
22361
|
-
* (today's behaviour on the fallback path).
|
|
22362
|
-
*/
|
|
22363
|
-
nativeCropRef: NativeCropRefSchema.optional()
|
|
22364
|
-
}), PipelineRunResultBridge, { kind: "mutation" }),
|
|
22365
|
-
/**
|
|
22366
|
-
* Batched run — N raw frames packed into one cap call. The provider
|
|
22367
|
-
* routes the batch through `SharedInferencePool.inferBatch`
|
|
22368
|
-
* (`MSG_INFER_BATCH = 0x03`) so the IPC framing and JSON response
|
|
22369
|
-
* envelope cost is amortised N:1 vs N concurrent `runPipeline`
|
|
22370
|
-
* calls. Single root step + uniform model assumed; trees with crop
|
|
22371
|
-
* children fall back to sequential execution.
|
|
22372
|
-
*
|
|
22373
|
-
* Used by `scripts/bench-batch-style.mts` for batch benchmarking —
|
|
22374
|
-
* N frames in one call to amortise per-call IPC overhead.
|
|
22375
|
-
*/
|
|
22376
|
-
runPipelineBatch: method(object({
|
|
22377
|
-
engine: PipelineEngineChoiceSchema.optional(),
|
|
22378
|
-
steps: array(PipelineStepInputSchema).min(1),
|
|
22379
|
-
frames: array(FrameInputSchema).min(1).max(255),
|
|
22380
|
-
deviceId: number().optional(),
|
|
22381
|
-
sessionId: string().optional(),
|
|
22382
|
-
/**
|
|
22383
|
-
* Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
|
|
22384
|
-
* the batch to the Python pool's bench preprocess cache
|
|
22385
|
-
* (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
|
|
22386
|
-
* preprocessed ONCE and every later inference is a pure-inference cache
|
|
22387
|
-
* hit — the sustained-throughput run measures inference, not
|
|
22388
|
-
* decode+preprocess+infer. Omitted/0 for live frames (all different →
|
|
22389
|
-
* full preprocess every call, correct). Fresh per sustained run;
|
|
22390
|
-
* released via `uncacheFrame`.
|
|
22391
|
-
*/
|
|
22392
|
-
frameId: number().int().nonnegative().optional(),
|
|
22393
|
-
/** Inference-device selector (Phase 2 multi-device); see runPipeline. */
|
|
22394
|
-
deviceKey: string().optional()
|
|
22395
|
-
}), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }),
|
|
22396
|
-
/**
|
|
22397
|
-
* Cache a raw frame inside the Python inference pool's memory.
|
|
22398
|
-
* Returns a numeric `frameId` that `inferCached` references —
|
|
22399
|
-
* subsequent calls send only 5 bytes through the pipe instead of
|
|
22400
|
-
* 1.2MB raw data, eliminating the pipe transfer bottleneck.
|
|
22401
|
-
*/
|
|
22402
|
-
cacheFrameInPool: method(object({
|
|
22403
|
-
data: _instanceof(Uint8Array),
|
|
22404
|
-
width: number().int().positive(),
|
|
22405
|
-
height: number().int().positive(),
|
|
22406
|
-
format: _enum([
|
|
22407
|
-
"rgb",
|
|
22408
|
-
"bgr",
|
|
22409
|
-
"gray"
|
|
22410
|
-
])
|
|
22411
|
-
}), object({
|
|
22412
|
-
frameId: number(),
|
|
22413
|
-
width: number(),
|
|
22414
|
-
height: number()
|
|
22415
|
-
}), { kind: "mutation" }),
|
|
22416
|
-
/**
|
|
22417
|
-
* Run inference on a previously cached frame. Sends only 5 bytes
|
|
22418
|
-
* (model_idx + frameId) through the IPC pipe — eliminates the
|
|
22419
|
-
* ~35ms per-call overhead of transferring 1.2MB raw data.
|
|
22420
|
-
*/
|
|
22421
|
-
inferCached: method(object({
|
|
22422
|
-
stepId: string(),
|
|
22423
|
-
frameId: number().int()
|
|
22424
|
-
}), record(string(), unknown()), { kind: "mutation" }),
|
|
22425
|
-
/**
|
|
22426
|
-
* Release a cached frame from the Python pool's memory.
|
|
22427
|
-
*/
|
|
22428
|
-
uncacheFrame: method(object({ frameId: number().int() }), _void(), { kind: "mutation" }),
|
|
22429
|
-
/** Returns the effective pool tuning (resolved from user overrides + backend defaults). */
|
|
22430
|
-
getEffectiveTuning: method(_void(), object({
|
|
22431
|
-
batchMode: string(),
|
|
22432
|
-
windowMs: number(),
|
|
22433
|
-
maxBatchSize: number(),
|
|
22434
|
-
concurrency: number()
|
|
22435
|
-
})),
|
|
22436
|
-
/**
|
|
22437
|
-
* List every EngineFactory currently loaded in this executor's RAM,
|
|
22438
|
-
* with the models resident and a coarse "in use" marker derived from
|
|
22439
|
-
* ongoing inference activity. Used by the Pipeline page Engines tab.
|
|
22440
|
-
*/
|
|
22441
|
-
listLoadedEngines: method(_void(), array(object({
|
|
22442
|
-
engineKey: string(),
|
|
22443
|
-
engine: PipelineEngineChoiceSchema,
|
|
22444
|
-
modelsLoaded: array(string()).readonly(),
|
|
22445
|
-
inUseByCameras: array(number()).readonly(),
|
|
22446
|
-
/**
|
|
22447
|
-
* Origin of this resident factory.
|
|
22448
|
-
* - `runtime` — main camera-serving engine (no idle TTL).
|
|
22449
|
-
* - `warm-override` — benchmark/test override held in the warm
|
|
22450
|
-
* cache; auto-disposed after the idle TTL.
|
|
22451
|
-
* - `device-pool` — a concurrent per-device pool (Phase 2
|
|
22452
|
-
* multi-device, keyed by `deviceKey`) resolved
|
|
22453
|
-
* via `resolveDeviceFactory`. Runs alongside the
|
|
22454
|
-
* `runtime` engine on a DIFFERENT accelerator
|
|
22455
|
-
* (NPU / iGPU / Coral) — this is how the
|
|
22456
|
-
* Engines tab shows all pools running at once.
|
|
22457
|
-
*/
|
|
22458
|
-
kind: _enum([
|
|
22459
|
-
"runtime",
|
|
22460
|
-
"warm-override",
|
|
22461
|
-
"device-pool"
|
|
22462
|
-
]),
|
|
22463
|
-
/** Native pid of the underlying Python pool (null when no pool). */
|
|
22464
|
-
poolPid: number().nullable(),
|
|
22465
|
-
/** ms since this factory was last used (null when not warm-tracked). */
|
|
22466
|
-
idleMs: number().nullable(),
|
|
22467
|
-
/** Idle TTL after which `warm-override` factories self-evict (null when not applicable). */
|
|
22468
|
-
idleTtlMs: number().nullable()
|
|
22469
|
-
})).readonly()),
|
|
22470
|
-
/** Warm up an engine without running a frame. No-op if already loaded. */
|
|
22471
|
-
spinEngine: method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
|
|
22472
|
-
kind: "mutation",
|
|
22473
|
-
auth: "admin"
|
|
22474
|
-
}),
|
|
22475
|
-
/**
|
|
22476
|
-
* Unload an engine from RAM. `force:true` unloads even when cameras
|
|
22477
|
-
* are actively using it (they re-spin on next frame). Default is
|
|
22478
|
-
* gated — returns `{success:false, reason}` when in use.
|
|
22479
|
-
*/
|
|
22480
|
-
killEngine: method(object({
|
|
22481
|
-
engine: PipelineEngineChoiceSchema,
|
|
22482
|
-
force: boolean().optional()
|
|
22483
|
-
}), object({
|
|
22484
|
-
success: boolean(),
|
|
22485
|
-
reason: string().optional()
|
|
22486
|
-
}), {
|
|
22487
|
-
kind: "mutation",
|
|
22488
|
-
auth: "admin"
|
|
22489
|
-
}),
|
|
22490
|
-
listReferenceImages: method(_void(), array(ReferenceImageEntrySchema).readonly()),
|
|
22491
|
-
getReferenceImage: method(object({ filename: string() }), ReferenceImageBodySchema.nullable()),
|
|
22492
|
-
getReferenceAudioFiles: method(_void(), array(ReferenceAudioEntrySchema).readonly()),
|
|
22493
|
-
getReferenceAudio: method(object({ filename: string() }), ReferenceAudioBodySchema.nullable()),
|
|
22494
|
-
getAudioCapabilities: method(_void(), AudioCapabilitiesSchema),
|
|
22495
|
-
runAudioTest: method(object({
|
|
22496
|
-
addonId: string(),
|
|
22497
|
-
modelId: string(),
|
|
22498
|
-
filename: string().optional(),
|
|
22499
|
-
settings: record(string(), unknown()).optional()
|
|
22500
|
-
}), AudioTestResultSchema, { kind: "mutation" }),
|
|
22501
|
-
getDetectionConfigSchema: method(_void(), ConfigUISchemaNullableBridge)
|
|
22502
|
-
}
|
|
22503
|
-
};
|
|
22504
|
-
/**
|
|
22505
|
-
* Per-stage gating mode applied to the zones a rule references.
|
|
22506
|
-
*
|
|
22507
|
-
* - `include`: the rule contributes to a **whitelist** for its stage.
|
|
22508
|
-
* When at least one `include` rule fires for a stage, only entities
|
|
22509
|
-
* inside one of those zones pass that stage.
|
|
22510
|
-
* - `exclude`: the rule contributes to a **blacklist** for its stage.
|
|
22511
|
-
* Entities inside one of those zones are dropped at that stage.
|
|
22512
|
-
*
|
|
22513
|
-
* `monitor`-style observation (count without filtering) is not a rule
|
|
22514
|
-
* mode — zones without any matching rule are observed naturally by
|
|
22515
|
-
* `zone-analytics` (live snapshot + history), so an "I just want to
|
|
22516
|
-
* count, not filter" use case needs no rule at all.
|
|
22517
|
-
*/
|
|
22518
|
-
var ZoneRuleModeEnum = _enum(["include", "exclude"]);
|
|
22519
|
-
/**
|
|
22520
|
-
* Per-consumer rule that references existing zones (geometry) and
|
|
22521
|
-
* defines how a specific pipeline stage should treat them. Each
|
|
22522
|
-
* consumer addon owns its own `ZoneRule[]` array in its per-device
|
|
22523
|
-
* settings:
|
|
22524
|
-
*
|
|
22525
|
-
* - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
|
|
22526
|
-
* - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
|
|
22527
|
-
* - future: notification rules, audio gating, etc.
|
|
22528
|
-
*
|
|
22529
|
-
* One rule applies to N zones (`zoneIds[]`) so the operator can
|
|
22530
|
-
* express "ignore motion in ALL of {garden, street}" with a single
|
|
22531
|
-
* rule. `classFilter` narrows the rule to specific object classes —
|
|
22532
|
-
* "drop person detections in the street, but keep cars" is one
|
|
22533
|
-
* `exclude` rule with `classFilter: ['person']`.
|
|
22534
|
-
*
|
|
22535
|
-
* `enabled` is a soft toggle — the operator can keep the rule
|
|
22536
|
-
* configured but inert without deleting it.
|
|
22537
|
-
*/
|
|
22538
|
-
var ZoneRuleSchema = object({
|
|
22539
|
-
/** Stable rule id — survives edits, used by the UI for diffing. */
|
|
22540
|
-
id: string(),
|
|
22541
|
-
/** Optional human-readable label rendered in the rule editor. */
|
|
22542
|
-
name: string().optional(),
|
|
22543
|
-
/** Zones this rule targets. The rule's `mode` applies to ALL
|
|
22544
|
-
* listed zones (OR-set: a detection in any one of them counts).
|
|
22545
|
-
* At least one zone id required — a rule with no targets is a
|
|
22546
|
-
* configuration mistake and the form validator rejects it. */
|
|
22547
|
-
zoneIds: array(string()).min(1).readonly(),
|
|
22548
|
-
mode: ZoneRuleModeEnum,
|
|
22549
|
-
/**
|
|
22550
|
-
* Class names this rule applies to. Empty / undefined ⇒ rule
|
|
22551
|
-
* applies to every class. Class strings match the `macroClass`
|
|
22552
|
-
* field on detections (e.g. `person`, `car`, `dog`).
|
|
22553
|
-
*/
|
|
22554
|
-
classFilter: array(string()).readonly().optional(),
|
|
22555
|
-
/**
|
|
22556
|
-
* Minimum bbox/mask overlap (0–1) with any of the rule's zones
|
|
22557
|
-
* required to consider an entity "in the zone". Defaults to the
|
|
22558
|
-
* consumer's stage default when omitted. Kept for back-compat with
|
|
22559
|
-
* existing per-rule overrides; new operators pick the value via
|
|
22560
|
-
* `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
|
|
22561
|
-
* set, the lower-level engine reads it as a 0–1 fraction.
|
|
22562
|
-
*/
|
|
22563
|
-
overlapThreshold: number().min(0).max(1).optional(),
|
|
22564
|
-
/**
|
|
22565
|
-
* Operator-friendly version of `overlapThreshold` — the percentage
|
|
22566
|
-
* of the detection's bbox that must lie inside the zone for the
|
|
22567
|
-
* rule to match. Documented default is 85%; the engine substitutes
|
|
22568
|
-
* that when the field is omitted (kept optional so existing rules
|
|
22569
|
-
* stored without it stay valid).
|
|
22570
|
-
*
|
|
22571
|
-
* When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
|
|
22572
|
-
* rule, the engine prefers `bboxInclusionPct` because it's the
|
|
22573
|
-
* field exposed in the UI. Internally both feed the same gate.
|
|
22574
|
-
*/
|
|
22575
|
-
bboxInclusionPct: number().min(0).max(100).optional(),
|
|
22576
|
-
/**
|
|
22577
|
-
* When `true` and a detection has a segmentation mask, use the
|
|
22578
|
-
* mask for overlap instead of the bbox. Detection-stage only;
|
|
22579
|
-
* motion rules ignore this field.
|
|
22580
|
-
*/
|
|
22581
|
-
preferMask: boolean().optional(),
|
|
22582
|
-
/**
|
|
22583
|
-
* Soft-toggle: `false` disables the rule without deleting it.
|
|
22584
|
-
* Defaults to `true` so operators creating a rule via the UI
|
|
22585
|
-
* see it active immediately.
|
|
22586
|
-
*/
|
|
22587
|
-
enabled: boolean().default(true)
|
|
22588
|
-
});
|
|
22589
|
-
array(ZoneRuleSchema).readonly();
|
|
22590
|
-
/**
|
|
22591
|
-
* Zone — pure geometry + identity. NO filtering behaviour.
|
|
22592
|
-
*
|
|
22593
|
-
* Zones describe **where** in the frame the operator wants to flag
|
|
22594
|
-
* something; consumer-owned {@link ZoneRule} arrays describe **how**
|
|
22595
|
-
* each pipeline stage uses them. Splitting the two means a single
|
|
22596
|
-
* polygon "Driveway" can simultaneously back a motion-exclude rule,
|
|
22597
|
-
* a detection-include rule on `['car']`, and an occupancy aggregate
|
|
22598
|
-
* — without three duplicated polygons.
|
|
22599
|
-
*
|
|
22600
|
-
* Owned by the orchestrator addon (provider) and mirrored into the
|
|
22601
|
-
* `zones` device-state slice on every mutation. Consumers
|
|
22602
|
-
* (motion-wasm, pipeline-executor, analytics, admin UI) read either
|
|
22603
|
-
* via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
|
|
22604
|
-
* mirror with `onChanged`).
|
|
22605
|
-
*
|
|
22606
|
-
* Coordinates are normalised fractions of the frame (0–1) so zones
|
|
22607
|
-
* survive resolution changes and stream profile switches.
|
|
22608
|
-
*
|
|
22609
|
-
* `kind` discriminates between full polygons (closed regions used
|
|
22610
|
-
* for intrusion / occupancy filters) and tripwires (open 2-point
|
|
22611
|
-
* line segments used for cross events). Onboard / firmware-reported
|
|
22612
|
-
* zones (Reolink, ONVIF) are out of scope for now — see the deferred
|
|
22613
|
-
* task list.
|
|
22614
|
-
*/
|
|
22615
|
-
var ZoneKindEnum = _enum(["polygon", "tripwire"]);
|
|
22616
|
-
/** Polygon vertex in fraction-of-frame coordinates (0–1). */
|
|
22617
|
-
var PolygonPointSchema = object({
|
|
22618
|
-
x: number(),
|
|
22619
|
-
y: number()
|
|
22620
|
-
});
|
|
22621
|
-
/** A camera detection zone — pure geometry/identity. */
|
|
22622
|
-
var ZoneSchema = object({
|
|
22623
|
-
id: string(),
|
|
22624
|
-
name: string(),
|
|
22625
|
-
kind: ZoneKindEnum.default("polygon"),
|
|
22626
|
-
/** Polygon vertices, fraction of frame (0–1). */
|
|
22627
|
-
polygon: array(PolygonPointSchema).readonly(),
|
|
22628
|
-
/** Visual color for UI rendering. */
|
|
22629
|
-
color: string().default("#3b82f6")
|
|
22630
|
-
});
|
|
22631
|
-
DeviceType.Camera, method(object({ deviceId: number() }), array(ZoneSchema).readonly()), method(object({
|
|
22632
|
-
deviceId: number(),
|
|
22633
|
-
zone: ZoneSchema
|
|
22634
|
-
}), _void(), {
|
|
22635
|
-
kind: "mutation",
|
|
22636
|
-
auth: "admin"
|
|
22637
|
-
}), method(object({
|
|
22638
|
-
deviceId: number(),
|
|
22639
|
-
zoneId: string()
|
|
22640
|
-
}), _void(), {
|
|
22641
|
-
kind: "mutation",
|
|
22642
|
-
auth: "admin"
|
|
22643
|
-
}), method(object({
|
|
22644
|
-
deviceId: number(),
|
|
22645
|
-
zone: ZoneSchema
|
|
22646
|
-
}), _void(), {
|
|
22647
|
-
kind: "mutation",
|
|
22648
|
-
auth: "admin"
|
|
22649
|
-
}), object({ zones: array(ZoneSchema).readonly() });
|
|
22650
|
-
/**
|
|
22651
21954
|
* pipeline-analytics — device-scoped wrapper cap. Refines raw
|
|
22652
21955
|
* per-frame detections emitted by the pipeline runner into tracked
|
|
22653
21956
|
* objects, per-kind event collections (motion / object / audio), and
|
|
@@ -23218,6 +22521,22 @@ var TrackSchema = object({
|
|
|
23218
22521
|
* omit it. Render nothing when absent.
|
|
23219
22522
|
*/
|
|
23220
22523
|
previewMissReason: string().optional(),
|
|
22524
|
+
/**
|
|
22525
|
+
* The ROOT model that produced this track — the `FrameResult.rootModelId` of
|
|
22526
|
+
* the frame it was BORN on (D646). What a later replay compares against, so
|
|
22527
|
+
* "the original model" is named rather than assumed. A birth fact: never
|
|
22528
|
+
* rewritten by a later frame. **Absent ≠ unknown-model**: a row from before
|
|
22529
|
+
* the column, and a track the camera's own detector produced, carry none.
|
|
22530
|
+
*/
|
|
22531
|
+
modelId: string().optional(),
|
|
22532
|
+
/**
|
|
22533
|
+
* Size (px) of the detection frame this track's `positions[].bbox` live in —
|
|
22534
|
+
* the frame it was born on, else the camera's latest analysis frame at
|
|
22535
|
+
* persist time. Divide a position by these to normalise it. Absent when the
|
|
22536
|
+
* size was never known.
|
|
22537
|
+
*/
|
|
22538
|
+
frameWidth: number().optional(),
|
|
22539
|
+
frameHeight: number().optional(),
|
|
23221
22540
|
...TrackFlagFields,
|
|
23222
22541
|
...TrackRetrainFields
|
|
23223
22542
|
});
|
|
@@ -24224,24 +23543,6 @@ var MediaReclaimStatusSchema = object({
|
|
|
24224
23543
|
finishedAtMs: number().nullable(),
|
|
24225
23544
|
error: string().nullable()
|
|
24226
23545
|
});
|
|
24227
|
-
var ReplayFrameInputSchema = object({
|
|
24228
|
-
timestamp: number(),
|
|
24229
|
-
frame: PipelineRunResultBridge
|
|
24230
|
-
});
|
|
24231
|
-
var RunReplayFrameProcessorResultSchema = object({ tracks: array(object({
|
|
24232
|
-
className: string(),
|
|
24233
|
-
firstSeenMs: number(),
|
|
24234
|
-
lastSeenMs: number(),
|
|
24235
|
-
/** Bbox of the track's FIRST matched detection, pixel-space in the clip's
|
|
24236
|
-
* frame — a representative box for the diff's `(className, window, IoU)`
|
|
24237
|
-
* pairing (`replay-diff.ts`). A replay does not need the full per-frame
|
|
24238
|
-
* trajectory production's `Track.positions` keeps. */
|
|
24239
|
-
bbox: BoundingBoxSchema,
|
|
24240
|
-
/** How many of the input frames this track matched a real detection on
|
|
24241
|
-
* (never a coasted/extrapolated frame) — the replay's own signal for "how
|
|
24242
|
-
* solid is this track", cheaper than re-deriving it from a trajectory. */
|
|
24243
|
-
framesMatched: number().int()
|
|
24244
|
-
})).readonly() });
|
|
24245
23546
|
/**
|
|
24246
23547
|
* The long-term series a camera produces. A closed union so a typo cannot
|
|
24247
23548
|
* invent one, and the ONE declaration of it — the addon's `AnalyticsLtsSeries`
|
|
@@ -24472,16 +23773,6 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
24472
23773
|
}), RetrainAssistResultSchema, {
|
|
24473
23774
|
kind: "mutation",
|
|
24474
23775
|
auth: "admin"
|
|
24475
|
-
}), method(object({
|
|
24476
|
-
deviceId: number(),
|
|
24477
|
-
source: DetectionSourceSchema,
|
|
24478
|
-
zones: array(ZoneSchema).readonly().optional(),
|
|
24479
|
-
detectionRules: array(ZoneRuleSchema).readonly().optional(),
|
|
24480
|
-
zoneMembershipMinOverlap: number().min(0).max(1).optional(),
|
|
24481
|
-
frames: array(ReplayFrameInputSchema).min(1)
|
|
24482
|
-
}), RunReplayFrameProcessorResultSchema, {
|
|
24483
|
-
kind: "mutation",
|
|
24484
|
-
auth: "admin"
|
|
24485
23776
|
}), method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
|
|
24486
23777
|
kind: "query",
|
|
24487
23778
|
auth: "admin"
|
|
@@ -24557,6 +23848,674 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
24557
23848
|
eventId: string(),
|
|
24558
23849
|
timestamp: number()
|
|
24559
23850
|
});
|
|
23851
|
+
/**
|
|
23852
|
+
* Reference to the frame's retained NATIVE surface + the parent crop's placement
|
|
23853
|
+
* within the frame, so the executor can re-cut a leaf child ROI at native
|
|
23854
|
+
* resolution on the detail plane. See the `runPipeline` `nativeCropRef` field.
|
|
23855
|
+
*/
|
|
23856
|
+
var NativeCropRefSchema = object({
|
|
23857
|
+
/** Handle keying the retained native surface (node-pinned to its owner). */
|
|
23858
|
+
handle: FrameHandleSchema,
|
|
23859
|
+
/** The parent crop's padded/clamped rectangle in FRAME-space pixels. */
|
|
23860
|
+
cropFrameSpace: object({
|
|
23861
|
+
x: number(),
|
|
23862
|
+
y: number(),
|
|
23863
|
+
w: number(),
|
|
23864
|
+
h: number()
|
|
23865
|
+
})
|
|
23866
|
+
});
|
|
23867
|
+
object({
|
|
23868
|
+
crop: object({
|
|
23869
|
+
left: number(),
|
|
23870
|
+
top: number(),
|
|
23871
|
+
width: number().positive(),
|
|
23872
|
+
height: number().positive()
|
|
23873
|
+
}).optional(),
|
|
23874
|
+
content: object({
|
|
23875
|
+
width: number().int().positive(),
|
|
23876
|
+
height: number().int().positive()
|
|
23877
|
+
}),
|
|
23878
|
+
fit: _enum(["stretch", "contain"]),
|
|
23879
|
+
format: _enum([
|
|
23880
|
+
"rgb",
|
|
23881
|
+
"gray",
|
|
23882
|
+
"jpeg"
|
|
23883
|
+
])
|
|
23884
|
+
});
|
|
23885
|
+
/**
|
|
23886
|
+
* Process-local frame identity. It is serializable so it can ride an in-process
|
|
23887
|
+
* capability call, but `registryId` deliberately prevents resolution in any
|
|
23888
|
+
* other process or execution group.
|
|
23889
|
+
*/
|
|
23890
|
+
var FrameRefSchema = object({
|
|
23891
|
+
registryId: string().min(1),
|
|
23892
|
+
id: string().min(1),
|
|
23893
|
+
width: number().int().positive(),
|
|
23894
|
+
height: number().int().positive(),
|
|
23895
|
+
format: _enum(["rgb", "gray"]),
|
|
23896
|
+
timestamp: number(),
|
|
23897
|
+
capturedAt: number().optional()
|
|
23898
|
+
});
|
|
23899
|
+
var ModelFormatSchema$1 = _enum([
|
|
23900
|
+
"onnx",
|
|
23901
|
+
"coreml",
|
|
23902
|
+
"openvino",
|
|
23903
|
+
"tflite",
|
|
23904
|
+
"pt",
|
|
23905
|
+
"gguf"
|
|
23906
|
+
]);
|
|
23907
|
+
var PipelineSlotSchema = _enum([
|
|
23908
|
+
"detector",
|
|
23909
|
+
"cropper",
|
|
23910
|
+
"classifier",
|
|
23911
|
+
"refiner",
|
|
23912
|
+
"audio-classifier"
|
|
23913
|
+
]);
|
|
23914
|
+
var PipelineEngineChoiceSchema = object({
|
|
23915
|
+
runtime: literal("python"),
|
|
23916
|
+
backend: string(),
|
|
23917
|
+
format: ModelFormatSchema$1,
|
|
23918
|
+
device: string().optional()
|
|
23919
|
+
});
|
|
23920
|
+
/**
|
|
23921
|
+
* One pool's resolved provisioning (`getEffectiveTuning`). `numWorkers` is the
|
|
23922
|
+
* knob that lifts a saturated pool (`inference-devices.md`); `concurrency` is
|
|
23923
|
+
* per-worker in-flight requests. `deviceKey` echoes the pool asked about —
|
|
23924
|
+
* `null` for the node-default pool.
|
|
23925
|
+
*/
|
|
23926
|
+
var EffectiveTuningSchema = object({
|
|
23927
|
+
deviceKey: string().nullable(),
|
|
23928
|
+
runtime: string(),
|
|
23929
|
+
batchMode: string(),
|
|
23930
|
+
windowMs: number(),
|
|
23931
|
+
maxBatchSize: number(),
|
|
23932
|
+
concurrency: number(),
|
|
23933
|
+
numWorkers: number()
|
|
23934
|
+
});
|
|
23935
|
+
var AvailableEngineSchema = object({
|
|
23936
|
+
engine: PipelineEngineChoiceSchema,
|
|
23937
|
+
devices: array(object({
|
|
23938
|
+
id: string(),
|
|
23939
|
+
label: string(),
|
|
23940
|
+
description: string().optional()
|
|
23941
|
+
})).readonly(),
|
|
23942
|
+
defaultDevice: string()
|
|
23943
|
+
});
|
|
23944
|
+
var PipelineDefaultStepSchema = lazy(() => object({
|
|
23945
|
+
addonId: string(),
|
|
23946
|
+
addonName: string(),
|
|
23947
|
+
slot: PipelineSlotSchema,
|
|
23948
|
+
inputClasses: array(string()).readonly(),
|
|
23949
|
+
outputClasses: array(string()).readonly(),
|
|
23950
|
+
enabled: boolean(),
|
|
23951
|
+
modelId: string(),
|
|
23952
|
+
children: array(PipelineDefaultStepSchema).readonly(),
|
|
23953
|
+
group: string().optional(),
|
|
23954
|
+
settings: record(string(), unknown()).optional()
|
|
23955
|
+
}));
|
|
23956
|
+
var PipelineTemplateStepSchema = lazy(() => object({
|
|
23957
|
+
addonId: string(),
|
|
23958
|
+
enabled: boolean(),
|
|
23959
|
+
modelId: string(),
|
|
23960
|
+
children: array(PipelineTemplateStepSchema).readonly(),
|
|
23961
|
+
settings: record(string(), unknown()).optional()
|
|
23962
|
+
}));
|
|
23963
|
+
var PipelineTemplateSchema$1 = object({
|
|
23964
|
+
id: string(),
|
|
23965
|
+
name: string(),
|
|
23966
|
+
createdAt: string(),
|
|
23967
|
+
updatedAt: string(),
|
|
23968
|
+
engine: PipelineEngineChoiceSchema,
|
|
23969
|
+
steps: array(PipelineTemplateStepSchema).readonly()
|
|
23970
|
+
});
|
|
23971
|
+
var PipelineModelOptionSchema = object({
|
|
23972
|
+
id: string(),
|
|
23973
|
+
name: string(),
|
|
23974
|
+
formats: record(string(), object({
|
|
23975
|
+
downloaded: boolean(),
|
|
23976
|
+
sizeMB: number()
|
|
23977
|
+
})),
|
|
23978
|
+
group: ModelVariantGroupSchema.optional(),
|
|
23979
|
+
legacy: boolean().optional(),
|
|
23980
|
+
provider: ModelProviderIdSchema.optional()
|
|
23981
|
+
});
|
|
23982
|
+
var ConfigFieldBridge = custom();
|
|
23983
|
+
var PipelineAddonSchemaSchema = object({
|
|
23984
|
+
id: string(),
|
|
23985
|
+
name: string(),
|
|
23986
|
+
slot: PipelineSlotSchema,
|
|
23987
|
+
inputClasses: array(string()).readonly(),
|
|
23988
|
+
outputClasses: array(string()).readonly(),
|
|
23989
|
+
childSlots: array(PipelineSlotSchema).readonly(),
|
|
23990
|
+
models: array(PipelineModelOptionSchema).readonly(),
|
|
23991
|
+
defaultModelId: string(),
|
|
23992
|
+
defaultModelIdByFormat: record(string(), string()).optional(),
|
|
23993
|
+
enabledByDefault: boolean().optional(),
|
|
23994
|
+
backfillIntoExistingOverrides: boolean().optional(),
|
|
23995
|
+
defaultConfidence: number(),
|
|
23996
|
+
group: string().optional(),
|
|
23997
|
+
configSchema: array(ConfigFieldBridge).readonly().optional()
|
|
23998
|
+
});
|
|
23999
|
+
var PipelineSlotSchemaSchema = object({
|
|
24000
|
+
id: PipelineSlotSchema,
|
|
24001
|
+
label: string(),
|
|
24002
|
+
priority: number(),
|
|
24003
|
+
parentSlot: PipelineSlotSchema.nullable(),
|
|
24004
|
+
addons: array(PipelineAddonSchemaSchema).readonly()
|
|
24005
|
+
});
|
|
24006
|
+
var PipelineSchemaSchema = object({
|
|
24007
|
+
availableEngines: array(AvailableEngineSchema).readonly(),
|
|
24008
|
+
selectedEngine: PipelineEngineChoiceSchema,
|
|
24009
|
+
slots: array(PipelineSlotSchemaSchema).readonly()
|
|
24010
|
+
});
|
|
24011
|
+
var EngineProvisioningSchema = object({
|
|
24012
|
+
runtimeId: _enum([
|
|
24013
|
+
"onnx",
|
|
24014
|
+
"openvino",
|
|
24015
|
+
"coreml",
|
|
24016
|
+
"edgetpu"
|
|
24017
|
+
]).nullable(),
|
|
24018
|
+
device: string().nullable(),
|
|
24019
|
+
state: _enum([
|
|
24020
|
+
"idle",
|
|
24021
|
+
"installing",
|
|
24022
|
+
"verifying",
|
|
24023
|
+
"ready",
|
|
24024
|
+
"failed"
|
|
24025
|
+
]),
|
|
24026
|
+
progress: number().optional(),
|
|
24027
|
+
error: string().optional(),
|
|
24028
|
+
nextRetryAt: number().optional(),
|
|
24029
|
+
/**
|
|
24030
|
+
* Gate A (config-correctness gate at engine change): human-readable
|
|
24031
|
+
* config issues surfaced EAGERLY when the node's engine changes — model
|
|
24032
|
+
* substitutions ("chose X, running Y") and zero-build steps ("no model
|
|
24033
|
+
* has a <format> build"). Additive/optional: informational only, never
|
|
24034
|
+
* enforced here — `assertEngineReady` (readiness) still gates inference.
|
|
24035
|
+
* Absent/empty when the node-default tree resolves cleanly.
|
|
24036
|
+
*/
|
|
24037
|
+
configIssues: array(string()).optional()
|
|
24038
|
+
});
|
|
24039
|
+
var PipelineStepInputSchema = lazy(() => object({
|
|
24040
|
+
addonId: string(),
|
|
24041
|
+
modelId: string().optional(),
|
|
24042
|
+
enabled: boolean().default(true),
|
|
24043
|
+
children: array(PipelineStepInputSchema).optional(),
|
|
24044
|
+
settings: record(string(), unknown()).optional(),
|
|
24045
|
+
jumpDeviceKey: string().optional()
|
|
24046
|
+
}));
|
|
24047
|
+
var ModelSubstitutionSchema = object({
|
|
24048
|
+
addonId: string(),
|
|
24049
|
+
chosen: string(),
|
|
24050
|
+
running: string(),
|
|
24051
|
+
format: string()
|
|
24052
|
+
});
|
|
24053
|
+
var PipelineValidationIssueSchema = object({
|
|
24054
|
+
addonId: string(),
|
|
24055
|
+
kind: _enum([
|
|
24056
|
+
"unknown-addon",
|
|
24057
|
+
"no-format-build",
|
|
24058
|
+
"unknown-device"
|
|
24059
|
+
]),
|
|
24060
|
+
detail: string()
|
|
24061
|
+
});
|
|
24062
|
+
var PipelineValidationResultSchema = object({
|
|
24063
|
+
ok: boolean(),
|
|
24064
|
+
issues: array(PipelineValidationIssueSchema).readonly(),
|
|
24065
|
+
substitutions: array(ModelSubstitutionSchema).readonly(),
|
|
24066
|
+
/** The node's `currentEngine.format` this validation ran against. */
|
|
24067
|
+
format: string()
|
|
24068
|
+
});
|
|
24069
|
+
var ReferenceImageEntrySchema = object({
|
|
24070
|
+
filename: string(),
|
|
24071
|
+
stepIds: array(string()).readonly().optional()
|
|
24072
|
+
});
|
|
24073
|
+
var ReferenceImageBodySchema = object({
|
|
24074
|
+
base64: string(),
|
|
24075
|
+
filename: string()
|
|
24076
|
+
});
|
|
24077
|
+
var ReferenceAudioEntrySchema = object({
|
|
24078
|
+
filename: string(),
|
|
24079
|
+
sizeKb: number()
|
|
24080
|
+
});
|
|
24081
|
+
var ReferenceAudioBodySchema = object({ base64: string() });
|
|
24082
|
+
var AudioBackendSchema = object({
|
|
24083
|
+
id: string(),
|
|
24084
|
+
name: string(),
|
|
24085
|
+
description: string(),
|
|
24086
|
+
available: boolean(),
|
|
24087
|
+
/**
|
|
24088
|
+
* Raw classifier labels this backend can emit (e.g. YAMNet's
|
|
24089
|
+
* 521-class set or Apple SoundAnalysis's 303-class set). Used by
|
|
24090
|
+
* the benchmark UI to populate the `enabledMicroClasses` filter
|
|
24091
|
+
* specific to the selected backend without a separate fetch.
|
|
24092
|
+
*/
|
|
24093
|
+
rawLabels: array(string()).readonly().optional()
|
|
24094
|
+
});
|
|
24095
|
+
var AudioCapabilitiesSchema = object({
|
|
24096
|
+
activeBackend: string(),
|
|
24097
|
+
availableBackends: array(AudioBackendSchema).readonly(),
|
|
24098
|
+
sampleRate: number(),
|
|
24099
|
+
chunkDurationMs: number()
|
|
24100
|
+
});
|
|
24101
|
+
var DownloadModelResultSchema = object({
|
|
24102
|
+
filePath: string(),
|
|
24103
|
+
sizeMB: number(),
|
|
24104
|
+
durationMs: number()
|
|
24105
|
+
});
|
|
24106
|
+
/**
|
|
24107
|
+
* Wrapper carrying a single test run's result. Replaces the legacy
|
|
24108
|
+
* ad-hoc `{labels: [{className, originalClass, score}]}` shape with the
|
|
24109
|
+
* canonical `AudioResult` from the Phase 6 output rework: one
|
|
24110
|
+
* `AudioDetection` per class above `minScore`, top-N candidates in
|
|
24111
|
+
* `debug.alternateLabels['audio-classifier']`, per-source timings in
|
|
24112
|
+
* `debug.stepTimings`. The outer `success`/`error` fields stay so the
|
|
24113
|
+
* benchmark UI can still report a clean failure when the classifier
|
|
24114
|
+
* cap isn't available.
|
|
24115
|
+
*/
|
|
24116
|
+
var AudioTestResultSchema = object({
|
|
24117
|
+
success: boolean(),
|
|
24118
|
+
error: string().optional(),
|
|
24119
|
+
frame: custom().optional()
|
|
24120
|
+
});
|
|
24121
|
+
var PipelineConfigBridge = custom();
|
|
24122
|
+
var ConfigUISchemaBridge = custom();
|
|
24123
|
+
var ConfigUISchemaNullableBridge = custom();
|
|
24124
|
+
var InferenceCapabilitiesBridge = custom();
|
|
24125
|
+
var ModelAvailabilityListBridge = custom();
|
|
24126
|
+
var PipelineRunResultBridge = custom();
|
|
24127
|
+
/**
|
|
24128
|
+
* Pipeline executor — detection engine + configuration + inference API.
|
|
24129
|
+
*
|
|
24130
|
+
* Merged from: pipeline-executor, pipeline-config, inference, detection-config.
|
|
24131
|
+
* Implemented by the detection-pipeline addon.
|
|
24132
|
+
*
|
|
24133
|
+
* Per-device surface (DeviceSettingsContribution + the "is detection
|
|
24134
|
+
* enabled for this camera?" toggle) lives on the paired
|
|
24135
|
+
* `detection-pipeline` cap (device-scoped, singleton, wrapper
|
|
24136
|
+
* defaultActive) — same split pattern used by stream-broker /
|
|
24137
|
+
* camera-streams and audio-analyzer / audio-analysis.
|
|
24138
|
+
*/
|
|
24139
|
+
var pipelineExecutorCapability = {
|
|
24140
|
+
name: "pipeline-executor",
|
|
24141
|
+
scope: "system",
|
|
24142
|
+
mode: "singleton",
|
|
24143
|
+
methods: {
|
|
24144
|
+
getAvailableEngines: method(_void(), array(PipelineEngineChoiceSchema)),
|
|
24145
|
+
getSelectedEngine: method(_void(), PipelineEngineChoiceSchema),
|
|
24146
|
+
getDefaultSteps: method(PipelineEngineChoiceSchema, array(PipelineDefaultStepSchema)),
|
|
24147
|
+
/**
|
|
24148
|
+
* Per-node detection-engine provisioning snapshot. Returns the live
|
|
24149
|
+
* state of the lazy runtime-provisioning machine on `nodeId`
|
|
24150
|
+
* (idle / installing / verifying / ready / failed). The UI pairs this
|
|
24151
|
+
* one-shot query with the `pipeline.engine-provisioning` live event
|
|
24152
|
+
* (emitted on every transition) to drive a per-node "engine ready?"
|
|
24153
|
+
* indicator without polling. Phase 2.
|
|
24154
|
+
*/
|
|
24155
|
+
getEngineProvisioning: method(object({ nodeId: string() }), EngineProvisioningSchema),
|
|
24156
|
+
getVideoPipelineSteps: method(_void(), record(string(), object({
|
|
24157
|
+
modelId: string(),
|
|
24158
|
+
settings: record(string(), unknown()).readonly()
|
|
24159
|
+
}))),
|
|
24160
|
+
setVideoPipelineSteps: method(object({ steps: record(string(), object({
|
|
24161
|
+
modelId: string(),
|
|
24162
|
+
settings: record(string(), unknown()).readonly()
|
|
24163
|
+
})) }), object({ success: literal(true) }), {
|
|
24164
|
+
kind: "mutation",
|
|
24165
|
+
auth: "admin"
|
|
24166
|
+
}),
|
|
24167
|
+
/**
|
|
24168
|
+
* Clear THIS node's executor-side PER-DEVICE settings stores (the
|
|
24169
|
+
* per-camera step overrides the object-detection root reads via
|
|
24170
|
+
* `applyDeviceOverridesToTree`). `nodeId` is the ROUTING key — the
|
|
24171
|
+
* generated cap-router strips it (default `nodeIdMode: 'routing'`) and
|
|
24172
|
+
* dispatches to that node, so the provider method runs ON the target
|
|
24173
|
+
* node and receives no `nodeId`.
|
|
24174
|
+
*
|
|
24175
|
+
* This is the slimmed executor leg of the orchestrator's
|
|
24176
|
+
* `resetNodePipelineDefaults` flow (which owns the real reset: node
|
|
24177
|
+
* addonDefaults pins + per-camera orchestrator overrides). The legacy
|
|
24178
|
+
* `resetToDefault` — which reset a persisted global step-tree seed
|
|
24179
|
+
* nothing in the live per-camera path read — was removed together with
|
|
24180
|
+
* that seed.
|
|
24181
|
+
*/
|
|
24182
|
+
clearDeviceOverrides: method(object({ nodeId: string() }), object({
|
|
24183
|
+
success: literal(true),
|
|
24184
|
+
clearedDevices: number()
|
|
24185
|
+
}), {
|
|
24186
|
+
kind: "mutation",
|
|
24187
|
+
auth: "admin"
|
|
24188
|
+
}),
|
|
24189
|
+
/**
|
|
24190
|
+
* Which of THIS node's inference devices the executor currently refuses,
|
|
24191
|
+
* and why. `nodeId` is the ROUTING key (stripped by the generated router).
|
|
24192
|
+
*
|
|
24193
|
+
* The channel that did not exist. Pool health was known only inside the
|
|
24194
|
+
* detection addon and was an input to no routing decision anywhere: the
|
|
24195
|
+
* per-dispatch capability gate is keyed on model FORMAT and so can never
|
|
24196
|
+
* separate `openvino:gpu` from `openvino:npu`, and the orchestrator's live
|
|
24197
|
+
* eligibility probe (`platformProbe.getCapabilities`) answers about
|
|
24198
|
+
* HARDWARE — which was present throughout. So when the hub's `openvino:gpu`
|
|
24199
|
+
* Python worker was SIGABRT'd by the Intel GPU plugin on 2026-08-25, the
|
|
24200
|
+
* balancer went on handing that dead pool cameras by rotation for 31 hours:
|
|
24201
|
+
* ~370 000 `PoolWorker[w0]: not initialized` lines, every frame lost.
|
|
24202
|
+
*
|
|
24203
|
+
* Read semantics the caller depends on, and which the provider guarantees:
|
|
24204
|
+
* this is a synchronous read of in-memory state. It never probes hardware,
|
|
24205
|
+
* never spawns a pool and never throws — an EMPTY `unhealthy` means "asked,
|
|
24206
|
+
* nothing is refused", which is what re-admits a device. A read that FAILS
|
|
24207
|
+
* (node offline, version skew) must therefore be distinguishable from an
|
|
24208
|
+
* empty answer, and it is: it rejects.
|
|
24209
|
+
*/
|
|
24210
|
+
getInferenceDeviceHealth: method(object({ nodeId: string() }), object({ unhealthy: array(object({
|
|
24211
|
+
/** `<backend>:<device>`, e.g. `openvino:gpu`. */
|
|
24212
|
+
deviceKey: string(),
|
|
24213
|
+
/**
|
|
24214
|
+
* `failed` — the per-device restart budget is exhausted; no pool
|
|
24215
|
+
* will be spawned until an operator re-arms it or the runner
|
|
24216
|
+
* respawns. `backoff` — under budget, waiting out the backoff (or
|
|
24217
|
+
* a cached pool observed dead and not yet condemned).
|
|
24218
|
+
*/
|
|
24219
|
+
state: _enum(["failed", "backoff"]),
|
|
24220
|
+
/** Epoch ms of the death that produced this state. */
|
|
24221
|
+
since: number(),
|
|
24222
|
+
/** Pool deaths inside the current window. */
|
|
24223
|
+
deaths: number(),
|
|
24224
|
+
/** The last death's message. */
|
|
24225
|
+
lastError: string()
|
|
24226
|
+
})).readonly() })),
|
|
24227
|
+
/**
|
|
24228
|
+
* Re-arm a terminally `failed` inference device on `nodeId`: forget its
|
|
24229
|
+
* restart budget so the next dispatch builds a fresh pool.
|
|
24230
|
+
*
|
|
24231
|
+
* The terminal state is deliberate (the abort it bounds is deterministic —
|
|
24232
|
+
* an automatic probation would just respawn Python forever, more slowly),
|
|
24233
|
+
* and a terminal state an operator cannot leave is a silent fault. This is
|
|
24234
|
+
* the way out. `rearmed:false` means there was nothing to forget.
|
|
24235
|
+
*/
|
|
24236
|
+
rearmInferenceDevice: method(object({
|
|
24237
|
+
nodeId: string(),
|
|
24238
|
+
deviceKey: string()
|
|
24239
|
+
}), object({ rearmed: boolean() }), {
|
|
24240
|
+
kind: "mutation",
|
|
24241
|
+
auth: "admin"
|
|
24242
|
+
}),
|
|
24243
|
+
getSchema: method(_void(), PipelineSchemaSchema),
|
|
24244
|
+
getGlobalSteps: method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()),
|
|
24245
|
+
getGlobalPipelineConfig: method(_void(), PipelineConfigBridge),
|
|
24246
|
+
getOrchestratorConfigSchema: method(_void(), ConfigUISchemaBridge),
|
|
24247
|
+
/**
|
|
24248
|
+
* Gate B (pre-init config-correctness gate). PURE COMPUTE against this
|
|
24249
|
+
* node's `currentEngine.format` — resolves `steps` the same way the
|
|
24250
|
+
* runtime dispatch path would, and reports what WOULD happen without
|
|
24251
|
+
* touching any node-global state. Called by the orchestrator at attach
|
|
24252
|
+
* time (`attachOn`), node-pinned to the TARGET node, so config problems
|
|
24253
|
+
* surface BEFORE `pipelineRunner.attachCamera` rather than at the first
|
|
24254
|
+
* per-frame resolve. `ok` is false iff `issues` is non-empty (both
|
|
24255
|
+
* `unknown-addon` and `no-format-build` are HARD issues); `substitutions`
|
|
24256
|
+
* is informational (a degraded-but-loadable model swap) and never
|
|
24257
|
+
* affects `ok`. Never throws.
|
|
24258
|
+
*
|
|
24259
|
+
* `deviceKey` validates against THAT device pool's format instead of the
|
|
24260
|
+
* node default (D646) — the cheap question a pinned replay asks before it
|
|
24261
|
+
* reads any media: can this (node, deviceKey) run exactly this model?
|
|
24262
|
+
*/
|
|
24263
|
+
validatePipeline: method(object({
|
|
24264
|
+
steps: array(PipelineStepInputSchema),
|
|
24265
|
+
deviceKey: string().optional()
|
|
24266
|
+
}), PipelineValidationResultSchema),
|
|
24267
|
+
listTemplates: method(_void(), array(PipelineTemplateSchema$1).readonly()),
|
|
24268
|
+
saveTemplate: method(object({
|
|
24269
|
+
name: string(),
|
|
24270
|
+
steps: array(PipelineTemplateStepSchema).readonly(),
|
|
24271
|
+
engine: PipelineEngineChoiceSchema
|
|
24272
|
+
}), PipelineTemplateSchema$1, { kind: "mutation" }),
|
|
24273
|
+
updateTemplate: method(object({
|
|
24274
|
+
id: string(),
|
|
24275
|
+
name: string().optional(),
|
|
24276
|
+
steps: array(PipelineTemplateStepSchema).readonly().optional()
|
|
24277
|
+
}), PipelineTemplateSchema$1, { kind: "mutation" }),
|
|
24278
|
+
deleteTemplate: method(object({ id: string() }), _void(), { kind: "mutation" }),
|
|
24279
|
+
getCapabilities: method(_void(), InferenceCapabilitiesBridge),
|
|
24280
|
+
getAddonModels: method(object({ addonId: string() }), ModelAvailabilityListBridge),
|
|
24281
|
+
downloadModel: method(object({
|
|
24282
|
+
addonId: string(),
|
|
24283
|
+
modelId: string(),
|
|
24284
|
+
format: ModelFormatSchema$1
|
|
24285
|
+
}), DownloadModelResultSchema, { kind: "mutation" }),
|
|
24286
|
+
deleteModel: method(object({
|
|
24287
|
+
addonId: string(),
|
|
24288
|
+
modelId: string(),
|
|
24289
|
+
format: ModelFormatSchema$1
|
|
24290
|
+
}), object({ success: literal(true) }), { kind: "mutation" }),
|
|
24291
|
+
/**
|
|
24292
|
+
* Stateless single-frame execution. Callers (runner, benchmark) pass
|
|
24293
|
+
* the complete `steps` tree; the executor holds no state about cameras
|
|
24294
|
+
* or saved pipelines. WHICH pool answers is the node's own engine, or the
|
|
24295
|
+
* `deviceKey` pool when one is named.
|
|
24296
|
+
*
|
|
24297
|
+
* There is no `engine` field (D646). It existed for the benchmark and was
|
|
24298
|
+
* honoured by swapping the SHARED provider's engine, factory and executor
|
|
24299
|
+
* for the duration of the call, on a node serving live cameras. A caller
|
|
24300
|
+
* that still sends the key has it stripped here and changes nothing.
|
|
24301
|
+
*
|
|
24302
|
+
* Exactly one of `frame`, `frameRef`, `frameHandle`, `imageBase64`,
|
|
24303
|
+
* `referenceImage` must be provided:
|
|
24304
|
+
* - `frame`: runtime dispatch path (runner → decoded broker frame).
|
|
24305
|
+
* Carries the raw buffer, dimensions, and format; the executor
|
|
24306
|
+
* uses it directly without base64 round-tripping.
|
|
24307
|
+
* - `frameHandle` (CB5): a zero-pixel shm `FrameHandle` for the SAME
|
|
24308
|
+
* decoded frame. Both runner and executor are hub-local processes
|
|
24309
|
+
* sharing `/dev/shm`, so the executor maps the named segment and
|
|
24310
|
+
* reads the pixels back zero-copy — eliminating the ~1.2MB
|
|
24311
|
+
* re-serialisation over UDS/MsgPack the `frame` path pays per call.
|
|
24312
|
+
* High-risk: the FrameRing is a latest-wins seqlock with no
|
|
24313
|
+
* refcount, so a recycled slot yields a null read; the executor
|
|
24314
|
+
* then degrades to an empty result and the runner ships pixels via
|
|
24315
|
+
* `frame` as the fallback (queue-depth gated on the runner side).
|
|
24316
|
+
* - `imageBase64`: one-shot test path (benchmark ImageTab).
|
|
24317
|
+
* - `referenceImage`: named file from the reference-image store.
|
|
24318
|
+
*/
|
|
24319
|
+
runPipeline: method(object({
|
|
24320
|
+
steps: array(PipelineStepInputSchema).min(1),
|
|
24321
|
+
frame: FrameInputSchema.optional(),
|
|
24322
|
+
/**
|
|
24323
|
+
* Process-local lazy frame. Valid only when caller and provider resolve
|
|
24324
|
+
* in the same execution-group process; split/cross-node callers use
|
|
24325
|
+
* `frame`/`image` inline compatibility instead.
|
|
24326
|
+
*/
|
|
24327
|
+
frameRef: FrameRefSchema.optional(),
|
|
24328
|
+
/**
|
|
24329
|
+
* CB5 shm passthrough — a `FrameHandle` naming the same ring slot
|
|
24330
|
+
* the decoded pixels live in. One more member of the one-of
|
|
24331
|
+
* frame/frameHandle/image/imageBase64/referenceImage group.
|
|
24332
|
+
*/
|
|
24333
|
+
frameHandle: FrameHandleSchema.optional(),
|
|
24334
|
+
imageBase64: string().optional(),
|
|
24335
|
+
/**
|
|
24336
|
+
* Binary JPEG bytes — preferred over `imageBase64` on internal
|
|
24337
|
+
* hops (hub → forked worker via Moleculer MsgPack) because it
|
|
24338
|
+
* skips the 33% base64 overhead + the per-call base64 decode on
|
|
24339
|
+
* the detection-pipeline worker. Callers can pass either; exactly
|
|
24340
|
+
* one of `frame`/`image`/`imageBase64`/`referenceImage` is required.
|
|
24341
|
+
*/
|
|
24342
|
+
image: _instanceof(Uint8Array).optional(),
|
|
24343
|
+
referenceImage: string().optional(),
|
|
24344
|
+
deviceId: number().optional(),
|
|
24345
|
+
sessionId: string().optional(),
|
|
24346
|
+
/**
|
|
24347
|
+
* Execution plane. 'full' (default) runs the whole tree — benchmark,
|
|
24348
|
+
* reference-image, and detail-subtree calls. 'frame' is the live
|
|
24349
|
+
* per-frame dispatch: ONLY root-plane steps run; crop children
|
|
24350
|
+
* (inputClasses ≠ null) are skipped and served per-track via
|
|
24351
|
+
* pipelineRunner.runDetailSubtree (two-plane design).
|
|
24352
|
+
*/
|
|
24353
|
+
plane: _enum(["full", "frame"]).optional(),
|
|
24354
|
+
/**
|
|
24355
|
+
* Inference-device selector (Phase 2 multi-device). Format
|
|
24356
|
+
* `<backend>:<device>` (e.g. `openvino:gpu`, `edgetpu:usb`, `cpu`).
|
|
24357
|
+
* Omitted ⇒ the runner's default device (current single-engine
|
|
24358
|
+
* behaviour). Selects WHICH device pool of the node runs the call.
|
|
24359
|
+
*/
|
|
24360
|
+
deviceKey: string().optional(),
|
|
24361
|
+
/**
|
|
24362
|
+
* Two-plane NATIVE child-crop reference. Set by `runDetailSubtree` ONLY
|
|
24363
|
+
* when the parent crop was resolved from the frame's retained NATIVE
|
|
24364
|
+
* surface (a frameHandle HIT). Lets the executor re-cut a LEAF crop
|
|
24365
|
+
* child's ROI (plate-ocr, face-embedding, leaf classifiers) at native
|
|
24366
|
+
* resolution from that surface — the SAME quality path faces already
|
|
24367
|
+
* had — instead of the downscaled parent tile. `handle` keys the native
|
|
24368
|
+
* surface (node-pinned to its owner); `cropFrameSpace` is the parent
|
|
24369
|
+
* crop's padded/clamped rectangle in FRAME-space pixels, used to compose
|
|
24370
|
+
* the executor's crop-normalized child ROI back into frame-normalized
|
|
24371
|
+
* coordinates. Auxiliary to the image source (`image`/`frame`/…), NOT one
|
|
24372
|
+
* of the mutually-exclusive image inputs. Absent ⇒ tile-crop children
|
|
24373
|
+
* (today's behaviour on the fallback path).
|
|
24374
|
+
*/
|
|
24375
|
+
nativeCropRef: NativeCropRefSchema.optional(),
|
|
24376
|
+
/**
|
|
24377
|
+
* Offline REPLAY of stored media for `deviceId` (D56, D646): the
|
|
24378
|
+
* camera's zone gate and overrides apply as live, every executing
|
|
24379
|
+
* step runs EXACTLY the model it names on the `deviceKey` pool it
|
|
24380
|
+
* names or the call refuses (`model-not-servable` /
|
|
24381
|
+
* `device-not-servable`), and nothing reaches the camera's live
|
|
24382
|
+
* trace stream. Absent ⇒ unchanged behaviour.
|
|
24383
|
+
*/
|
|
24384
|
+
replay: literal(true).optional()
|
|
24385
|
+
}), PipelineRunResultBridge, { kind: "mutation" }),
|
|
24386
|
+
/**
|
|
24387
|
+
* Batched run — N raw frames packed into one cap call. The provider
|
|
24388
|
+
* routes the batch through `SharedInferencePool.inferBatch`
|
|
24389
|
+
* (`MSG_INFER_BATCH = 0x03`) so the IPC framing and JSON response
|
|
24390
|
+
* envelope cost is amortised N:1 vs N concurrent `runPipeline`
|
|
24391
|
+
* calls. Single root step + uniform model assumed; trees with crop
|
|
24392
|
+
* children fall back to sequential execution.
|
|
24393
|
+
*
|
|
24394
|
+
* Used by `scripts/bench-batch-style.mts` for batch benchmarking —
|
|
24395
|
+
* N frames in one call to amortise per-call IPC overhead.
|
|
24396
|
+
*/
|
|
24397
|
+
runPipelineBatch: method(object({
|
|
24398
|
+
steps: array(PipelineStepInputSchema).min(1),
|
|
24399
|
+
frames: array(FrameInputSchema).min(1).max(255),
|
|
24400
|
+
deviceId: number().optional(),
|
|
24401
|
+
sessionId: string().optional(),
|
|
24402
|
+
/**
|
|
24403
|
+
* Pure-inference benchmark hint. A NONZERO uint32 pins every frame in
|
|
24404
|
+
* the batch to the Python pool's bench preprocess cache
|
|
24405
|
+
* (`_bench_frame_id`) so a REPEATED benchmark frame is decoded +
|
|
24406
|
+
* preprocessed ONCE and every later inference is a pure-inference cache
|
|
24407
|
+
* hit — the sustained-throughput run measures inference, not
|
|
24408
|
+
* decode+preprocess+infer. Omitted/0 for live frames (all different →
|
|
24409
|
+
* full preprocess every call, correct). Fresh per sustained run;
|
|
24410
|
+
* released via `uncacheFrame`.
|
|
24411
|
+
*/
|
|
24412
|
+
frameId: number().int().nonnegative().optional(),
|
|
24413
|
+
/** Inference-device selector (Phase 2 multi-device); see runPipeline. */
|
|
24414
|
+
deviceKey: string().optional()
|
|
24415
|
+
}), object({ results: array(PipelineRunResultBridge).readonly() }), { kind: "mutation" }),
|
|
24416
|
+
/**
|
|
24417
|
+
* Cache a raw frame inside the Python inference pool's memory.
|
|
24418
|
+
* Returns a numeric `frameId` that `inferCached` references —
|
|
24419
|
+
* subsequent calls send only 5 bytes through the pipe instead of
|
|
24420
|
+
* 1.2MB raw data, eliminating the pipe transfer bottleneck.
|
|
24421
|
+
*/
|
|
24422
|
+
cacheFrameInPool: method(object({
|
|
24423
|
+
data: _instanceof(Uint8Array),
|
|
24424
|
+
width: number().int().positive(),
|
|
24425
|
+
height: number().int().positive(),
|
|
24426
|
+
format: _enum([
|
|
24427
|
+
"rgb",
|
|
24428
|
+
"bgr",
|
|
24429
|
+
"gray"
|
|
24430
|
+
])
|
|
24431
|
+
}), object({
|
|
24432
|
+
frameId: number(),
|
|
24433
|
+
width: number(),
|
|
24434
|
+
height: number()
|
|
24435
|
+
}), { kind: "mutation" }),
|
|
24436
|
+
/**
|
|
24437
|
+
* Run inference on a previously cached frame. Sends only 5 bytes
|
|
24438
|
+
* (model_idx + frameId) through the IPC pipe — eliminates the
|
|
24439
|
+
* ~35ms per-call overhead of transferring 1.2MB raw data.
|
|
24440
|
+
*/
|
|
24441
|
+
inferCached: method(object({
|
|
24442
|
+
stepId: string(),
|
|
24443
|
+
frameId: number().int()
|
|
24444
|
+
}), record(string(), unknown()), { kind: "mutation" }),
|
|
24445
|
+
/**
|
|
24446
|
+
* Release a cached frame from the Python pool's memory.
|
|
24447
|
+
*/
|
|
24448
|
+
uncacheFrame: method(object({ frameId: number().int() }), _void(), { kind: "mutation" }),
|
|
24449
|
+
/**
|
|
24450
|
+
* The effective provisioning of ONE pool of this node (operator override →
|
|
24451
|
+
* the pool's own runtime row). `deviceKey` names the device pool; absent is
|
|
24452
|
+
* the node-default pool. It answered only for the node default until
|
|
24453
|
+
* D646, so a benchmark on `openvino:npu` recorded the onnx pool's tuning
|
|
24454
|
+
* beside an NPU measurement.
|
|
24455
|
+
*/
|
|
24456
|
+
getEffectiveTuning: method(object({ deviceKey: string().optional() }), EffectiveTuningSchema),
|
|
24457
|
+
/**
|
|
24458
|
+
* List every EngineFactory currently loaded in this executor's RAM,
|
|
24459
|
+
* with the models resident and a coarse "in use" marker derived from
|
|
24460
|
+
* ongoing inference activity. Used by the Pipeline page Engines tab.
|
|
24461
|
+
*/
|
|
24462
|
+
listLoadedEngines: method(_void(), array(object({
|
|
24463
|
+
engineKey: string(),
|
|
24464
|
+
engine: PipelineEngineChoiceSchema,
|
|
24465
|
+
modelsLoaded: array(string()).readonly(),
|
|
24466
|
+
inUseByCameras: array(number()).readonly(),
|
|
24467
|
+
/**
|
|
24468
|
+
* Origin of this resident factory.
|
|
24469
|
+
* - `runtime` — main camera-serving engine (no idle TTL).
|
|
24470
|
+
* - `device-pool` — a concurrent per-device pool (Phase 2
|
|
24471
|
+
* multi-device, keyed by `deviceKey`) resolved
|
|
24472
|
+
* via `resolveDeviceFactory`. Runs alongside the
|
|
24473
|
+
* `runtime` engine on a DIFFERENT accelerator
|
|
24474
|
+
* (NPU / iGPU / Coral) — this is how the
|
|
24475
|
+
* Engines tab shows all pools running at once.
|
|
24476
|
+
*/
|
|
24477
|
+
kind: _enum(["runtime", "device-pool"]),
|
|
24478
|
+
/** Native pid of the underlying Python pool (null when no pool). */
|
|
24479
|
+
poolPid: number().nullable(),
|
|
24480
|
+
/** ms since this factory was last used (null when not warm-tracked). */
|
|
24481
|
+
idleMs: number().nullable(),
|
|
24482
|
+
/** Idle TTL after which the factory self-evicts (null when not applicable). */
|
|
24483
|
+
idleTtlMs: number().nullable()
|
|
24484
|
+
})).readonly()),
|
|
24485
|
+
/** Warm up an engine without running a frame. No-op if already loaded. */
|
|
24486
|
+
spinEngine: method(object({ engine: PipelineEngineChoiceSchema }), object({ success: literal(true) }), {
|
|
24487
|
+
kind: "mutation",
|
|
24488
|
+
auth: "admin"
|
|
24489
|
+
}),
|
|
24490
|
+
/**
|
|
24491
|
+
* Unload an engine from RAM. `force:true` unloads even when cameras
|
|
24492
|
+
* are actively using it (they re-spin on next frame). Default is
|
|
24493
|
+
* gated — returns `{success:false, reason}` when in use.
|
|
24494
|
+
*/
|
|
24495
|
+
killEngine: method(object({
|
|
24496
|
+
engine: PipelineEngineChoiceSchema,
|
|
24497
|
+
force: boolean().optional()
|
|
24498
|
+
}), object({
|
|
24499
|
+
success: boolean(),
|
|
24500
|
+
reason: string().optional()
|
|
24501
|
+
}), {
|
|
24502
|
+
kind: "mutation",
|
|
24503
|
+
auth: "admin"
|
|
24504
|
+
}),
|
|
24505
|
+
listReferenceImages: method(_void(), array(ReferenceImageEntrySchema).readonly()),
|
|
24506
|
+
getReferenceImage: method(object({ filename: string() }), ReferenceImageBodySchema.nullable()),
|
|
24507
|
+
getReferenceAudioFiles: method(_void(), array(ReferenceAudioEntrySchema).readonly()),
|
|
24508
|
+
getReferenceAudio: method(object({ filename: string() }), ReferenceAudioBodySchema.nullable()),
|
|
24509
|
+
getAudioCapabilities: method(_void(), AudioCapabilitiesSchema),
|
|
24510
|
+
runAudioTest: method(object({
|
|
24511
|
+
addonId: string(),
|
|
24512
|
+
modelId: string(),
|
|
24513
|
+
filename: string().optional(),
|
|
24514
|
+
settings: record(string(), unknown()).optional()
|
|
24515
|
+
}), AudioTestResultSchema, { kind: "mutation" }),
|
|
24516
|
+
getDetectionConfigSchema: method(_void(), ConfigUISchemaNullableBridge)
|
|
24517
|
+
}
|
|
24518
|
+
};
|
|
24560
24519
|
object({
|
|
24561
24520
|
activeCameras: number(),
|
|
24562
24521
|
throttledCameras: number(),
|
|
@@ -24582,6 +24541,66 @@ var CameraMetricsSchema = object({
|
|
|
24582
24541
|
});
|
|
24583
24542
|
var CameraMetricsWithDeviceIdSchema = CameraMetricsSchema.extend({ deviceId: number() });
|
|
24584
24543
|
/**
|
|
24544
|
+
* Zone — pure geometry + identity. NO filtering behaviour.
|
|
24545
|
+
*
|
|
24546
|
+
* Zones describe **where** in the frame the operator wants to flag
|
|
24547
|
+
* something; consumer-owned {@link ZoneRule} arrays describe **how**
|
|
24548
|
+
* each pipeline stage uses them. Splitting the two means a single
|
|
24549
|
+
* polygon "Driveway" can simultaneously back a motion-exclude rule,
|
|
24550
|
+
* a detection-include rule on `['car']`, and an occupancy aggregate
|
|
24551
|
+
* — without three duplicated polygons.
|
|
24552
|
+
*
|
|
24553
|
+
* Owned by the orchestrator addon (provider) and mirrored into the
|
|
24554
|
+
* `zones` device-state slice on every mutation. Consumers
|
|
24555
|
+
* (motion-wasm, pipeline-executor, analytics, admin UI) read either
|
|
24556
|
+
* via `api.zones.listZones` (one-shot) or via `dev.state.zones` (live
|
|
24557
|
+
* mirror with `onChanged`).
|
|
24558
|
+
*
|
|
24559
|
+
* Coordinates are normalised fractions of the frame (0–1) so zones
|
|
24560
|
+
* survive resolution changes and stream profile switches.
|
|
24561
|
+
*
|
|
24562
|
+
* `kind` discriminates between full polygons (closed regions used
|
|
24563
|
+
* for intrusion / occupancy filters) and tripwires (open 2-point
|
|
24564
|
+
* line segments used for cross events). Onboard / firmware-reported
|
|
24565
|
+
* zones (Reolink, ONVIF) are out of scope for now — see the deferred
|
|
24566
|
+
* task list.
|
|
24567
|
+
*/
|
|
24568
|
+
var ZoneKindEnum = _enum(["polygon", "tripwire"]);
|
|
24569
|
+
/** Polygon vertex in fraction-of-frame coordinates (0–1). */
|
|
24570
|
+
var PolygonPointSchema = object({
|
|
24571
|
+
x: number(),
|
|
24572
|
+
y: number()
|
|
24573
|
+
});
|
|
24574
|
+
/** A camera detection zone — pure geometry/identity. */
|
|
24575
|
+
var ZoneSchema = object({
|
|
24576
|
+
id: string(),
|
|
24577
|
+
name: string(),
|
|
24578
|
+
kind: ZoneKindEnum.default("polygon"),
|
|
24579
|
+
/** Polygon vertices, fraction of frame (0–1). */
|
|
24580
|
+
polygon: array(PolygonPointSchema).readonly(),
|
|
24581
|
+
/** Visual color for UI rendering. */
|
|
24582
|
+
color: string().default("#3b82f6")
|
|
24583
|
+
});
|
|
24584
|
+
DeviceType.Camera, method(object({ deviceId: number() }), array(ZoneSchema).readonly()), method(object({
|
|
24585
|
+
deviceId: number(),
|
|
24586
|
+
zone: ZoneSchema
|
|
24587
|
+
}), _void(), {
|
|
24588
|
+
kind: "mutation",
|
|
24589
|
+
auth: "admin"
|
|
24590
|
+
}), method(object({
|
|
24591
|
+
deviceId: number(),
|
|
24592
|
+
zoneId: string()
|
|
24593
|
+
}), _void(), {
|
|
24594
|
+
kind: "mutation",
|
|
24595
|
+
auth: "admin"
|
|
24596
|
+
}), method(object({
|
|
24597
|
+
deviceId: number(),
|
|
24598
|
+
zone: ZoneSchema
|
|
24599
|
+
}), _void(), {
|
|
24600
|
+
kind: "mutation",
|
|
24601
|
+
auth: "admin"
|
|
24602
|
+
}), object({ zones: array(ZoneSchema).readonly() });
|
|
24603
|
+
/**
|
|
24585
24604
|
* A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
|
|
24586
24605
|
* decode worker resolves it against the RETAINED native frame's real pixel dims,
|
|
24587
24606
|
* so the caller supplies only the detection-res bbox divided by the detection
|
|
@@ -24768,7 +24787,17 @@ var DetailResultSchema = object({
|
|
|
24768
24787
|
/** Face short side (px) measured on the NATIVE crop surface. The `bbox`
|
|
24769
24788
|
* above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
|
|
24770
24789
|
* consumers MUST prefer this when present (2026-07-22 native-gate fix). */
|
|
24771
|
-
nativeFaceShortSidePx: number().optional()
|
|
24790
|
+
nativeFaceShortSidePx: number().optional(),
|
|
24791
|
+
/**
|
|
24792
|
+
* The five facial landmarks of a `face` detail, in the SAME frame space as
|
|
24793
|
+
* `bbox` (D644). Additive and optional: post-analysis stores them beside the
|
|
24794
|
+
* face's source frame so a future alignment can be re-derived exactly; a
|
|
24795
|
+
* runner that predates the field sends none and the face is stored without.
|
|
24796
|
+
*/
|
|
24797
|
+
landmarks: array(object({
|
|
24798
|
+
x: number(),
|
|
24799
|
+
y: number()
|
|
24800
|
+
})).optional()
|
|
24772
24801
|
});
|
|
24773
24802
|
/**
|
|
24774
24803
|
* Why an executing node REFUSED a stateless step run (`runStatelessStep`).
|
|
@@ -33770,7 +33799,7 @@ var HardwareInfoSchema = object({
|
|
|
33770
33799
|
coral: CoralInfoSchema.nullable().optional()
|
|
33771
33800
|
});
|
|
33772
33801
|
var PlatformScoreSchema = object({
|
|
33773
|
-
runtime:
|
|
33802
|
+
runtime: literal("python"),
|
|
33774
33803
|
backend: string(),
|
|
33775
33804
|
format: _enum([
|
|
33776
33805
|
"onnx",
|
|
@@ -33807,7 +33836,7 @@ var ModelRequirementSchema = object({
|
|
|
33807
33836
|
});
|
|
33808
33837
|
var ResolvedInferenceConfigSchema = object({
|
|
33809
33838
|
modelId: string(),
|
|
33810
|
-
runtime:
|
|
33839
|
+
runtime: literal("python"),
|
|
33811
33840
|
backend: string(),
|
|
33812
33841
|
format: ModelFormatSchema,
|
|
33813
33842
|
reason: string()
|
|
@@ -36534,6 +36563,92 @@ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSche
|
|
|
36534
36563
|
kind: "mutation",
|
|
36535
36564
|
auth: "admin"
|
|
36536
36565
|
});
|
|
36566
|
+
/**
|
|
36567
|
+
* Per-stage gating mode applied to the zones a rule references.
|
|
36568
|
+
*
|
|
36569
|
+
* - `include`: the rule contributes to a **whitelist** for its stage.
|
|
36570
|
+
* When at least one `include` rule fires for a stage, only entities
|
|
36571
|
+
* inside one of those zones pass that stage.
|
|
36572
|
+
* - `exclude`: the rule contributes to a **blacklist** for its stage.
|
|
36573
|
+
* Entities inside one of those zones are dropped at that stage.
|
|
36574
|
+
*
|
|
36575
|
+
* `monitor`-style observation (count without filtering) is not a rule
|
|
36576
|
+
* mode — zones without any matching rule are observed naturally by
|
|
36577
|
+
* `zone-analytics` (live snapshot + history), so an "I just want to
|
|
36578
|
+
* count, not filter" use case needs no rule at all.
|
|
36579
|
+
*/
|
|
36580
|
+
var ZoneRuleModeEnum = _enum(["include", "exclude"]);
|
|
36581
|
+
/**
|
|
36582
|
+
* Per-consumer rule that references existing zones (geometry) and
|
|
36583
|
+
* defines how a specific pipeline stage should treat them. Each
|
|
36584
|
+
* consumer addon owns its own `ZoneRule[]` array in its per-device
|
|
36585
|
+
* settings:
|
|
36586
|
+
*
|
|
36587
|
+
* - `addon-motion-wasm` → `motionZoneRules: ZoneRule[]` (motion stage)
|
|
36588
|
+
* - `addon-detection-pipeline` → `detectionZoneRules: ZoneRule[]` (detection stage)
|
|
36589
|
+
* - future: notification rules, audio gating, etc.
|
|
36590
|
+
*
|
|
36591
|
+
* One rule applies to N zones (`zoneIds[]`) so the operator can
|
|
36592
|
+
* express "ignore motion in ALL of {garden, street}" with a single
|
|
36593
|
+
* rule. `classFilter` narrows the rule to specific object classes —
|
|
36594
|
+
* "drop person detections in the street, but keep cars" is one
|
|
36595
|
+
* `exclude` rule with `classFilter: ['person']`.
|
|
36596
|
+
*
|
|
36597
|
+
* `enabled` is a soft toggle — the operator can keep the rule
|
|
36598
|
+
* configured but inert without deleting it.
|
|
36599
|
+
*/
|
|
36600
|
+
var ZoneRuleSchema = object({
|
|
36601
|
+
/** Stable rule id — survives edits, used by the UI for diffing. */
|
|
36602
|
+
id: string(),
|
|
36603
|
+
/** Optional human-readable label rendered in the rule editor. */
|
|
36604
|
+
name: string().optional(),
|
|
36605
|
+
/** Zones this rule targets. The rule's `mode` applies to ALL
|
|
36606
|
+
* listed zones (OR-set: a detection in any one of them counts).
|
|
36607
|
+
* At least one zone id required — a rule with no targets is a
|
|
36608
|
+
* configuration mistake and the form validator rejects it. */
|
|
36609
|
+
zoneIds: array(string()).min(1).readonly(),
|
|
36610
|
+
mode: ZoneRuleModeEnum,
|
|
36611
|
+
/**
|
|
36612
|
+
* Class names this rule applies to. Empty / undefined ⇒ rule
|
|
36613
|
+
* applies to every class. Class strings match the `macroClass`
|
|
36614
|
+
* field on detections (e.g. `person`, `car`, `dog`).
|
|
36615
|
+
*/
|
|
36616
|
+
classFilter: array(string()).readonly().optional(),
|
|
36617
|
+
/**
|
|
36618
|
+
* Minimum bbox/mask overlap (0–1) with any of the rule's zones
|
|
36619
|
+
* required to consider an entity "in the zone". Defaults to the
|
|
36620
|
+
* consumer's stage default when omitted. Kept for back-compat with
|
|
36621
|
+
* existing per-rule overrides; new operators pick the value via
|
|
36622
|
+
* `bboxInclusionPct` (operator-friendly 0–100). Whichever field is
|
|
36623
|
+
* set, the lower-level engine reads it as a 0–1 fraction.
|
|
36624
|
+
*/
|
|
36625
|
+
overlapThreshold: number().min(0).max(1).optional(),
|
|
36626
|
+
/**
|
|
36627
|
+
* Operator-friendly version of `overlapThreshold` — the percentage
|
|
36628
|
+
* of the detection's bbox that must lie inside the zone for the
|
|
36629
|
+
* rule to match. Documented default is 85%; the engine substitutes
|
|
36630
|
+
* that when the field is omitted (kept optional so existing rules
|
|
36631
|
+
* stored without it stay valid).
|
|
36632
|
+
*
|
|
36633
|
+
* When BOTH `overlapThreshold` and `bboxInclusionPct` are set on a
|
|
36634
|
+
* rule, the engine prefers `bboxInclusionPct` because it's the
|
|
36635
|
+
* field exposed in the UI. Internally both feed the same gate.
|
|
36636
|
+
*/
|
|
36637
|
+
bboxInclusionPct: number().min(0).max(100).optional(),
|
|
36638
|
+
/**
|
|
36639
|
+
* When `true` and a detection has a segmentation mask, use the
|
|
36640
|
+
* mask for overlap instead of the bbox. Detection-stage only;
|
|
36641
|
+
* motion rules ignore this field.
|
|
36642
|
+
*/
|
|
36643
|
+
preferMask: boolean().optional(),
|
|
36644
|
+
/**
|
|
36645
|
+
* Soft-toggle: `false` disables the rule without deleting it.
|
|
36646
|
+
* Defaults to `true` so operators creating a rule via the UI
|
|
36647
|
+
* see it active immediately.
|
|
36648
|
+
*/
|
|
36649
|
+
enabled: boolean().default(true)
|
|
36650
|
+
});
|
|
36651
|
+
array(ZoneRuleSchema).readonly();
|
|
36537
36652
|
object({
|
|
36538
36653
|
/** Whether the script is currently executing. */
|
|
36539
36654
|
isRunning: boolean(),
|
|
@@ -43009,6 +43124,12 @@ Object.freeze({
|
|
|
43009
43124
|
addonId: null,
|
|
43010
43125
|
access: "view"
|
|
43011
43126
|
},
|
|
43127
|
+
"notificationRules.previewStateSummary": {
|
|
43128
|
+
capName: "notification-rules",
|
|
43129
|
+
capScope: "system",
|
|
43130
|
+
addonId: null,
|
|
43131
|
+
access: "view"
|
|
43132
|
+
},
|
|
43012
43133
|
"notificationRules.previewTemplate": {
|
|
43013
43134
|
capName: "notification-rules",
|
|
43014
43135
|
capScope: "system",
|
|
@@ -43585,12 +43706,6 @@ Object.freeze({
|
|
|
43585
43706
|
addonId: null,
|
|
43586
43707
|
access: "create"
|
|
43587
43708
|
},
|
|
43588
|
-
"pipelineAnalytics.runReplayFrameProcessor": {
|
|
43589
|
-
capName: "pipeline-analytics",
|
|
43590
|
-
capScope: "device",
|
|
43591
|
-
addonId: null,
|
|
43592
|
-
access: "create"
|
|
43593
|
-
},
|
|
43594
43709
|
"pipelineAnalytics.saveRetrainAnnotations": {
|
|
43595
43710
|
capName: "pipeline-analytics",
|
|
43596
43711
|
capScope: "device",
|
|
@@ -47688,11 +47803,6 @@ Object.freeze({
|
|
|
47688
47803
|
form: "single",
|
|
47689
47804
|
optional: false
|
|
47690
47805
|
}],
|
|
47691
|
-
"pipelineAnalytics.runReplayFrameProcessor": [{
|
|
47692
|
-
name: "deviceId",
|
|
47693
|
-
form: "single",
|
|
47694
|
-
optional: false
|
|
47695
|
-
}],
|
|
47696
47806
|
"pipelineAnalytics.saveRetrainAnnotations": [{
|
|
47697
47807
|
name: "deviceId",
|
|
47698
47808
|
form: "single",
|
|
@@ -48722,6 +48832,98 @@ new Map(Object.entries({
|
|
|
48722
48832
|
"camera-enabled": null
|
|
48723
48833
|
}));
|
|
48724
48834
|
[...NC_SYSTEM_EVENT_FILTER_KEYS];
|
|
48835
|
+
var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
|
|
48836
|
+
{
|
|
48837
|
+
id: "device-state",
|
|
48838
|
+
family: "device-events",
|
|
48839
|
+
label: "Device state",
|
|
48840
|
+
kinds: [
|
|
48841
|
+
"device-offline",
|
|
48842
|
+
"device-online",
|
|
48843
|
+
"device-disabled",
|
|
48844
|
+
"device-enabled"
|
|
48845
|
+
]
|
|
48846
|
+
},
|
|
48847
|
+
{
|
|
48848
|
+
id: "streams",
|
|
48849
|
+
family: "device-events",
|
|
48850
|
+
label: "Streams",
|
|
48851
|
+
kinds: ["stream-offline", "stream-online"]
|
|
48852
|
+
},
|
|
48853
|
+
{
|
|
48854
|
+
id: "battery",
|
|
48855
|
+
family: "device-events",
|
|
48856
|
+
label: "Battery",
|
|
48857
|
+
kinds: ["device-battery-low", "device-battery-normal"]
|
|
48858
|
+
},
|
|
48859
|
+
{
|
|
48860
|
+
id: "consumables",
|
|
48861
|
+
family: "device-events",
|
|
48862
|
+
label: "Consumables",
|
|
48863
|
+
kinds: ["device-consumable-low", "device-consumable-normal"]
|
|
48864
|
+
},
|
|
48865
|
+
{
|
|
48866
|
+
id: "tanks",
|
|
48867
|
+
family: "device-events",
|
|
48868
|
+
label: "Tanks",
|
|
48869
|
+
kinds: [
|
|
48870
|
+
"device-tank-low",
|
|
48871
|
+
"device-tank-full",
|
|
48872
|
+
"device-tank-normal"
|
|
48873
|
+
]
|
|
48874
|
+
},
|
|
48875
|
+
{
|
|
48876
|
+
id: "detection-blind",
|
|
48877
|
+
family: "device-events",
|
|
48878
|
+
label: "Detecting nothing",
|
|
48879
|
+
kinds: ["detection-blind"]
|
|
48880
|
+
},
|
|
48881
|
+
{
|
|
48882
|
+
id: "nodes",
|
|
48883
|
+
family: "system",
|
|
48884
|
+
label: "Nodes",
|
|
48885
|
+
kinds: [
|
|
48886
|
+
"node-offline",
|
|
48887
|
+
"node-online",
|
|
48888
|
+
"node-inference-unavailable"
|
|
48889
|
+
]
|
|
48890
|
+
},
|
|
48891
|
+
{
|
|
48892
|
+
id: "addons",
|
|
48893
|
+
family: "system",
|
|
48894
|
+
label: "Addons",
|
|
48895
|
+
kinds: [
|
|
48896
|
+
"addon-crash-loop",
|
|
48897
|
+
"addon-update-available",
|
|
48898
|
+
"addon-updated"
|
|
48899
|
+
]
|
|
48900
|
+
},
|
|
48901
|
+
{
|
|
48902
|
+
id: "updates",
|
|
48903
|
+
family: "system",
|
|
48904
|
+
label: "Server & container updates",
|
|
48905
|
+
kinds: [
|
|
48906
|
+
"server-update-available",
|
|
48907
|
+
"server-updated",
|
|
48908
|
+
"wrapper-update-available"
|
|
48909
|
+
]
|
|
48910
|
+
},
|
|
48911
|
+
{
|
|
48912
|
+
id: "backups-exports",
|
|
48913
|
+
family: "system",
|
|
48914
|
+
label: "Backups & exports",
|
|
48915
|
+
kinds: ["backup-completed", "export-completed"]
|
|
48916
|
+
},
|
|
48917
|
+
{
|
|
48918
|
+
id: "alarm",
|
|
48919
|
+
family: "alarm",
|
|
48920
|
+
label: "Alarm",
|
|
48921
|
+
kinds: NC_ALARM_SYSTEM_EVENT_KINDS
|
|
48922
|
+
}
|
|
48923
|
+
];
|
|
48924
|
+
function systemEventKindsOfFamily(family) {
|
|
48925
|
+
return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
|
|
48926
|
+
}
|
|
48725
48927
|
NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
|
|
48726
48928
|
/**
|
|
48727
48929
|
* WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
|
|
@@ -48796,7 +48998,183 @@ var DEVICE_EVENTS = [
|
|
|
48796
48998
|
"detection-blind",
|
|
48797
48999
|
...ALARM
|
|
48798
49000
|
];
|
|
48799
|
-
|
|
49001
|
+
/** Every kind a summary may speak about — the two summarisable families. */
|
|
49002
|
+
var SUMMARISABLE = [...systemEventKindsOfFamily("device-events"), ...systemEventKindsOfFamily("system")];
|
|
49003
|
+
/** One per summary SECTION (`NC_STATE_SUMMARY_SECTIONS` in the notification
|
|
49004
|
+
* centre): the section's lines, offered when one of its kinds is selected. */
|
|
49005
|
+
var STATE_SUMMARY_SECTION_VARS = [
|
|
49006
|
+
{
|
|
49007
|
+
name: "summaryDevices",
|
|
49008
|
+
label: "Summary: devices offline / switched off",
|
|
49009
|
+
example: "Garage — offline since 07:12",
|
|
49010
|
+
kinds: [
|
|
49011
|
+
"device-offline",
|
|
49012
|
+
"device-online",
|
|
49013
|
+
"device-disabled",
|
|
49014
|
+
"device-enabled"
|
|
49015
|
+
]
|
|
49016
|
+
},
|
|
49017
|
+
{
|
|
49018
|
+
name: "summaryStreams",
|
|
49019
|
+
label: "Summary: streams offline",
|
|
49020
|
+
example: "Ingresso (high) — since 06:40",
|
|
49021
|
+
kinds: ["stream-offline", "stream-online"]
|
|
49022
|
+
},
|
|
49023
|
+
{
|
|
49024
|
+
name: "summaryBattery",
|
|
49025
|
+
label: "Summary: low batteries",
|
|
49026
|
+
example: "Sensore porta — 8 %",
|
|
49027
|
+
kinds: BATTERY
|
|
49028
|
+
},
|
|
49029
|
+
{
|
|
49030
|
+
name: "summaryConsumables",
|
|
49031
|
+
label: "Summary: consumables",
|
|
49032
|
+
example: "Robot — filter 10 %",
|
|
49033
|
+
kinds: CONSUMABLE
|
|
49034
|
+
},
|
|
49035
|
+
{
|
|
49036
|
+
name: "summaryTanks",
|
|
49037
|
+
label: "Summary: tanks",
|
|
49038
|
+
example: "Robot — clean water low",
|
|
49039
|
+
kinds: TANK
|
|
49040
|
+
},
|
|
49041
|
+
{
|
|
49042
|
+
name: "summaryDetection",
|
|
49043
|
+
label: "Summary: cameras detecting nothing",
|
|
49044
|
+
example: "Ingresso — since 05:10",
|
|
49045
|
+
kinds: ["detection-blind"]
|
|
49046
|
+
},
|
|
49047
|
+
{
|
|
49048
|
+
name: "summaryNodes",
|
|
49049
|
+
label: "Summary: nodes",
|
|
49050
|
+
example: "little-unraid — offline since 03:00",
|
|
49051
|
+
kinds: [
|
|
49052
|
+
"node-offline",
|
|
49053
|
+
"node-online",
|
|
49054
|
+
"node-inference-unavailable"
|
|
49055
|
+
]
|
|
49056
|
+
},
|
|
49057
|
+
{
|
|
49058
|
+
name: "summaryAddons",
|
|
49059
|
+
label: "Summary: addons",
|
|
49060
|
+
example: "@camstack/addon-recorder 1.2.3 → 1.2.4",
|
|
49061
|
+
kinds: [
|
|
49062
|
+
"addon-crash-loop",
|
|
49063
|
+
"addon-update-available",
|
|
49064
|
+
"addon-updated"
|
|
49065
|
+
]
|
|
49066
|
+
},
|
|
49067
|
+
{
|
|
49068
|
+
name: "summaryUpdates",
|
|
49069
|
+
label: "Summary: server & container updates",
|
|
49070
|
+
example: "server 1.2.377 → 1.2.378",
|
|
49071
|
+
kinds: [
|
|
49072
|
+
"server-update-available",
|
|
49073
|
+
"server-updated",
|
|
49074
|
+
"wrapper-update-available"
|
|
49075
|
+
]
|
|
49076
|
+
}
|
|
49077
|
+
];
|
|
49078
|
+
var STATE_SUMMARY_VARS = [
|
|
49079
|
+
{
|
|
49080
|
+
name: "summaryText",
|
|
49081
|
+
label: "Summary: every section",
|
|
49082
|
+
description: "One block per section with something to report, one line per item.",
|
|
49083
|
+
example: "Batterie scariche: Sensore porta — 8 %",
|
|
49084
|
+
group: "system",
|
|
49085
|
+
families: ["rule"],
|
|
49086
|
+
fields: [...RULE_TEXT],
|
|
49087
|
+
kinds: ["system"],
|
|
49088
|
+
systemEventKinds: [...SUMMARISABLE],
|
|
49089
|
+
systemEventDeliveries: ["summary"]
|
|
49090
|
+
},
|
|
49091
|
+
{
|
|
49092
|
+
name: "summaryCount",
|
|
49093
|
+
label: "Summary: items reported",
|
|
49094
|
+
example: "3",
|
|
49095
|
+
group: "system",
|
|
49096
|
+
families: ["rule"],
|
|
49097
|
+
fields: [...RULE_TEXT],
|
|
49098
|
+
kinds: ["system"],
|
|
49099
|
+
systemEventKinds: [...SUMMARISABLE],
|
|
49100
|
+
systemEventDeliveries: ["summary"]
|
|
49101
|
+
},
|
|
49102
|
+
...STATE_SUMMARY_SECTION_VARS.map((v) => ({
|
|
49103
|
+
name: v.name,
|
|
49104
|
+
label: v.label,
|
|
49105
|
+
example: v.example,
|
|
49106
|
+
group: "system",
|
|
49107
|
+
families: ["rule"],
|
|
49108
|
+
fields: [...RULE_TEXT],
|
|
49109
|
+
kinds: ["system"],
|
|
49110
|
+
systemEventKinds: [...v.kinds],
|
|
49111
|
+
systemEventDeliveries: ["summary"]
|
|
49112
|
+
}))
|
|
49113
|
+
];
|
|
49114
|
+
[
|
|
49115
|
+
[...DEVICE_EVENTS],
|
|
49116
|
+
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
49117
|
+
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
49118
|
+
[...RULE_TEXT],
|
|
49119
|
+
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
49120
|
+
([...RULE_TEXT], [...PACKAGE_UPDATES]),
|
|
49121
|
+
[...RULE_TEXT],
|
|
49122
|
+
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
49123
|
+
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
49124
|
+
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
49125
|
+
[...RULE_TEXT],
|
|
49126
|
+
[...RULE_TEXT],
|
|
49127
|
+
[...RULE_TEXT],
|
|
49128
|
+
[...RULE_TEXT],
|
|
49129
|
+
[...RULE_TEXT],
|
|
49130
|
+
[...RULE_TEXT],
|
|
49131
|
+
[...RULE_TEXT],
|
|
49132
|
+
[...RULE_TEXT],
|
|
49133
|
+
[...RULE_BODY_ONLY],
|
|
49134
|
+
[...RULE_TEXT],
|
|
49135
|
+
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
49136
|
+
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
49137
|
+
([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
|
|
49138
|
+
([...RULE_TEXT], [...CONSUMABLE]),
|
|
49139
|
+
([...RULE_TEXT], [...TANK]),
|
|
49140
|
+
[...RULE_TEXT],
|
|
49141
|
+
([...RULE_TEXT], [...UPDATES]),
|
|
49142
|
+
([...RULE_TEXT], [...UPDATES]),
|
|
49143
|
+
[...RULE_TEXT],
|
|
49144
|
+
[...RULE_TEXT],
|
|
49145
|
+
[...RULE_TEXT],
|
|
49146
|
+
([...RULE_TEXT], [...UPDATES]),
|
|
49147
|
+
([...RULE_TEXT], [...PACKAGE_UPDATES]),
|
|
49148
|
+
([...RULE_TEXT], [...PACKAGE_UPDATES]),
|
|
49149
|
+
([...RULE_TEXT], [...UPDATES]),
|
|
49150
|
+
([...RULE_TEXT], [...UPDATES]),
|
|
49151
|
+
([...RULE_TEXT], [...PACKAGE_UPDATES]),
|
|
49152
|
+
[...RULE_TEXT],
|
|
49153
|
+
[...RULE_TEXT],
|
|
49154
|
+
[...RULE_TEXT],
|
|
49155
|
+
[...RULE_TEXT],
|
|
49156
|
+
[...RULE_TEXT],
|
|
49157
|
+
[...RULE_TEXT],
|
|
49158
|
+
[...RULE_TEXT],
|
|
49159
|
+
[...RULE_TEXT],
|
|
49160
|
+
[...RULE_TEXT],
|
|
49161
|
+
[...RULE_TEXT],
|
|
49162
|
+
[...RULE_TEXT],
|
|
49163
|
+
[...RULE_TEXT],
|
|
49164
|
+
[...RULE_TEXT],
|
|
49165
|
+
[...RULE_TEXT],
|
|
49166
|
+
[...RULE_TEXT],
|
|
49167
|
+
[...RULE_TEXT],
|
|
49168
|
+
[...RULE_TEXT],
|
|
49169
|
+
[...RULE_TEXT],
|
|
49170
|
+
[...RULE_TEXT],
|
|
49171
|
+
[...RULE_TEXT],
|
|
49172
|
+
[...RULE_TEXT],
|
|
49173
|
+
[...RULE_TEXT],
|
|
49174
|
+
[...RULE_TEXT],
|
|
49175
|
+
[...RULE_TEXT],
|
|
49176
|
+
...STATE_SUMMARY_VARS
|
|
49177
|
+
];
|
|
48800
49178
|
/**
|
|
48801
49179
|
* TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
|
|
48802
49180
|
*
|
|
@@ -49622,6 +50000,110 @@ function pickNativeLeaseOverride(view) {
|
|
|
49622
50000
|
}
|
|
49623
50001
|
return readNativeLeaseOverride(flat);
|
|
49624
50002
|
}
|
|
50003
|
+
function snapshot(run) {
|
|
50004
|
+
return {
|
|
50005
|
+
runId: run.runId,
|
|
50006
|
+
state: run.state,
|
|
50007
|
+
startedAtMs: run.startedAtMs,
|
|
50008
|
+
finishedAtMs: run.finishedAtMs,
|
|
50009
|
+
progress: run.progress,
|
|
50010
|
+
result: run.result,
|
|
50011
|
+
error: run.error
|
|
50012
|
+
};
|
|
50013
|
+
}
|
|
50014
|
+
var BackgroundRunRegistry = class {
|
|
50015
|
+
opts;
|
|
50016
|
+
runs = /* @__PURE__ */ new Map();
|
|
50017
|
+
now;
|
|
50018
|
+
constructor(opts) {
|
|
50019
|
+
this.opts = opts;
|
|
50020
|
+
this.now = opts.now ?? Date.now;
|
|
50021
|
+
}
|
|
50022
|
+
/** Runs still going. */
|
|
50023
|
+
get runningCount() {
|
|
50024
|
+
let n = 0;
|
|
50025
|
+
for (const run of this.runs.values()) if (run.state === "running") n += 1;
|
|
50026
|
+
return n;
|
|
50027
|
+
}
|
|
50028
|
+
/**
|
|
50029
|
+
* Register and start `work`. Returns the `running` receipt immediately.
|
|
50030
|
+
* Throws only for a refusal the caller must see (duplicate id, busy) — a
|
|
50031
|
+
* failure of the work itself lands in `status`, never here.
|
|
50032
|
+
*/
|
|
50033
|
+
start(runId, work) {
|
|
50034
|
+
const existing = this.runs.get(runId);
|
|
50035
|
+
if (existing !== void 0 && existing.state === "running") throw new Error(`run '${runId}' is already running`);
|
|
50036
|
+
if (this.runningCount >= this.opts.maxConcurrent) throw new Error(`busy: ${this.runningCount}/${this.opts.maxConcurrent} run(s) already in progress — wait for one to finish or abort it`);
|
|
50037
|
+
const run = {
|
|
50038
|
+
runId,
|
|
50039
|
+
state: "running",
|
|
50040
|
+
startedAtMs: this.now(),
|
|
50041
|
+
finishedAtMs: null,
|
|
50042
|
+
progress: null,
|
|
50043
|
+
result: null,
|
|
50044
|
+
error: null,
|
|
50045
|
+
controller: new AbortController()
|
|
50046
|
+
};
|
|
50047
|
+
this.runs.delete(runId);
|
|
50048
|
+
this.runs.set(runId, run);
|
|
50049
|
+
const ctx = {
|
|
50050
|
+
signal: run.controller.signal,
|
|
50051
|
+
report: (progress) => {
|
|
50052
|
+
run.progress = progress;
|
|
50053
|
+
}
|
|
50054
|
+
};
|
|
50055
|
+
const settle = (patch) => {
|
|
50056
|
+
if (patch.error !== void 0) {
|
|
50057
|
+
run.state = "failed";
|
|
50058
|
+
run.error = patch.error;
|
|
50059
|
+
} else {
|
|
50060
|
+
run.state = run.controller.signal.aborted ? "aborted" : "done";
|
|
50061
|
+
run.result = patch.result ?? null;
|
|
50062
|
+
}
|
|
50063
|
+
run.finishedAtMs = this.now();
|
|
50064
|
+
this.prune();
|
|
50065
|
+
this.opts.onSettled?.(snapshot(run));
|
|
50066
|
+
};
|
|
50067
|
+
let pending;
|
|
50068
|
+
try {
|
|
50069
|
+
pending = work(ctx);
|
|
50070
|
+
} catch (err) {
|
|
50071
|
+
pending = Promise.reject(err);
|
|
50072
|
+
}
|
|
50073
|
+
pending.then((result) => settle({ result }), (err) => settle({ error: err instanceof Error ? err.message : String(err) }));
|
|
50074
|
+
return snapshot(run);
|
|
50075
|
+
}
|
|
50076
|
+
status(runId) {
|
|
50077
|
+
const run = this.runs.get(runId);
|
|
50078
|
+
return run === void 0 ? null : snapshot(run);
|
|
50079
|
+
}
|
|
50080
|
+
/** Running runs first (newest start first), then finished ones (newest finish first). */
|
|
50081
|
+
list() {
|
|
50082
|
+
const all = [...this.runs.values()];
|
|
50083
|
+
const running = all.filter((r) => r.state === "running").toSorted((a, b) => b.startedAtMs - a.startedAtMs);
|
|
50084
|
+
const finished = all.filter((r) => r.state !== "running").toSorted((a, b) => (b.finishedAtMs ?? 0) - (a.finishedAtMs ?? 0));
|
|
50085
|
+
return [...running, ...finished].map(snapshot);
|
|
50086
|
+
}
|
|
50087
|
+
/** Signal a running run to stop. `false` for an unknown or already finished run. */
|
|
50088
|
+
abort(runId) {
|
|
50089
|
+
const run = this.runs.get(runId);
|
|
50090
|
+
if (run === void 0 || run.state !== "running") return false;
|
|
50091
|
+
run.controller.abort();
|
|
50092
|
+
return true;
|
|
50093
|
+
}
|
|
50094
|
+
/** Abort every running run (shutdown). */
|
|
50095
|
+
abortAll() {
|
|
50096
|
+
for (const run of this.runs.values()) if (run.state === "running") run.controller.abort();
|
|
50097
|
+
}
|
|
50098
|
+
prune() {
|
|
50099
|
+
const finished = [...this.runs.values()].filter((r) => r.state !== "running").toSorted((a, b) => (a.finishedAtMs ?? 0) - (b.finishedAtMs ?? 0));
|
|
50100
|
+
const excess = finished.length - this.opts.keepFinished;
|
|
50101
|
+
for (let i = 0; i < excess; i++) {
|
|
50102
|
+
const victim = finished[i];
|
|
50103
|
+
if (victim !== void 0) this.runs.delete(victim.runId);
|
|
50104
|
+
}
|
|
50105
|
+
}
|
|
50106
|
+
};
|
|
49625
50107
|
function hfModelUrl(repo, path) {
|
|
49626
50108
|
return `https://huggingface.co/${repo}/resolve/main/${path}`;
|
|
49627
50109
|
}
|
|
@@ -50023,9 +50505,9 @@ var RingBuffer = class {
|
|
|
50023
50505
|
};
|
|
50024
50506
|
/**
|
|
50025
50507
|
* DEVICE-POOL backend → model format — the SSOT for the multi-device inference
|
|
50026
|
-
* DESCRIPTOR path (Python per-device pools),
|
|
50027
|
-
*
|
|
50028
|
-
* onnxruntime-node's CoreML EP
|
|
50508
|
+
* DESCRIPTOR path (Python per-device pools), distinct from the former BACKEND_TO_FORMAT,
|
|
50509
|
+
* which was the removed NODE runtime map (where `coreml → onnx` because
|
|
50510
|
+
* onnxruntime-node's CoreML EP loaded `.onnx`). A device pool runs the
|
|
50029
50511
|
* backend NATIVELY, so `coreml → coreml` and `edgetpu → tflite`. Consumed by
|
|
50030
50512
|
* `resolveDeviceEngine` (addon-pipeline) and the orchestrator's
|
|
50031
50513
|
* `parseDeviceKey`/`getNodeInferenceDevices` — the previously-duplicated
|
|
@@ -50353,6 +50835,12 @@ Object.defineProperty(exports, "BOOT_RECOVERY_BACKOFF_MS", {
|
|
|
50353
50835
|
return BOOT_RECOVERY_BACKOFF_MS;
|
|
50354
50836
|
}
|
|
50355
50837
|
});
|
|
50838
|
+
Object.defineProperty(exports, "BackgroundRunRegistry", {
|
|
50839
|
+
enumerable: true,
|
|
50840
|
+
get: function() {
|
|
50841
|
+
return BackgroundRunRegistry;
|
|
50842
|
+
}
|
|
50843
|
+
});
|
|
50356
50844
|
Object.defineProperty(exports, "BaseAddon", {
|
|
50357
50845
|
enumerable: true,
|
|
50358
50846
|
get: function() {
|