@camstack/addon-pipeline 1.2.115 → 1.2.119
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/{addon-utils-C0ht2KPO.js → addon-utils-RAHF2hmW.js} +1 -1
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +171 -167
- package/dist/detection-pipeline/index.mjs +169 -165
- package/dist/{dist-BqIbYW1A.js → dist-DIsM42Ex.js} +121 -11
- package/dist/{dist-DGQkkWc6.mjs → dist-Db664g2O.mjs} +116 -12
- package/dist/{event-loop-stall-monitor-ylMGpz48.js → event-loop-stall-monitor-CSpdFZb6.js} +1 -1
- package/dist/{event-loop-stall-monitor-D5jK5v4Z.mjs → event-loop-stall-monitor-DY6339bX.mjs} +1 -1
- package/dist/{lazy-sharp-cOsy7l_P.js → lazy-sharp-LSlEXQe_.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +12 -14
- package/dist/pipeline-runner/index.mjs +11 -13
- package/dist/{process-memory-BtT7WfbJ.mjs → process-memory-CPm5TMBl.mjs} +1 -1
- package/dist/{process-memory-45juuHpN.js → process-memory-DpSJQF-p.js} +1 -1
- package/dist/recorder/index.js +261 -32
- package/dist/recorder/index.mjs +260 -31
- package/dist/session-decode/decode-worker-child.js +34 -26
- package/dist/session-decode/decode-worker-child.mjs +33 -25
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_SFFNGT.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CstvrzmK.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BiNja8Hw.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-BAMyzCWw.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D-NFPV_g.mjs} +1 -1
- package/dist/stream-broker/{hostInit-CG28NvlV.mjs → hostInit-kHeLqEod.mjs} +2 -2
- package/dist/stream-broker/index.js +8 -6
- package/dist/stream-broker/index.mjs +8 -6
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-CIrqGbpG.js → worker-protocol-C1tr8WP3.js} +1 -1
- package/dist/{worker-protocol-DyocTIx_.mjs → worker-protocol-_zMPqSas.mjs} +1 -1
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B1LDFHY9.mjs +0 -26
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as __require } from "../chunk-DnnnRqeS.mjs";
|
|
2
|
-
import { $ as
|
|
3
|
-
import { a as ALL_PIPELINE_STEPS, c as getDefaultModelForFormatFromDef, d as resolveModelForFormat, f as landmarkPrecisionVerdict, l as getStep, n as startEventLoopStallMonitor, o as ALL_STEPS, r as localFrameRegistry, s as getDefaultModelForFormat, u as getStepDefinition } from "../event-loop-stall-monitor-
|
|
2
|
+
import { $ as pickClusterStepModels, D as YAMNET_TO_MACRO, Dt as hydrateSchema, Et as createEvent, Ft as sleep, G as evaluateZoneRules, Gt as string, I as defaultDeviceFor$1, Kt as union, Lt as array, Mt as parseJsonUnknown, St as BaseAddon, Ut as object, V as detectionPipelineCapability, W as enumerateInferenceDevices, Z as overlayClusterStepSettings, c as DEFAULT_CLUSTER_STEP_MODELS, dt as runtimeDevices$1, et as pickClusterStepSettings, gt as errMsg, jt as nodePin, l as DEFAULT_CLUSTER_STEP_SETTINGS, lt as resolvePoolMemoryPolicy, mt as supportedRuntimes$1, p as DEVICE_BACKEND_TO_FORMAT, q as inferModelProvider, qt as EventCategory, rt as pipelineExecutorCapability, st as resolveClusterStepModelId, t as APPLE_SA_TO_MACRO, x as PoolMemoryWatchdog } from "../dist-Db664g2O.mjs";
|
|
3
|
+
import { a as ALL_PIPELINE_STEPS, c as getDefaultModelForFormatFromDef, d as resolveModelForFormat, f as landmarkPrecisionVerdict, l as getStep, n as startEventLoopStallMonitor, o as ALL_STEPS, r as localFrameRegistry, s as getDefaultModelForFormat, u as getStepDefinition } from "../event-loop-stall-monitor-DY6339bX.mjs";
|
|
4
4
|
import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
|
|
5
|
-
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-
|
|
5
|
+
import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-CPm5TMBl.mjs";
|
|
6
6
|
import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../addon-utils-CgBCF-mE.mjs";
|
|
7
7
|
import sharp from "sharp";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
@@ -249,6 +249,113 @@ function normalizeEngineNodeId(rawNodeId) {
|
|
|
249
249
|
return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
|
|
250
250
|
}
|
|
251
251
|
//#endregion
|
|
252
|
+
//#region src/shared/cluster-model-source.ts
|
|
253
|
+
/**
|
|
254
|
+
* Where the runner gets the ONE cluster model choice per cluster-scoped step.
|
|
255
|
+
*
|
|
256
|
+
* The sibling of `crop-convention-source.ts`, and deliberately built the same
|
|
257
|
+
* way, because it answers the neighbouring half of the same question: the crop
|
|
258
|
+
* convention decides WHAT pixels the encoder sees, this decides WHICH encoder
|
|
259
|
+
* sees them. Both are cluster-wide, both are read by the runner from the
|
|
260
|
+
* `pipeline-orchestrator` global settings over the hub-routed `addon-settings`
|
|
261
|
+
* cap, both pass NO `nodeId`.
|
|
262
|
+
*
|
|
263
|
+
* ## Deliberately NOT node-scoped, and not on the attach config
|
|
264
|
+
*
|
|
265
|
+
* With a `nodeId` the settings cap would project that node's scoped value and
|
|
266
|
+
* two nodes could embed into one index with two encoders — the original defect,
|
|
267
|
+
* relocated to the transport. And the attach config is the wrong carrier for
|
|
268
|
+
* the same reason it was wrong for the crop: eleven builders construct a
|
|
269
|
+
* `RunnerCameraConfig`, a cluster-wide constant threaded through eleven
|
|
270
|
+
* builders eventually reaches ten of them, and the cameras attached by the
|
|
271
|
+
* forgotten one quietly write a foreign feature space into the shared index.
|
|
272
|
+
*
|
|
273
|
+
* ## Why the failure mode keeps the last value
|
|
274
|
+
*
|
|
275
|
+
* A failed read must never revert to the registry default MID-PASS: that would
|
|
276
|
+
* silently change what every remaining vector of that dispatch means, which is
|
|
277
|
+
* the exact class of harm the scope exists to prevent. So a failure keeps the
|
|
278
|
+
* last known row and says so, loudly, once per failure.
|
|
279
|
+
*/
|
|
280
|
+
/**
|
|
281
|
+
* The addon whose GLOBAL settings own the cluster row. Same owner as the crop
|
|
282
|
+
* convention and the native-frame lease: hub-resident and neutral between the
|
|
283
|
+
* addons that read it (post-analysis pins it for a re-embed pass, the runner
|
|
284
|
+
* enforces it live — neither should own a value the other depends on).
|
|
285
|
+
*/
|
|
286
|
+
var CLUSTER_MODEL_OWNER_ADDON_ID = "pipeline-orchestrator";
|
|
287
|
+
/** TTL-cached read of the cluster model row. */
|
|
288
|
+
var ClusterModelSource = class {
|
|
289
|
+
models = DEFAULT_CLUSTER_STEP_MODELS;
|
|
290
|
+
settings = DEFAULT_CLUSTER_STEP_SETTINGS;
|
|
291
|
+
lastReadAtMs = Number.NEGATIVE_INFINITY;
|
|
292
|
+
inFlight = null;
|
|
293
|
+
logger;
|
|
294
|
+
now;
|
|
295
|
+
ttlMs;
|
|
296
|
+
constructor(options) {
|
|
297
|
+
this.logger = options.logger;
|
|
298
|
+
this.now = options.now ?? (() => Date.now());
|
|
299
|
+
this.ttlMs = options.ttlMs ?? 6e4;
|
|
300
|
+
}
|
|
301
|
+
/** The row in force right now — the registry defaults until a read lands. */
|
|
302
|
+
current() {
|
|
303
|
+
return this.models;
|
|
304
|
+
}
|
|
305
|
+
/** Cluster-scoped step knobs in force right now (admission floors, …). */
|
|
306
|
+
currentSettings() {
|
|
307
|
+
return this.settings;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Re-read if the cached row has expired. Concurrent callers share one
|
|
311
|
+
* in-flight read; a failure keeps the LAST KNOWN row.
|
|
312
|
+
*/
|
|
313
|
+
async refresh(api) {
|
|
314
|
+
if (api === void 0) return this.models;
|
|
315
|
+
if (this.now() - this.lastReadAtMs < this.ttlMs) return this.models;
|
|
316
|
+
this.inFlight ??= this.read(api).finally(() => {
|
|
317
|
+
this.inFlight = null;
|
|
318
|
+
});
|
|
319
|
+
await this.inFlight;
|
|
320
|
+
return this.models;
|
|
321
|
+
}
|
|
322
|
+
async read(api) {
|
|
323
|
+
try {
|
|
324
|
+
const view = await api.addonSettings.getGlobalSettings.query({ addonId: CLUSTER_MODEL_OWNER_ADDON_ID });
|
|
325
|
+
const next = pickClusterStepModels(view);
|
|
326
|
+
const nextSettings = pickClusterStepSettings(view);
|
|
327
|
+
this.lastReadAtMs = this.now();
|
|
328
|
+
const changed = Object.keys(next).filter((stepId) => next[stepId] !== this.models[stepId]);
|
|
329
|
+
const settingsChanged = Object.keys(nextSettings).some((stepId) => {
|
|
330
|
+
const from = this.settings[stepId] ?? {};
|
|
331
|
+
const to = nextSettings[stepId] ?? {};
|
|
332
|
+
return [...new Set([...Object.keys(from), ...Object.keys(to)])].some((key) => from[key] !== to[key]);
|
|
333
|
+
});
|
|
334
|
+
if (changed.length > 0) {
|
|
335
|
+
this.logger.info("cluster step model changed — vectors already stored by the previous model need a re-embed pass", { meta: { changed: changed.map((stepId) => ({
|
|
336
|
+
stepId,
|
|
337
|
+
from: this.models[stepId],
|
|
338
|
+
to: next[stepId]
|
|
339
|
+
})) } });
|
|
340
|
+
this.models = next;
|
|
341
|
+
}
|
|
342
|
+
if (settingsChanged) {
|
|
343
|
+
this.logger.info("cluster step settings changed", { meta: {
|
|
344
|
+
from: { ...this.settings },
|
|
345
|
+
to: { ...nextSettings }
|
|
346
|
+
} });
|
|
347
|
+
this.settings = nextSettings;
|
|
348
|
+
}
|
|
349
|
+
} catch (err) {
|
|
350
|
+
this.logger.warn("cluster step model read failed — keeping the last known row", { meta: {
|
|
351
|
+
owner: CLUSTER_MODEL_OWNER_ADDON_ID,
|
|
352
|
+
inUse: { ...this.models },
|
|
353
|
+
error: err instanceof Error ? err.message : String(err)
|
|
354
|
+
} });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
//#endregion
|
|
252
359
|
//#region src/detection-pipeline/default-detection-model.ts
|
|
253
360
|
/** The object-detection step id — the only slot this resolver applies to. */
|
|
254
361
|
var OBJECT_DETECTION_STEP_ID = "object-detection";
|
|
@@ -2594,6 +2701,59 @@ var EngineProvisioner = class {
|
|
|
2594
2701
|
}
|
|
2595
2702
|
};
|
|
2596
2703
|
//#endregion
|
|
2704
|
+
//#region src/session-decode/frame-view-payload.ts
|
|
2705
|
+
var FRAME_VIEW_PAYLOAD_POLICY = "jpeg";
|
|
2706
|
+
//#endregion
|
|
2707
|
+
//#region src/detection-pipeline/root-frame-view.ts
|
|
2708
|
+
/** Resolve the pre-padding content requested from the decode worker for one root. */
|
|
2709
|
+
function resolveRootFrameViewSpec(model, source, crop, format = FRAME_VIEW_PAYLOAD_POLICY) {
|
|
2710
|
+
const sourceWidth = crop?.width ?? source.width;
|
|
2711
|
+
const sourceHeight = crop?.height ?? source.height;
|
|
2712
|
+
if (model.preprocessMode !== "letterbox") return {
|
|
2713
|
+
...crop ? { crop } : {},
|
|
2714
|
+
content: {
|
|
2715
|
+
width: model.inputSize.width,
|
|
2716
|
+
height: model.inputSize.height
|
|
2717
|
+
},
|
|
2718
|
+
fit: "stretch",
|
|
2719
|
+
format
|
|
2720
|
+
};
|
|
2721
|
+
const scale = Math.min(model.inputSize.width / sourceWidth, model.inputSize.height / sourceHeight);
|
|
2722
|
+
return {
|
|
2723
|
+
...crop ? { crop } : {},
|
|
2724
|
+
content: {
|
|
2725
|
+
width: Math.max(1, Math.round(sourceWidth * scale)),
|
|
2726
|
+
height: Math.max(1, Math.round(sourceHeight * scale))
|
|
2727
|
+
},
|
|
2728
|
+
fit: "contain",
|
|
2729
|
+
format
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
//#endregion
|
|
2733
|
+
//#region src/detection-pipeline/frame-ref-view-provider.ts
|
|
2734
|
+
var LocalFrameRefMissError = class extends Error {
|
|
2735
|
+
reason;
|
|
2736
|
+
constructor(reason) {
|
|
2737
|
+
super(`local frameRef miss: ${reason}`);
|
|
2738
|
+
this.reason = reason;
|
|
2739
|
+
this.name = "LocalFrameRefMissError";
|
|
2740
|
+
}
|
|
2741
|
+
};
|
|
2742
|
+
/** Bind one registry lease for all heterogeneous roots of a pipeline invocation. */
|
|
2743
|
+
function createRootFrameViewResolver(registry, ref) {
|
|
2744
|
+
const lease = registry.acquire(ref);
|
|
2745
|
+
if (!lease) throw new LocalFrameRefMissError(ref.registryId === registry.registryId ? "stale-ref" : "foreign-registry");
|
|
2746
|
+
const resolve = async (root, crop) => {
|
|
2747
|
+
const model = root.definition.models.find((candidate) => candidate.id === root.modelId);
|
|
2748
|
+
if (!model) throw new Error(`frame view model not found: ${root.modelId}`);
|
|
2749
|
+
const spec = resolveRootFrameViewSpec(model, ref, crop);
|
|
2750
|
+
const result = await lease.resolve(spec);
|
|
2751
|
+
if (result.kind === "miss") throw new LocalFrameRefMissError(result.reason);
|
|
2752
|
+
return result;
|
|
2753
|
+
};
|
|
2754
|
+
return Object.assign(resolve, { release: (reason) => lease.release(reason) });
|
|
2755
|
+
}
|
|
2756
|
+
//#endregion
|
|
2597
2757
|
//#region src/detection-pipeline/pipeline/crop-utils.ts
|
|
2598
2758
|
/**
|
|
2599
2759
|
* Crop utilities — ROI extraction and bbox coordinate transforms.
|
|
@@ -5179,59 +5339,6 @@ function capitalize(s) {
|
|
|
5179
5339
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
5180
5340
|
}
|
|
5181
5341
|
//#endregion
|
|
5182
|
-
//#region src/session-decode/frame-view-payload.ts
|
|
5183
|
-
var FRAME_VIEW_PAYLOAD_POLICY = "jpeg";
|
|
5184
|
-
//#endregion
|
|
5185
|
-
//#region src/detection-pipeline/root-frame-view.ts
|
|
5186
|
-
/** Resolve the pre-padding content requested from the decode worker for one root. */
|
|
5187
|
-
function resolveRootFrameViewSpec(model, source, crop, format = FRAME_VIEW_PAYLOAD_POLICY) {
|
|
5188
|
-
const sourceWidth = crop?.width ?? source.width;
|
|
5189
|
-
const sourceHeight = crop?.height ?? source.height;
|
|
5190
|
-
if (model.preprocessMode !== "letterbox") return {
|
|
5191
|
-
...crop ? { crop } : {},
|
|
5192
|
-
content: {
|
|
5193
|
-
width: model.inputSize.width,
|
|
5194
|
-
height: model.inputSize.height
|
|
5195
|
-
},
|
|
5196
|
-
fit: "stretch",
|
|
5197
|
-
format
|
|
5198
|
-
};
|
|
5199
|
-
const scale = Math.min(model.inputSize.width / sourceWidth, model.inputSize.height / sourceHeight);
|
|
5200
|
-
return {
|
|
5201
|
-
...crop ? { crop } : {},
|
|
5202
|
-
content: {
|
|
5203
|
-
width: Math.max(1, Math.round(sourceWidth * scale)),
|
|
5204
|
-
height: Math.max(1, Math.round(sourceHeight * scale))
|
|
5205
|
-
},
|
|
5206
|
-
fit: "contain",
|
|
5207
|
-
format
|
|
5208
|
-
};
|
|
5209
|
-
}
|
|
5210
|
-
//#endregion
|
|
5211
|
-
//#region src/detection-pipeline/frame-ref-view-provider.ts
|
|
5212
|
-
var LocalFrameRefMissError = class extends Error {
|
|
5213
|
-
reason;
|
|
5214
|
-
constructor(reason) {
|
|
5215
|
-
super(`local frameRef miss: ${reason}`);
|
|
5216
|
-
this.reason = reason;
|
|
5217
|
-
this.name = "LocalFrameRefMissError";
|
|
5218
|
-
}
|
|
5219
|
-
};
|
|
5220
|
-
/** Bind one registry lease for all heterogeneous roots of a pipeline invocation. */
|
|
5221
|
-
function createRootFrameViewResolver(registry, ref) {
|
|
5222
|
-
const lease = registry.acquire(ref);
|
|
5223
|
-
if (!lease) throw new LocalFrameRefMissError(ref.registryId === registry.registryId ? "stale-ref" : "foreign-registry");
|
|
5224
|
-
const resolve = async (root, crop) => {
|
|
5225
|
-
const model = root.definition.models.find((candidate) => candidate.id === root.modelId);
|
|
5226
|
-
if (!model) throw new Error(`frame view model not found: ${root.modelId}`);
|
|
5227
|
-
const spec = resolveRootFrameViewSpec(model, ref, crop);
|
|
5228
|
-
const result = await lease.resolve(spec);
|
|
5229
|
-
if (result.kind === "miss") throw new LocalFrameRefMissError(result.reason);
|
|
5230
|
-
return result;
|
|
5231
|
-
};
|
|
5232
|
-
return Object.assign(resolve, { release: (reason) => lease.release(reason) });
|
|
5233
|
-
}
|
|
5234
|
-
//#endregion
|
|
5235
5342
|
//#region src/detection-pipeline/pipeline/native-crop-compose.ts
|
|
5236
5343
|
/** Clamp `v` into `[lo, hi]`. */
|
|
5237
5344
|
function clamp(v, lo, hi) {
|
|
@@ -5360,7 +5467,10 @@ function mergeCustomModels(staticModels, customModels) {
|
|
|
5360
5467
|
for (const m of customModels) {
|
|
5361
5468
|
if (seen.has(m.id)) continue;
|
|
5362
5469
|
seen.add(m.id);
|
|
5363
|
-
merged.push(
|
|
5470
|
+
merged.push({
|
|
5471
|
+
...m,
|
|
5472
|
+
provider: inferModelProvider(m)
|
|
5473
|
+
});
|
|
5364
5474
|
}
|
|
5365
5475
|
return merged;
|
|
5366
5476
|
}
|
|
@@ -5659,113 +5769,6 @@ function applyZoneRuleGate(result, zones, rules) {
|
|
|
5659
5769
|
};
|
|
5660
5770
|
}
|
|
5661
5771
|
//#endregion
|
|
5662
|
-
//#region src/shared/cluster-model-source.ts
|
|
5663
|
-
/**
|
|
5664
|
-
* Where the runner gets the ONE cluster model choice per cluster-scoped step.
|
|
5665
|
-
*
|
|
5666
|
-
* The sibling of `crop-convention-source.ts`, and deliberately built the same
|
|
5667
|
-
* way, because it answers the neighbouring half of the same question: the crop
|
|
5668
|
-
* convention decides WHAT pixels the encoder sees, this decides WHICH encoder
|
|
5669
|
-
* sees them. Both are cluster-wide, both are read by the runner from the
|
|
5670
|
-
* `pipeline-orchestrator` global settings over the hub-routed `addon-settings`
|
|
5671
|
-
* cap, both pass NO `nodeId`.
|
|
5672
|
-
*
|
|
5673
|
-
* ## Deliberately NOT node-scoped, and not on the attach config
|
|
5674
|
-
*
|
|
5675
|
-
* With a `nodeId` the settings cap would project that node's scoped value and
|
|
5676
|
-
* two nodes could embed into one index with two encoders — the original defect,
|
|
5677
|
-
* relocated to the transport. And the attach config is the wrong carrier for
|
|
5678
|
-
* the same reason it was wrong for the crop: eleven builders construct a
|
|
5679
|
-
* `RunnerCameraConfig`, a cluster-wide constant threaded through eleven
|
|
5680
|
-
* builders eventually reaches ten of them, and the cameras attached by the
|
|
5681
|
-
* forgotten one quietly write a foreign feature space into the shared index.
|
|
5682
|
-
*
|
|
5683
|
-
* ## Why the failure mode keeps the last value
|
|
5684
|
-
*
|
|
5685
|
-
* A failed read must never revert to the registry default MID-PASS: that would
|
|
5686
|
-
* silently change what every remaining vector of that dispatch means, which is
|
|
5687
|
-
* the exact class of harm the scope exists to prevent. So a failure keeps the
|
|
5688
|
-
* last known row and says so, loudly, once per failure.
|
|
5689
|
-
*/
|
|
5690
|
-
/**
|
|
5691
|
-
* The addon whose GLOBAL settings own the cluster row. Same owner as the crop
|
|
5692
|
-
* convention and the native-frame lease: hub-resident and neutral between the
|
|
5693
|
-
* addons that read it (post-analysis pins it for a re-embed pass, the runner
|
|
5694
|
-
* enforces it live — neither should own a value the other depends on).
|
|
5695
|
-
*/
|
|
5696
|
-
var CLUSTER_MODEL_OWNER_ADDON_ID = "pipeline-orchestrator";
|
|
5697
|
-
/** TTL-cached read of the cluster model row. */
|
|
5698
|
-
var ClusterModelSource = class {
|
|
5699
|
-
models = DEFAULT_CLUSTER_STEP_MODELS;
|
|
5700
|
-
settings = DEFAULT_CLUSTER_STEP_SETTINGS;
|
|
5701
|
-
lastReadAtMs = Number.NEGATIVE_INFINITY;
|
|
5702
|
-
inFlight = null;
|
|
5703
|
-
logger;
|
|
5704
|
-
now;
|
|
5705
|
-
ttlMs;
|
|
5706
|
-
constructor(options) {
|
|
5707
|
-
this.logger = options.logger;
|
|
5708
|
-
this.now = options.now ?? (() => Date.now());
|
|
5709
|
-
this.ttlMs = options.ttlMs ?? 6e4;
|
|
5710
|
-
}
|
|
5711
|
-
/** The row in force right now — the registry defaults until a read lands. */
|
|
5712
|
-
current() {
|
|
5713
|
-
return this.models;
|
|
5714
|
-
}
|
|
5715
|
-
/** Cluster-scoped step knobs in force right now (admission floors, …). */
|
|
5716
|
-
currentSettings() {
|
|
5717
|
-
return this.settings;
|
|
5718
|
-
}
|
|
5719
|
-
/**
|
|
5720
|
-
* Re-read if the cached row has expired. Concurrent callers share one
|
|
5721
|
-
* in-flight read; a failure keeps the LAST KNOWN row.
|
|
5722
|
-
*/
|
|
5723
|
-
async refresh(api) {
|
|
5724
|
-
if (api === void 0) return this.models;
|
|
5725
|
-
if (this.now() - this.lastReadAtMs < this.ttlMs) return this.models;
|
|
5726
|
-
this.inFlight ??= this.read(api).finally(() => {
|
|
5727
|
-
this.inFlight = null;
|
|
5728
|
-
});
|
|
5729
|
-
await this.inFlight;
|
|
5730
|
-
return this.models;
|
|
5731
|
-
}
|
|
5732
|
-
async read(api) {
|
|
5733
|
-
try {
|
|
5734
|
-
const view = await api.addonSettings.getGlobalSettings.query({ addonId: CLUSTER_MODEL_OWNER_ADDON_ID });
|
|
5735
|
-
const next = pickClusterStepModels(view);
|
|
5736
|
-
const nextSettings = pickClusterStepSettings(view);
|
|
5737
|
-
this.lastReadAtMs = this.now();
|
|
5738
|
-
const changed = Object.keys(next).filter((stepId) => next[stepId] !== this.models[stepId]);
|
|
5739
|
-
const settingsChanged = Object.keys(nextSettings).some((stepId) => {
|
|
5740
|
-
const from = this.settings[stepId] ?? {};
|
|
5741
|
-
const to = nextSettings[stepId] ?? {};
|
|
5742
|
-
return [...new Set([...Object.keys(from), ...Object.keys(to)])].some((key) => from[key] !== to[key]);
|
|
5743
|
-
});
|
|
5744
|
-
if (changed.length > 0) {
|
|
5745
|
-
this.logger.info("cluster step model changed — vectors already stored by the previous model need a re-embed pass", { meta: { changed: changed.map((stepId) => ({
|
|
5746
|
-
stepId,
|
|
5747
|
-
from: this.models[stepId],
|
|
5748
|
-
to: next[stepId]
|
|
5749
|
-
})) } });
|
|
5750
|
-
this.models = next;
|
|
5751
|
-
}
|
|
5752
|
-
if (settingsChanged) {
|
|
5753
|
-
this.logger.info("cluster step settings changed", { meta: {
|
|
5754
|
-
from: { ...this.settings },
|
|
5755
|
-
to: { ...nextSettings }
|
|
5756
|
-
} });
|
|
5757
|
-
this.settings = nextSettings;
|
|
5758
|
-
}
|
|
5759
|
-
} catch (err) {
|
|
5760
|
-
this.logger.warn("cluster step model read failed — keeping the last known row", { meta: {
|
|
5761
|
-
owner: CLUSTER_MODEL_OWNER_ADDON_ID,
|
|
5762
|
-
inUse: { ...this.models },
|
|
5763
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5764
|
-
} });
|
|
5765
|
-
}
|
|
5766
|
-
}
|
|
5767
|
-
};
|
|
5768
|
-
//#endregion
|
|
5769
5772
|
//#region src/detection-pipeline/provider.ts
|
|
5770
5773
|
/**
|
|
5771
5774
|
* DetectionPipelineProvider — implements IPipelineExecutorProvider.
|
|
@@ -8756,7 +8759,8 @@ function buildSchemaSlots(formats, modelsDir, customByStep) {
|
|
|
8756
8759
|
sizeMB: entry.sizeMB
|
|
8757
8760
|
}])),
|
|
8758
8761
|
...m.group ? { group: m.group } : {},
|
|
8759
|
-
...m.legacy !== void 0 ? { legacy: m.legacy } : {}
|
|
8762
|
+
...m.legacy !== void 0 ? { legacy: m.legacy } : {},
|
|
8763
|
+
provider: m.provider ?? "camstack"
|
|
8760
8764
|
})),
|
|
8761
8765
|
defaultModelId: step.defaultModelId,
|
|
8762
8766
|
...step.defaultModelIdByFormat ? { defaultModelIdByFormat: { ...step.defaultModelIdByFormat } } : {},
|
|
@@ -9173,6 +9173,25 @@ var ModelVariantGroupSchema = object({
|
|
|
9173
9173
|
*/
|
|
9174
9174
|
resolution: number().int().positive().optional()
|
|
9175
9175
|
});
|
|
9176
|
+
var ModelProviderIdSchema = _enum([
|
|
9177
|
+
"camstack",
|
|
9178
|
+
"frigate",
|
|
9179
|
+
"scrypted",
|
|
9180
|
+
"custom"
|
|
9181
|
+
]);
|
|
9182
|
+
/**
|
|
9183
|
+
* Resolve a catalog entry's picker provider. An explicit stamp always wins;
|
|
9184
|
+
* otherwise Frigate/Scrypted are recognised from id prefix or description.
|
|
9185
|
+
* Unstamped BYO registry rows fall through to `custom`. Family `yolov9` is
|
|
9186
|
+
* NOT a Scrypted signal — CamStack ships that family too.
|
|
9187
|
+
*/
|
|
9188
|
+
function inferModelProvider(entry) {
|
|
9189
|
+
if (entry.provider !== void 0) return entry.provider;
|
|
9190
|
+
const haystack = `${entry.id} ${entry.description ?? ""}`;
|
|
9191
|
+
if (/scrypted/i.test(haystack)) return "scrypted";
|
|
9192
|
+
if (/frigate/i.test(haystack)) return "frigate";
|
|
9193
|
+
return "custom";
|
|
9194
|
+
}
|
|
9176
9195
|
var ModelCatalogEntrySchema = object({
|
|
9177
9196
|
id: string(),
|
|
9178
9197
|
name: string(),
|
|
@@ -9270,6 +9289,12 @@ var ModelCatalogEntrySchema = object({
|
|
|
9270
9289
|
*/
|
|
9271
9290
|
group: ModelVariantGroupSchema.optional(),
|
|
9272
9291
|
/**
|
|
9292
|
+
* Catalog source for the pipeline stepper's provider-first picker. Absent on
|
|
9293
|
+
* built-in CamStack entries (treated as `camstack`) and on registry rows
|
|
9294
|
+
* persisted before this field existed (`inferModelProvider` fills those).
|
|
9295
|
+
*/
|
|
9296
|
+
provider: ModelProviderIdSchema.optional(),
|
|
9297
|
+
/**
|
|
9273
9298
|
* Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
|
|
9274
9299
|
* applies (Frigate / COCO public catalog). Set on a custom model whose raw
|
|
9275
9300
|
* labels already ARE the CamStack macros (Scrypted identity map).
|
|
@@ -16236,12 +16261,15 @@ var NcOccupancyConditionSchema = object({
|
|
|
16236
16261
|
* there is no second switch that can disagree with the first and every rule
|
|
16237
16262
|
* authored before the decision migrates for free (`audioModeOf`):
|
|
16238
16263
|
*
|
|
16239
|
-
* - **LABEL mode — `labels` present.** The rule fires
|
|
16240
|
-
*
|
|
16241
|
-
* `hitPercent` and `samplingSeconds` are ignored
|
|
16242
|
-
*
|
|
16243
|
-
*
|
|
16244
|
-
*
|
|
16264
|
+
* - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
|
|
16265
|
+
* labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
|
|
16266
|
+
* `hitPercent` and `samplingSeconds` are still ignored — a percentage of
|
|
16267
|
+
* frames is the wrong question for a classifier that labels 1–3 frames
|
|
16268
|
+
* per episode. The count window is the brake that drops a single-frame
|
|
16269
|
+
* false positive; the rule's own `throttle` cooldown is the other. The
|
|
16270
|
+
* per-label confidence floor is the analyzer's (`classificationMinScore`,
|
|
16271
|
+
* per device) — a label only reaches this condition if the classifier was
|
|
16272
|
+
* already confident enough. `confirmHits: 1` restores first-frame fire.
|
|
16245
16273
|
* - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
|
|
16246
16274
|
* the condition: at least `hitPercent`% of the samples over
|
|
16247
16275
|
* `samplingSeconds` must be at or above `dbThreshold` dBFS (see
|
|
@@ -16268,14 +16296,22 @@ var NcOccupancyConditionSchema = object({
|
|
|
16268
16296
|
* an operator who typed `dog` mean the same thing.
|
|
16269
16297
|
*/
|
|
16270
16298
|
var NcAudioConditionSchema = object({
|
|
16271
|
-
/** LABEL MODE: audio macro labels. Present ⇒
|
|
16299
|
+
/** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
|
|
16272
16300
|
labels: array(string().min(1)).min(1).optional(),
|
|
16273
16301
|
/** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
|
|
16274
16302
|
dbThreshold: number().min(-96).max(0).optional(),
|
|
16275
16303
|
/** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
|
|
16276
16304
|
hitPercent: number().int().min(1).max(100).default(60),
|
|
16277
16305
|
/** LEVEL MODE ONLY: length of the sampling window in seconds. */
|
|
16278
|
-
samplingSeconds: number().int().min(1).max(300).default(10)
|
|
16306
|
+
samplingSeconds: number().int().min(1).max(300).default(10),
|
|
16307
|
+
/**
|
|
16308
|
+
* LABEL MODE: how many labelled frames must land inside
|
|
16309
|
+
* {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
|
|
16310
|
+
* Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
|
|
16311
|
+
*/
|
|
16312
|
+
confirmHits: number().int().min(1).max(20).optional(),
|
|
16313
|
+
/** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
|
|
16314
|
+
confirmWindowSec: number().int().min(1).max(60).optional()
|
|
16279
16315
|
});
|
|
16280
16316
|
/**
|
|
16281
16317
|
* Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
|
|
@@ -18649,6 +18685,46 @@ var RecentTracksPageSchema = object({
|
|
|
18649
18685
|
/** Cursor for the next page, or null when this page is the last. */
|
|
18650
18686
|
nextCursor: string().nullable()
|
|
18651
18687
|
});
|
|
18688
|
+
var LIST_GROUPS_DEFAULT_LIMIT = 40;
|
|
18689
|
+
var LIST_GROUPS_MAX_LIMIT = 100;
|
|
18690
|
+
var AnalyticsGroupRecordSchema = object({
|
|
18691
|
+
id: string(),
|
|
18692
|
+
deviceId: number().int(),
|
|
18693
|
+
openedAt: number().int(),
|
|
18694
|
+
closedAt: number().int(),
|
|
18695
|
+
timestamp: number().int(),
|
|
18696
|
+
memberCount: number().int(),
|
|
18697
|
+
memberTrackIds: array(string()).readonly(),
|
|
18698
|
+
className: string(),
|
|
18699
|
+
classes: array(string()).readonly(),
|
|
18700
|
+
/** Relative event-media path, or null when the group has no picture yet. */
|
|
18701
|
+
mediaUrl: string().nullable(),
|
|
18702
|
+
singleton: boolean()
|
|
18703
|
+
});
|
|
18704
|
+
var AnalyticsGroupMemberSchema = object({
|
|
18705
|
+
trackId: string(),
|
|
18706
|
+
deviceId: number().int(),
|
|
18707
|
+
className: string(),
|
|
18708
|
+
firstSeen: number().int(),
|
|
18709
|
+
lastSeen: number().int(),
|
|
18710
|
+
mediaUrl: string().nullable()
|
|
18711
|
+
});
|
|
18712
|
+
var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
|
|
18713
|
+
var ListGroupsQueryInput = object({
|
|
18714
|
+
/** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
|
|
18715
|
+
deviceIds: array(number()),
|
|
18716
|
+
/** Window lower bound on `closedAt` (inclusive). */
|
|
18717
|
+
since: number().optional(),
|
|
18718
|
+
/** Window upper bound on `openedAt` (inclusive). */
|
|
18719
|
+
until: number().optional(),
|
|
18720
|
+
limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
|
|
18721
|
+
/** Opaque continuation cursor from a previous page's `nextCursor`. */
|
|
18722
|
+
cursor: string().optional()
|
|
18723
|
+
});
|
|
18724
|
+
var ListGroupsPageSchema = object({
|
|
18725
|
+
groups: array(AnalyticsGroupRecordSchema).readonly(),
|
|
18726
|
+
nextCursor: string().nullable()
|
|
18727
|
+
});
|
|
18652
18728
|
var KeyEventQueryInput = object({
|
|
18653
18729
|
deviceId: number(),
|
|
18654
18730
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
@@ -18724,7 +18800,9 @@ var TrackCascadeCountsSchema = object({
|
|
|
18724
18800
|
/** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
|
|
18725
18801
|
plates: number().int(),
|
|
18726
18802
|
/** Per-track CLIP search vectors removed (best-effort). */
|
|
18727
|
-
embeddings: number().int()
|
|
18803
|
+
embeddings: number().int(),
|
|
18804
|
+
/** Group membership + group rows removed with their last member (best-effort). */
|
|
18805
|
+
groups: number().int()
|
|
18728
18806
|
});
|
|
18729
18807
|
/** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
|
|
18730
18808
|
var DiskReconcileCountsSchema = object({
|
|
@@ -18870,7 +18948,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
18870
18948
|
* stationary registry). Default false: the timeline lists passages,
|
|
18871
18949
|
* not parking records (operator decision, 2026-08-15). */
|
|
18872
18950
|
includeStationary: boolean().optional()
|
|
18873
|
-
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(
|
|
18951
|
+
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
|
|
18952
|
+
deviceId: number(),
|
|
18953
|
+
groupId: string().min(1)
|
|
18954
|
+
}), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
|
|
18874
18955
|
kind: "mutation",
|
|
18875
18956
|
auth: "admin"
|
|
18876
18957
|
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
|
|
@@ -19180,7 +19261,8 @@ var PipelineModelOptionSchema = object({
|
|
|
19180
19261
|
sizeMB: number()
|
|
19181
19262
|
})),
|
|
19182
19263
|
group: ModelVariantGroupSchema.optional(),
|
|
19183
|
-
legacy: boolean().optional()
|
|
19264
|
+
legacy: boolean().optional(),
|
|
19265
|
+
provider: ModelProviderIdSchema.optional()
|
|
19184
19266
|
});
|
|
19185
19267
|
var ConfigFieldBridge = custom();
|
|
19186
19268
|
var PipelineAddonSchemaSchema = object({
|
|
@@ -31862,6 +31944,12 @@ Object.freeze({
|
|
|
31862
31944
|
addonId: null,
|
|
31863
31945
|
access: "view"
|
|
31864
31946
|
},
|
|
31947
|
+
"pipelineAnalytics.getGroup": {
|
|
31948
|
+
capName: "pipeline-analytics",
|
|
31949
|
+
capScope: "device",
|
|
31950
|
+
addonId: null,
|
|
31951
|
+
access: "view"
|
|
31952
|
+
},
|
|
31865
31953
|
"pipelineAnalytics.getKeyEvents": {
|
|
31866
31954
|
capName: "pipeline-analytics",
|
|
31867
31955
|
capScope: "device",
|
|
@@ -31946,6 +32034,12 @@ Object.freeze({
|
|
|
31946
32034
|
addonId: null,
|
|
31947
32035
|
access: "view"
|
|
31948
32036
|
},
|
|
32037
|
+
"pipelineAnalytics.listGroups": {
|
|
32038
|
+
capName: "pipeline-analytics",
|
|
32039
|
+
capScope: "device",
|
|
32040
|
+
addonId: null,
|
|
32041
|
+
access: "view"
|
|
32042
|
+
},
|
|
31949
32043
|
"pipelineAnalytics.listOpsLog": {
|
|
31950
32044
|
capName: "pipeline-analytics",
|
|
31951
32045
|
capScope: "device",
|
|
@@ -35365,6 +35459,11 @@ Object.freeze({
|
|
|
35365
35459
|
form: "single",
|
|
35366
35460
|
optional: false
|
|
35367
35461
|
}],
|
|
35462
|
+
"pipelineAnalytics.getGroup": [{
|
|
35463
|
+
name: "deviceId",
|
|
35464
|
+
form: "single",
|
|
35465
|
+
optional: false
|
|
35466
|
+
}],
|
|
35368
35467
|
"pipelineAnalytics.getKeyEvents": [{
|
|
35369
35468
|
name: "deviceId",
|
|
35370
35469
|
form: "single",
|
|
@@ -35420,6 +35519,11 @@ Object.freeze({
|
|
|
35420
35519
|
form: "array",
|
|
35421
35520
|
optional: false
|
|
35422
35521
|
}],
|
|
35522
|
+
"pipelineAnalytics.listGroups": [{
|
|
35523
|
+
name: "deviceIds",
|
|
35524
|
+
form: "array",
|
|
35525
|
+
optional: false
|
|
35526
|
+
}],
|
|
35423
35527
|
"pipelineAnalytics.listOpsLog": [{
|
|
35424
35528
|
name: "deviceId",
|
|
35425
35529
|
form: "single",
|
|
@@ -38090,6 +38194,12 @@ Object.defineProperty(exports, "hydrateSchema", {
|
|
|
38090
38194
|
return hydrateSchema;
|
|
38091
38195
|
}
|
|
38092
38196
|
});
|
|
38197
|
+
Object.defineProperty(exports, "inferModelProvider", {
|
|
38198
|
+
enumerable: true,
|
|
38199
|
+
get: function() {
|
|
38200
|
+
return inferModelProvider;
|
|
38201
|
+
}
|
|
38202
|
+
});
|
|
38093
38203
|
Object.defineProperty(exports, "invocationFromEncodeProfile", {
|
|
38094
38204
|
enumerable: true,
|
|
38095
38205
|
get: function() {
|