@camstack/types 1.2.93 → 1.2.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/index.d.ts +3 -3
- package/dist/capabilities/local-network.cap.d.ts +41 -1
- package/dist/capabilities/pipeline-analytics.cap.d.ts +7 -1
- package/dist/capabilities/pipeline-orchestrator.cap.d.ts +38 -1
- package/dist/capabilities/pipeline-runner.cap.d.ts +3 -0
- package/dist/device/index.d.ts +2 -2
- package/dist/device/runtime-state-helpers.d.ts +63 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +456 -19
- package/dist/index.mjs +444 -20
- package/dist/interfaces/config-ui.d.ts +34 -0
- package/dist/interfaces/recording-config.d.ts +16 -3
- package/dist/pipeline/cluster-model-scope.d.ts +108 -0
- package/dist/pipeline/detail-crop.d.ts +19 -0
- package/dist/types/pipeline-step.d.ts +36 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1530,12 +1530,25 @@ var RecordingBandSchema = zod.z.object({
|
|
|
1530
1530
|
* segment is ever finalized and the camera records nothing anyway. At the 10 s
|
|
1531
1531
|
* default segment length, 30 s leaves room for two full segments. `preBufferSec`
|
|
1532
1532
|
* is retroactive only — it costs nothing at record time, it merely keeps
|
|
1533
|
-
* already-written segments
|
|
1533
|
+
* already-written segments.
|
|
1534
1534
|
*
|
|
1535
|
-
*
|
|
1535
|
+
* ## Why `preBufferSec` is 15 and not one segment length
|
|
1536
|
+
*
|
|
1537
|
+
* It has to cover the broker's RECORDING pre-roll, which is media handed to the
|
|
1538
|
+
* writer from BEFORE the trigger (D192/D193). If this bound were narrower, the
|
|
1539
|
+
* keep gate would delete the very seconds the ring just supplied — two
|
|
1540
|
+
* authorities disagreeing about the same footage, which is the failure D191 was
|
|
1541
|
+
* written against. It is therefore set to `RECORDING_PRE_ROLL_MAX_MS` (15 s),
|
|
1542
|
+
* the widest window the broker can be configured to serve, so an operator
|
|
1543
|
+
* raising the cluster pre-roll can never walk past the gate silently. The
|
|
1544
|
+
* addon-side guard is
|
|
1545
|
+
* `packages/addon-pipeline/src/recorder/addon/__tests__/band-decision.spec.ts`.
|
|
1546
|
+
*
|
|
1547
|
+
* An EXPLICIT `0` is left alone: that is an operator statement, not an omission,
|
|
1548
|
+
* and it is excluded from that invariant by name.
|
|
1536
1549
|
*/
|
|
1537
1550
|
var DEFAULT_EVENTS_BAND_BUFFER_SEC = {
|
|
1538
|
-
preBufferSec:
|
|
1551
|
+
preBufferSec: 15,
|
|
1539
1552
|
postBufferSec: 30
|
|
1540
1553
|
};
|
|
1541
1554
|
/**
|
|
@@ -4223,11 +4236,15 @@ export default function block(ctx: AddonContext) {
|
|
|
4223
4236
|
): Promise<void> {
|
|
4224
4237
|
const resolved: Record<string, unknown> = {}
|
|
4225
4238
|
for (const [k, v] of Object.entries(args)) resolved[k] = substitute(v, vars)
|
|
4226
|
-
const client = (ctx.api
|
|
4227
|
-
|
|
4239
|
+
const client: unknown = Reflect.get(ctx.api, capToRouterKey(cap))
|
|
4240
|
+
const proc: unknown =
|
|
4241
|
+
typeof client === 'object' && client !== null ? Reflect.get(client, method) : undefined
|
|
4242
|
+
const mutate: unknown =
|
|
4243
|
+
typeof proc === 'object' && proc !== null ? Reflect.get(proc, 'mutate') : undefined
|
|
4244
|
+
if (typeof mutate !== 'function') {
|
|
4228
4245
|
throw new Error('no such device method: ' + cap + '.' + method)
|
|
4229
4246
|
}
|
|
4230
|
-
await
|
|
4247
|
+
await mutate({ ...resolved, deviceId })
|
|
4231
4248
|
}
|
|
4232
4249
|
|
|
4233
4250
|
async function evaluateConditions(): Promise<boolean> {
|
|
@@ -14688,7 +14705,7 @@ var TrackEnvelopeSchema = zod.z.object({
|
|
|
14688
14705
|
* `snapshots[]` references — megabytes across a page of tracks. `slim`
|
|
14689
14706
|
* keeps every scalar the list surfaces actually render (ids, class(es),
|
|
14690
14707
|
* label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
|
|
14691
|
-
* zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
|
|
14708
|
+
* zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
|
|
14692
14709
|
* `snapshots` as EMPTY arrays — detail views re-fetch the full row via
|
|
14693
14710
|
* `getTrack`. Mirrors the event-store `projection` convention
|
|
14694
14711
|
* (`getObjectEvents` et al.).
|
|
@@ -14999,6 +15016,35 @@ var TrackSchema = zod.z.object({
|
|
|
14999
15016
|
*/
|
|
15000
15017
|
hasFace: zod.z.boolean().optional(),
|
|
15001
15018
|
/**
|
|
15019
|
+
* This track has a face row IN THE GALLERY: a crop **and** an embedding — a
|
|
15020
|
+
* face an operator could ASSIGN to an identity.
|
|
15021
|
+
*
|
|
15022
|
+
* The STRICT twin of {@link hasFace}, and the pair only earns its keep
|
|
15023
|
+
* because the two disagree. `hasFace` is stamped at the TOP of the face
|
|
15024
|
+
* branch, before every gate, and means no more than "a face detector produced
|
|
15025
|
+
* a face detail". This one is stamped at the single moment the gallery row
|
|
15026
|
+
* LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
|
|
15027
|
+
* past the embedding-magnitude verdict, the `minFacePx` detection gate, the
|
|
15028
|
+
* candidate gate, the imageless-track drop (no crop was ever captured) and
|
|
15029
|
+
* the crop-store drop. Everything between the detector and that insert can
|
|
15030
|
+
* legitimately refuse the face, so a flag written any earlier promises the
|
|
15031
|
+
* operator something to assign and delivers nothing.
|
|
15032
|
+
*
|
|
15033
|
+
* **Independent of recognition.** A face collected but never auto-matched is
|
|
15034
|
+
* still assignable — it is in fact the face an operator most wants to reach —
|
|
15035
|
+
* so this is NOT gated on `recognizedIdentityId`. Recognition lands in
|
|
15036
|
+
* `subLabel`; this says only that the raw material exists.
|
|
15037
|
+
*
|
|
15038
|
+
* **Set once, never cleared.** A track that produced a gallery row produced
|
|
15039
|
+
* one; deleting the row later is the gallery's business, not this flag's.
|
|
15040
|
+
*
|
|
15041
|
+
* **Absent ≠ false**, the same rule as {@link hasFace}: every row written
|
|
15042
|
+
* before the column omits it, and so does every server that predates the
|
|
15043
|
+
* field. A consumer must test `=== true` and render nothing otherwise —
|
|
15044
|
+
* never infer "no assignable face".
|
|
15045
|
+
*/
|
|
15046
|
+
hasEmbeddedFace: zod.z.boolean().optional(),
|
|
15047
|
+
/**
|
|
15002
15048
|
* This subject CONTAINS a folded rider — a person the rider-pairing step
|
|
15003
15049
|
* ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
|
|
15004
15050
|
* so the passage is tracked once and as a VEHICLE.
|
|
@@ -16965,6 +17011,26 @@ var DetailResultSchema = zod.z.object({
|
|
|
16965
17011
|
* the consuming gate treats as accept — never as reject.
|
|
16966
17012
|
*/
|
|
16967
17013
|
embeddingMagnitude: zod.z.number().optional(),
|
|
17014
|
+
/**
|
|
17015
|
+
* Model that produced `embedding` — the SAME contract `labelModelId` has for
|
|
17016
|
+
* `label`, and for the same reason: the producing node is the only side that
|
|
17017
|
+
* knows which model actually ran, and re-deriving it downstream is exactly
|
|
17018
|
+
* the inference this model exists to forbid.
|
|
17019
|
+
*
|
|
17020
|
+
* Additive and optional, so a runner that predates it degrades to
|
|
17021
|
+
* "unmeasurable" rather than to a wrong answer — the consumer falls back to
|
|
17022
|
+
* its previous assumption instead of refusing.
|
|
17023
|
+
*
|
|
17024
|
+
* Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
|
|
17025
|
+
* to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
|
|
17026
|
+
* with nothing on the wire it took the id from the GALLERY, so the gate
|
|
17027
|
+
* compared the gallery against itself and could never fire. That turned the
|
|
17028
|
+
* one guard protecting the face feature space into a tautology, and it is
|
|
17029
|
+
* why swapping `face-embedding`'s model would NOT have stranded the enrolled
|
|
17030
|
+
* gallery as the catalog comment predicts — it would have scored the new
|
|
17031
|
+
* model's vectors against the old model's samples in one cosine space.
|
|
17032
|
+
*/
|
|
17033
|
+
embeddingModelId: zod.z.string().optional(),
|
|
16968
17034
|
label: zod.z.string().optional(),
|
|
16969
17035
|
/**
|
|
16970
17036
|
* The tier `label` occupies, copied VERBATIM from the producing step's
|
|
@@ -18170,6 +18236,24 @@ var CameraStatusSchema = zod.z.object({
|
|
|
18170
18236
|
/** Unix timestamp (ms) when this snapshot was composed server-side. */
|
|
18171
18237
|
fetchedAt: zod.z.number()
|
|
18172
18238
|
});
|
|
18239
|
+
/**
|
|
18240
|
+
* Why the dispatcher does NOT consider a device the node HAS for the
|
|
18241
|
+
* camera-root election. The list is the CONTRACT between the orchestrator's
|
|
18242
|
+
* `resolveInferenceDeviceEligibility` (which computes it) and the admin UI
|
|
18243
|
+
* (which renders it) — the orchestrator's `InferenceDeviceExclusionReason`
|
|
18244
|
+
* derives from this tuple so the two can never drift.
|
|
18245
|
+
*
|
|
18246
|
+
* Every one of these drops used to be silent, and an accelerator that is
|
|
18247
|
+
* enabled, available, and never chosen is indistinguishable from one that was
|
|
18248
|
+
* never configured. The reason is the difference.
|
|
18249
|
+
*/
|
|
18250
|
+
var INFERENCE_DEVICE_EXCLUSION_REASONS = [
|
|
18251
|
+
"disabled",
|
|
18252
|
+
"unavailable",
|
|
18253
|
+
"cannot-host-camera-root",
|
|
18254
|
+
"accelerator-preferred"
|
|
18255
|
+
];
|
|
18256
|
+
var InferenceDeviceExclusionReasonSchema = zod.z.enum(INFERENCE_DEVICE_EXCLUSION_REASONS);
|
|
18173
18257
|
var NodeInferenceDeviceSchema = zod.z.object({
|
|
18174
18258
|
/** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
|
|
18175
18259
|
key: zod.z.string(),
|
|
@@ -18200,7 +18284,17 @@ var NodeInferenceDeviceSchema = zod.z.object({
|
|
|
18200
18284
|
* available per format; this is the stored selection that becomes the
|
|
18201
18285
|
* default for EVERY camera landing on this accelerator.
|
|
18202
18286
|
*/
|
|
18203
|
-
steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional()
|
|
18287
|
+
steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional(),
|
|
18288
|
+
/**
|
|
18289
|
+
* `null` when the device IS a camera-root candidate on this node; otherwise
|
|
18290
|
+
* the reason the dispatcher drops it. Computed by the SAME
|
|
18291
|
+
* `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
|
|
18292
|
+
* never disagree with the election — deriving it in the UI from
|
|
18293
|
+
* `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
|
|
18294
|
+
* the node's model catalog) and `accelerator-preferred` (needs the node-wide
|
|
18295
|
+
* "an accelerator is serving" predicate).
|
|
18296
|
+
*/
|
|
18297
|
+
exclusion: InferenceDeviceExclusionReasonSchema.nullable()
|
|
18204
18298
|
});
|
|
18205
18299
|
var NodeInferenceDevicesSchema = zod.z.object({
|
|
18206
18300
|
nodeId: zod.z.string(),
|
|
@@ -24520,7 +24614,41 @@ var ConnectionEndpointSchema = zod.z.object({
|
|
|
24520
24614
|
*/
|
|
24521
24615
|
priority: zod.z.number()
|
|
24522
24616
|
});
|
|
24523
|
-
|
|
24617
|
+
/**
|
|
24618
|
+
* Where the advertised local port came from. Ordered most → least
|
|
24619
|
+
* authoritative, and the whole point of returning it: a client must be able to
|
|
24620
|
+
* tell a FACT about the hub's socket from an echo of its own guess.
|
|
24621
|
+
*/
|
|
24622
|
+
var LocalPortSourceEnum = zod.z.enum([
|
|
24623
|
+
"server-config",
|
|
24624
|
+
"server-env",
|
|
24625
|
+
"caller-hint",
|
|
24626
|
+
"default"
|
|
24627
|
+
]);
|
|
24628
|
+
/** The port every LAN/loopback `baseUrl` in the same result was built with. */
|
|
24629
|
+
var AdvertisedLocalPortSchema = zod.z.object({
|
|
24630
|
+
port: zod.z.number().int().min(1).max(65535),
|
|
24631
|
+
source: LocalPortSourceEnum
|
|
24632
|
+
});
|
|
24633
|
+
var GetConnectionEndpointsResultSchema = zod.z.object({
|
|
24634
|
+
endpoints: zod.z.array(ConnectionEndpointSchema).readonly(),
|
|
24635
|
+
/**
|
|
24636
|
+
* The port the hub built the LAN/loopback URLs with, and where that number
|
|
24637
|
+
* came from.
|
|
24638
|
+
*
|
|
24639
|
+
* Returned rather than merely applied, because "the URL is right" and "the
|
|
24640
|
+
* client can KNOW the URL is right" are different properties. A client that
|
|
24641
|
+
* only sees a corrected URL cannot distinguish a hub that fixed the port from
|
|
24642
|
+
* a hub that echoed the port the client sent, so it cannot decide whether to
|
|
24643
|
+
* race the candidate or discard it. With `source` it can: anything but
|
|
24644
|
+
* `caller-hint` is the hub's own socket.
|
|
24645
|
+
*
|
|
24646
|
+
* Absent on hubs predating this field — a client that finds it missing is
|
|
24647
|
+
* talking to an echoing hub and must degrade exactly as it does for
|
|
24648
|
+
* `caller-hint`.
|
|
24649
|
+
*/
|
|
24650
|
+
localPort: AdvertisedLocalPortSchema
|
|
24651
|
+
});
|
|
24524
24652
|
/**
|
|
24525
24653
|
* The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
|
|
24526
24654
|
* AUTO (resolved from the candidate ranking at send time); `resolved` reports
|
|
@@ -24566,10 +24694,35 @@ var localNetworkCapability = {
|
|
|
24566
24694
|
* Honours `getAllowedAddresses()` when set — addresses outside
|
|
24567
24695
|
* the allowlist are dropped (the public tunnel + loopback are
|
|
24568
24696
|
* always included as escape hatches).
|
|
24697
|
+
*
|
|
24698
|
+
* **The port is the hub's, not the caller's** (D62 — a function's fact
|
|
24699
|
+
* belongs to whoever already owns it). This method used to take a `port`
|
|
24700
|
+
* and echo it onto every LAN and loopback URL it advertised, which made
|
|
24701
|
+
* every client a second authority on a socket only the hub binds. A viewer
|
|
24702
|
+
* configured with a port-less public URL (`https://camstack.example.top`)
|
|
24703
|
+
* infers 443 from the scheme, asks with 443, and was handed
|
|
24704
|
+
* `https://192.168.1.9:443` for a hub listening on 4443. That candidate
|
|
24705
|
+
* cannot ever answer, and losing its race reads in the log exactly like
|
|
24706
|
+
* "the LAN was tried and it was slower" — so an operator sitting on his own
|
|
24707
|
+
* WiFi stayed on the public tunnel for weeks, with the LAN candidate born
|
|
24708
|
+
* dead on every single connect.
|
|
24709
|
+
*
|
|
24710
|
+
* The hub now resolves its own listen port and reports it as `localPort`,
|
|
24711
|
+
* with the SOURCE of the number, so a client can tell a fact from an echo.
|
|
24712
|
+
*
|
|
24713
|
+
* Out of scope on purpose: a LAN URL that is not the hub's own socket (a
|
|
24714
|
+
* reverse proxy in front of it on another port) is not this method's to
|
|
24715
|
+
* invent either — pin it with `setNotificationEndpoint` / a configured
|
|
24716
|
+
* origin, which is an operator statement rather than a guess.
|
|
24569
24717
|
*/
|
|
24570
24718
|
getConnectionEndpoints: require_sleep.method(zod.z.object({
|
|
24571
|
-
/**
|
|
24572
|
-
|
|
24719
|
+
/**
|
|
24720
|
+
* LEGACY HINT — do not send from new code. Kept optional so clients
|
|
24721
|
+
* written against the echoing contract keep working; the hub uses it
|
|
24722
|
+
* only when it cannot read its own port, and says so via
|
|
24723
|
+
* `localPort.source === 'caller-hint'`.
|
|
24724
|
+
*/
|
|
24725
|
+
port: zod.z.number().int().min(1).max(65535).optional(),
|
|
24573
24726
|
/** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
|
|
24574
24727
|
* candidate. Default `true`. */
|
|
24575
24728
|
includeLoopback: zod.z.boolean().optional(),
|
|
@@ -32343,11 +32496,63 @@ function startReachabilityPoll(options) {
|
|
|
32343
32496
|
//#endregion
|
|
32344
32497
|
//#region src/device/runtime-state-helpers.ts
|
|
32345
32498
|
var LAST_FETCHED_FIELD = "lastFetchedAt";
|
|
32499
|
+
/**
|
|
32500
|
+
* How long a bridge stops re-attempting a refresh that did not land.
|
|
32501
|
+
*
|
|
32502
|
+
* Sized against the failure it exists for: a camera whose control plane
|
|
32503
|
+
* is unreachable costs the FULL connect give-up (~3s on Linux when the
|
|
32504
|
+
* neighbour never answers, up to the client's own timeout otherwise) on
|
|
32505
|
+
* every attempt, and a failed refresh never advances `lastFetchedAt`, so
|
|
32506
|
+
* without a cooldown the slice is permanently stale and EVERY read pays
|
|
32507
|
+
* that price. One attempt per minute is enough to notice the camera
|
|
32508
|
+
* coming back; per-read is enough to stall the viewer's first paint.
|
|
32509
|
+
*/
|
|
32510
|
+
var RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS = 6e4;
|
|
32511
|
+
/**
|
|
32512
|
+
* The freshness window for a cap whose value changes ONLY when WE write it.
|
|
32513
|
+
*
|
|
32514
|
+
* The usual `staleMs: 10_000` is tuned for a reading that moves on its own — a
|
|
32515
|
+
* battery level, a day/night state that flips at dusk. It is the wrong number
|
|
32516
|
+
* for a cap like `privacy-mask`, whose two facts (is the video mask on, is the
|
|
32517
|
+
* microphone on) change when an operator changes them, through a `setMask` /
|
|
32518
|
+
* `setAudioEnabled` that **re-reads the camera and rewrites this very slice**.
|
|
32519
|
+
* A write is therefore its own invalidation: the window never delays an
|
|
32520
|
+
* operator's own change, however long it is.
|
|
32521
|
+
*
|
|
32522
|
+
* What the window really bounds is the ONE case we do not write: somebody
|
|
32523
|
+
* changing the mask in the vendor app or the camera's web UI. Ten minutes is
|
|
32524
|
+
* the trade — that change is visible within ten minutes, and a fleet of 29
|
|
32525
|
+
* cameras behind a page that polls every 5s costs about 3 camera round trips a
|
|
32526
|
+
* minute instead of 696
|
|
32527
|
+
* ([D221](../../../../docs/decisions/adr-0221-a-polled-list-never-dials-a-camera.md)).
|
|
32528
|
+
*
|
|
32529
|
+
* Use it ONLY with {@link RuntimeStateStaleReadPolicy} `'serve-and-revalidate'`.
|
|
32530
|
+
* On the awaiting default a window this long does not remove the stall, it
|
|
32531
|
+
* merely makes it rarer and just as long.
|
|
32532
|
+
*/
|
|
32533
|
+
var OPERATOR_WRITTEN_STALE_MS = 10 * 6e4;
|
|
32346
32534
|
function createRuntimeStateBridge(params) {
|
|
32347
32535
|
const { runtimeState, cap, ownDeviceId, refresh, staleMs, empty, logger } = params;
|
|
32348
32536
|
const missCooldownMs = params.refreshMissCooldownMs ?? 6e4;
|
|
32537
|
+
const staleRead = params.staleRead ?? "await-refresh";
|
|
32349
32538
|
/** Epoch ms until which a refresh is not re-attempted. 0 = no cooldown. */
|
|
32350
32539
|
let missCooldownUntil = 0;
|
|
32540
|
+
/**
|
|
32541
|
+
* The refresh this bridge currently has in the air, if any.
|
|
32542
|
+
*
|
|
32543
|
+
* Providers single-flight their own camera client, so this is not what stops
|
|
32544
|
+
* a second round trip. What it stops is a POLLED reader JOINING one: under
|
|
32545
|
+
* `'serve-and-revalidate'` a read that finds a refresh already outstanding is
|
|
32546
|
+
* answered from the slice at once — even a cold, empty slice, which reports
|
|
32547
|
+
* UNKNOWN, which is the truth about a camera nobody has reached.
|
|
32548
|
+
*
|
|
32549
|
+
* That distinction is the whole cost of an unreachable camera. Measured on
|
|
32550
|
+
* the live hub (device 3629, an offline battery Reolink): its refresh takes
|
|
32551
|
+
* 23.1s to give up, so without this every 5s poll landing inside those 23s
|
|
32552
|
+
* joined the wait and spent the caller's full 1.2s source budget — five
|
|
32553
|
+
* stalled polls per cooldown cycle, for one camera, forever.
|
|
32554
|
+
*/
|
|
32555
|
+
let refreshInFlight = null;
|
|
32351
32556
|
const readFetchedAt = () => {
|
|
32352
32557
|
const value = runtimeState.getCapState(cap.name)?.[LAST_FETCHED_FIELD];
|
|
32353
32558
|
return typeof value === "number" ? value : 0;
|
|
@@ -32368,14 +32573,14 @@ function createRuntimeStateBridge(params) {
|
|
|
32368
32573
|
}
|
|
32369
32574
|
});
|
|
32370
32575
|
};
|
|
32371
|
-
|
|
32372
|
-
|
|
32373
|
-
|
|
32374
|
-
|
|
32375
|
-
|
|
32376
|
-
|
|
32377
|
-
|
|
32378
|
-
|
|
32576
|
+
/**
|
|
32577
|
+
* One refresh attempt, plus the LANDED check that decides the cooldown.
|
|
32578
|
+
*
|
|
32579
|
+
* @param fetchedAt What `lastFetchedAt` was before the attempt — the only
|
|
32580
|
+
* evidence the bridge has that the refresh persisted
|
|
32581
|
+
* anything, since providers swallow their own camera errors.
|
|
32582
|
+
*/
|
|
32583
|
+
const runRefresh = async (fetchedAt) => {
|
|
32379
32584
|
try {
|
|
32380
32585
|
await refresh();
|
|
32381
32586
|
} catch (err) {
|
|
@@ -32391,6 +32596,41 @@ function createRuntimeStateBridge(params) {
|
|
|
32391
32596
|
}
|
|
32392
32597
|
openMissCooldown(void 0);
|
|
32393
32598
|
};
|
|
32599
|
+
/**
|
|
32600
|
+
* Start a refresh and remember it, at most one at a time.
|
|
32601
|
+
*
|
|
32602
|
+
* It never rejects: under `'serve-and-revalidate'` the caller is answered
|
|
32603
|
+
* from the slice either way, so a floating rejection would take the process
|
|
32604
|
+
* down for a fault the miss cooldown has already recorded and logged.
|
|
32605
|
+
*/
|
|
32606
|
+
const startRefresh = (fetchedAt) => {
|
|
32607
|
+
const existing = refreshInFlight;
|
|
32608
|
+
if (existing !== null) return existing;
|
|
32609
|
+
const started = runRefresh(fetchedAt).catch(() => void 0).finally(() => {
|
|
32610
|
+
refreshInFlight = null;
|
|
32611
|
+
});
|
|
32612
|
+
refreshInFlight = started;
|
|
32613
|
+
return started;
|
|
32614
|
+
};
|
|
32615
|
+
const ensureFresh = async () => {
|
|
32616
|
+
const slice = runtimeState.getCapState(cap.name);
|
|
32617
|
+
const fetchedAt = readFetchedAt();
|
|
32618
|
+
if (slice && Date.now() - fetchedAt <= staleMs) {
|
|
32619
|
+
missCooldownUntil = 0;
|
|
32620
|
+
return;
|
|
32621
|
+
}
|
|
32622
|
+
if (Date.now() < missCooldownUntil) return;
|
|
32623
|
+
if (staleRead === "serve-and-revalidate") {
|
|
32624
|
+
if (refreshInFlight !== null) return;
|
|
32625
|
+
if (slice && fetchedAt > 0) {
|
|
32626
|
+
startRefresh(fetchedAt);
|
|
32627
|
+
return;
|
|
32628
|
+
}
|
|
32629
|
+
await startRefresh(fetchedAt);
|
|
32630
|
+
return;
|
|
32631
|
+
}
|
|
32632
|
+
await runRefresh(fetchedAt);
|
|
32633
|
+
};
|
|
32394
32634
|
const projectStatus = () => {
|
|
32395
32635
|
const slice = runtimeState.getCapState(cap.name);
|
|
32396
32636
|
if (!slice) return empty();
|
|
@@ -44334,6 +44574,166 @@ function readTimelapseGeneratedAt(rule, deviceId) {
|
|
|
44334
44574
|
return rule.lastGeneratedAt ?? 0;
|
|
44335
44575
|
}
|
|
44336
44576
|
//#endregion
|
|
44577
|
+
//#region src/pipeline/cluster-model-scope.ts
|
|
44578
|
+
/**
|
|
44579
|
+
* A model choice has a SCOPE, and a scope change has a workflow.
|
|
44580
|
+
*
|
|
44581
|
+
* ## The two scopes
|
|
44582
|
+
*
|
|
44583
|
+
* Almost every pipeline step's model is a **node** question: which build runs
|
|
44584
|
+
* fastest on THIS accelerator. Two nodes may legitimately answer differently —
|
|
44585
|
+
* a Coral runs the tflite SSD, a Mac runs the coreml YOLO — because the answers
|
|
44586
|
+
* are compared against nothing. They produce boxes, and a box is a box.
|
|
44587
|
+
*
|
|
44588
|
+
* Two steps are not like that. `face-embedding` and `clip-embedding` produce
|
|
44589
|
+
* **vectors that land in ONE shared index** (`identity_samples`,
|
|
44590
|
+
* `vec_object_clip`) and are ranked against each other by cosine similarity.
|
|
44591
|
+
* Cosine similarity between two encoders is not a weak signal, it is a
|
|
44592
|
+
* meaningless number — and nothing inside a vector can tell you it happened.
|
|
44593
|
+
* So for those two steps the model is a **cluster** question with exactly one
|
|
44594
|
+
* answer, stored in exactly one row.
|
|
44595
|
+
*
|
|
44596
|
+
* That is the same argument [D52](../../../../docs/decisions/adr-0052.md) makes
|
|
44597
|
+
* for the crop convention (`detail-crop.ts`), one level up: the crop decides
|
|
44598
|
+
* WHAT pixels the encoder sees, this decides WHICH encoder sees them. Both are
|
|
44599
|
+
* cluster-wide for the same reason and neither may become per-node.
|
|
44600
|
+
*
|
|
44601
|
+
* ## Why the registry lives here and not in the step catalog
|
|
44602
|
+
*
|
|
44603
|
+
* The scope itself is declared on the step, in
|
|
44604
|
+
* `detection-pipeline/registry/step-definitions.ts` (`StepDefinition.modelScope`)
|
|
44605
|
+
* — that is where a new step declares what kind of choice its model is. But the
|
|
44606
|
+
* catalog lives inside `addon-pipeline`, and three packages that must NOT
|
|
44607
|
+
* import an addon need to know the set: the orchestrator (which renders and
|
|
44608
|
+
* stores the row), post-analysis (which pins the row for a re-embed pass) and
|
|
44609
|
+
* admin-ui (which mirrors it). So the set is mirrored here, and
|
|
44610
|
+
* `scripts/check-cluster-model-scope.ts` fails the build when the two disagree.
|
|
44611
|
+
* A hand-copied key that stops matching is exactly how a setting silently stops
|
|
44612
|
+
* arriving while both sides still look correct.
|
|
44613
|
+
*
|
|
44614
|
+
* ## The defaults ARE today's behaviour
|
|
44615
|
+
*
|
|
44616
|
+
* {@link DEFAULT_CLUSTER_STEP_MODELS} reproduces the catalog `defaultModelId`
|
|
44617
|
+
* of both steps. Declaring the scope therefore changes nothing on the day it
|
|
44618
|
+
* ships: the cluster row starts out saying what every node was already doing.
|
|
44619
|
+
* Changing it is legitimate — that is what the knob is for — but it invalidates
|
|
44620
|
+
* the stored vectors of that surface, and must be followed by a re-embed pass
|
|
44621
|
+
* (`face-reembed-pass.ts`, `embedding-rebuild.ts`).
|
|
44622
|
+
*/
|
|
44623
|
+
/**
|
|
44624
|
+
* The cluster-scoped steps. Mirrored from the catalog's `modelScope: 'cluster'`
|
|
44625
|
+
* declarations and guarded by `scripts/check-cluster-model-scope.ts`.
|
|
44626
|
+
*/
|
|
44627
|
+
var CLUSTER_MODEL_SCOPED_STEPS = [{
|
|
44628
|
+
stepId: "face-embedding",
|
|
44629
|
+
label: "Face recognition model",
|
|
44630
|
+
defaultModelId: "arcface-r100",
|
|
44631
|
+
indexName: "the enrolled face gallery",
|
|
44632
|
+
options: [
|
|
44633
|
+
{
|
|
44634
|
+
id: "arcface-r100",
|
|
44635
|
+
label: "ArcFace (ResNet34)"
|
|
44636
|
+
},
|
|
44637
|
+
{
|
|
44638
|
+
id: "auraface-r100",
|
|
44639
|
+
label: "AuraFace R100"
|
|
44640
|
+
},
|
|
44641
|
+
{
|
|
44642
|
+
id: "inception-resnet-v1",
|
|
44643
|
+
label: "Inception ResNet V1"
|
|
44644
|
+
}
|
|
44645
|
+
]
|
|
44646
|
+
}, {
|
|
44647
|
+
stepId: "clip-embedding",
|
|
44648
|
+
label: "Semantic search model",
|
|
44649
|
+
defaultModelId: "mobileclip-s1",
|
|
44650
|
+
indexName: "the object semantic-search index",
|
|
44651
|
+
options: [{
|
|
44652
|
+
id: "mobileclip-s1",
|
|
44653
|
+
label: "MobileCLIP S1"
|
|
44654
|
+
}, {
|
|
44655
|
+
id: "mobileclip-s2",
|
|
44656
|
+
label: "MobileCLIP S2"
|
|
44657
|
+
}]
|
|
44658
|
+
}];
|
|
44659
|
+
/**
|
|
44660
|
+
* Store identity of the cluster row inside `pipeline-orchestrator`'s GLOBAL
|
|
44661
|
+
* (cluster-wide) settings — the same owner, and for the same reason, as the
|
|
44662
|
+
* detail-crop convention: hub-resident and neutral between the addons that
|
|
44663
|
+
* read it.
|
|
44664
|
+
*/
|
|
44665
|
+
var CLUSTER_MODEL_SECTION_ID = "cluster-step-models";
|
|
44666
|
+
/**
|
|
44667
|
+
* The settings key holding one step's cluster model choice.
|
|
44668
|
+
*
|
|
44669
|
+
* Step-qualified so the keys stay unique across the addon's whole schema, which
|
|
44670
|
+
* is what lets {@link pickClusterStepModels} walk every section instead of
|
|
44671
|
+
* trusting the section id.
|
|
44672
|
+
*/
|
|
44673
|
+
function clusterModelSettingKey(stepId) {
|
|
44674
|
+
return `clusterModel:${stepId}`;
|
|
44675
|
+
}
|
|
44676
|
+
/** True when this step's model is a cluster-wide choice rather than a per-node one. */
|
|
44677
|
+
function isClusterScopedStep(stepId) {
|
|
44678
|
+
return CLUSTER_MODEL_SCOPED_STEPS.some((s) => s.stepId === stepId);
|
|
44679
|
+
}
|
|
44680
|
+
/** The cluster row when nobody has configured one — today's catalog defaults. */
|
|
44681
|
+
var DEFAULT_CLUSTER_STEP_MODELS = Object.freeze(Object.fromEntries(CLUSTER_MODEL_SCOPED_STEPS.map((s) => [s.stepId, s.defaultModelId])));
|
|
44682
|
+
/** A stored choice is a non-empty model id, or it is not a choice. */
|
|
44683
|
+
var ChosenModelIdSchema = zod.z.string().min(1);
|
|
44684
|
+
/**
|
|
44685
|
+
* Narrow a FLAT settings record to the cluster row.
|
|
44686
|
+
*
|
|
44687
|
+
* Per-FIELD fallback, deliberately (same rule as `readDetailCropConvention`): a
|
|
44688
|
+
* junk face model must not also discard a valid clip model. An absent, empty or
|
|
44689
|
+
* non-string value resolves to the step's catalog default — the historical
|
|
44690
|
+
* behaviour — never to a blank id, because a blank id downstream becomes
|
|
44691
|
+
* "substitute the format default", which is precisely the substitution this
|
|
44692
|
+
* scope exists to forbid.
|
|
44693
|
+
*/
|
|
44694
|
+
function readClusterStepModels(config) {
|
|
44695
|
+
const out = {};
|
|
44696
|
+
for (const step of CLUSTER_MODEL_SCOPED_STEPS) {
|
|
44697
|
+
const parsed = ChosenModelIdSchema.safeParse(config[clusterModelSettingKey(step.stepId)]);
|
|
44698
|
+
out[step.stepId] = parsed.success ? parsed.data : step.defaultModelId;
|
|
44699
|
+
}
|
|
44700
|
+
return out;
|
|
44701
|
+
}
|
|
44702
|
+
function isHydratedField$2(entry) {
|
|
44703
|
+
return typeof entry === "object" && entry !== null && "key" in entry;
|
|
44704
|
+
}
|
|
44705
|
+
/**
|
|
44706
|
+
* Extract the cluster row from an `addon-settings.getGlobalSettings` payload.
|
|
44707
|
+
*
|
|
44708
|
+
* Walks EVERY section rather than looking inside {@link CLUSTER_MODEL_SECTION_ID}
|
|
44709
|
+
* alone: the keys are unique across the addon's schema, and a section rename
|
|
44710
|
+
* must not silently revert the whole cluster to the defaults. A `null` payload
|
|
44711
|
+
* (addon mid-boot) is the defaults.
|
|
44712
|
+
*/
|
|
44713
|
+
function pickClusterStepModels(view) {
|
|
44714
|
+
if (view === null) return DEFAULT_CLUSTER_STEP_MODELS;
|
|
44715
|
+
const flat = {};
|
|
44716
|
+
const wanted = new Set(CLUSTER_MODEL_SCOPED_STEPS.map((s) => clusterModelSettingKey(s.stepId)));
|
|
44717
|
+
for (const section of view.sections) for (const entry of section.fields) {
|
|
44718
|
+
if (!isHydratedField$2(entry) || typeof entry.key !== "string") continue;
|
|
44719
|
+
if (wanted.has(entry.key)) flat[entry.key] = entry.value;
|
|
44720
|
+
}
|
|
44721
|
+
return readClusterStepModels(flat);
|
|
44722
|
+
}
|
|
44723
|
+
/**
|
|
44724
|
+
* The cluster model for `stepId`, or `null` when the step is node-scoped.
|
|
44725
|
+
*
|
|
44726
|
+
* `null` is the signal to LEAVE THE CALLER'S RESOLUTION ALONE — it is not "no
|
|
44727
|
+
* model". Returning a default here for a node-scoped step would quietly
|
|
44728
|
+
* override every per-(node,device) selection in the fleet.
|
|
44729
|
+
*/
|
|
44730
|
+
function resolveClusterStepModelId(stepId, models) {
|
|
44731
|
+
const step = CLUSTER_MODEL_SCOPED_STEPS.find((s) => s.stepId === stepId);
|
|
44732
|
+
if (step === void 0) return null;
|
|
44733
|
+
const chosen = models[stepId];
|
|
44734
|
+
return chosen !== void 0 && chosen !== "" ? chosen : step.defaultModelId;
|
|
44735
|
+
}
|
|
44736
|
+
//#endregion
|
|
44337
44737
|
//#region src/pipeline/detail-crop.ts
|
|
44338
44738
|
/**
|
|
44339
44739
|
* THE detail-crop convention — the single derivation of the rectangle a
|
|
@@ -44448,6 +44848,30 @@ function pickDetailCropConvention(view) {
|
|
|
44448
44848
|
}
|
|
44449
44849
|
return readDetailCropConvention(flat);
|
|
44450
44850
|
}
|
|
44851
|
+
/**
|
|
44852
|
+
* The normalised box that means "the image IS the subject".
|
|
44853
|
+
*
|
|
44854
|
+
* Used when the stored pixels are ALREADY the model's input — the 112×112
|
|
44855
|
+
* aligned face template a gallery sample carries. There is no rectangle left to
|
|
44856
|
+
* choose, and the convention must not choose one: re-detecting and re-aligning
|
|
44857
|
+
* over a template applies a SECOND warp, passes every gate, and produces a
|
|
44858
|
+
* plausible useless vector (session 2026-08-20 §3).
|
|
44859
|
+
*
|
|
44860
|
+
* Sending it as the whole frame with this box is exact rather than merely
|
|
44861
|
+
* convenient, and the reason is a property of {@link deriveDetailCropRect}, not
|
|
44862
|
+
* a coincidence: padding a full-frame box can only push OUTSIDE the frame, and
|
|
44863
|
+
* both edge modes bring it straight back — unsquared truncates, squared cannot
|
|
44864
|
+
* exceed the frame's short side and then slides inside. So for a SQUARE image
|
|
44865
|
+
* the derivation is the identity under every convention value, and an operator
|
|
44866
|
+
* changing the crop margin cannot silently re-cut an enrolled template.
|
|
44867
|
+
* `face-reembed-pass.spec.ts` asserts that identity against the real function.
|
|
44868
|
+
*/
|
|
44869
|
+
var FULL_IMAGE_BBOX = Object.freeze({
|
|
44870
|
+
x: 0,
|
|
44871
|
+
y: 0,
|
|
44872
|
+
w: 1,
|
|
44873
|
+
h: 1
|
|
44874
|
+
});
|
|
44451
44875
|
/** Slider bounds for the operator-facing padding knob (orchestrator settings UI). */
|
|
44452
44876
|
var DETAIL_CROP_PADDING_FIELD = {
|
|
44453
44877
|
min: 0,
|
|
@@ -46782,6 +47206,8 @@ exports.CAPABILITY_ROUTER_KEYS = CAPABILITY_ROUTER_KEYS;
|
|
|
46782
47206
|
exports.CAP_NAMES_WITH_STATUS = CAP_NAMES_WITH_STATUS;
|
|
46783
47207
|
exports.CAP_NODE_PIN_CONTEXT_KEY = require_sleep.CAP_NODE_PIN_CONTEXT_KEY;
|
|
46784
47208
|
exports.CAP_PROVIDER_KIND_MAP = CAP_PROVIDER_KIND_MAP;
|
|
47209
|
+
exports.CLUSTER_MODEL_SCOPED_STEPS = CLUSTER_MODEL_SCOPED_STEPS;
|
|
47210
|
+
exports.CLUSTER_MODEL_SECTION_ID = CLUSTER_MODEL_SECTION_ID;
|
|
46785
47211
|
exports.COCO_80_LABELS = COCO_80_LABELS;
|
|
46786
47212
|
exports.COCO_TO_MACRO = COCO_TO_MACRO;
|
|
46787
47213
|
exports.CONNECTION_TEST_TIMEOUT_MS = CONNECTION_TEST_TIMEOUT_MS;
|
|
@@ -46869,6 +47295,7 @@ exports.DECLARED_DEVICE_SWEEP_LIMIT = DECLARED_DEVICE_SWEEP_LIMIT;
|
|
|
46869
47295
|
exports.DECLARED_INTEGRATION_FIXED_KEY = DECLARED_INTEGRATION_FIXED_KEY;
|
|
46870
47296
|
exports.DEFAULT_ADDON_PLACEMENT = DEFAULT_ADDON_PLACEMENT;
|
|
46871
47297
|
exports.DEFAULT_AUDIO_ANALYZER_CONFIG = DEFAULT_AUDIO_ANALYZER_CONFIG;
|
|
47298
|
+
exports.DEFAULT_CLUSTER_STEP_MODELS = DEFAULT_CLUSTER_STEP_MODELS;
|
|
46872
47299
|
exports.DEFAULT_DECODER_HWACCEL_CONFIG = DEFAULT_DECODER_HWACCEL_CONFIG;
|
|
46873
47300
|
exports.DEFAULT_DETAIL_CROP_CONVENTION = DEFAULT_DETAIL_CROP_CONVENTION;
|
|
46874
47301
|
exports.DEFAULT_EVENTS_BAND_BUFFER_SEC = DEFAULT_EVENTS_BAND_BUFFER_SEC;
|
|
@@ -46987,6 +47414,7 @@ exports.ExpressionGlobalBindingSchema = ExpressionGlobalBindingSchema;
|
|
|
46987
47414
|
exports.ExpressionLiteralBindingSchema = ExpressionLiteralBindingSchema;
|
|
46988
47415
|
exports.ExpressionParseError = ExpressionParseError;
|
|
46989
47416
|
exports.ExpressionSourceSchema = ExpressionSourceSchema;
|
|
47417
|
+
exports.FULL_IMAGE_BBOX = FULL_IMAGE_BBOX;
|
|
46990
47418
|
exports.FanControlStatusSchema = FanControlStatusSchema;
|
|
46991
47419
|
exports.FanDirectionSchema = FanDirectionSchema;
|
|
46992
47420
|
exports.FeatureManifestSchema = FeatureManifestSchema;
|
|
@@ -47013,6 +47441,7 @@ exports.HistoryResolutionEnum = HistoryResolutionEnum;
|
|
|
47013
47441
|
exports.HumidifierStatusSchema = HumidifierStatusSchema;
|
|
47014
47442
|
exports.HumiditySensorStatusSchema = HumiditySensorStatusSchema;
|
|
47015
47443
|
exports.HvacModeSchema = HvacModeSchema;
|
|
47444
|
+
exports.INFERENCE_DEVICE_EXCLUSION_REASONS = INFERENCE_DEVICE_EXCLUSION_REASONS;
|
|
47016
47445
|
exports.ImageContractSchema = ImageContractSchema;
|
|
47017
47446
|
exports.ImageContractStateSchema = ImageContractStateSchema;
|
|
47018
47447
|
exports.ImageRotateSchema = ImageRotateSchema;
|
|
@@ -47020,6 +47449,7 @@ exports.ImageSettingsOptionsSchema = ImageSettingsOptionsSchema;
|
|
|
47020
47449
|
exports.ImageSettingsPatchSchema = ImageSettingsPatchSchema;
|
|
47021
47450
|
exports.ImageSettingsStatusSchema = ImageSettingsStatusSchema;
|
|
47022
47451
|
exports.ImageStatusSchema = ImageStatusSchema;
|
|
47452
|
+
exports.InferenceDeviceExclusionReasonSchema = InferenceDeviceExclusionReasonSchema;
|
|
47023
47453
|
exports.IngestOwnerSchema = IngestOwnerSchema;
|
|
47024
47454
|
exports.InstalledPackageSchema = InstalledPackageSchema;
|
|
47025
47455
|
exports.IntegrationLiteSchema = IntegrationLiteSchema;
|
|
@@ -47232,6 +47662,7 @@ exports.NotificationFormatSchema = NotificationFormatSchema;
|
|
|
47232
47662
|
exports.NotificationSchema = NotificationSchema;
|
|
47233
47663
|
exports.NotifierStatusSchema = NotifierStatusSchema;
|
|
47234
47664
|
exports.NumericSensorStatusSchema = NumericSensorStatusSchema;
|
|
47665
|
+
exports.OPERATOR_WRITTEN_STALE_MS = OPERATOR_WRITTEN_STALE_MS;
|
|
47235
47666
|
exports.OPS_LOG_DEFAULT_LIMIT = OPS_LOG_DEFAULT_LIMIT;
|
|
47236
47667
|
exports.OPS_LOG_RING_DEFAULT_MAX = OPS_LOG_RING_DEFAULT_MAX;
|
|
47237
47668
|
exports.OauthIntegrationDescriptorSchema = OauthIntegrationDescriptorSchema;
|
|
@@ -47314,6 +47745,7 @@ exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
|
|
|
47314
47745
|
exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
|
|
47315
47746
|
exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
|
|
47316
47747
|
exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
|
|
47748
|
+
exports.RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS = RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS;
|
|
47317
47749
|
exports.RUNTIME_TO_FORMAT = RUNTIME_TO_FORMAT;
|
|
47318
47750
|
exports.RawStateResultSchema = require_sleep.RawStateResultSchema;
|
|
47319
47751
|
exports.ReadGopBytesResultSchema = ReadGopBytesResultSchema;
|
|
@@ -47645,6 +48077,7 @@ exports.classifyBearerPrincipal = classifyBearerPrincipal;
|
|
|
47645
48077
|
exports.classifyStream = classifyStream;
|
|
47646
48078
|
exports.classifyStreams = classifyStreams;
|
|
47647
48079
|
exports.climateControlCapability = climateControlCapability;
|
|
48080
|
+
exports.clusterModelSettingKey = clusterModelSettingKey;
|
|
47648
48081
|
exports.collectHydratedFieldEntries = require_sleep.collectHydratedFieldEntries;
|
|
47649
48082
|
exports.collectHydratedFieldValues = require_sleep.collectHydratedFieldValues;
|
|
47650
48083
|
exports.colorCapability = colorCapability;
|
|
@@ -47763,6 +48196,7 @@ exports.isAudioLabelSelected = isAudioLabelSelected;
|
|
|
47763
48196
|
exports.isAudioRule = isAudioRule;
|
|
47764
48197
|
exports.isBaseConditionKey = isBaseConditionKey;
|
|
47765
48198
|
exports.isBatteryPresenceFault = isBatteryPresenceFault;
|
|
48199
|
+
exports.isClusterScopedStep = isClusterScopedStep;
|
|
47766
48200
|
exports.isCollectionArrayMethod = isCollectionArrayMethod;
|
|
47767
48201
|
exports.isDeployableToAgent = isDeployableToAgent;
|
|
47768
48202
|
exports.isDetectionMacroClass = isDetectionMacroClass;
|
|
@@ -47846,6 +48280,7 @@ exports.parseStreamParamsFormPatch = parseStreamParamsFormPatch;
|
|
|
47846
48280
|
exports.patchAudio = patchAudio;
|
|
47847
48281
|
exports.petFeederCapability = petFeederCapability;
|
|
47848
48282
|
exports.pickAccessoryControl = pickAccessoryControl;
|
|
48283
|
+
exports.pickClusterStepModels = pickClusterStepModels;
|
|
47849
48284
|
exports.pickDetailCropConvention = pickDetailCropConvention;
|
|
47850
48285
|
exports.pickNativeLeaseOverride = pickNativeLeaseOverride;
|
|
47851
48286
|
exports.pickPreferredRtspEntry = pickPreferredRtspEntry;
|
|
@@ -47869,6 +48304,7 @@ exports.procedureAuthKey = procedureAuthKey;
|
|
|
47869
48304
|
exports.ptzAutotrackCapability = ptzAutotrackCapability;
|
|
47870
48305
|
exports.ptzCapability = ptzCapability;
|
|
47871
48306
|
exports.pythonScriptForBackend = pythonScriptForBackend;
|
|
48307
|
+
exports.readClusterStepModels = readClusterStepModels;
|
|
47872
48308
|
exports.readDetailCropConvention = readDetailCropConvention;
|
|
47873
48309
|
exports.readDeviceStateFrom = readDeviceStateFrom;
|
|
47874
48310
|
exports.readNativeLeaseOverride = readNativeLeaseOverride;
|
|
@@ -47886,6 +48322,7 @@ exports.resolveAddonGroup = resolveAddonGroup;
|
|
|
47886
48322
|
exports.resolveAddonPlacement = resolveAddonPlacement;
|
|
47887
48323
|
exports.resolveAddonRuntime = resolveAddonRuntime;
|
|
47888
48324
|
exports.resolveCapMount = require_sleep.resolveCapMount;
|
|
48325
|
+
exports.resolveClusterStepModelId = resolveClusterStepModelId;
|
|
47889
48326
|
exports.resolveDetectionRuntime = resolveDetectionRuntime;
|
|
47890
48327
|
exports.resolveDeviceControlKind = resolveDeviceControlKind;
|
|
47891
48328
|
exports.resolveDeviceProfile = resolveDeviceProfile;
|