@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.
Files changed (31) hide show
  1. package/dist/audio-analyzer/index.js +4 -4
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/detection-pipeline/index.js +9 -9
  4. package/dist/detection-pipeline/index.mjs +3 -3
  5. package/dist/{dist-COK_1Ot8.mjs → dist-B-VVBzrL.mjs} +889 -20
  6. package/dist/{dist-BdDd3ql_.js → dist-UHbKZiSa.js} +906 -19
  7. package/dist/{event-loop-stall-monitor-DNFRgNef.js → event-loop-stall-monitor-C7_L6vHO.js} +1 -1
  8. package/dist/{event-loop-stall-monitor-CWOJtTNc.mjs → event-loop-stall-monitor-DfKdC7G4.mjs} +1 -1
  9. package/dist/{model-download-service-Cp9f4dk6-fsdDExML.js → model-download-service-D8B-4ktF-7-YxE9Wx.js} +2 -2
  10. package/dist/{model-download-service-Cp9f4dk6-CwUb5v3Y.mjs → model-download-service-D8B-4ktF-BB7oZL3y.mjs} +2 -2
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/pipeline-runner/index.js +160 -4
  14. package/dist/pipeline-runner/index.mjs +160 -4
  15. package/dist/recorder/index.js +6 -6
  16. package/dist/recorder/index.mjs +3 -3
  17. package/dist/stream-broker/_stub.js +2 -2
  18. 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
  19. 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
  20. 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
  21. package/dist/stream-broker/{hostInit-mEtjQWcr.mjs → hostInit-CKYS9yZO.mjs} +2 -2
  22. package/dist/stream-broker/index.js +16202 -15821
  23. package/dist/stream-broker/index.mjs +16201 -15820
  24. package/dist/stream-broker/remoteEntry.js +1 -1
  25. package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-C0TIfr0G.js → MaskShapeCanvas-DI4BY7W2-DxZ75vB8.js} +1 -1
  26. package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-x0SkfHwv.js → MotionZonesSettings-NcxxQN8r-BBY2Ztz5.js} +1 -1
  27. package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BoaAB4Ta.js → PrivacyMaskSettings-APgPLF7p-BC-3QmHa.js} +1 -1
  28. package/embed-dist/assets/{index-BgRdCCVy.js → index-on1wyOnd.js} +12 -12
  29. package/embed-dist/index.html +1 -1
  30. package/package.json +1 -1
  31. 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
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-BdDd3ql_.js");
1
+ const require_dist = require("./dist-UHbKZiSa.js");
2
2
  let node_perf_hooks = require("node:perf_hooks");
3
3
  //#region src/detection-pipeline/registry/model-catalogs.ts
4
4
  var HF_REPO = "camstack/camstack-models";
@@ -1,4 +1,4 @@
1
- import { a as COCO_TO_MACRO, i as COCO_80_LABELS, k as hfModelUrl, r as AUDIO_MACRO_LABELS } from "./dist-COK_1Ot8.mjs";
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
- require("node:crypto");
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
- import "node:crypto";
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-BdDd3ql_.js");
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 { M as motionDetectionCapability, O as evaluateZoneRules, W as BaseAddon, Y as hydrateSchema, q as DeviceType } from "../dist-COK_1Ot8.mjs";
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-BdDd3ql_.js");
7
- const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-DNFRgNef.js");
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, input.parent.bbox, convention);
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 { $ as nodePin, C as defineCustomActions, F as pipelineRunnerCapability, J as createEvent, N as pickDetailCropConvention, U as errMsg, W as BaseAddon, at as array, c as DEVICE_BACKEND_TO_FORMAT, ct as lazy, dt as object, ht as EventCategory, it as _enum, lt as literal, ot as boolean, pt as string, s as DEFAULT_DETAIL_CROP_CONVENTION, ut as number, w as deriveDetailCropRect, x as customAction } from "../dist-COK_1Ot8.mjs";
2
- import { c as getStepDefinition, t as attributeStall } from "../event-loop-stall-monitor-CWOJtTNc.mjs";
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, input.parent.bbox, convention);
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.
@@ -1,12 +1,12 @@
1
1
  const require_chunk = require("../chunk-emK7D4bc.js");
2
- const require_dist = require("../dist-BdDd3ql_.js");
2
+ const require_dist = require("../dist-UHbKZiSa.js");
3
3
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
4
- const require_model_download_service_Cp9f4dk6 = require("../model-download-service-Cp9f4dk6-fsdDExML.js");
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
- }, require_model_download_service_Cp9f4dk6.createFileDataPlaneHandler({ getRoots: () => this.playbackRoots() }));
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": require_model_download_service_Cp9f4dk6.contentTypeFor(absPath),
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 = require_model_download_service_Cp9f4dk6.parseRangeHeader(req.headers.range, size);
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;
@@ -1,10 +1,10 @@
1
- import { $ as nodePin, I as recordingCapability, L as recordingExportCapability, T as deriveRecordingMode, U as errMsg, W as BaseAddon, Y as hydrateSchema, at as array, d as ExportRecordSchema, ft as record, ht as EventCategory, l as EVENT_PAD_MS, m as RecordingConfigSchema, nt as selectAssignedProfileSlots, p as OpsLogEntrySchema, pt as string, q as DeviceType, z as storageEvictableCapability } from "../dist-COK_1Ot8.mjs";
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-Cp9f4dk6-CwUb5v3Y.mjs";
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-DAjAxXHm.mjs";
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-SU_6F3Y7.mjs";
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.41",
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.29",
36
+ version: "1.2.30",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_stream_broker_widgets",