@camstack/addon-pipeline 1.2.295 → 1.2.296
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 +8 -0
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-CAUBVbgK.mjs → default-detection-model-Co578D8C.mjs} +75 -46
- package/dist/{default-detection-model-wNEISVA9.js → default-detection-model-D1daTtqT.js} +75 -46
- package/dist/detection-pipeline/index.js +1023 -80
- package/dist/detection-pipeline/index.mjs +1023 -80
- package/dist/{dist-BsVcf5wO.js → dist-8up-f2TX.js} +877 -365
- package/dist/{dist-CuxSNLKW.mjs → dist-CCd0Q3nr.mjs} +872 -366
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-Cqb1QiXk.mjs → node-DgMSXSWP.mjs} +1 -1
- package/dist/{node-UFk6I2f6.js → node-lpQgHes9.js} +1 -1
- package/dist/pipeline-runner/index.js +789 -203
- package/dist/pipeline-runner/index.mjs +789 -203
- package/dist/{process-memory-CJV29sPf.js → process-memory-CX_92V_r.js} +1 -1
- package/dist/{process-memory-D0Nvs9rr.mjs → process-memory-DFC_O5zE.mjs} +1 -1
- package/dist/recorder/index.js +4 -6
- package/dist/recorder/index.mjs +4 -6
- package/dist/{segment-demux-js-DGwmf5hu.js → segment-demux-js-DzBx6NN2.js} +1 -1
- package/dist/{segment-demux-js-DIDWw1iE.mjs → segment-demux-js-FZbBuk3F.mjs} +1 -1
- package/dist/session-decode/{decode-worker-child.js → decode-worker-main.js} +481 -72
- package/dist/session-decode/{decode-worker-child.mjs → decode-worker-main.mjs} +482 -71
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B7nBFqva.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_i7oFBl.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DUGQKsKL.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-CkbplMHA.mjs +26 -0
- 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-BKlb3qac.mjs → hostInit-BPtppL3W.mjs} +2 -2
- package/dist/stream-broker/index.js +4 -4
- package/dist/stream-broker/index.mjs +4 -4
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-B2MfQLlu.js → worker-protocol-C-G8qmye.js} +3 -1
- package/dist/{worker-protocol-C_W-P_g-.mjs → worker-protocol-D_NzPcnh.mjs} +3 -1
- package/package.json +1 -1
- package/python/inference_pool.py +422 -64
- package/python/test_inference_pool_compile_off_loop.py +414 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCIyBvRa.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DeD_UFdb.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-BXv0ZcRa.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).
|
|
@@ -6193,6 +6193,40 @@ var BaseAddon = class {
|
|
|
6193
6193
|
const node = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
|
|
6194
6194
|
return keys.size > 0 ? projectStore(raw, keys, node) : raw;
|
|
6195
6195
|
}
|
|
6196
|
+
/**
|
|
6197
|
+
* Whether ONE global key of this addon's store was written, read three-way
|
|
6198
|
+
* (D651). For a question a hydrated view cannot answer: `getGlobalSettings`
|
|
6199
|
+
* reports `value ?? default`, so "the operator chose the default" and "nobody
|
|
6200
|
+
* chose" look the same, and a failed read looks like both. A `perNode` key is
|
|
6201
|
+
* not supported here: its stored name is scoped.
|
|
6202
|
+
*/
|
|
6203
|
+
async readStoredGlobalKey(key) {
|
|
6204
|
+
const read = this._ctx?.settings?.readAddonStoreResult;
|
|
6205
|
+
if (read === void 0) return {
|
|
6206
|
+
kind: "unreadable",
|
|
6207
|
+
reason: "three-way-read-unavailable"
|
|
6208
|
+
};
|
|
6209
|
+
let result;
|
|
6210
|
+
try {
|
|
6211
|
+
result = await read.call(this._ctx?.settings);
|
|
6212
|
+
} catch (err) {
|
|
6213
|
+
return {
|
|
6214
|
+
kind: "unreadable",
|
|
6215
|
+
reason: "store-read-failed",
|
|
6216
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6217
|
+
};
|
|
6218
|
+
}
|
|
6219
|
+
if (result.kind === "failed") return {
|
|
6220
|
+
kind: "unreadable",
|
|
6221
|
+
reason: "store-read-failed",
|
|
6222
|
+
error: result.error
|
|
6223
|
+
};
|
|
6224
|
+
if (result.kind === "missing" || !Object.prototype.hasOwnProperty.call(result.value, key)) return { kind: "absent" };
|
|
6225
|
+
return {
|
|
6226
|
+
kind: "present",
|
|
6227
|
+
value: result.value[key]
|
|
6228
|
+
};
|
|
6229
|
+
}
|
|
6196
6230
|
async updateGlobalSettings(patch, nodeId) {
|
|
6197
6231
|
const keys = this.perNodeKeys();
|
|
6198
6232
|
const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
|
|
@@ -11299,6 +11333,43 @@ var FailureCounters = class {
|
|
|
11299
11333
|
this.counters.clear();
|
|
11300
11334
|
}
|
|
11301
11335
|
};
|
|
11336
|
+
var ClipModelMetaSchema = object({
|
|
11337
|
+
/** The paired text encoder's catalog id (the embedding-encoder's text catalog). */
|
|
11338
|
+
textModelId: string().min(1),
|
|
11339
|
+
/** Output dimension of BOTH towers. A vector of any other length is refused. */
|
|
11340
|
+
embeddingDim: number().int().positive(),
|
|
11341
|
+
/**
|
|
11342
|
+
* The `vector-store` index this model's vectors live in. ONE feature space
|
|
11343
|
+
* per index: two models share an index only when their vectors are ranked
|
|
11344
|
+
* against each other, which for CLIP means never across families. MobileCLIP
|
|
11345
|
+
* S1 and S2 share `object-clip` for history (separated by the row's
|
|
11346
|
+
* `modelId`); any other model gets its own.
|
|
11347
|
+
*/
|
|
11348
|
+
vectorIndex: string().regex(/^object-clip(-[a-z0-9][a-z0-9-]*)?$/),
|
|
11349
|
+
/**
|
|
11350
|
+
* The text encoder's tokenizer, a declared SIBLING file of its onnx. The name
|
|
11351
|
+
* must be unique per tokenizer: every model's siblings land in ONE flat
|
|
11352
|
+
* models directory, so two different tokenizers under the same name would
|
|
11353
|
+
* silently overwrite each other (whichever model downloaded last wins, and
|
|
11354
|
+
* the other one tokenises with the wrong vocabulary).
|
|
11355
|
+
*/
|
|
11356
|
+
tokenizerFile: string().min(1),
|
|
11357
|
+
/** Token window the text graph takes (SigLIP2: 64; CLIP BPE: 77). */
|
|
11358
|
+
contextLength: number().int().positive(),
|
|
11359
|
+
/** Id the tokenizer pads with. */
|
|
11360
|
+
padId: number().int().nonnegative(),
|
|
11361
|
+
/**
|
|
11362
|
+
* The search cosine floor at `strictness: 'loose'` — calibrated per model,
|
|
11363
|
+
* because cosine bands are a property of the model: 0.2 keeps 96% of matched
|
|
11364
|
+
* COCO caption-image pairs on MobileCLIP S1 and 2.7% on SigLIP2.
|
|
11365
|
+
*/
|
|
11366
|
+
searchMinScore: number().min(0).max(1)
|
|
11367
|
+
});
|
|
11368
|
+
var ClipSearchStrictnessSchema = _enum([
|
|
11369
|
+
"loose",
|
|
11370
|
+
"balanced",
|
|
11371
|
+
"strict"
|
|
11372
|
+
]);
|
|
11302
11373
|
var MODEL_FORMATS = [
|
|
11303
11374
|
"onnx",
|
|
11304
11375
|
"coreml",
|
|
@@ -11515,7 +11586,14 @@ var ModelCatalogEntrySchema = object({
|
|
|
11515
11586
|
* applies (Frigate / COCO public catalog). Set on a custom model whose raw
|
|
11516
11587
|
* labels already ARE the CamStack macros (Scrypted identity map).
|
|
11517
11588
|
*/
|
|
11518
|
-
classMap: DetectionCatalogClassMapSchema.optional()
|
|
11589
|
+
classMap: DetectionCatalogClassMapSchema.optional(),
|
|
11590
|
+
/**
|
|
11591
|
+
* The model's CLIP feature-space contract (text encoder, dimension, vector
|
|
11592
|
+
* index, token window, search floor) — see `clip-model.ts`. Present ONLY on a
|
|
11593
|
+
* CLIP image encoder; its absence is what "not a CLIP model" means to the
|
|
11594
|
+
* embedding encoder and the semantic-search store (D649).
|
|
11595
|
+
*/
|
|
11596
|
+
clip: ClipModelMetaSchema.optional()
|
|
11519
11597
|
});
|
|
11520
11598
|
var ConvertTargetSchema = discriminatedUnion("format", [object({
|
|
11521
11599
|
format: literal("openvino"),
|
|
@@ -16236,6 +16314,13 @@ var LinkedDeviceSchema = object({
|
|
|
16236
16314
|
features: array(string()),
|
|
16237
16315
|
producesTrackedEvents: boolean().optional()
|
|
16238
16316
|
});
|
|
16317
|
+
/**
|
|
16318
|
+
* The `linked-devices` extension slot's editable value (D648): the operator's
|
|
16319
|
+
* pick list, exactly what `_linkedDeviceIds` persists. Tree children are NOT
|
|
16320
|
+
* in it — they are always linked and are not a pick.
|
|
16321
|
+
*/
|
|
16322
|
+
var LinkedDevicesSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
|
|
16323
|
+
DeviceType.Camera;
|
|
16239
16324
|
/** One camera's resolved linked set, tagged with the camera it belongs to.
|
|
16240
16325
|
* The batch answer needs the tag; the single-device answer already has it
|
|
16241
16326
|
* from the input, which is why `getLinkedDevices` keeps the untagged shape. */
|
|
@@ -16761,16 +16846,101 @@ var EmbeddingResultSchema = object({
|
|
|
16761
16846
|
embedding: array(number()),
|
|
16762
16847
|
inferenceMs: number()
|
|
16763
16848
|
});
|
|
16849
|
+
/** The two CLIP towers the encoder runs; a crash budget is kept per tower. */
|
|
16850
|
+
var EmbeddingTowerSchema = _enum(["image", "text"]);
|
|
16851
|
+
/**
|
|
16852
|
+
* A tower in the terminal `failed` state (D649, D6): it crashed or failed to
|
|
16853
|
+
* load `crashes` times within `windowMs`, and is refused by name until an
|
|
16854
|
+
* operator `resetFailedModels` or a model change — never on a timer.
|
|
16855
|
+
*
|
|
16856
|
+
* `nodeId` is the node whose tower failed. The state lives in THAT node's
|
|
16857
|
+
* encoder process (in memory — a runner respawn clears it), and one node's
|
|
16858
|
+
* answer says nothing about another's.
|
|
16859
|
+
*/
|
|
16860
|
+
var EmbeddingFailedModelSchema = object({
|
|
16861
|
+
modelId: string(),
|
|
16862
|
+
tower: EmbeddingTowerSchema,
|
|
16863
|
+
nodeId: string(),
|
|
16864
|
+
crashes: number(),
|
|
16865
|
+
windowMs: number(),
|
|
16866
|
+
sinceMs: number(),
|
|
16867
|
+
lastReason: string()
|
|
16868
|
+
});
|
|
16869
|
+
/**
|
|
16870
|
+
* A model whose REQUIREMENTS can never be met on this node (D649 round 5):
|
|
16871
|
+
* an id the catalog does not know, a model URL answering 404/410, a declared
|
|
16872
|
+
* sibling (the tokenizer) that is not there. After `attempts` failed builds
|
|
16873
|
+
* it is terminally `requirements-failed`, refused by name until an operator
|
|
16874
|
+
* `resetFailedModels` or a model change — the same exits as a crashed tower.
|
|
16875
|
+
*/
|
|
16876
|
+
var EmbeddingFailedRequirementSchema = object({
|
|
16877
|
+
modelId: string(),
|
|
16878
|
+
tower: EmbeddingTowerSchema,
|
|
16879
|
+
nodeId: string(),
|
|
16880
|
+
attempts: number(),
|
|
16881
|
+
sinceMs: number(),
|
|
16882
|
+
lastReason: string()
|
|
16883
|
+
});
|
|
16764
16884
|
var EmbeddingInfoSchema = object({
|
|
16765
16885
|
modelId: string(),
|
|
16766
16886
|
embeddingDim: number(),
|
|
16767
|
-
ready: boolean()
|
|
16768
|
-
|
|
16887
|
+
ready: boolean(),
|
|
16888
|
+
/**
|
|
16889
|
+
* The node that answered. Absent only from an encoder older than D649's
|
|
16890
|
+
* crash budget — whose `failedModels` is then UNKNOWN (D315), never "none".
|
|
16891
|
+
*/
|
|
16892
|
+
nodeId: string().optional(),
|
|
16893
|
+
/**
|
|
16894
|
+
* Towers in `failed` on the answering node, each row naming it. `undefined`
|
|
16895
|
+
* means the node could not say (pre-budget encoder); a node that has the
|
|
16896
|
+
* budget always answers an array, empty included.
|
|
16897
|
+
*/
|
|
16898
|
+
failedModels: array(EmbeddingFailedModelSchema).optional(),
|
|
16899
|
+
/** Models terminally `requirements-failed` on the answering node; same reading as above. */
|
|
16900
|
+
failedRequirements: array(EmbeddingFailedRequirementSchema).optional()
|
|
16901
|
+
});
|
|
16902
|
+
/**
|
|
16903
|
+
* The THREE codes an encoder refusal can carry (D649, D6). Same shape as
|
|
16904
|
+
* `VECTOR_INDEX_DIM_CONFLICT`: the code rides in the MESSAGE, because an
|
|
16905
|
+
* Error's own properties do not survive the UDS/tRPC hop and its message does,
|
|
16906
|
+
* and both sides go through the helpers below — the encoder builds the
|
|
16907
|
+
* refusal, the live detail plane classifies on it to count refusals per
|
|
16908
|
+
* camera instead of logging one warning per track. The bracketed token
|
|
16909
|
+
* carries the code, the model, the tower and the node so a consumer never
|
|
16910
|
+
* parses prose.
|
|
16911
|
+
*
|
|
16912
|
+
* - `EMBEDDING_MODEL_FAILED`: the tower crashed / failed to load past the crash
|
|
16913
|
+
* budget — terminal until `resetFailedModels` or a model change.
|
|
16914
|
+
* - `EMBEDDING_REQUIREMENTS_UNAVAILABLE`: files or runtime not there right now
|
|
16915
|
+
* (a download or Python that failed, or the cooldown after one) — the next
|
|
16916
|
+
* request past the cooldown tries again.
|
|
16917
|
+
* - `EMBEDDING_REQUIREMENTS_FAILED`: the requirements can never be met (unknown
|
|
16918
|
+
* id, 404/410, missing sibling) and the bounded attempts are spent —
|
|
16919
|
+
* terminal, same exits as a crashed tower.
|
|
16920
|
+
*/
|
|
16921
|
+
var EMBEDDING_MODEL_FAILED = "embedding-model-failed";
|
|
16922
|
+
var EMBEDDING_REQUIREMENTS_UNAVAILABLE = "embedding-requirements-unavailable";
|
|
16923
|
+
var EMBEDDING_REQUIREMENTS_FAILED = "embedding-requirements-failed";
|
|
16924
|
+
_enum([
|
|
16925
|
+
EMBEDDING_MODEL_FAILED,
|
|
16926
|
+
EMBEDDING_REQUIREMENTS_UNAVAILABLE,
|
|
16927
|
+
EMBEDDING_REQUIREMENTS_FAILED
|
|
16928
|
+
]);
|
|
16929
|
+
new RegExp(`\\[(${EMBEDDING_MODEL_FAILED}|${EMBEDDING_REQUIREMENTS_UNAVAILABLE}|${EMBEDDING_REQUIREMENTS_FAILED}) model=([^\\s\\]]+) tower=(image|text) node=([^\\s\\]]+)\\]`);
|
|
16769
16930
|
method(object({
|
|
16770
16931
|
crop: _instanceof(Uint8Array),
|
|
16771
16932
|
width: number(),
|
|
16772
16933
|
height: number()
|
|
16773
|
-
}), EmbeddingResultSchema, { auth: "admin" }), method(object({
|
|
16934
|
+
}), EmbeddingResultSchema, { auth: "admin" }), method(object({
|
|
16935
|
+
text: string(),
|
|
16936
|
+
modelId: string().min(1).optional()
|
|
16937
|
+
}), EmbeddingResultSchema, { auth: "admin" }), method(_void(), EmbeddingInfoSchema, { auth: "admin" }), method(object({ modelId: string().min(1).optional() }), object({
|
|
16938
|
+
cleared: array(string()),
|
|
16939
|
+
nodeId: string()
|
|
16940
|
+
}), {
|
|
16941
|
+
kind: "mutation",
|
|
16942
|
+
auth: "admin"
|
|
16943
|
+
});
|
|
16774
16944
|
/**
|
|
16775
16945
|
* `failure-contribution` — the capability an addon reports its OWN losses
|
|
16776
16946
|
* through, per camera, with the denominator attached. It stores nothing.
|
|
@@ -20573,7 +20743,12 @@ var NcMediaPolicySchema = object({
|
|
|
20573
20743
|
* the degrade engine, which drops the video and keeps the still.
|
|
20574
20744
|
*/
|
|
20575
20745
|
clip: boolean().optional(),
|
|
20576
|
-
/**
|
|
20746
|
+
/**
|
|
20747
|
+
* Seconds of footage BEFORE / AFTER the event instant. Absent =
|
|
20748
|
+
* {@link NC_CLIP_PRE_ROLL_DEFAULT_SEC} / {@link NC_CLIP_POST_ROLL_DEFAULT_SEC}
|
|
20749
|
+
* (4 / 6), resolved by the Notification Center at dispatch — deliberately not
|
|
20750
|
+
* a Zod default, so a rule that never chose keeps following the hub's.
|
|
20751
|
+
*/
|
|
20577
20752
|
clipPreRollSec: number().int().min(0).max(30).optional(),
|
|
20578
20753
|
clipPostRollSec: number().int().min(0).max(30).optional(),
|
|
20579
20754
|
/**
|
|
@@ -21951,6 +22126,257 @@ var OauthIntegrationDescriptorSchema = object({
|
|
|
21951
22126
|
});
|
|
21952
22127
|
method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
|
|
21953
22128
|
/**
|
|
22129
|
+
* A model choice has a SCOPE, and a scope change has a workflow.
|
|
22130
|
+
*
|
|
22131
|
+
* ## The two scopes
|
|
22132
|
+
*
|
|
22133
|
+
* Almost every pipeline step's model is a **node** question: which build runs
|
|
22134
|
+
* fastest on THIS accelerator. Two nodes may legitimately answer differently —
|
|
22135
|
+
* a Coral runs the tflite SSD, a Mac runs the coreml YOLO — because the answers
|
|
22136
|
+
* are compared against nothing. They produce boxes, and a box is a box.
|
|
22137
|
+
*
|
|
22138
|
+
* Two steps are not like that. `face-embedding` and `clip-embedding` produce
|
|
22139
|
+
* **vectors that land in ONE shared index** (`identity_samples`,
|
|
22140
|
+
* `vec_object_clip`) and are ranked against each other by cosine similarity.
|
|
22141
|
+
* Cosine similarity between two encoders is not a weak signal, it is a
|
|
22142
|
+
* meaningless number — and nothing inside a vector can tell you it happened.
|
|
22143
|
+
* So for those two steps the model is a **cluster** question with exactly one
|
|
22144
|
+
* answer, stored in exactly one row.
|
|
22145
|
+
*
|
|
22146
|
+
* That is the same argument [D52](../../../../docs/decisions/adr-0052.md) makes
|
|
22147
|
+
* for the crop convention (`detail-crop.ts`), one level up: the crop decides
|
|
22148
|
+
* WHAT pixels the encoder sees, this decides WHICH encoder sees them. Both are
|
|
22149
|
+
* cluster-wide for the same reason and neither may become per-node.
|
|
22150
|
+
*
|
|
22151
|
+
* ## Why the registry lives here and not in the step catalog
|
|
22152
|
+
*
|
|
22153
|
+
* The scope itself is declared on the step, in
|
|
22154
|
+
* `detection-pipeline/registry/step-definitions.ts` (`StepDefinition.modelScope`)
|
|
22155
|
+
* — that is where a new step declares what kind of choice its model is. But the
|
|
22156
|
+
* catalog lives inside `addon-pipeline`, and three packages that must NOT
|
|
22157
|
+
* import an addon need to know the set: the orchestrator (which renders and
|
|
22158
|
+
* stores the row), post-analysis (which pins the row for a re-embed pass) and
|
|
22159
|
+
* admin-ui (which mirrors it). So the set is mirrored here, and
|
|
22160
|
+
* `scripts/check-cluster-model-scope.ts` fails the build when the two disagree.
|
|
22161
|
+
* A hand-copied key that stops matching is exactly how a setting silently stops
|
|
22162
|
+
* arriving while both sides still look correct.
|
|
22163
|
+
*
|
|
22164
|
+
* ## The defaults ARE today's behaviour
|
|
22165
|
+
*
|
|
22166
|
+
* {@link DEFAULT_CLUSTER_STEP_MODELS} reproduces the catalog `defaultModelId`
|
|
22167
|
+
* of both steps. Declaring the scope therefore changes nothing on the day it
|
|
22168
|
+
* ships: the cluster row starts out saying what every node was already doing.
|
|
22169
|
+
* Changing it is legitimate — that is what the knob is for — but it invalidates
|
|
22170
|
+
* the stored vectors of that surface, and must be followed by a re-embed pass
|
|
22171
|
+
* (`face-reembed-pass.ts`, `embedding-rebuild.ts`).
|
|
22172
|
+
*/
|
|
22173
|
+
/**
|
|
22174
|
+
* Why the orchestrator cannot say which model the operator chose for a
|
|
22175
|
+
* cluster-scoped step (D651).
|
|
22176
|
+
*/
|
|
22177
|
+
var CLUSTER_MODEL_CHOICE_UNREADABLE_REASONS = [
|
|
22178
|
+
"owner-not-ready",
|
|
22179
|
+
"store-read-failed",
|
|
22180
|
+
"three-way-read-unavailable",
|
|
22181
|
+
"invalid-stored",
|
|
22182
|
+
"not-cluster-scoped"
|
|
22183
|
+
];
|
|
22184
|
+
/**
|
|
22185
|
+
* The cluster-scoped steps. Mirrored from the catalog's `modelScope: 'cluster'`
|
|
22186
|
+
* declarations and guarded by `scripts/check-cluster-model-scope.ts`.
|
|
22187
|
+
*/
|
|
22188
|
+
var CLUSTER_MODEL_SCOPED_STEPS = [{
|
|
22189
|
+
stepId: "face-embedding",
|
|
22190
|
+
label: "Face recognition model",
|
|
22191
|
+
defaultModelId: "auraface-r100",
|
|
22192
|
+
indexName: "the enrolled face gallery",
|
|
22193
|
+
options: [
|
|
22194
|
+
{
|
|
22195
|
+
id: "auraface-r100",
|
|
22196
|
+
label: "AuraFace R100"
|
|
22197
|
+
},
|
|
22198
|
+
{
|
|
22199
|
+
id: "arcface-r100",
|
|
22200
|
+
label: "ArcFace (ResNet34)"
|
|
22201
|
+
},
|
|
22202
|
+
{
|
|
22203
|
+
id: "inception-resnet-v1",
|
|
22204
|
+
label: "Inception ResNet V1"
|
|
22205
|
+
}
|
|
22206
|
+
]
|
|
22207
|
+
}, {
|
|
22208
|
+
stepId: "clip-embedding",
|
|
22209
|
+
label: "Semantic search model",
|
|
22210
|
+
defaultModelId: "mobileclip-s1",
|
|
22211
|
+
indexName: "the object semantic-search index",
|
|
22212
|
+
options: [
|
|
22213
|
+
{
|
|
22214
|
+
id: "mobileclip-s1",
|
|
22215
|
+
label: "MobileCLIP S1"
|
|
22216
|
+
},
|
|
22217
|
+
{
|
|
22218
|
+
id: "mobileclip-s2",
|
|
22219
|
+
label: "MobileCLIP S2"
|
|
22220
|
+
},
|
|
22221
|
+
{
|
|
22222
|
+
id: "siglip2-b16-224",
|
|
22223
|
+
label: "SigLIP2 B/16 (Apache-2.0)"
|
|
22224
|
+
}
|
|
22225
|
+
]
|
|
22226
|
+
}];
|
|
22227
|
+
/**
|
|
22228
|
+
* The settings key holding one step's cluster model choice.
|
|
22229
|
+
*
|
|
22230
|
+
* Step-qualified so the keys stay unique across the addon's whole schema, which
|
|
22231
|
+
* is what lets {@link pickClusterStepModels} walk every section instead of
|
|
22232
|
+
* trusting the section id.
|
|
22233
|
+
*/
|
|
22234
|
+
function clusterModelSettingKey(stepId) {
|
|
22235
|
+
return `clusterModel:${stepId}`;
|
|
22236
|
+
}
|
|
22237
|
+
/** The cluster row when nobody has configured one — today's catalog defaults. */
|
|
22238
|
+
var DEFAULT_CLUSTER_STEP_MODELS = Object.freeze(Object.fromEntries(CLUSTER_MODEL_SCOPED_STEPS.map((s) => [s.stepId, s.defaultModelId])));
|
|
22239
|
+
function interpretStoredClusterModel(raw) {
|
|
22240
|
+
if (raw === void 0 || raw === null || raw === "") return { kind: "absent" };
|
|
22241
|
+
if (typeof raw === "string") return {
|
|
22242
|
+
kind: "chosen",
|
|
22243
|
+
modelId: raw
|
|
22244
|
+
};
|
|
22245
|
+
return {
|
|
22246
|
+
kind: "invalid",
|
|
22247
|
+
value: raw
|
|
22248
|
+
};
|
|
22249
|
+
}
|
|
22250
|
+
/**
|
|
22251
|
+
* The cluster-scoped steps whose stored value is `invalid` under
|
|
22252
|
+
* {@link interpretStoredClusterModel} — for the runtime's log line, so the
|
|
22253
|
+
* default it runs on such a row is never run in silence.
|
|
22254
|
+
*/
|
|
22255
|
+
function invalidClusterStepModelEntries(config) {
|
|
22256
|
+
const out = [];
|
|
22257
|
+
for (const step of CLUSTER_MODEL_SCOPED_STEPS) {
|
|
22258
|
+
const read = interpretStoredClusterModel(config[clusterModelSettingKey(step.stepId)]);
|
|
22259
|
+
if (read.kind === "invalid") out.push({
|
|
22260
|
+
stepId: step.stepId,
|
|
22261
|
+
value: read.value
|
|
22262
|
+
});
|
|
22263
|
+
}
|
|
22264
|
+
return out;
|
|
22265
|
+
}
|
|
22266
|
+
/**
|
|
22267
|
+
* Narrow a FLAT settings record to the cluster row.
|
|
22268
|
+
*
|
|
22269
|
+
* Per-FIELD fallback, deliberately (same rule as `readDetailCropConvention`): a
|
|
22270
|
+
* junk face model must not also discard a valid clip model. An `absent` or
|
|
22271
|
+
* `invalid` value ({@link interpretStoredClusterModel}) resolves to the step's
|
|
22272
|
+
* catalog default — never to a blank id, because a blank id downstream becomes
|
|
22273
|
+
* "substitute the format default", which is precisely the substitution this
|
|
22274
|
+
* scope exists to forbid.
|
|
22275
|
+
*/
|
|
22276
|
+
function readClusterStepModels(config) {
|
|
22277
|
+
const out = {};
|
|
22278
|
+
for (const step of CLUSTER_MODEL_SCOPED_STEPS) {
|
|
22279
|
+
const read = interpretStoredClusterModel(config[clusterModelSettingKey(step.stepId)]);
|
|
22280
|
+
out[step.stepId] = read.kind === "chosen" ? read.modelId : step.defaultModelId;
|
|
22281
|
+
}
|
|
22282
|
+
return out;
|
|
22283
|
+
}
|
|
22284
|
+
function isHydratedField$2(entry) {
|
|
22285
|
+
return typeof entry === "object" && entry !== null && "key" in entry;
|
|
22286
|
+
}
|
|
22287
|
+
/**
|
|
22288
|
+
* Extract the cluster row from an `addon-settings.getGlobalSettings` payload.
|
|
22289
|
+
*
|
|
22290
|
+
* Walks EVERY section rather than looking inside {@link CLUSTER_MODEL_SECTION_ID}
|
|
22291
|
+
* alone: the keys are unique across the addon's schema, and a section rename
|
|
22292
|
+
* must not silently revert the whole cluster to the defaults. A `null` payload
|
|
22293
|
+
* (addon mid-boot) is the defaults.
|
|
22294
|
+
*/
|
|
22295
|
+
function pickClusterStepModels(view) {
|
|
22296
|
+
if (view === null) return DEFAULT_CLUSTER_STEP_MODELS;
|
|
22297
|
+
return readClusterStepModels(flattenHydratedView(view, clusterModelKeys()));
|
|
22298
|
+
}
|
|
22299
|
+
/**
|
|
22300
|
+
* The `invalid` stored cluster model values a hydrated view carries — what the
|
|
22301
|
+
* runtime logs when it runs a default on a row it could not read as a choice.
|
|
22302
|
+
* A `null` view (owner mid-boot) carries nothing to judge.
|
|
22303
|
+
*/
|
|
22304
|
+
function pickInvalidClusterStepModels(view) {
|
|
22305
|
+
if (view === null) return [];
|
|
22306
|
+
return invalidClusterStepModelEntries(flattenHydratedView(view, clusterModelKeys()));
|
|
22307
|
+
}
|
|
22308
|
+
function clusterModelKeys() {
|
|
22309
|
+
return new Set(CLUSTER_MODEL_SCOPED_STEPS.map((s) => clusterModelSettingKey(s.stepId)));
|
|
22310
|
+
}
|
|
22311
|
+
/** The `wanted` keys of a hydrated view as one flat record, every section walked. */
|
|
22312
|
+
function flattenHydratedView(view, wanted) {
|
|
22313
|
+
const flat = {};
|
|
22314
|
+
for (const section of view.sections) for (const entry of section.fields) {
|
|
22315
|
+
if (!isHydratedField$2(entry) || typeof entry.key !== "string") continue;
|
|
22316
|
+
if (wanted.has(entry.key)) flat[entry.key] = entry.value;
|
|
22317
|
+
}
|
|
22318
|
+
return flat;
|
|
22319
|
+
}
|
|
22320
|
+
/**
|
|
22321
|
+
* The cluster model for `stepId`, or `null` when the step is node-scoped.
|
|
22322
|
+
*
|
|
22323
|
+
* `null` is the signal to LEAVE THE CALLER'S RESOLUTION ALONE — it is not "no
|
|
22324
|
+
* model". Returning a default here for a node-scoped step would quietly
|
|
22325
|
+
* override every per-(node,device) selection in the fleet.
|
|
22326
|
+
*/
|
|
22327
|
+
function resolveClusterStepModelId(stepId, models) {
|
|
22328
|
+
const step = CLUSTER_MODEL_SCOPED_STEPS.find((s) => s.stepId === stepId);
|
|
22329
|
+
if (step === void 0) return null;
|
|
22330
|
+
const chosen = models[stepId];
|
|
22331
|
+
return chosen !== void 0 && chosen !== "" ? chosen : step.defaultModelId;
|
|
22332
|
+
}
|
|
22333
|
+
var CLUSTER_STEP_SETTING_FIELDS = [{
|
|
22334
|
+
stepId: "face-embedding",
|
|
22335
|
+
key: "minLandmarkFaceSize",
|
|
22336
|
+
label: "Min face size for recognition (detection px)",
|
|
22337
|
+
description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
|
|
22338
|
+
type: "slider",
|
|
22339
|
+
min: 0,
|
|
22340
|
+
max: 64,
|
|
22341
|
+
step: 2,
|
|
22342
|
+
default: 24
|
|
22343
|
+
}];
|
|
22344
|
+
function clusterStepSettingKey(stepId, fieldKey) {
|
|
22345
|
+
return `clusterStepSetting:${stepId}:${fieldKey}`;
|
|
22346
|
+
}
|
|
22347
|
+
var ClusterSettingNumberSchema = number().finite();
|
|
22348
|
+
function readClusterStepSettings(config) {
|
|
22349
|
+
const out = {};
|
|
22350
|
+
for (const field of CLUSTER_STEP_SETTING_FIELDS) {
|
|
22351
|
+
const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
|
|
22352
|
+
const value = parsed.success ? parsed.data : field.default;
|
|
22353
|
+
const existing = out[field.stepId] ?? {};
|
|
22354
|
+
out[field.stepId] = {
|
|
22355
|
+
...existing,
|
|
22356
|
+
[field.key]: value
|
|
22357
|
+
};
|
|
22358
|
+
}
|
|
22359
|
+
return out;
|
|
22360
|
+
}
|
|
22361
|
+
var DEFAULT_CLUSTER_STEP_SETTINGS = readClusterStepSettings({});
|
|
22362
|
+
function pickClusterStepSettings(view) {
|
|
22363
|
+
if (view === null) return DEFAULT_CLUSTER_STEP_SETTINGS;
|
|
22364
|
+
return readClusterStepSettings(flattenHydratedView(view, new Set(CLUSTER_STEP_SETTING_FIELDS.map((field) => clusterStepSettingKey(field.stepId, field.key)))));
|
|
22365
|
+
}
|
|
22366
|
+
/**
|
|
22367
|
+
* Cluster knobs win over leftover per-device values. The cluster row is the
|
|
22368
|
+
* authority; a device-local copy is a leftover of the old per-node editor.
|
|
22369
|
+
*/
|
|
22370
|
+
function overlayClusterStepSettings(stepId, deviceSettings, cluster) {
|
|
22371
|
+
const overlay = cluster[stepId];
|
|
22372
|
+
if (overlay === void 0 || Object.keys(overlay).length === 0) return deviceSettings === void 0 ? void 0 : { ...deviceSettings };
|
|
22373
|
+
if (deviceSettings === void 0) return { ...overlay };
|
|
22374
|
+
return {
|
|
22375
|
+
...deviceSettings,
|
|
22376
|
+
...overlay
|
|
22377
|
+
};
|
|
22378
|
+
}
|
|
22379
|
+
/**
|
|
21954
22380
|
* pipeline-analytics — device-scoped wrapper cap. Refines raw
|
|
21955
22381
|
* per-frame detections emitted by the pipeline runner into tracked
|
|
21956
22382
|
* objects, per-kind event collections (motion / object / audio), and
|
|
@@ -22766,7 +23192,8 @@ var MediaFileKindEnum = _enum([
|
|
|
22766
23192
|
"thumbnailSmall",
|
|
22767
23193
|
"confirmationCrop",
|
|
22768
23194
|
"debugCrop",
|
|
22769
|
-
"sceneFrame"
|
|
23195
|
+
"sceneFrame",
|
|
23196
|
+
"lastSceneFrame"
|
|
22770
23197
|
]);
|
|
22771
23198
|
/**
|
|
22772
23199
|
* One media row ON THE WIRE: what it is, how big it is, and WHERE ITS BYTES
|
|
@@ -23318,7 +23745,23 @@ var SearchObjectEventsInput = object({
|
|
|
23318
23745
|
until: number().optional(),
|
|
23319
23746
|
classFilter: string().optional(),
|
|
23320
23747
|
limit: number().default(50),
|
|
23321
|
-
|
|
23748
|
+
/**
|
|
23749
|
+
* An ABSOLUTE cosine floor. Optional since D649: a floor is a property of the
|
|
23750
|
+
* model (0.2 kept 96% of matched pairs on MobileCLIP S1 and 2.7% on SigLIP2),
|
|
23751
|
+
* so an absent one resolves to the searched model's own `searchMinScore`
|
|
23752
|
+
* scaled by `strictness`. A number sent here is honoured verbatim.
|
|
23753
|
+
*/
|
|
23754
|
+
minScore: number().min(0).max(1).optional(),
|
|
23755
|
+
/** Model-relative floor — see `resolveClipSearchMinScore`. Default `loose`. */
|
|
23756
|
+
strictness: ClipSearchStrictnessSchema.optional(),
|
|
23757
|
+
/**
|
|
23758
|
+
* Search THIS CLIP model's index instead of the cluster row's. Every index a
|
|
23759
|
+
* model ever wrote stays searchable until it is wiped (the MobileCLIP index
|
|
23760
|
+
* through a SigLIP2 cutover and its rollback window). An id with no CLIP
|
|
23761
|
+
* metadata is refused; an id other than the cluster row's is ADMIN-only
|
|
23762
|
+
* (`model-override-requires-admin`).
|
|
23763
|
+
*/
|
|
23764
|
+
modelId: string().min(1).optional()
|
|
23322
23765
|
});
|
|
23323
23766
|
var TrackCascadeCountsSchema = object({
|
|
23324
23767
|
/** Persisted track roots deleted (authoritative). */
|
|
@@ -23453,7 +23896,41 @@ var RebuildObjectEmbeddingsInput = object({
|
|
|
23453
23896
|
* comparable, so wiping and rebuilding is the only way to be sure every vector
|
|
23454
23897
|
* means the same thing.
|
|
23455
23898
|
*/
|
|
23456
|
-
var WipeObjectEmbeddingsResultSchema = object({
|
|
23899
|
+
var WipeObjectEmbeddingsResultSchema = object({
|
|
23900
|
+
deleted: number(),
|
|
23901
|
+
/**
|
|
23902
|
+
* CLIP indexes that could NOT be emptied (unreachable, or refused at their
|
|
23903
|
+
* dimension) — not a clean slate. Additive and optional (D649).
|
|
23904
|
+
*/
|
|
23905
|
+
failedIndexes: array(string()).optional()
|
|
23906
|
+
});
|
|
23907
|
+
/**
|
|
23908
|
+
* Why a rebuild did not start, BY NAME — ONE vocabulary for both gates in front
|
|
23909
|
+
* of a pass (D649 over D651). A bare `started: false` read the same for "the
|
|
23910
|
+
* stores are booting" and "the cluster row names a model with no CLIP
|
|
23911
|
+
* metadata", which need different fixes.
|
|
23912
|
+
* - `stores-unavailable` — the analytics stores or the API are not up yet.
|
|
23913
|
+
* - the owner's confirmation of the cluster model (D651, `ClusterPinConfirmer`):
|
|
23914
|
+
* a pass that rewrites an index needs two agreeing reads of the OWNER's
|
|
23915
|
+
* answer (`pipelineOrchestrator.getClusterModelChoice`) — a pass that cannot
|
|
23916
|
+
* name its model does not run (D54):
|
|
23917
|
+
* - the owner's own `unreadable` reasons (`owner-not-ready`,
|
|
23918
|
+
* `store-read-failed`, `three-way-read-unavailable`, `invalid-stored`,
|
|
23919
|
+
* `not-cluster-scoped`);
|
|
23920
|
+
* - `method-unavailable` — the orchestrator predates the method;
|
|
23921
|
+
* - `read-failed` — the call failed or answered something unparseable;
|
|
23922
|
+
* - `reads-disagree` — the two reads named different choices.
|
|
23923
|
+
* - `not-a-clip-model` — the CONFIRMED id has no CLIP metadata, so there is no
|
|
23924
|
+
* index to fill and no dimension to check against (D649).
|
|
23925
|
+
*/
|
|
23926
|
+
var RebuildObjectEmbeddingsRefusalSchema = _enum([
|
|
23927
|
+
"stores-unavailable",
|
|
23928
|
+
...CLUSTER_MODEL_CHOICE_UNREADABLE_REASONS,
|
|
23929
|
+
"method-unavailable",
|
|
23930
|
+
"read-failed",
|
|
23931
|
+
"reads-disagree",
|
|
23932
|
+
"not-a-clip-model"
|
|
23933
|
+
]);
|
|
23457
23934
|
/**
|
|
23458
23935
|
* Acknowledgement that a rebuild STARTED.
|
|
23459
23936
|
*
|
|
@@ -23466,7 +23943,12 @@ var WipeObjectEmbeddingsResultSchema = object({ deleted: number() });
|
|
|
23466
23943
|
var RebuildObjectEmbeddingsResultSchema = object({
|
|
23467
23944
|
started: boolean(),
|
|
23468
23945
|
/** True when a pass was already running; the new request is ignored. */
|
|
23469
|
-
alreadyRunning: boolean()
|
|
23946
|
+
alreadyRunning: boolean(),
|
|
23947
|
+
/** Present when the pass refused to start — see `RebuildObjectEmbeddingsRefusalSchema`. */
|
|
23948
|
+
refusal: RebuildObjectEmbeddingsRefusalSchema.optional(),
|
|
23949
|
+
/** The CLIP model the pass pinned, and the index it fills (when it started). */
|
|
23950
|
+
modelId: string().optional(),
|
|
23951
|
+
index: string().optional()
|
|
23470
23952
|
});
|
|
23471
23953
|
var RebuildStatusSchema = object({
|
|
23472
23954
|
running: boolean(),
|
|
@@ -23500,7 +23982,11 @@ var RebuildStatusSchema = object({
|
|
|
23500
23982
|
startedAtMs: number().nullable(),
|
|
23501
23983
|
finishedAtMs: number().nullable(),
|
|
23502
23984
|
/** Present when the pass ended by throwing. */
|
|
23503
|
-
error: string().nullable()
|
|
23985
|
+
error: string().nullable(),
|
|
23986
|
+
/** The CLIP model the current / last pass pinned (D54), `null` before any. */
|
|
23987
|
+
modelId: string().nullable(),
|
|
23988
|
+
/** The index that pass fills — `ClipModelMeta.vectorIndex` of `modelId` (D649). */
|
|
23989
|
+
index: string().nullable()
|
|
23504
23990
|
});
|
|
23505
23991
|
/**
|
|
23506
23992
|
* Acknowledgement that a debug-media reclaim STARTED.
|
|
@@ -23821,7 +24307,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
23821
24307
|
deviceId: number(),
|
|
23822
24308
|
ownerKind: MediaPresenceOwnerKindSchema,
|
|
23823
24309
|
ownerIds: array(string()).max(5e3)
|
|
23824
|
-
}), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
|
|
24310
|
+
}), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly(), { caller: "required" }), method(object({}), WipeObjectEmbeddingsResultSchema, {
|
|
23825
24311
|
kind: "mutation",
|
|
23826
24312
|
auth: "admin"
|
|
23827
24313
|
}), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
|
|
@@ -25052,6 +25538,17 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
|
|
|
25052
25538
|
hubHostnameOverride: string().optional()
|
|
25053
25539
|
})]).describe("Per-camera frame-source mode for the runner (P2c)");
|
|
25054
25540
|
/**
|
|
25541
|
+
* What ONE step runs with on ONE roster device for THIS camera (D650): the
|
|
25542
|
+
* device's per-(node,device) base with the camera's override for that device
|
|
25543
|
+
* on top — the same `override ?? base` merge the orchestrator applies to the
|
|
25544
|
+
* elected device (`applyDeviceProvisioning`). Absent `modelId` ⇒ the executor
|
|
25545
|
+
* resolves the device format's default, exactly as on the elected device.
|
|
25546
|
+
*/
|
|
25547
|
+
var RunnerJumpStepProvisioningSchema = object({
|
|
25548
|
+
modelId: string().optional(),
|
|
25549
|
+
settings: record(string(), unknown()).optional()
|
|
25550
|
+
});
|
|
25551
|
+
/**
|
|
25055
25552
|
* One ENABLED inference device on the runner's node, as the step-tree
|
|
25056
25553
|
* device-jump resolver sees it (phase 1). The orchestrator populates this
|
|
25057
25554
|
* roster on the attach payload whenever the camera is elected onto a specific
|
|
@@ -25063,7 +25560,16 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
|
|
|
25063
25560
|
var RunnerInferenceDeviceSchema = object({
|
|
25064
25561
|
deviceKey: string(),
|
|
25065
25562
|
weight: number().positive().default(1),
|
|
25066
|
-
maxSessions: number().int().positive().nullable().default(null)
|
|
25563
|
+
maxSessions: number().int().positive().nullable().default(null),
|
|
25564
|
+
/**
|
|
25565
|
+
* Per-step provisioning for this camera on THIS device (D650), keyed by step
|
|
25566
|
+
* id. A step the runner jumps here runs with these fields, never with the
|
|
25567
|
+
* elected device's: `modelId` / `settings` are REPLACED on the jumped subtree
|
|
25568
|
+
* (a step absent from the map runs with neither — the target format default).
|
|
25569
|
+
* Absent field ⇒ an orchestrator that predates D650: the jumped step keeps
|
|
25570
|
+
* the elected device's provisioning, as before.
|
|
25571
|
+
*/
|
|
25572
|
+
steps: record(string(), RunnerJumpStepProvisioningSchema).optional()
|
|
25067
25573
|
});
|
|
25068
25574
|
/**
|
|
25069
25575
|
* Camera assignment payload sent by `addon-pipeline-orchestrator` to a
|
|
@@ -26130,6 +26636,28 @@ var NodeInferenceDeviceSchema = object({
|
|
|
26130
26636
|
*/
|
|
26131
26637
|
exclusion: InferenceDeviceExclusionReasonSchema.nullable()
|
|
26132
26638
|
});
|
|
26639
|
+
/**
|
|
26640
|
+
* The operator's model choice for one cluster-scoped step, answered by the
|
|
26641
|
+
* addon that OWNS the `clusterModel:*` settings (D651). Three answers, never
|
|
26642
|
+
* folded: a written choice, a key nobody wrote (the cluster runs the default
|
|
26643
|
+
* BY ABSENCE), or no answer. A caller that rewrites an index acts only on the
|
|
26644
|
+
* first two, read twice and agreeing.
|
|
26645
|
+
*/
|
|
26646
|
+
var ClusterModelChoiceSchema = discriminatedUnion("kind", [
|
|
26647
|
+
object({
|
|
26648
|
+
kind: literal("stored"),
|
|
26649
|
+
modelId: string()
|
|
26650
|
+
}),
|
|
26651
|
+
object({
|
|
26652
|
+
kind: literal("absent"),
|
|
26653
|
+
defaultModelId: string()
|
|
26654
|
+
}),
|
|
26655
|
+
object({
|
|
26656
|
+
kind: literal("unreadable"),
|
|
26657
|
+
reason: _enum(CLUSTER_MODEL_CHOICE_UNREADABLE_REASONS),
|
|
26658
|
+
error: string().optional()
|
|
26659
|
+
})
|
|
26660
|
+
]);
|
|
26133
26661
|
var NodeInferenceDevicesSchema = object({
|
|
26134
26662
|
nodeId: string(),
|
|
26135
26663
|
/** False when the node's platform-probe was unreachable (no live device set). */
|
|
@@ -26157,10 +26685,17 @@ method(StorageMigrationLeaseInputSchema, object({ paused: literal(true) }), {
|
|
|
26157
26685
|
}), object({ success: literal(true) }), {
|
|
26158
26686
|
kind: "mutation",
|
|
26159
26687
|
auth: "admin"
|
|
26160
|
-
}), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({
|
|
26688
|
+
}), method(object({ deviceId: number() }), object({ deviceKey: string().nullable() })), method(_void(), object({
|
|
26689
|
+
migrated: number(),
|
|
26690
|
+
skipped: array(object({
|
|
26691
|
+
deviceId: number(),
|
|
26692
|
+
targetNodeId: string(),
|
|
26693
|
+
reason: string()
|
|
26694
|
+
})).default([])
|
|
26695
|
+
}), {
|
|
26161
26696
|
kind: "mutation",
|
|
26162
26697
|
auth: "admin"
|
|
26163
|
-
}), method(_void(), array(PipelineAssignmentSchema).readonly()), method(object({ deviceId: number() }), PipelineAssignmentSchema.nullable()), method(_void(), array(AgentLoadSummarySchema).readonly()), method(_void(), GlobalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(object({ nodeId: string() }), CapabilityBindingsSchema), method(object({
|
|
26698
|
+
}), method(_void(), array(PipelineAssignmentSchema).readonly()), method(object({ deviceId: number() }), PipelineAssignmentSchema.nullable()), method(_void(), array(AgentLoadSummarySchema).readonly()), method(object({ stepId: string() }), ClusterModelChoiceSchema), method(_void(), GlobalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(object({ nodeId: string() }), CapabilityBindingsSchema), method(object({
|
|
26164
26699
|
nodeId: string(),
|
|
26165
26700
|
capName: string(),
|
|
26166
26701
|
addonId: string()
|
|
@@ -28756,6 +29291,271 @@ var videoclipsCapability = {
|
|
|
28756
29291
|
}
|
|
28757
29292
|
};
|
|
28758
29293
|
/**
|
|
29294
|
+
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
29295
|
+
* valutatore puro del suo FRONTE.
|
|
29296
|
+
*
|
|
29297
|
+
* Viveva dentro il builtin virtual-doorbell
|
|
29298
|
+
* (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
|
|
29299
|
+
* predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
|
|
29300
|
+
* `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
|
|
29301
|
+
* tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
|
|
29302
|
+
* fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
|
|
29303
|
+
* SPOSTA qui e il doorbell la ri-esporta.
|
|
29304
|
+
*
|
|
29305
|
+
* ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
|
|
29306
|
+
* due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
|
|
29307
|
+
* regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
|
|
29308
|
+
* booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
|
|
29309
|
+
*/
|
|
29310
|
+
/**
|
|
29311
|
+
* Known binary / switch source caps → the boolean slice field whose
|
|
29312
|
+
* false→true rise counts as ACTIVE. Every entry is "fire on active".
|
|
29313
|
+
* Sensors whose "active" reading is not a plain boolean (presence's string
|
|
29314
|
+
* state, connectivity's connected flag) are deliberately excluded — a
|
|
29315
|
+
* reconnect is not a doorbell press, and it is not a recording either.
|
|
29316
|
+
*/
|
|
29317
|
+
var SOURCE_CAP_ACTIVE_FIELD = {
|
|
29318
|
+
contact: "entryOpen",
|
|
29319
|
+
binary: "on",
|
|
29320
|
+
switch: "on",
|
|
29321
|
+
motion: "detected",
|
|
29322
|
+
flood: "flooded",
|
|
29323
|
+
gas: "detected",
|
|
29324
|
+
smoke: "detected",
|
|
29325
|
+
"carbon-monoxide": "detected",
|
|
29326
|
+
vibration: "detected",
|
|
29327
|
+
tamper: "tampered"
|
|
29328
|
+
};
|
|
29329
|
+
/**
|
|
29330
|
+
* The same caps → the slice field carrying the ms-epoch timestamp of the
|
|
29331
|
+
* last transition. Every source cap MUST appear here (guarded by a spec):
|
|
29332
|
+
* without a transition timestamp the evaluator cannot tell a genuine rise
|
|
29333
|
+
* from a boot-time hydration when the FIRST slice it ever sees is already
|
|
29334
|
+
* active, and errs towards silence — swallowing the rise.
|
|
29335
|
+
*
|
|
29336
|
+
* These timestamps are UPSTREAM ones, not ingest ones: the Home Assistant
|
|
29337
|
+
* provider derives them from `state.last_changed`, so they survive our own
|
|
29338
|
+
* restarts and correctly read as "hours ago" for a state that has been
|
|
29339
|
+
* active for hours. `motion` names its rise timestamp `lastDetectedAt`.
|
|
29340
|
+
*/
|
|
29341
|
+
var SOURCE_CAP_CHANGED_AT_FIELD = {
|
|
29342
|
+
contact: "lastChangedAt",
|
|
29343
|
+
binary: "lastChangedAt",
|
|
29344
|
+
switch: "lastChangedAt",
|
|
29345
|
+
motion: "lastDetectedAt",
|
|
29346
|
+
flood: "lastChangedAt",
|
|
29347
|
+
gas: "lastChangedAt",
|
|
29348
|
+
smoke: "lastChangedAt",
|
|
29349
|
+
"carbon-monoxide": "lastChangedAt",
|
|
29350
|
+
vibration: "lastChangedAt",
|
|
29351
|
+
tamper: "lastChangedAt"
|
|
29352
|
+
};
|
|
29353
|
+
/** True when a cap is a recognised binary/switch source. */
|
|
29354
|
+
function isSourceCap(capName) {
|
|
29355
|
+
return Object.prototype.hasOwnProperty.call(SOURCE_CAP_ACTIVE_FIELD, capName);
|
|
29356
|
+
}
|
|
29357
|
+
/** Extract the "active" boolean a source cap's slice carries, or null when
|
|
29358
|
+
* the cap is unknown or the field is missing / non-boolean. */
|
|
29359
|
+
function sliceActiveValue(capName, slice) {
|
|
29360
|
+
const field = SOURCE_CAP_ACTIVE_FIELD[capName];
|
|
29361
|
+
if (field === void 0) return null;
|
|
29362
|
+
const raw = slice[field];
|
|
29363
|
+
return typeof raw === "boolean" ? raw : null;
|
|
29364
|
+
}
|
|
29365
|
+
/** Ms-epoch transition timestamp a source cap's slice carries, or null when
|
|
29366
|
+
* it is absent, non-numeric or the zero "never observed" sentinel. */
|
|
29367
|
+
function sliceChangedAt(capName, slice) {
|
|
29368
|
+
const field = SOURCE_CAP_CHANGED_AT_FIELD[capName];
|
|
29369
|
+
if (field === void 0) return null;
|
|
29370
|
+
const raw = slice[field];
|
|
29371
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return null;
|
|
29372
|
+
return raw;
|
|
29373
|
+
}
|
|
29374
|
+
/**
|
|
29375
|
+
* How recent a source's own transition timestamp must be for a FIRST
|
|
29376
|
+
* sighting that is already active to count as a genuine rise rather than a
|
|
29377
|
+
* hydration of long-standing state.
|
|
29378
|
+
*/
|
|
29379
|
+
var DEFAULT_FIRST_SIGHTING_FRESHNESS_MS = 3e4;
|
|
29380
|
+
/**
|
|
29381
|
+
* IL fronte. Puro: nessun orologio proprio, nessuna memoria — il chiamante
|
|
29382
|
+
* porta `prior`, `nowMs` e il proprio `startedAtMs`.
|
|
29383
|
+
*
|
|
29384
|
+
* Il caso della PRIMA slice già attiva è trattato esplicitamente e vale come
|
|
29385
|
+
* fronte solo se il timestamp UPSTREAM della transizione è posteriore a
|
|
29386
|
+
* `startedAtMs` **e** entro `firstSightingFreshnessMs`. Entrambe le metà
|
|
29387
|
+
* servono: la sola freschezza scatterebbe su un'idratazione al boot di uno
|
|
29388
|
+
* stato flippato pochi secondi prima del riavvio, e il solo "dopo che abbiamo
|
|
29389
|
+
* iniziato" scatterebbe, su un processo di lunga vita, per una sorgente
|
|
29390
|
+
* adottata oggi il cui stato è cambiato ieri. Il caso ambiguo ERRA VERSO IL
|
|
29391
|
+
* SILENZIO e lo dichiara (`baseline-seeded-stale-active`).
|
|
29392
|
+
*/
|
|
29393
|
+
function evaluateSensorEdge(input) {
|
|
29394
|
+
const value = sliceActiveValue(input.capName, input.slice);
|
|
29395
|
+
if (value === null) return {
|
|
29396
|
+
edge: "none",
|
|
29397
|
+
value: null,
|
|
29398
|
+
reason: isSourceCap(input.capName) ? "non-boolean-value" : "unknown-cap"
|
|
29399
|
+
};
|
|
29400
|
+
if (input.prior === void 0) {
|
|
29401
|
+
if (!value) return {
|
|
29402
|
+
edge: "none",
|
|
29403
|
+
value,
|
|
29404
|
+
reason: "baseline-seeded-inactive"
|
|
29405
|
+
};
|
|
29406
|
+
const changedAt = sliceChangedAt(input.capName, input.slice);
|
|
29407
|
+
const floor = Math.max(input.startedAtMs, input.nowMs - input.firstSightingFreshnessMs);
|
|
29408
|
+
if (changedAt === null || changedAt < floor) return {
|
|
29409
|
+
edge: "none",
|
|
29410
|
+
value,
|
|
29411
|
+
reason: "baseline-seeded-stale-active"
|
|
29412
|
+
};
|
|
29413
|
+
return {
|
|
29414
|
+
edge: "rising",
|
|
29415
|
+
value: true
|
|
29416
|
+
};
|
|
29417
|
+
}
|
|
29418
|
+
if (input.prior === value) return {
|
|
29419
|
+
edge: "none",
|
|
29420
|
+
value,
|
|
29421
|
+
reason: "no-change"
|
|
29422
|
+
};
|
|
29423
|
+
if (!value) return {
|
|
29424
|
+
edge: "none",
|
|
29425
|
+
value,
|
|
29426
|
+
reason: "falling-edge"
|
|
29427
|
+
};
|
|
29428
|
+
return {
|
|
29429
|
+
edge: "rising",
|
|
29430
|
+
value: true
|
|
29431
|
+
};
|
|
29432
|
+
}
|
|
29433
|
+
object({
|
|
29434
|
+
/** Ms epoch of the last press. null = never observed since this provider started. */
|
|
29435
|
+
lastPressedAt: number().nullable(),
|
|
29436
|
+
/** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
|
|
29437
|
+
pressCountSinceStart: number()
|
|
29438
|
+
});
|
|
29439
|
+
object({
|
|
29440
|
+
deviceId: number(),
|
|
29441
|
+
timestamp: number()
|
|
29442
|
+
});
|
|
29443
|
+
/**
|
|
29444
|
+
* The `doorbell-source` extension slot's editable value (D648): the camera's
|
|
29445
|
+
* virtual-doorbell trigger devices, exactly what the wrapper's
|
|
29446
|
+
* `doorbellSources` persists (as `{ deviceId }` records).
|
|
29447
|
+
*/
|
|
29448
|
+
var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
|
|
29449
|
+
DeviceType.Camera;
|
|
29450
|
+
DeviceType.Button, DeviceType.Camera;
|
|
29451
|
+
var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
|
|
29452
|
+
DeviceType.Camera, DeviceType.Camera;
|
|
29453
|
+
/** A slot's editable value, discriminated on `slot`. */
|
|
29454
|
+
var ExtensionSlotValueSchema = discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
|
|
29455
|
+
DeviceType.Camera;
|
|
29456
|
+
var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
|
|
29457
|
+
state: literal("unreachable"),
|
|
29458
|
+
reason: string()
|
|
29459
|
+
})]);
|
|
29460
|
+
/** One provider of slots — the D554 source-listing row. */
|
|
29461
|
+
var ExtensionSourceSchema = object({
|
|
29462
|
+
/** The routing prefix of every `slotRef` this provider serves. */
|
|
29463
|
+
source: string(),
|
|
29464
|
+
label: string(),
|
|
29465
|
+
/** Stamped by the dispatcher from the registry (D557). */
|
|
29466
|
+
addonId: string().optional(),
|
|
29467
|
+
availability: ExtensionSourceAvailabilitySchema
|
|
29468
|
+
});
|
|
29469
|
+
/**
|
|
29470
|
+
* How one picked source stands. `missing` — no such device (deleted, or an id
|
|
29471
|
+
* that never existed); `foreign` — a device the slot's owner cannot read from
|
|
29472
|
+
* (slice 2's cross-owner graft source); `unavailable` — it exists but is not a
|
|
29473
|
+
* valid source now (a container, which is never a pick). A source in any of
|
|
29474
|
+
* these states is SHOWN, never silently dropped or re-pointed.
|
|
29475
|
+
*/
|
|
29476
|
+
var ExtensionSourceHealthSchema = _enum([
|
|
29477
|
+
"ok",
|
|
29478
|
+
"missing",
|
|
29479
|
+
"foreign",
|
|
29480
|
+
"unavailable"
|
|
29481
|
+
]);
|
|
29482
|
+
var ExtensionSlotSourceSchema = object({
|
|
29483
|
+
deviceId: number().int(),
|
|
29484
|
+
/** The device's name, or null when it could not be resolved (`missing`). */
|
|
29485
|
+
name: string().nullable(),
|
|
29486
|
+
health: ExtensionSourceHealthSchema,
|
|
29487
|
+
detail: string().optional()
|
|
29488
|
+
});
|
|
29489
|
+
/**
|
|
29490
|
+
* A DIFFERENT authority that gates whether the slot's sources do anything —
|
|
29491
|
+
* reported read-only, named, never written through this contract (D62). The
|
|
29492
|
+
* virtual doorbell's sources ring only while the wrapper is bound to the
|
|
29493
|
+
* camera, and that binding belongs to `deviceManager.setWrapperActive`.
|
|
29494
|
+
* `unknown` means the gate could not be read; it is not "inactive" (D315).
|
|
29495
|
+
*/
|
|
29496
|
+
var ExtensionSlotActivationSchema = object({
|
|
29497
|
+
state: _enum([
|
|
29498
|
+
"active",
|
|
29499
|
+
"inactive",
|
|
29500
|
+
"unknown"
|
|
29501
|
+
]),
|
|
29502
|
+
authority: string(),
|
|
29503
|
+
detail: string().optional()
|
|
29504
|
+
});
|
|
29505
|
+
var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
|
|
29506
|
+
state: literal("ok"),
|
|
29507
|
+
/** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
|
|
29508
|
+
slotRef: string(),
|
|
29509
|
+
slot: ExtensionSlotIdSchema,
|
|
29510
|
+
/** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
|
|
29511
|
+
authority: string(),
|
|
29512
|
+
/** graft: its switch. association: null — removing every source is "off". */
|
|
29513
|
+
enabled: boolean().nullable(),
|
|
29514
|
+
/** graft: the cap registered on the host. association: null. */
|
|
29515
|
+
projects: string().nullable(),
|
|
29516
|
+
activation: ExtensionSlotActivationSchema.nullable(),
|
|
29517
|
+
value: ExtensionSlotValueSchema,
|
|
29518
|
+
sources: array(ExtensionSlotSourceSchema)
|
|
29519
|
+
}), object({
|
|
29520
|
+
state: literal("unreachable"),
|
|
29521
|
+
addonId: string(),
|
|
29522
|
+
reason: string()
|
|
29523
|
+
})]);
|
|
29524
|
+
var ExtensionCandidatesSchema = object({ candidates: array(object({
|
|
29525
|
+
deviceId: number().int(),
|
|
29526
|
+
name: string(),
|
|
29527
|
+
type: string(),
|
|
29528
|
+
location: string().nullable(),
|
|
29529
|
+
/** The container a child belongs to — "Spotlight 2" alone does not say which. */
|
|
29530
|
+
parentName: string().nullable(),
|
|
29531
|
+
/** Pre-marked by the owner's suggestion rule. NEVER saved on its own (D356). */
|
|
29532
|
+
suggested: boolean(),
|
|
29533
|
+
/** Why it is suggested ("same location"), null when it is not. */
|
|
29534
|
+
reason: string().nullable()
|
|
29535
|
+
})) });
|
|
29536
|
+
/**
|
|
29537
|
+
* What a write did. `none` — applied in place. `host` / `parent` — the owner
|
|
29538
|
+
* reloaded the host (or, for an accessory child, its parent) because the
|
|
29539
|
+
* write changed what the host REGISTERS (a graft switched on or off, slice 2).
|
|
29540
|
+
*/
|
|
29541
|
+
var ExtensionSetResultSchema = object({
|
|
29542
|
+
applied: literal(true),
|
|
29543
|
+
reload: _enum([
|
|
29544
|
+
"none",
|
|
29545
|
+
"host",
|
|
29546
|
+
"parent"
|
|
29547
|
+
])
|
|
29548
|
+
});
|
|
29549
|
+
var ExtensionSlotRefInputSchema = object({
|
|
29550
|
+
deviceId: number().int(),
|
|
29551
|
+
slotRef: string().min(1)
|
|
29552
|
+
});
|
|
29553
|
+
var ExtensionSetSlotInputSchema = ExtensionSlotRefInputSchema.extend({ value: ExtensionSlotValueSchema });
|
|
29554
|
+
method(object({ deviceId: number().int() }), array(ExtensionSourceSchema)), method(object({ deviceId: number().int() }), array(ExtensionSlotRowSchema)), method(ExtensionSlotRefInputSchema, ExtensionCandidatesSchema), method(ExtensionSetSlotInputSchema, ExtensionSetResultSchema, {
|
|
29555
|
+
kind: "mutation",
|
|
29556
|
+
auth: "admin"
|
|
29557
|
+
});
|
|
29558
|
+
/**
|
|
28759
29559
|
* Optional client-side hints sent at session creation to help the provider
|
|
28760
29560
|
* pick the best native source. All fields optional — a viewer that knows
|
|
28761
29561
|
* nothing still gets a sane default. (Relocated from the retired `webrtc`
|
|
@@ -30708,17 +31508,6 @@ object({
|
|
|
30708
31508
|
deviceId: number(),
|
|
30709
31509
|
status: DeviceStatusSchema
|
|
30710
31510
|
});
|
|
30711
|
-
object({
|
|
30712
|
-
/** Ms epoch of the last press. null = never observed since this provider started. */
|
|
30713
|
-
lastPressedAt: number().nullable(),
|
|
30714
|
-
/** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
|
|
30715
|
-
pressCountSinceStart: number()
|
|
30716
|
-
});
|
|
30717
|
-
object({
|
|
30718
|
-
deviceId: number(),
|
|
30719
|
-
timestamp: number()
|
|
30720
|
-
});
|
|
30721
|
-
DeviceType.Button, DeviceType.Camera;
|
|
30722
31511
|
/**
|
|
30723
31512
|
* Enum-state sensor — a string value picked from a finite option set.
|
|
30724
31513
|
* Drives HA `sensor` entries with `state_class: enum` (HVAC action
|
|
@@ -38029,146 +38818,6 @@ DeviceType.Camera, method(object({
|
|
|
38029
38818
|
detection: array(ZoneRuleSchema).readonly(),
|
|
38030
38819
|
package: array(ZoneRuleSchema).readonly()
|
|
38031
38820
|
});
|
|
38032
|
-
/**
|
|
38033
|
-
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
38034
|
-
* valutatore puro del suo FRONTE.
|
|
38035
|
-
*
|
|
38036
|
-
* Viveva dentro il builtin virtual-doorbell
|
|
38037
|
-
* (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
|
|
38038
|
-
* predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
|
|
38039
|
-
* `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
|
|
38040
|
-
* tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
|
|
38041
|
-
* fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
|
|
38042
|
-
* SPOSTA qui e il doorbell la ri-esporta.
|
|
38043
|
-
*
|
|
38044
|
-
* ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
|
|
38045
|
-
* due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
|
|
38046
|
-
* regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
|
|
38047
|
-
* booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
|
|
38048
|
-
*/
|
|
38049
|
-
/**
|
|
38050
|
-
* Known binary / switch source caps → the boolean slice field whose
|
|
38051
|
-
* false→true rise counts as ACTIVE. Every entry is "fire on active".
|
|
38052
|
-
* Sensors whose "active" reading is not a plain boolean (presence's string
|
|
38053
|
-
* state, connectivity's connected flag) are deliberately excluded — a
|
|
38054
|
-
* reconnect is not a doorbell press, and it is not a recording either.
|
|
38055
|
-
*/
|
|
38056
|
-
var SOURCE_CAP_ACTIVE_FIELD = {
|
|
38057
|
-
contact: "entryOpen",
|
|
38058
|
-
binary: "on",
|
|
38059
|
-
switch: "on",
|
|
38060
|
-
motion: "detected",
|
|
38061
|
-
flood: "flooded",
|
|
38062
|
-
gas: "detected",
|
|
38063
|
-
smoke: "detected",
|
|
38064
|
-
"carbon-monoxide": "detected",
|
|
38065
|
-
vibration: "detected",
|
|
38066
|
-
tamper: "tampered"
|
|
38067
|
-
};
|
|
38068
|
-
/**
|
|
38069
|
-
* The same caps → the slice field carrying the ms-epoch timestamp of the
|
|
38070
|
-
* last transition. Every source cap MUST appear here (guarded by a spec):
|
|
38071
|
-
* without a transition timestamp the evaluator cannot tell a genuine rise
|
|
38072
|
-
* from a boot-time hydration when the FIRST slice it ever sees is already
|
|
38073
|
-
* active, and errs towards silence — swallowing the rise.
|
|
38074
|
-
*
|
|
38075
|
-
* These timestamps are UPSTREAM ones, not ingest ones: the Home Assistant
|
|
38076
|
-
* provider derives them from `state.last_changed`, so they survive our own
|
|
38077
|
-
* restarts and correctly read as "hours ago" for a state that has been
|
|
38078
|
-
* active for hours. `motion` names its rise timestamp `lastDetectedAt`.
|
|
38079
|
-
*/
|
|
38080
|
-
var SOURCE_CAP_CHANGED_AT_FIELD = {
|
|
38081
|
-
contact: "lastChangedAt",
|
|
38082
|
-
binary: "lastChangedAt",
|
|
38083
|
-
switch: "lastChangedAt",
|
|
38084
|
-
motion: "lastDetectedAt",
|
|
38085
|
-
flood: "lastChangedAt",
|
|
38086
|
-
gas: "lastChangedAt",
|
|
38087
|
-
smoke: "lastChangedAt",
|
|
38088
|
-
"carbon-monoxide": "lastChangedAt",
|
|
38089
|
-
vibration: "lastChangedAt",
|
|
38090
|
-
tamper: "lastChangedAt"
|
|
38091
|
-
};
|
|
38092
|
-
/** True when a cap is a recognised binary/switch source. */
|
|
38093
|
-
function isSourceCap(capName) {
|
|
38094
|
-
return Object.prototype.hasOwnProperty.call(SOURCE_CAP_ACTIVE_FIELD, capName);
|
|
38095
|
-
}
|
|
38096
|
-
/** Extract the "active" boolean a source cap's slice carries, or null when
|
|
38097
|
-
* the cap is unknown or the field is missing / non-boolean. */
|
|
38098
|
-
function sliceActiveValue(capName, slice) {
|
|
38099
|
-
const field = SOURCE_CAP_ACTIVE_FIELD[capName];
|
|
38100
|
-
if (field === void 0) return null;
|
|
38101
|
-
const raw = slice[field];
|
|
38102
|
-
return typeof raw === "boolean" ? raw : null;
|
|
38103
|
-
}
|
|
38104
|
-
/** Ms-epoch transition timestamp a source cap's slice carries, or null when
|
|
38105
|
-
* it is absent, non-numeric or the zero "never observed" sentinel. */
|
|
38106
|
-
function sliceChangedAt(capName, slice) {
|
|
38107
|
-
const field = SOURCE_CAP_CHANGED_AT_FIELD[capName];
|
|
38108
|
-
if (field === void 0) return null;
|
|
38109
|
-
const raw = slice[field];
|
|
38110
|
-
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return null;
|
|
38111
|
-
return raw;
|
|
38112
|
-
}
|
|
38113
|
-
/**
|
|
38114
|
-
* How recent a source's own transition timestamp must be for a FIRST
|
|
38115
|
-
* sighting that is already active to count as a genuine rise rather than a
|
|
38116
|
-
* hydration of long-standing state.
|
|
38117
|
-
*/
|
|
38118
|
-
var DEFAULT_FIRST_SIGHTING_FRESHNESS_MS = 3e4;
|
|
38119
|
-
/**
|
|
38120
|
-
* IL fronte. Puro: nessun orologio proprio, nessuna memoria — il chiamante
|
|
38121
|
-
* porta `prior`, `nowMs` e il proprio `startedAtMs`.
|
|
38122
|
-
*
|
|
38123
|
-
* Il caso della PRIMA slice già attiva è trattato esplicitamente e vale come
|
|
38124
|
-
* fronte solo se il timestamp UPSTREAM della transizione è posteriore a
|
|
38125
|
-
* `startedAtMs` **e** entro `firstSightingFreshnessMs`. Entrambe le metà
|
|
38126
|
-
* servono: la sola freschezza scatterebbe su un'idratazione al boot di uno
|
|
38127
|
-
* stato flippato pochi secondi prima del riavvio, e il solo "dopo che abbiamo
|
|
38128
|
-
* iniziato" scatterebbe, su un processo di lunga vita, per una sorgente
|
|
38129
|
-
* adottata oggi il cui stato è cambiato ieri. Il caso ambiguo ERRA VERSO IL
|
|
38130
|
-
* SILENZIO e lo dichiara (`baseline-seeded-stale-active`).
|
|
38131
|
-
*/
|
|
38132
|
-
function evaluateSensorEdge(input) {
|
|
38133
|
-
const value = sliceActiveValue(input.capName, input.slice);
|
|
38134
|
-
if (value === null) return {
|
|
38135
|
-
edge: "none",
|
|
38136
|
-
value: null,
|
|
38137
|
-
reason: isSourceCap(input.capName) ? "non-boolean-value" : "unknown-cap"
|
|
38138
|
-
};
|
|
38139
|
-
if (input.prior === void 0) {
|
|
38140
|
-
if (!value) return {
|
|
38141
|
-
edge: "none",
|
|
38142
|
-
value,
|
|
38143
|
-
reason: "baseline-seeded-inactive"
|
|
38144
|
-
};
|
|
38145
|
-
const changedAt = sliceChangedAt(input.capName, input.slice);
|
|
38146
|
-
const floor = Math.max(input.startedAtMs, input.nowMs - input.firstSightingFreshnessMs);
|
|
38147
|
-
if (changedAt === null || changedAt < floor) return {
|
|
38148
|
-
edge: "none",
|
|
38149
|
-
value,
|
|
38150
|
-
reason: "baseline-seeded-stale-active"
|
|
38151
|
-
};
|
|
38152
|
-
return {
|
|
38153
|
-
edge: "rising",
|
|
38154
|
-
value: true
|
|
38155
|
-
};
|
|
38156
|
-
}
|
|
38157
|
-
if (input.prior === value) return {
|
|
38158
|
-
edge: "none",
|
|
38159
|
-
value,
|
|
38160
|
-
reason: "no-change"
|
|
38161
|
-
};
|
|
38162
|
-
if (!value) return {
|
|
38163
|
-
edge: "none",
|
|
38164
|
-
value,
|
|
38165
|
-
reason: "falling-edge"
|
|
38166
|
-
};
|
|
38167
|
-
return {
|
|
38168
|
-
edge: "rising",
|
|
38169
|
-
value: true
|
|
38170
|
-
};
|
|
38171
|
-
}
|
|
38172
38821
|
var HF_BASE_URL = `https://huggingface.co/camstack/camstack-models/resolve/main`;
|
|
38173
38822
|
/**
|
|
38174
38823
|
* Accessory device helpers — shared across drivers.
|
|
@@ -40988,6 +41637,30 @@ Object.freeze({
|
|
|
40988
41637
|
addonId: null,
|
|
40989
41638
|
access: "create"
|
|
40990
41639
|
},
|
|
41640
|
+
"deviceExtension.listCandidates": {
|
|
41641
|
+
capName: "device-extension",
|
|
41642
|
+
capScope: "device",
|
|
41643
|
+
addonId: null,
|
|
41644
|
+
access: "view"
|
|
41645
|
+
},
|
|
41646
|
+
"deviceExtension.listSlots": {
|
|
41647
|
+
capName: "device-extension",
|
|
41648
|
+
capScope: "device",
|
|
41649
|
+
addonId: null,
|
|
41650
|
+
access: "view"
|
|
41651
|
+
},
|
|
41652
|
+
"deviceExtension.listSources": {
|
|
41653
|
+
capName: "device-extension",
|
|
41654
|
+
capScope: "device",
|
|
41655
|
+
addonId: null,
|
|
41656
|
+
access: "view"
|
|
41657
|
+
},
|
|
41658
|
+
"deviceExtension.setSlot": {
|
|
41659
|
+
capName: "device-extension",
|
|
41660
|
+
capScope: "device",
|
|
41661
|
+
addonId: null,
|
|
41662
|
+
access: "create"
|
|
41663
|
+
},
|
|
40991
41664
|
"deviceManager.addLocation": {
|
|
40992
41665
|
capName: "device-manager",
|
|
40993
41666
|
capScope: "system",
|
|
@@ -41738,6 +42411,12 @@ Object.freeze({
|
|
|
41738
42411
|
addonId: null,
|
|
41739
42412
|
access: "view"
|
|
41740
42413
|
},
|
|
42414
|
+
"embeddingEncoder.resetFailedModels": {
|
|
42415
|
+
capName: "embedding-encoder",
|
|
42416
|
+
capScope: "system",
|
|
42417
|
+
addonId: null,
|
|
42418
|
+
access: "delete"
|
|
42419
|
+
},
|
|
41741
42420
|
"enumSensor.getStatus": {
|
|
41742
42421
|
capName: "enum-sensor",
|
|
41743
42422
|
capScope: "device",
|
|
@@ -44072,6 +44751,12 @@ Object.freeze({
|
|
|
44072
44751
|
addonId: null,
|
|
44073
44752
|
access: "view"
|
|
44074
44753
|
},
|
|
44754
|
+
"pipelineOrchestrator.getClusterModelChoice": {
|
|
44755
|
+
capName: "pipeline-orchestrator",
|
|
44756
|
+
capScope: "system",
|
|
44757
|
+
addonId: null,
|
|
44758
|
+
access: "view"
|
|
44759
|
+
},
|
|
44075
44760
|
"pipelineOrchestrator.getDeviceLiveContribution": {
|
|
44076
44761
|
capName: "pipeline-orchestrator",
|
|
44077
44762
|
capScope: "system",
|
|
@@ -46897,6 +47582,26 @@ Object.freeze({
|
|
|
46897
47582
|
form: "single",
|
|
46898
47583
|
optional: false
|
|
46899
47584
|
}],
|
|
47585
|
+
"deviceExtension.listCandidates": [{
|
|
47586
|
+
name: "deviceId",
|
|
47587
|
+
form: "single",
|
|
47588
|
+
optional: false
|
|
47589
|
+
}],
|
|
47590
|
+
"deviceExtension.listSlots": [{
|
|
47591
|
+
name: "deviceId",
|
|
47592
|
+
form: "single",
|
|
47593
|
+
optional: false
|
|
47594
|
+
}],
|
|
47595
|
+
"deviceExtension.listSources": [{
|
|
47596
|
+
name: "deviceId",
|
|
47597
|
+
form: "single",
|
|
47598
|
+
optional: false
|
|
47599
|
+
}],
|
|
47600
|
+
"deviceExtension.setSlot": [{
|
|
47601
|
+
name: "deviceId",
|
|
47602
|
+
form: "single",
|
|
47603
|
+
optional: false
|
|
47604
|
+
}],
|
|
46900
47605
|
"deviceManager.adoptionRelease": [{
|
|
46901
47606
|
name: "camDeviceId",
|
|
46902
47607
|
form: "single",
|
|
@@ -49406,205 +50111,6 @@ TimelapseRuleInputSchema.extend({
|
|
|
49406
50111
|
createdAt: number(),
|
|
49407
50112
|
updatedAt: number()
|
|
49408
50113
|
});
|
|
49409
|
-
/**
|
|
49410
|
-
* A model choice has a SCOPE, and a scope change has a workflow.
|
|
49411
|
-
*
|
|
49412
|
-
* ## The two scopes
|
|
49413
|
-
*
|
|
49414
|
-
* Almost every pipeline step's model is a **node** question: which build runs
|
|
49415
|
-
* fastest on THIS accelerator. Two nodes may legitimately answer differently —
|
|
49416
|
-
* a Coral runs the tflite SSD, a Mac runs the coreml YOLO — because the answers
|
|
49417
|
-
* are compared against nothing. They produce boxes, and a box is a box.
|
|
49418
|
-
*
|
|
49419
|
-
* Two steps are not like that. `face-embedding` and `clip-embedding` produce
|
|
49420
|
-
* **vectors that land in ONE shared index** (`identity_samples`,
|
|
49421
|
-
* `vec_object_clip`) and are ranked against each other by cosine similarity.
|
|
49422
|
-
* Cosine similarity between two encoders is not a weak signal, it is a
|
|
49423
|
-
* meaningless number — and nothing inside a vector can tell you it happened.
|
|
49424
|
-
* So for those two steps the model is a **cluster** question with exactly one
|
|
49425
|
-
* answer, stored in exactly one row.
|
|
49426
|
-
*
|
|
49427
|
-
* That is the same argument [D52](../../../../docs/decisions/adr-0052.md) makes
|
|
49428
|
-
* for the crop convention (`detail-crop.ts`), one level up: the crop decides
|
|
49429
|
-
* WHAT pixels the encoder sees, this decides WHICH encoder sees them. Both are
|
|
49430
|
-
* cluster-wide for the same reason and neither may become per-node.
|
|
49431
|
-
*
|
|
49432
|
-
* ## Why the registry lives here and not in the step catalog
|
|
49433
|
-
*
|
|
49434
|
-
* The scope itself is declared on the step, in
|
|
49435
|
-
* `detection-pipeline/registry/step-definitions.ts` (`StepDefinition.modelScope`)
|
|
49436
|
-
* — that is where a new step declares what kind of choice its model is. But the
|
|
49437
|
-
* catalog lives inside `addon-pipeline`, and three packages that must NOT
|
|
49438
|
-
* import an addon need to know the set: the orchestrator (which renders and
|
|
49439
|
-
* stores the row), post-analysis (which pins the row for a re-embed pass) and
|
|
49440
|
-
* admin-ui (which mirrors it). So the set is mirrored here, and
|
|
49441
|
-
* `scripts/check-cluster-model-scope.ts` fails the build when the two disagree.
|
|
49442
|
-
* A hand-copied key that stops matching is exactly how a setting silently stops
|
|
49443
|
-
* arriving while both sides still look correct.
|
|
49444
|
-
*
|
|
49445
|
-
* ## The defaults ARE today's behaviour
|
|
49446
|
-
*
|
|
49447
|
-
* {@link DEFAULT_CLUSTER_STEP_MODELS} reproduces the catalog `defaultModelId`
|
|
49448
|
-
* of both steps. Declaring the scope therefore changes nothing on the day it
|
|
49449
|
-
* ships: the cluster row starts out saying what every node was already doing.
|
|
49450
|
-
* Changing it is legitimate — that is what the knob is for — but it invalidates
|
|
49451
|
-
* the stored vectors of that surface, and must be followed by a re-embed pass
|
|
49452
|
-
* (`face-reembed-pass.ts`, `embedding-rebuild.ts`).
|
|
49453
|
-
*/
|
|
49454
|
-
/**
|
|
49455
|
-
* The cluster-scoped steps. Mirrored from the catalog's `modelScope: 'cluster'`
|
|
49456
|
-
* declarations and guarded by `scripts/check-cluster-model-scope.ts`.
|
|
49457
|
-
*/
|
|
49458
|
-
var CLUSTER_MODEL_SCOPED_STEPS = [{
|
|
49459
|
-
stepId: "face-embedding",
|
|
49460
|
-
label: "Face recognition model",
|
|
49461
|
-
defaultModelId: "arcface-r100",
|
|
49462
|
-
indexName: "the enrolled face gallery",
|
|
49463
|
-
options: [
|
|
49464
|
-
{
|
|
49465
|
-
id: "arcface-r100",
|
|
49466
|
-
label: "ArcFace (ResNet34)"
|
|
49467
|
-
},
|
|
49468
|
-
{
|
|
49469
|
-
id: "auraface-r100",
|
|
49470
|
-
label: "AuraFace R100"
|
|
49471
|
-
},
|
|
49472
|
-
{
|
|
49473
|
-
id: "inception-resnet-v1",
|
|
49474
|
-
label: "Inception ResNet V1"
|
|
49475
|
-
}
|
|
49476
|
-
]
|
|
49477
|
-
}, {
|
|
49478
|
-
stepId: "clip-embedding",
|
|
49479
|
-
label: "Semantic search model",
|
|
49480
|
-
defaultModelId: "mobileclip-s1",
|
|
49481
|
-
indexName: "the object semantic-search index",
|
|
49482
|
-
options: [{
|
|
49483
|
-
id: "mobileclip-s1",
|
|
49484
|
-
label: "MobileCLIP S1"
|
|
49485
|
-
}, {
|
|
49486
|
-
id: "mobileclip-s2",
|
|
49487
|
-
label: "MobileCLIP S2"
|
|
49488
|
-
}]
|
|
49489
|
-
}];
|
|
49490
|
-
/**
|
|
49491
|
-
* The settings key holding one step's cluster model choice.
|
|
49492
|
-
*
|
|
49493
|
-
* Step-qualified so the keys stay unique across the addon's whole schema, which
|
|
49494
|
-
* is what lets {@link pickClusterStepModels} walk every section instead of
|
|
49495
|
-
* trusting the section id.
|
|
49496
|
-
*/
|
|
49497
|
-
function clusterModelSettingKey(stepId) {
|
|
49498
|
-
return `clusterModel:${stepId}`;
|
|
49499
|
-
}
|
|
49500
|
-
/** The cluster row when nobody has configured one — today's catalog defaults. */
|
|
49501
|
-
var DEFAULT_CLUSTER_STEP_MODELS = Object.freeze(Object.fromEntries(CLUSTER_MODEL_SCOPED_STEPS.map((s) => [s.stepId, s.defaultModelId])));
|
|
49502
|
-
/** A stored choice is a non-empty model id, or it is not a choice. */
|
|
49503
|
-
var ChosenModelIdSchema = string().min(1);
|
|
49504
|
-
/**
|
|
49505
|
-
* Narrow a FLAT settings record to the cluster row.
|
|
49506
|
-
*
|
|
49507
|
-
* Per-FIELD fallback, deliberately (same rule as `readDetailCropConvention`): a
|
|
49508
|
-
* junk face model must not also discard a valid clip model. An absent, empty or
|
|
49509
|
-
* non-string value resolves to the step's catalog default — the historical
|
|
49510
|
-
* behaviour — never to a blank id, because a blank id downstream becomes
|
|
49511
|
-
* "substitute the format default", which is precisely the substitution this
|
|
49512
|
-
* scope exists to forbid.
|
|
49513
|
-
*/
|
|
49514
|
-
function readClusterStepModels(config) {
|
|
49515
|
-
const out = {};
|
|
49516
|
-
for (const step of CLUSTER_MODEL_SCOPED_STEPS) {
|
|
49517
|
-
const parsed = ChosenModelIdSchema.safeParse(config[clusterModelSettingKey(step.stepId)]);
|
|
49518
|
-
out[step.stepId] = parsed.success ? parsed.data : step.defaultModelId;
|
|
49519
|
-
}
|
|
49520
|
-
return out;
|
|
49521
|
-
}
|
|
49522
|
-
function isHydratedField$2(entry) {
|
|
49523
|
-
return typeof entry === "object" && entry !== null && "key" in entry;
|
|
49524
|
-
}
|
|
49525
|
-
/**
|
|
49526
|
-
* Extract the cluster row from an `addon-settings.getGlobalSettings` payload.
|
|
49527
|
-
*
|
|
49528
|
-
* Walks EVERY section rather than looking inside {@link CLUSTER_MODEL_SECTION_ID}
|
|
49529
|
-
* alone: the keys are unique across the addon's schema, and a section rename
|
|
49530
|
-
* must not silently revert the whole cluster to the defaults. A `null` payload
|
|
49531
|
-
* (addon mid-boot) is the defaults.
|
|
49532
|
-
*/
|
|
49533
|
-
function pickClusterStepModels(view) {
|
|
49534
|
-
if (view === null) return DEFAULT_CLUSTER_STEP_MODELS;
|
|
49535
|
-
const flat = {};
|
|
49536
|
-
const wanted = new Set(CLUSTER_MODEL_SCOPED_STEPS.map((s) => clusterModelSettingKey(s.stepId)));
|
|
49537
|
-
for (const section of view.sections) for (const entry of section.fields) {
|
|
49538
|
-
if (!isHydratedField$2(entry) || typeof entry.key !== "string") continue;
|
|
49539
|
-
if (wanted.has(entry.key)) flat[entry.key] = entry.value;
|
|
49540
|
-
}
|
|
49541
|
-
return readClusterStepModels(flat);
|
|
49542
|
-
}
|
|
49543
|
-
/**
|
|
49544
|
-
* The cluster model for `stepId`, or `null` when the step is node-scoped.
|
|
49545
|
-
*
|
|
49546
|
-
* `null` is the signal to LEAVE THE CALLER'S RESOLUTION ALONE — it is not "no
|
|
49547
|
-
* model". Returning a default here for a node-scoped step would quietly
|
|
49548
|
-
* override every per-(node,device) selection in the fleet.
|
|
49549
|
-
*/
|
|
49550
|
-
function resolveClusterStepModelId(stepId, models) {
|
|
49551
|
-
const step = CLUSTER_MODEL_SCOPED_STEPS.find((s) => s.stepId === stepId);
|
|
49552
|
-
if (step === void 0) return null;
|
|
49553
|
-
const chosen = models[stepId];
|
|
49554
|
-
return chosen !== void 0 && chosen !== "" ? chosen : step.defaultModelId;
|
|
49555
|
-
}
|
|
49556
|
-
var CLUSTER_STEP_SETTING_FIELDS = [{
|
|
49557
|
-
stepId: "face-embedding",
|
|
49558
|
-
key: "minLandmarkFaceSize",
|
|
49559
|
-
label: "Min face size for recognition (detection px)",
|
|
49560
|
-
description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
|
|
49561
|
-
type: "slider",
|
|
49562
|
-
min: 0,
|
|
49563
|
-
max: 64,
|
|
49564
|
-
step: 2,
|
|
49565
|
-
default: 24
|
|
49566
|
-
}];
|
|
49567
|
-
function clusterStepSettingKey(stepId, fieldKey) {
|
|
49568
|
-
return `clusterStepSetting:${stepId}:${fieldKey}`;
|
|
49569
|
-
}
|
|
49570
|
-
var ClusterSettingNumberSchema = number().finite();
|
|
49571
|
-
function readClusterStepSettings(config) {
|
|
49572
|
-
const out = {};
|
|
49573
|
-
for (const field of CLUSTER_STEP_SETTING_FIELDS) {
|
|
49574
|
-
const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
|
|
49575
|
-
const value = parsed.success ? parsed.data : field.default;
|
|
49576
|
-
const existing = out[field.stepId] ?? {};
|
|
49577
|
-
out[field.stepId] = {
|
|
49578
|
-
...existing,
|
|
49579
|
-
[field.key]: value
|
|
49580
|
-
};
|
|
49581
|
-
}
|
|
49582
|
-
return out;
|
|
49583
|
-
}
|
|
49584
|
-
var DEFAULT_CLUSTER_STEP_SETTINGS = readClusterStepSettings({});
|
|
49585
|
-
function pickClusterStepSettings(view) {
|
|
49586
|
-
if (view === null) return DEFAULT_CLUSTER_STEP_SETTINGS;
|
|
49587
|
-
const flat = {};
|
|
49588
|
-
const wanted = new Set(CLUSTER_STEP_SETTING_FIELDS.map((field) => clusterStepSettingKey(field.stepId, field.key)));
|
|
49589
|
-
for (const section of view.sections) for (const entry of section.fields) {
|
|
49590
|
-
if (!isHydratedField$2(entry) || typeof entry.key !== "string") continue;
|
|
49591
|
-
if (wanted.has(entry.key)) flat[entry.key] = entry.value;
|
|
49592
|
-
}
|
|
49593
|
-
return readClusterStepSettings(flat);
|
|
49594
|
-
}
|
|
49595
|
-
/**
|
|
49596
|
-
* Cluster knobs win over leftover per-device values. The cluster row is the
|
|
49597
|
-
* authority; a device-local copy is a leftover of the old per-node editor.
|
|
49598
|
-
*/
|
|
49599
|
-
function overlayClusterStepSettings(stepId, deviceSettings, cluster) {
|
|
49600
|
-
const overlay = cluster[stepId];
|
|
49601
|
-
if (overlay === void 0 || Object.keys(overlay).length === 0) return deviceSettings === void 0 ? void 0 : { ...deviceSettings };
|
|
49602
|
-
if (deviceSettings === void 0) return { ...overlay };
|
|
49603
|
-
return {
|
|
49604
|
-
...deviceSettings,
|
|
49605
|
-
...overlay
|
|
49606
|
-
};
|
|
49607
|
-
}
|
|
49608
50114
|
var DETAIL_CROP_PADDING_KEY = "detailCropPaddingRatio";
|
|
49609
50115
|
var DETAIL_CROP_SQUARE_KEY = "detailCropSquare";
|
|
49610
50116
|
/**
|
|
@@ -50805,4 +51311,4 @@ function enumerateInferenceDevices(hw) {
|
|
|
50805
51311
|
return out;
|
|
50806
51312
|
}
|
|
50807
51313
|
//#endregion
|
|
50808
|
-
export { declareLogChannel as $,
|
|
51314
|
+
export { declareLogChannel as $, videoclipsCapability as $t, OpsLogEntrySchema as A, discriminatedUnion as An, pickClusterStepSettings as At, VIDEOCLIPS_MAX_OFFER_BYTES as B, recordingSignalCapability as Bt, ExportRecordSchema as C, parseJsonUnknown as Cn, mayWriteToLocation as Ct, MOTION_CLOSE_AFTER_MS as D, _enum as Dn, overlayClusterStepSettings as Dt, HF_BASE_URL as E, sleep as En, motionDetectionCapability as Et, RECORDING_SOURCE_CAMSTACK as F, preprocess as Fn, pipelineRunnerCapability as Ft, audioAnalyzerCapability as G, resolvePoolMemoryPolicy as Gt, YAMNET_TO_MACRO as H, resolveCreateExportSubject as Ht, RECORDING_SOURCE_CAMSTACK_ADDON as I, record as In, rankStreams as It, cameraStreamsCapability as J, storageEvictableCapability as Jt, audioKindId as K, resolveRecordingProfiles as Kt, RecordingConfigSchema as L, string as Ln, recordingArchiveCapability as Lt, RATE_CONTROL_RELAXED as M, literal as Mn, pickInvalidClusterStepModels as Mt, RATE_CONTROL_TIGHT as N, number as Nn, pickNativeLeaseOverride as Nt, NativeLeaseAdmissionSchema as O, array as On, parseProcStatus as Ot, RECORDING_EXPORT_MAX_READ_BYTES as P, object as Pn, pipelineExecutorCapability as Pt, createLogChannelsProvider as Q, supportedRuntimes as Qt, RecordingSignalStatusSchema as R, union as Rn, recordingCapability as Rt, EncodeProfileSchema as S, nodePin as Sn, mayReadLocation as St, G711_SCALE_CORRECTION_DB as T, selectAssignedProfileSlots as Tn, modeMayWrite as Tt, addonWidgetsSourceCapability as U, resolveEgressDecodeHwAccel as Ut, VISIT_MERGE_GAP_MS as V, resolveClusterStepModelId as Vt, audioAnalysisCapability as W, resolveLocationMode as Wt, clipExportFailureOf as X, streamBrokerCapability as Xt, clipExportFailure as Y, storageOccupancyCapability as Yt, createHwAccelCache as Z, stripRetiredBandPreBufferSec as Zt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as _, expandAudioChunkToF32le as _n, isFirstLevelMacroClass as _t, BatteryStatusSchema as a, invocationFromEncodeProfile as an, egressTranscodeSharingKey as at, EVENT_OWNER_TYPES as b, makeProfileBrokerId as bn, mapAudioLabelToMacro as bt, CLIP_STILL_REASON_HEADER as c, BaseAddon as cn, evaluateSensorEdge as ct, COCO_TO_MACRO as d, DeviceFeature as dn, evictionPolicyOfLocation as dt, webrtcSessionCapability as en, defaultDeviceFor as et, DEFAULT_AUDIO_ANALYZER_CONFIG as f, DeviceType as fn, exportSubjectOf as ft, DEFAULT_EVENTS_BAND_BUFFER_SEC as g, defineCustomActions as gn, inferModelProvider as gt, DEFAULT_DETAIL_CROP_CONVENTION as h, customAction as hn, hfModelUrl as ht, BackgroundRunRegistry as i, buildFfmpegArgs as in, detectionPipelineCapability as it, PoolMemoryWatchdog as j, lazy as jn, pickDetailCropConvention as jt, NativeLeaseSettingsSchema as k, boolean as kn, pickClusterStepModels as kt, CLIP_STREAM_TRANSPORT_HEADER as l, CAM_PROFILE_ORDER as ln, evaluateZoneRules as lt, DEFAULT_CLUSTER_STEP_SETTINGS as m, createEvent as mn, gbToBytes as mt, AUDIO_BACKEND_CHOICES as n, AUDIO_PRESETS as nn, deriveDetailCropRect as nt, CLIP_BROKER_PACED_RATES as o, isSoftwareDecode as on, egressTransportFromRequest as ot, DEFAULT_CLUSTER_STEP_MODELS as p, audioChunkBytesPerSample as pn, failureContributionCapability as pt, batteryCapability as q, runtimeDevices as qt, AUDIO_MACRO_LABELS as r, Fmp4BoxSplitter as rn, deriveRecordingMode as rt, CLIP_STILL_REASON_CODE_HEADER as s, BOOT_RECOVERY_BACKOFF_MS as sn, enumerateInferenceDevices as st, APPLE_SA_TO_MACRO as t, errMsg as tn, deriveBatteryPresence as tt, COCO_80_LABELS as u, CamProfileSchema as un, evictionPolicyForMode as ut, DEFAULT_NATIVE_LEASE_SETTINGS as v, hydrateSchema as vn, loadContributionCapability as vt, FailureCounters as w, parseProfileBrokerId as wn, migrateLegacyDeviceSignalConfig as wt, EVENT_PAD_MS as x, makeSourceBrokerId as xn, maskUrlCredentials as xt, DEVICE_BACKEND_TO_FORMAT as y, isEvent as yn, logChannelsCapability as yt, RingBuffer as z, EventCategory as zn, recordingExportCapability as zt };
|