@camstack/addon-pipeline 1.2.42 → 1.2.43
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/audio-analyzer/index.js +4 -4
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/detection-pipeline/index.js +9 -9
- package/dist/detection-pipeline/index.mjs +3 -3
- package/dist/{dist-COK_1Ot8.mjs → dist-B-VVBzrL.mjs} +889 -20
- package/dist/{dist-BdDd3ql_.js → dist-UHbKZiSa.js} +906 -19
- package/dist/{event-loop-stall-monitor-DNFRgNef.js → event-loop-stall-monitor-C7_L6vHO.js} +1 -1
- package/dist/{event-loop-stall-monitor-CWOJtTNc.mjs → event-loop-stall-monitor-DfKdC7G4.mjs} +1 -1
- package/dist/{model-download-service-Cp9f4dk6-fsdDExML.js → model-download-service-D8B-4ktF-7-YxE9Wx.js} +2 -2
- package/dist/{model-download-service-Cp9f4dk6-CwUb5v3Y.mjs → model-download-service-D8B-4ktF-BB7oZL3y.mjs} +2 -2
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +160 -4
- package/dist/pipeline-runner/index.mjs +160 -4
- package/dist/recorder/index.js +6 -6
- package/dist/recorder/index.mjs +3 -3
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C8ovZN0g.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D7iQbuKY.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCC9fqHH.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-DAjAxXHm.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-54IklEep.mjs} +1 -1
- package/dist/stream-broker/{hostInit-mEtjQWcr.mjs → hostInit-CKYS9yZO.mjs} +2 -2
- package/dist/stream-broker/index.js +16202 -15821
- package/dist/stream-broker/index.mjs +16201 -15820
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C0TIfr0G.js → MaskShapeCanvas-DI4BY7W2-DxZ75vB8.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-x0SkfHwv.js → MotionZonesSettings-NcxxQN8r-BBY2Ztz5.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BoaAB4Ta.js → PrivacyMaskSettings-APgPLF7p-BC-3QmHa.js} +1 -1
- package/embed-dist/assets/{index-BgRdCCVy.js → index-on1wyOnd.js} +12 -12
- package/embed-dist/index.html +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-SU_6F3Y7.mjs +0 -26
package/dist/{event-loop-stall-monitor-CWOJtTNc.mjs → event-loop-stall-monitor-DfKdC7G4.mjs}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as COCO_80_LABELS, j as hfModelUrl, o as COCO_TO_MACRO, r as AUDIO_MACRO_LABELS } from "./dist-B-VVBzrL.mjs";
|
|
2
2
|
import { PerformanceObserver } from "node:perf_hooks";
|
|
3
3
|
//#region src/detection-pipeline/registry/model-catalogs.ts
|
|
4
4
|
var HF_REPO = "camstack/camstack-models";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-emK7D4bc.js");
|
|
2
|
+
require("node:crypto");
|
|
2
3
|
let node_fs = require("node:fs");
|
|
3
4
|
node_fs = require_chunk.__toESM(node_fs, 1);
|
|
4
5
|
let node_path = require("node:path");
|
|
5
6
|
node_path = require_chunk.__toESM(node_path, 1);
|
|
6
|
-
|
|
7
|
-
//#region ../system/dist/model-download-service-Cp9f4dk6.mjs
|
|
7
|
+
//#region ../system/dist/model-download-service-D8B-4ktF.mjs
|
|
8
8
|
/**
|
|
9
9
|
* Map a rel path to one candidate absolute path PER root, keeping only roots the
|
|
10
10
|
* path stays within (traversal guard). The handler serves the first candidate
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import "node:crypto";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import { createReadStream, promises } from "node:fs";
|
|
3
4
|
import * as path$1 from "node:path";
|
|
4
5
|
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
//#region ../system/dist/model-download-service-Cp9f4dk6.mjs
|
|
6
|
+
//#region ../system/dist/model-download-service-D8B-4ktF.mjs
|
|
7
7
|
/**
|
|
8
8
|
* Map a rel path to one candidate absolute path PER root, keeping only roots the
|
|
9
9
|
* path stays within (traversal guard). The handler serves the first candidate
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-UHbKZiSa.js");
|
|
6
6
|
const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as evaluateZoneRules, F as motionDetectionCapability, Q as hydrateSchema, X as DeviceType, q as BaseAddon } from "../dist-B-VVBzrL.mjs";
|
|
2
2
|
import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-
|
|
6
|
+
const require_dist = require("../dist-UHbKZiSa.js");
|
|
7
|
+
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-C7_L6vHO.js");
|
|
8
8
|
const require_worker_protocol = require("../worker-protocol-DextwlTX.js");
|
|
9
9
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
10
10
|
const require_lazy_sharp = require("../lazy-sharp-CcYuXtsa.js");
|
|
@@ -1874,6 +1874,15 @@ function encodeEmbeddingBase64(embedding) {
|
|
|
1874
1874
|
function pickLabel(detection) {
|
|
1875
1875
|
return detection.labels.find((l) => !l.label.startsWith("embedding:"))?.label;
|
|
1876
1876
|
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Turn one executor detection into the cap's `DetailResult`, reprojecting its
|
|
1879
|
+
* crop-space bbox back onto the frame the crop came from.
|
|
1880
|
+
*
|
|
1881
|
+
* Exported because the STATELESS step path (`stateless-step.ts`) produces the
|
|
1882
|
+
* same shape from the same executor output and must not grow its own mapping —
|
|
1883
|
+
* a second reprojection is a second set of coordinates, and the two would agree
|
|
1884
|
+
* right up until one of them changed.
|
|
1885
|
+
*/
|
|
1877
1886
|
function toDetailResult(detection, stepId, crop) {
|
|
1878
1887
|
const label = pickLabel(detection);
|
|
1879
1888
|
return {
|
|
@@ -1893,6 +1902,13 @@ function toDetailResult(detection, stepId, crop) {
|
|
|
1893
1902
|
* traffic: before it existed the only way to feed stored pixels in was a
|
|
1894
1903
|
* pre-cut tile, so the caller decided the rectangle and every caller decided
|
|
1895
1904
|
* differently.
|
|
1905
|
+
*
|
|
1906
|
+
* Exported so the STATELESS step path can reach `deriveDetailCropRect` through
|
|
1907
|
+
* this function rather than importing it — `scripts/check-clip-crop-single-owner.ts`
|
|
1908
|
+
* permits exactly one importer, and that is the enforcement of
|
|
1909
|
+
* [D52](../../../../docs/decisions/adr-0052.md). A second caller of the
|
|
1910
|
+
* derivation is a second cutter no matter how similar its arithmetic looks
|
|
1911
|
+
* today.
|
|
1896
1912
|
*/
|
|
1897
1913
|
async function cutFromFullFrame(frameJpegBase64, bbox, convention) {
|
|
1898
1914
|
const image = (await require_lazy_sharp.getSharp())(Buffer.from(frameJpegBase64, "base64"));
|
|
@@ -1900,7 +1916,7 @@ async function cutFromFullFrame(frameJpegBase64, bbox, convention) {
|
|
|
1900
1916
|
const frameWidth = meta.width ?? 0;
|
|
1901
1917
|
const frameHeight = meta.height ?? 0;
|
|
1902
1918
|
if (frameWidth <= 0 || frameHeight <= 0) return null;
|
|
1903
|
-
const rect = require_dist.deriveDetailCropRect(bbox, frameWidth, frameHeight, convention);
|
|
1919
|
+
const rect = require_dist.deriveDetailCropRect(bbox.kind === "frame-pixels" ? bbox.rect : denormalizeRect(bbox.rect, frameWidth, frameHeight), frameWidth, frameHeight, convention);
|
|
1904
1920
|
const left = Math.max(0, Math.min(Math.round(rect.x), frameWidth - 1));
|
|
1905
1921
|
const top = Math.max(0, Math.min(Math.round(rect.y), frameHeight - 1));
|
|
1906
1922
|
const width = Math.max(1, Math.min(Math.round(rect.w), frameWidth - left));
|
|
@@ -1932,7 +1948,10 @@ async function resolveCrop(deps, input, convention) {
|
|
|
1932
1948
|
source: "native"
|
|
1933
1949
|
};
|
|
1934
1950
|
}
|
|
1935
|
-
if (input.frameJpeg) return cutFromFullFrame(input.frameJpeg,
|
|
1951
|
+
if (input.frameJpeg) return cutFromFullFrame(input.frameJpeg, {
|
|
1952
|
+
kind: "frame-pixels",
|
|
1953
|
+
rect: input.parent.bbox
|
|
1954
|
+
}, convention);
|
|
1936
1955
|
if (input.cropJpeg) {
|
|
1937
1956
|
const jpeg = Buffer.from(input.cropJpeg, "base64");
|
|
1938
1957
|
const meta = await (await require_lazy_sharp.getSharp())(jpeg).metadata();
|
|
@@ -3397,6 +3416,83 @@ var PipelineRunner = class {
|
|
|
3397
3416
|
}
|
|
3398
3417
|
};
|
|
3399
3418
|
//#endregion
|
|
3419
|
+
//#region src/pipeline-runner/stateless-step.ts
|
|
3420
|
+
function modelServability(step, modelId, format) {
|
|
3421
|
+
const entry = step.models.find((m) => m.id === modelId);
|
|
3422
|
+
if (entry === void 0) return { kind: "custom-model" };
|
|
3423
|
+
if (entry.formats[format] !== void 0) return { kind: "catalog-build" };
|
|
3424
|
+
return {
|
|
3425
|
+
kind: "no-build",
|
|
3426
|
+
availableFormats: Object.keys(entry.formats)
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
/** Build the refusal answer, log it, and return it — never one without the other. */
|
|
3430
|
+
function refuse(deps, input, reason, detail) {
|
|
3431
|
+
deps.logger.warn("runStatelessStep refused", {
|
|
3432
|
+
tags: { deviceId: input.sourceDeviceId },
|
|
3433
|
+
meta: {
|
|
3434
|
+
nodeId: deps.nodeId,
|
|
3435
|
+
stepId: input.stepId,
|
|
3436
|
+
modelId: input.modelId,
|
|
3437
|
+
reason,
|
|
3438
|
+
detail
|
|
3439
|
+
}
|
|
3440
|
+
});
|
|
3441
|
+
return {
|
|
3442
|
+
kind: "refused",
|
|
3443
|
+
nodeId: deps.nodeId,
|
|
3444
|
+
reason,
|
|
3445
|
+
detail
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
3448
|
+
function errorMessage(err) {
|
|
3449
|
+
return err instanceof Error ? err.message : String(err);
|
|
3450
|
+
}
|
|
3451
|
+
/**
|
|
3452
|
+
* Run one step over one subject. See the module header for the two invariants.
|
|
3453
|
+
*
|
|
3454
|
+
* Never throws: every failure mode is a named, logged refusal, because the
|
|
3455
|
+
* caller is a bulk pass whose counters are the only signal an operator gets and
|
|
3456
|
+
* a thrown error there reads as "the whole pass broke".
|
|
3457
|
+
*/
|
|
3458
|
+
async function runStatelessStep(deps, input) {
|
|
3459
|
+
const definition = deps.lookupStep(input.stepId);
|
|
3460
|
+
if (definition === null) return refuse(deps, input, "unknown-step", `this node's step catalog has no "${input.stepId}" — version skew, or a custom step that is not installed here`);
|
|
3461
|
+
if (input.bbox.w <= 0 || input.bbox.h <= 0) return refuse(deps, input, "unreadable-frame", `degenerate normalized bbox ${JSON.stringify(input.bbox)} — nothing to crop`);
|
|
3462
|
+
const format = await deps.executionFormat();
|
|
3463
|
+
if (format === null) return refuse(deps, input, "model-not-servable", "this node could not report the engine format it would dispatch on, so the model pin cannot be verified — running anyway would risk a silent format substitution");
|
|
3464
|
+
const servability = modelServability(definition, input.modelId, format);
|
|
3465
|
+
if (servability.kind === "no-build") return refuse(deps, input, "model-not-servable", `model "${input.modelId}" has no ${format} build (catalog ships: ${servability.availableFormats.join(", ") || "none"}) — this node would have substituted the ${format} default`);
|
|
3466
|
+
let decodeError = null;
|
|
3467
|
+
const crop = await cutFromFullFrame(input.frameJpeg, {
|
|
3468
|
+
kind: "normalized",
|
|
3469
|
+
rect: input.bbox
|
|
3470
|
+
}, deps.resolveCropConvention()).catch((err) => {
|
|
3471
|
+
decodeError = errorMessage(err);
|
|
3472
|
+
return null;
|
|
3473
|
+
});
|
|
3474
|
+
if (crop === null) return refuse(deps, input, "unreadable-frame", decodeError ?? "the supplied frame decoded to no readable dimensions");
|
|
3475
|
+
const steps = [{
|
|
3476
|
+
addonId: input.stepId,
|
|
3477
|
+
modelId: input.modelId,
|
|
3478
|
+
enabled: true
|
|
3479
|
+
}];
|
|
3480
|
+
let result;
|
|
3481
|
+
try {
|
|
3482
|
+
result = await deps.runPipeline(steps, crop.jpeg, input.sourceDeviceId);
|
|
3483
|
+
} catch (err) {
|
|
3484
|
+
return refuse(deps, input, "execution-failed", errorMessage(err));
|
|
3485
|
+
}
|
|
3486
|
+
if (result === null) return refuse(deps, input, "execution-failed", "the executor returned no frame result for the crop");
|
|
3487
|
+
const details = result.detections.map((detection) => toDetailResult(detection, input.stepId, crop));
|
|
3488
|
+
return {
|
|
3489
|
+
kind: "ran",
|
|
3490
|
+
nodeId: deps.nodeId,
|
|
3491
|
+
modelId: input.modelId,
|
|
3492
|
+
details
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
//#endregion
|
|
3400
3496
|
//#region src/pipeline-runner/index.ts
|
|
3401
3497
|
var DEFAULT_CONFIG = {
|
|
3402
3498
|
maxQueueDepth: 30,
|
|
@@ -4765,6 +4861,66 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4765
4861
|
}, input);
|
|
4766
4862
|
}
|
|
4767
4863
|
/**
|
|
4864
|
+
* Stateless single-step execution — no attach, no handle, no device
|
|
4865
|
+
* affinity. Delegates the decision table (catalog lookup, MODEL PIN
|
|
4866
|
+
* servability, crop, dispatch) to the deps-injected pure function in
|
|
4867
|
+
* {@link runStatelessStepImpl}; this method only wires the real deps.
|
|
4868
|
+
*
|
|
4869
|
+
* Note what is NOT wired, deliberately: no `resolveChildSteps` (the step
|
|
4870
|
+
* comes from the catalog, not from an attachment) and no `deviceKey` /
|
|
4871
|
+
* device jump (the jump roster is part of the attach config). The step runs
|
|
4872
|
+
* on the node's default engine, which is exactly the engine
|
|
4873
|
+
* `executionFormat` reports — so the servability check and the dispatch
|
|
4874
|
+
* cannot disagree about which format was verified.
|
|
4875
|
+
*/
|
|
4876
|
+
async runStatelessStep(input) {
|
|
4877
|
+
const convention = await this.cropConvention.refresh(this.ctxIfReady?.api);
|
|
4878
|
+
return runStatelessStep({
|
|
4879
|
+
nodeId: this.nodeId,
|
|
4880
|
+
lookupStep: (stepId) => tryGetStepDefinition(stepId),
|
|
4881
|
+
executionFormat: () => this.resolveNodeExecutionFormat(),
|
|
4882
|
+
resolveCropConvention: () => convention,
|
|
4883
|
+
runPipeline: (steps, imageJpeg, sourceDeviceId) => this.runStatelessPipeline(steps, imageJpeg, sourceDeviceId),
|
|
4884
|
+
logger: this.detailSubtreeLogger()
|
|
4885
|
+
}, input);
|
|
4886
|
+
}
|
|
4887
|
+
/**
|
|
4888
|
+
* The model format this node dispatches on when no `deviceKey` is given —
|
|
4889
|
+
* its selected engine's format. `null` on any failure, which the caller
|
|
4890
|
+
* turns into a refusal rather than a guess: a model pin that cannot be
|
|
4891
|
+
* checked is a substitution waiting to happen.
|
|
4892
|
+
*/
|
|
4893
|
+
async resolveNodeExecutionFormat() {
|
|
4894
|
+
const api = this.ctxIfReady?.api;
|
|
4895
|
+
if (!api) return null;
|
|
4896
|
+
try {
|
|
4897
|
+
return (await api.pipelineExecutor.getSelectedEngine.query()).format;
|
|
4898
|
+
} catch (err) {
|
|
4899
|
+
this.ctxIfReady?.logger.warn("runStatelessStep: could not read this node selected engine — refusing rather than guessing a format", { meta: { error: require_dist.errMsg(err) } });
|
|
4900
|
+
return null;
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4903
|
+
/**
|
|
4904
|
+
* Real `runPipeline` dep for the stateless path: node-local
|
|
4905
|
+
* `pipelineExecutor.runPipeline` on the `'full'` plane with NO `deviceKey`.
|
|
4906
|
+
*
|
|
4907
|
+
* `deviceId` is passed so the executor's own gate/substitution lines are
|
|
4908
|
+
* tagged with the source camera. It is a routing hint on other transports,
|
|
4909
|
+
* but not here: the executor is co-located in the `detection` group, so
|
|
4910
|
+
* `localProviderLink` answers in-process before any router sees it — the
|
|
4911
|
+
* call cannot leave the node this one was pinned to.
|
|
4912
|
+
*/
|
|
4913
|
+
async runStatelessPipeline(steps, imageJpeg, sourceDeviceId) {
|
|
4914
|
+
const api = this.ctxIfReady?.api;
|
|
4915
|
+
if (!api) return null;
|
|
4916
|
+
return api.pipelineExecutor.runPipeline.mutate({
|
|
4917
|
+
steps: [...steps],
|
|
4918
|
+
image: new Uint8Array(imageJpeg),
|
|
4919
|
+
deviceId: sourceDeviceId,
|
|
4920
|
+
plane: "full"
|
|
4921
|
+
});
|
|
4922
|
+
}
|
|
4923
|
+
/**
|
|
4768
4924
|
* The cluster-wide detail-crop convention — read from the orchestrator's
|
|
4769
4925
|
* global settings, NOT from the attach config. See `crop-convention-source.ts`
|
|
4770
4926
|
* for why eleven attach builders make the attach config the wrong carrier.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-
|
|
1
|
+
import { C as customAction, E as deriveDetailCropRect, I as pickDetailCropConvention, K as errMsg, R as pipelineRunnerCapability, T as defineCustomActions, Z as createEvent, c as DEFAULT_DETAIL_CROP_CONVENTION, ct as array, dt as lazy, ft as literal, gt as string, l as DEVICE_BACKEND_TO_FORMAT, lt as boolean, mt as object, nt as nodePin, pt as number, q as BaseAddon, st as _enum, vt as EventCategory } from "../dist-B-VVBzrL.mjs";
|
|
2
|
+
import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-DfKdC7G4.mjs";
|
|
3
3
|
import { t as isWorkerReply } from "../worker-protocol-D7RzZIla.mjs";
|
|
4
4
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
5
5
|
import { t as getSharp } from "../lazy-sharp-B-mb8uWx.mjs";
|
|
@@ -1866,6 +1866,15 @@ function encodeEmbeddingBase64(embedding) {
|
|
|
1866
1866
|
function pickLabel(detection) {
|
|
1867
1867
|
return detection.labels.find((l) => !l.label.startsWith("embedding:"))?.label;
|
|
1868
1868
|
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Turn one executor detection into the cap's `DetailResult`, reprojecting its
|
|
1871
|
+
* crop-space bbox back onto the frame the crop came from.
|
|
1872
|
+
*
|
|
1873
|
+
* Exported because the STATELESS step path (`stateless-step.ts`) produces the
|
|
1874
|
+
* same shape from the same executor output and must not grow its own mapping —
|
|
1875
|
+
* a second reprojection is a second set of coordinates, and the two would agree
|
|
1876
|
+
* right up until one of them changed.
|
|
1877
|
+
*/
|
|
1869
1878
|
function toDetailResult(detection, stepId, crop) {
|
|
1870
1879
|
const label = pickLabel(detection);
|
|
1871
1880
|
return {
|
|
@@ -1885,6 +1894,13 @@ function toDetailResult(detection, stepId, crop) {
|
|
|
1885
1894
|
* traffic: before it existed the only way to feed stored pixels in was a
|
|
1886
1895
|
* pre-cut tile, so the caller decided the rectangle and every caller decided
|
|
1887
1896
|
* differently.
|
|
1897
|
+
*
|
|
1898
|
+
* Exported so the STATELESS step path can reach `deriveDetailCropRect` through
|
|
1899
|
+
* this function rather than importing it — `scripts/check-clip-crop-single-owner.ts`
|
|
1900
|
+
* permits exactly one importer, and that is the enforcement of
|
|
1901
|
+
* [D52](../../../../docs/decisions/adr-0052.md). A second caller of the
|
|
1902
|
+
* derivation is a second cutter no matter how similar its arithmetic looks
|
|
1903
|
+
* today.
|
|
1888
1904
|
*/
|
|
1889
1905
|
async function cutFromFullFrame(frameJpegBase64, bbox, convention) {
|
|
1890
1906
|
const image = (await getSharp())(Buffer.from(frameJpegBase64, "base64"));
|
|
@@ -1892,7 +1908,7 @@ async function cutFromFullFrame(frameJpegBase64, bbox, convention) {
|
|
|
1892
1908
|
const frameWidth = meta.width ?? 0;
|
|
1893
1909
|
const frameHeight = meta.height ?? 0;
|
|
1894
1910
|
if (frameWidth <= 0 || frameHeight <= 0) return null;
|
|
1895
|
-
const rect = deriveDetailCropRect(bbox, frameWidth, frameHeight, convention);
|
|
1911
|
+
const rect = deriveDetailCropRect(bbox.kind === "frame-pixels" ? bbox.rect : denormalizeRect(bbox.rect, frameWidth, frameHeight), frameWidth, frameHeight, convention);
|
|
1896
1912
|
const left = Math.max(0, Math.min(Math.round(rect.x), frameWidth - 1));
|
|
1897
1913
|
const top = Math.max(0, Math.min(Math.round(rect.y), frameHeight - 1));
|
|
1898
1914
|
const width = Math.max(1, Math.min(Math.round(rect.w), frameWidth - left));
|
|
@@ -1924,7 +1940,10 @@ async function resolveCrop(deps, input, convention) {
|
|
|
1924
1940
|
source: "native"
|
|
1925
1941
|
};
|
|
1926
1942
|
}
|
|
1927
|
-
if (input.frameJpeg) return cutFromFullFrame(input.frameJpeg,
|
|
1943
|
+
if (input.frameJpeg) return cutFromFullFrame(input.frameJpeg, {
|
|
1944
|
+
kind: "frame-pixels",
|
|
1945
|
+
rect: input.parent.bbox
|
|
1946
|
+
}, convention);
|
|
1928
1947
|
if (input.cropJpeg) {
|
|
1929
1948
|
const jpeg = Buffer.from(input.cropJpeg, "base64");
|
|
1930
1949
|
const meta = await (await getSharp())(jpeg).metadata();
|
|
@@ -3389,6 +3408,83 @@ var PipelineRunner = class {
|
|
|
3389
3408
|
}
|
|
3390
3409
|
};
|
|
3391
3410
|
//#endregion
|
|
3411
|
+
//#region src/pipeline-runner/stateless-step.ts
|
|
3412
|
+
function modelServability(step, modelId, format) {
|
|
3413
|
+
const entry = step.models.find((m) => m.id === modelId);
|
|
3414
|
+
if (entry === void 0) return { kind: "custom-model" };
|
|
3415
|
+
if (entry.formats[format] !== void 0) return { kind: "catalog-build" };
|
|
3416
|
+
return {
|
|
3417
|
+
kind: "no-build",
|
|
3418
|
+
availableFormats: Object.keys(entry.formats)
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
/** Build the refusal answer, log it, and return it — never one without the other. */
|
|
3422
|
+
function refuse(deps, input, reason, detail) {
|
|
3423
|
+
deps.logger.warn("runStatelessStep refused", {
|
|
3424
|
+
tags: { deviceId: input.sourceDeviceId },
|
|
3425
|
+
meta: {
|
|
3426
|
+
nodeId: deps.nodeId,
|
|
3427
|
+
stepId: input.stepId,
|
|
3428
|
+
modelId: input.modelId,
|
|
3429
|
+
reason,
|
|
3430
|
+
detail
|
|
3431
|
+
}
|
|
3432
|
+
});
|
|
3433
|
+
return {
|
|
3434
|
+
kind: "refused",
|
|
3435
|
+
nodeId: deps.nodeId,
|
|
3436
|
+
reason,
|
|
3437
|
+
detail
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
function errorMessage(err) {
|
|
3441
|
+
return err instanceof Error ? err.message : String(err);
|
|
3442
|
+
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Run one step over one subject. See the module header for the two invariants.
|
|
3445
|
+
*
|
|
3446
|
+
* Never throws: every failure mode is a named, logged refusal, because the
|
|
3447
|
+
* caller is a bulk pass whose counters are the only signal an operator gets and
|
|
3448
|
+
* a thrown error there reads as "the whole pass broke".
|
|
3449
|
+
*/
|
|
3450
|
+
async function runStatelessStep(deps, input) {
|
|
3451
|
+
const definition = deps.lookupStep(input.stepId);
|
|
3452
|
+
if (definition === null) return refuse(deps, input, "unknown-step", `this node's step catalog has no "${input.stepId}" — version skew, or a custom step that is not installed here`);
|
|
3453
|
+
if (input.bbox.w <= 0 || input.bbox.h <= 0) return refuse(deps, input, "unreadable-frame", `degenerate normalized bbox ${JSON.stringify(input.bbox)} — nothing to crop`);
|
|
3454
|
+
const format = await deps.executionFormat();
|
|
3455
|
+
if (format === null) return refuse(deps, input, "model-not-servable", "this node could not report the engine format it would dispatch on, so the model pin cannot be verified — running anyway would risk a silent format substitution");
|
|
3456
|
+
const servability = modelServability(definition, input.modelId, format);
|
|
3457
|
+
if (servability.kind === "no-build") return refuse(deps, input, "model-not-servable", `model "${input.modelId}" has no ${format} build (catalog ships: ${servability.availableFormats.join(", ") || "none"}) — this node would have substituted the ${format} default`);
|
|
3458
|
+
let decodeError = null;
|
|
3459
|
+
const crop = await cutFromFullFrame(input.frameJpeg, {
|
|
3460
|
+
kind: "normalized",
|
|
3461
|
+
rect: input.bbox
|
|
3462
|
+
}, deps.resolveCropConvention()).catch((err) => {
|
|
3463
|
+
decodeError = errorMessage(err);
|
|
3464
|
+
return null;
|
|
3465
|
+
});
|
|
3466
|
+
if (crop === null) return refuse(deps, input, "unreadable-frame", decodeError ?? "the supplied frame decoded to no readable dimensions");
|
|
3467
|
+
const steps = [{
|
|
3468
|
+
addonId: input.stepId,
|
|
3469
|
+
modelId: input.modelId,
|
|
3470
|
+
enabled: true
|
|
3471
|
+
}];
|
|
3472
|
+
let result;
|
|
3473
|
+
try {
|
|
3474
|
+
result = await deps.runPipeline(steps, crop.jpeg, input.sourceDeviceId);
|
|
3475
|
+
} catch (err) {
|
|
3476
|
+
return refuse(deps, input, "execution-failed", errorMessage(err));
|
|
3477
|
+
}
|
|
3478
|
+
if (result === null) return refuse(deps, input, "execution-failed", "the executor returned no frame result for the crop");
|
|
3479
|
+
const details = result.detections.map((detection) => toDetailResult(detection, input.stepId, crop));
|
|
3480
|
+
return {
|
|
3481
|
+
kind: "ran",
|
|
3482
|
+
nodeId: deps.nodeId,
|
|
3483
|
+
modelId: input.modelId,
|
|
3484
|
+
details
|
|
3485
|
+
};
|
|
3486
|
+
}
|
|
3487
|
+
//#endregion
|
|
3392
3488
|
//#region src/pipeline-runner/index.ts
|
|
3393
3489
|
var DEFAULT_CONFIG = {
|
|
3394
3490
|
maxQueueDepth: 30,
|
|
@@ -4757,6 +4853,66 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
4757
4853
|
}, input);
|
|
4758
4854
|
}
|
|
4759
4855
|
/**
|
|
4856
|
+
* Stateless single-step execution — no attach, no handle, no device
|
|
4857
|
+
* affinity. Delegates the decision table (catalog lookup, MODEL PIN
|
|
4858
|
+
* servability, crop, dispatch) to the deps-injected pure function in
|
|
4859
|
+
* {@link runStatelessStepImpl}; this method only wires the real deps.
|
|
4860
|
+
*
|
|
4861
|
+
* Note what is NOT wired, deliberately: no `resolveChildSteps` (the step
|
|
4862
|
+
* comes from the catalog, not from an attachment) and no `deviceKey` /
|
|
4863
|
+
* device jump (the jump roster is part of the attach config). The step runs
|
|
4864
|
+
* on the node's default engine, which is exactly the engine
|
|
4865
|
+
* `executionFormat` reports — so the servability check and the dispatch
|
|
4866
|
+
* cannot disagree about which format was verified.
|
|
4867
|
+
*/
|
|
4868
|
+
async runStatelessStep(input) {
|
|
4869
|
+
const convention = await this.cropConvention.refresh(this.ctxIfReady?.api);
|
|
4870
|
+
return runStatelessStep({
|
|
4871
|
+
nodeId: this.nodeId,
|
|
4872
|
+
lookupStep: (stepId) => tryGetStepDefinition(stepId),
|
|
4873
|
+
executionFormat: () => this.resolveNodeExecutionFormat(),
|
|
4874
|
+
resolveCropConvention: () => convention,
|
|
4875
|
+
runPipeline: (steps, imageJpeg, sourceDeviceId) => this.runStatelessPipeline(steps, imageJpeg, sourceDeviceId),
|
|
4876
|
+
logger: this.detailSubtreeLogger()
|
|
4877
|
+
}, input);
|
|
4878
|
+
}
|
|
4879
|
+
/**
|
|
4880
|
+
* The model format this node dispatches on when no `deviceKey` is given —
|
|
4881
|
+
* its selected engine's format. `null` on any failure, which the caller
|
|
4882
|
+
* turns into a refusal rather than a guess: a model pin that cannot be
|
|
4883
|
+
* checked is a substitution waiting to happen.
|
|
4884
|
+
*/
|
|
4885
|
+
async resolveNodeExecutionFormat() {
|
|
4886
|
+
const api = this.ctxIfReady?.api;
|
|
4887
|
+
if (!api) return null;
|
|
4888
|
+
try {
|
|
4889
|
+
return (await api.pipelineExecutor.getSelectedEngine.query()).format;
|
|
4890
|
+
} catch (err) {
|
|
4891
|
+
this.ctxIfReady?.logger.warn("runStatelessStep: could not read this node selected engine — refusing rather than guessing a format", { meta: { error: errMsg(err) } });
|
|
4892
|
+
return null;
|
|
4893
|
+
}
|
|
4894
|
+
}
|
|
4895
|
+
/**
|
|
4896
|
+
* Real `runPipeline` dep for the stateless path: node-local
|
|
4897
|
+
* `pipelineExecutor.runPipeline` on the `'full'` plane with NO `deviceKey`.
|
|
4898
|
+
*
|
|
4899
|
+
* `deviceId` is passed so the executor's own gate/substitution lines are
|
|
4900
|
+
* tagged with the source camera. It is a routing hint on other transports,
|
|
4901
|
+
* but not here: the executor is co-located in the `detection` group, so
|
|
4902
|
+
* `localProviderLink` answers in-process before any router sees it — the
|
|
4903
|
+
* call cannot leave the node this one was pinned to.
|
|
4904
|
+
*/
|
|
4905
|
+
async runStatelessPipeline(steps, imageJpeg, sourceDeviceId) {
|
|
4906
|
+
const api = this.ctxIfReady?.api;
|
|
4907
|
+
if (!api) return null;
|
|
4908
|
+
return api.pipelineExecutor.runPipeline.mutate({
|
|
4909
|
+
steps: [...steps],
|
|
4910
|
+
image: new Uint8Array(imageJpeg),
|
|
4911
|
+
deviceId: sourceDeviceId,
|
|
4912
|
+
plane: "full"
|
|
4913
|
+
});
|
|
4914
|
+
}
|
|
4915
|
+
/**
|
|
4760
4916
|
* The cluster-wide detail-crop convention — read from the orchestrator's
|
|
4761
4917
|
* global settings, NOT from the attach config. See `crop-convention-source.ts`
|
|
4762
4918
|
* for why eleven attach builders make the attach config the wrong carrier.
|
package/dist/recorder/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
2
|
-
const require_dist = require("../dist-
|
|
2
|
+
const require_dist = require("../dist-UHbKZiSa.js");
|
|
3
3
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
4
|
-
const
|
|
4
|
+
const require_model_download_service_D8B_4ktF = require("../model-download-service-D8B-4ktF-7-YxE9Wx.js");
|
|
5
|
+
let node_crypto = require("node:crypto");
|
|
5
6
|
let node_child_process = require("node:child_process");
|
|
6
7
|
let node_fs = require("node:fs");
|
|
7
8
|
let node_path = require("node:path");
|
|
8
9
|
node_path = require_chunk.__toESM(node_path);
|
|
9
|
-
let node_crypto = require("node:crypto");
|
|
10
10
|
let node_fs_promises = require("node:fs/promises");
|
|
11
11
|
//#region src/recorder/addon/calendar-scan.ts
|
|
12
12
|
/**
|
|
@@ -5235,7 +5235,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
5235
5235
|
source: this.calendar,
|
|
5236
5236
|
mfraFor: (deviceId, profile, startMs) => this.mfraTables.get(deviceId, profile, startMs),
|
|
5237
5237
|
onLog: (msg, meta) => this.ctx.logger.debug(msg, { meta })
|
|
5238
|
-
},
|
|
5238
|
+
}, require_model_download_service_D8B_4ktF.createFileDataPlaneHandler({ getRoots: () => this.playbackRoots() }));
|
|
5239
5239
|
const served = await this.ctx.dataPlane?.serve({
|
|
5240
5240
|
prefix: PLAYBACK_PREFIX,
|
|
5241
5241
|
access: "authenticated",
|
|
@@ -5709,11 +5709,11 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
5709
5709
|
return;
|
|
5710
5710
|
}
|
|
5711
5711
|
const baseHeaders = {
|
|
5712
|
-
"content-type":
|
|
5712
|
+
"content-type": require_model_download_service_D8B_4ktF.contentTypeFor(absPath),
|
|
5713
5713
|
"accept-ranges": "bytes",
|
|
5714
5714
|
"cache-control": "no-cache"
|
|
5715
5715
|
};
|
|
5716
|
-
const range =
|
|
5716
|
+
const range = require_model_download_service_D8B_4ktF.parseRangeHeader(req.headers.range, size);
|
|
5717
5717
|
const servesFinalByte = range ? range.end === size - 1 : true;
|
|
5718
5718
|
const recordDownloadOnFinish = () => {
|
|
5719
5719
|
if (req.method !== "GET" || !servesFinalByte) return;
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as recordingExportCapability, D as deriveRecordingMode, H as storageEvictableCapability, K as errMsg, Q as hydrateSchema, X as DeviceType, at as selectAssignedProfileSlots, ct as array, f as ExportRecordSchema, gt as string, h as RecordingConfigSchema, ht as record, m as OpsLogEntrySchema, nt as nodePin, q as BaseAddon, u as EVENT_PAD_MS, vt as EventCategory, z as recordingCapability } from "../dist-B-VVBzrL.mjs";
|
|
2
2
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
3
|
-
import { n as createFileDataPlaneHandler, s as parseRangeHeader, t as contentTypeFor } from "../model-download-service-
|
|
3
|
+
import { n as createFileDataPlaneHandler, s as parseRangeHeader, t as contentTypeFor } from "../model-download-service-D8B-4ktF-BB7oZL3y.mjs";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
4
5
|
import { spawn } from "node:child_process";
|
|
5
6
|
import { createReadStream, promises, realpathSync } from "node:fs";
|
|
6
7
|
import path from "node:path";
|
|
7
|
-
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { open } from "node:fs/promises";
|
|
9
9
|
//#region src/recorder/addon/calendar-scan.ts
|
|
10
10
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { a as e, c as t, d as n, i as r, l as i, n as a, o, r as s, s as c, t as l, u } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-
|
|
1
|
+
import { a as e, c as t, d as n, i as r, l as i, n as a, o, r as s, s as c, t as l, u } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-54IklEep.mjs";
|
|
2
2
|
import { a as d, i as f, n as p, o as m, r as h, t as g } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.js-C9j-2lBe.mjs";
|
|
3
3
|
import { n as _, r as v, t as y } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-XO0-Pyu6.mjs";
|
|
4
4
|
import { n as b, t as x } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-BO7TIbJV.mjs";
|
|
5
|
-
import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-
|
|
5
|
+
import { t as S } from "./_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCC9fqHH.mjs";
|
|
6
6
|
//#region ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
|
|
7
7
|
var C = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), w = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), T = (e) => {
|
|
8
8
|
let t = w(e);
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.42",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_stream_broker_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.30",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_stream_broker_widgets",
|