@camstack/addon-decoder-ffmpeg 1.1.9 → 1.1.10

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 (3) hide show
  1. package/dist/index.js +490 -525
  2. package/dist/index.mjs +488 -523
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4635,7 +4635,7 @@ function _instanceof(cls, params = {}) {
4635
4635
  return inst;
4636
4636
  }
4637
4637
  //#endregion
4638
- //#region ../types/dist/sleep-CZDdRBua.mjs
4638
+ //#region ../types/dist/sleep-DJaTV2D7.mjs
4639
4639
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4640
4640
  EventCategory["SystemBoot"] = "system.boot";
4641
4641
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -4821,6 +4821,18 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
4821
4821
  */
4822
4822
  EventCategory["PipelineCameraUpdated"] = "pipeline.camera-updated";
4823
4823
  /**
4824
+ * The cluster camera-source OWNER changed (`clusterRoles.ingestNode`).
4825
+ * Emitted by addon-pipeline-orchestrator whenever it (re)derives node
4826
+ * capabilities — at boot, on agent online/offline, and on an ingest-node
4827
+ * flip. Carries the resolved `ownerNodeId`. The stream-broker consumes it to
4828
+ * keep its ingest-owner-gate decision current WITHOUT a per-`ensureBroker`
4829
+ * cross-process `getIngestOwner` query (push the authority's decision instead
4830
+ * of polling it on the hot path). Idempotent state — re-emitted on every
4831
+ * topology change, so a dropped event self-heals on the next one (plus the
4832
+ * broker's long backstop reconcile query).
4833
+ */
4834
+ EventCategory["PipelineIngestOwnerChanged"] = "pipeline.ingest-owner-changed";
4835
+ /**
4824
4836
  * Periodic snapshot of per-node pipeline-runner load
4825
4837
  * (`RunnerLocalLoad`). Emitted ~1Hz by every runner so UI dashboards
4826
4838
  * subscribe instead of polling `pipelineRunner.getLocalLoad`.
@@ -5344,10 +5356,6 @@ function hydrateField(field, values) {
5344
5356
  };
5345
5357
  }
5346
5358
  const rawValue = storedValue !== void 0 ? storedValue : defaultValue !== void 0 ? defaultValue : null;
5347
- if (field.type === "password") return {
5348
- ...field,
5349
- value: ""
5350
- };
5351
5359
  const value = field.type === "textarea" && field.isJson && rawValue !== null && typeof rawValue === "object" ? JSON.stringify(rawValue, null, 2) : rawValue;
5352
5360
  return {
5353
5361
  ...field,
@@ -6731,6 +6739,9 @@ function method(input, output, options) {
6731
6739
  timeoutMs: options?.timeoutMs
6732
6740
  };
6733
6741
  }
6742
+ var StaticDirOutputSchema$1 = object({ staticDir: string() });
6743
+ var VersionOutputSchema$1 = object({ version: string() });
6744
+ method(_void(), StaticDirOutputSchema$1), method(_void(), VersionOutputSchema$1);
6734
6745
  var StaticDirOutputSchema = object({ staticDir: string() });
6735
6746
  var VersionOutputSchema = object({ version: string() });
6736
6747
  method(_void(), StaticDirOutputSchema), method(_void(), VersionOutputSchema);
@@ -6912,6 +6923,36 @@ var ModelFormatsSchema = object({
6912
6923
  tflite: ModelFormatEntrySchema.optional(),
6913
6924
  pt: ModelFormatEntrySchema.optional()
6914
6925
  });
6926
+ /**
6927
+ * Variant-selector grouping axes. Shared by the full `ModelCatalogEntry` and by
6928
+ * the reduced `PipelineModelOption` returned in `pipeline.getSchema()` so the
6929
+ * grouped Family→Tier→Variant picker renders identically in the config UI and
6930
+ * in the pipeline/device steppers. The flat `id` stays the source of truth for
6931
+ * resolution/download/persistence; this is a presentation overlay resolved back
6932
+ * to an `id`.
6933
+ */
6934
+ var ModelVariantGroupSchema = object({
6935
+ /** Top-level family, e.g. `yolo26` (later `d-fine`, `rf-detr`). */
6936
+ family: string(),
6937
+ /** Size within the family, e.g. `n` | `s` | `m` | `l`. */
6938
+ tier: string(),
6939
+ /** Quantization axis. Omit ⇒ the fp32 base build. */
6940
+ precision: _enum(["fp32", "int8"]).optional(),
6941
+ /**
6942
+ * Speed-optimization axis. Omit ⇒ the standard build. `fast` marks a
6943
+ * latency-optimized export (e.g. ReLU-activation variant) — the slot the
6944
+ * future performance variants plug into.
6945
+ */
6946
+ optimization: _enum(["standard", "fast"]).optional(),
6947
+ /**
6948
+ * Input-resolution axis (square input side, px). Omit ⇒ the family's native
6949
+ * resolution (640 for yolo26). Reduced-input builds (320 / 256) are a big,
6950
+ * cheap latency lever — especially on Apple ANE and the Intel N100 — at a
6951
+ * small-object accuracy cost. Mirrors the model's `inputSize` but lifted onto
6952
+ * the group so the selector can offer it as a variant axis.
6953
+ */
6954
+ resolution: number().int().positive().optional()
6955
+ });
6915
6956
  var ModelCatalogEntrySchema = object({
6916
6957
  id: string(),
6917
6958
  name: string(),
@@ -6941,7 +6982,43 @@ var ModelCatalogEntrySchema = object({
6941
6982
  * Auxiliary files required at runtime (labels JSON, charset dict, etc.).
6942
6983
  * Downloaded into the same modelsDir alongside the model file.
6943
6984
  */
6944
- extraFiles: array(ModelExtraFileSchema).readonly().optional()
6985
+ extraFiles: array(ModelExtraFileSchema).readonly().optional(),
6986
+ /**
6987
+ * LEGACY entry — retained in the catalog so a persisted operator selection
6988
+ * still RESOLVES (and can be re-activated), but hidden from the selectable
6989
+ * model list and excluded from the auto format-default pick. Set on the
6990
+ * superseded / consolidated models (older lineages, redundant fp16 IRs) so
6991
+ * the active lineup stays the coherent curated ladder without deleting a
6992
+ * model anyone may still be pinned to. `resolveModelForFormat` keeps honoring
6993
+ * an explicit legacy id that has a build for the node's format.
6994
+ */
6995
+ legacy: boolean().optional(),
6996
+ /**
6997
+ * Measured quality/latency metadata — populated from the benchmark addon on
6998
+ * the real node classes. Absent = not yet measured (most entries today; the
6999
+ * catalog historically carried only `sizeMB`, a poor cross-architecture
7000
+ * speed proxy). `p95LatencyMs` is keyed by node class (e.g. `n100`, `mac`).
7001
+ */
7002
+ metrics: object({
7003
+ map50: number().optional(),
7004
+ p95LatencyMs: record(string(), number()).optional()
7005
+ }).optional(),
7006
+ /**
7007
+ * SPDX-ish license id of the model weights (e.g. `AGPL-3.0` for Ultralytics
7008
+ * YOLO26, `GPL-3.0` for YOLOv9, `Apache-2.0` for D-FINE/RF-DETR). Matters for
7009
+ * the retraining addon and any future commercial distribution.
7010
+ */
7011
+ license: string().optional(),
7012
+ /**
7013
+ * Variant-selector grouping. The UI groups models by `family` + `tier` and
7014
+ * offers `precision` / `optimization` as variant axes WITHIN a tier — so all
7015
+ * of a family's sizes and quantizations collapse into one grouped picker
7016
+ * instead of a flat list of `yolo26s`, `yolo26s-int8`, … Absent ⇒ ungrouped
7017
+ * (legacy / custom models) — never shown in the grouped selector. The flat
7018
+ * `id` stays the source of truth for resolution/download/persistence; grouping
7019
+ * is a presentation overlay resolved back to an `id`.
7020
+ */
7021
+ group: ModelVariantGroupSchema.optional()
6945
7022
  });
6946
7023
  var ConvertTargetSchema = discriminatedUnion("format", [object({
6947
7024
  format: literal("openvino"),
@@ -7002,8 +7079,8 @@ var RecordingModeSchema = _enum([
7002
7079
  "onAudioThreshold"
7003
7080
  ]);
7004
7081
  /**
7005
- * First-class, authoritative per-camera storage mode — the netta choice the UI
7006
- * reads directly (never inferred from `rules`):
7082
+ * First-class, authoritative per-camera storage mode — the explicit choice the
7083
+ * UI reads directly (never inferred from `rules`):
7007
7084
  * - `off` — not recording.
7008
7085
  * - `events` — record only around triggers (motion / audio threshold),
7009
7086
  * with pre/post-buffer.
@@ -8651,26 +8728,13 @@ DeviceType.Light, method(object({
8651
8728
  percentage: number().min(0).max(100),
8652
8729
  lastChangedAt: number()
8653
8730
  });
8731
+ /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
8654
8732
  var StreamFormatSchema = _enum([
8655
8733
  "webrtc",
8656
8734
  "hls",
8657
8735
  "mjpeg",
8658
8736
  "rtsp"
8659
8737
  ]);
8660
- var StreamInfoSchema = object({
8661
- streamId: string(),
8662
- format: StreamFormatSchema,
8663
- url: string().nullable(),
8664
- active: boolean()
8665
- });
8666
- method(object({
8667
- streamId: string(),
8668
- sourceUrl: string(),
8669
- codec: string().optional()
8670
- }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), _void(), { kind: "mutation" }), method(object({
8671
- streamId: string(),
8672
- format: StreamFormatSchema
8673
- }), string().nullable()), method(_void(), array(StreamInfoSchema));
8674
8738
  var RtspRestreamEntrySchema = object({
8675
8739
  brokerId: string(),
8676
8740
  url: string(),
@@ -9335,7 +9399,7 @@ var ConsumablesStatusSchema = object({
9335
9399
  })),
9336
9400
  lastChangedAt: number()
9337
9401
  });
9338
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, method(object({
9402
+ Object.values(DeviceType), method(object({
9339
9403
  deviceId: number().int().nonnegative(),
9340
9404
  key: string().min(1)
9341
9405
  }), _void(), {
@@ -10250,7 +10314,7 @@ var BoundingBoxSchema = object({
10250
10314
  w: number(),
10251
10315
  h: number()
10252
10316
  });
10253
- var SpatialDetectionSchema = object({
10317
+ object({
10254
10318
  class: string(),
10255
10319
  originalClass: string(),
10256
10320
  score: number(),
@@ -10385,7 +10449,6 @@ var PipelineDefaultStepSchema = lazy(() => object({
10385
10449
  enabled: boolean(),
10386
10450
  modelId: string(),
10387
10451
  children: array(PipelineDefaultStepSchema).readonly(),
10388
- engine: PipelineEngineChoiceSchema.optional(),
10389
10452
  group: string().optional(),
10390
10453
  settings: record(string(), unknown()).optional()
10391
10454
  }));
@@ -10410,7 +10473,9 @@ var PipelineModelOptionSchema = object({
10410
10473
  formats: record(string(), object({
10411
10474
  downloaded: boolean(),
10412
10475
  sizeMB: number()
10413
- }))
10476
+ })),
10477
+ group: ModelVariantGroupSchema.optional(),
10478
+ legacy: boolean().optional()
10414
10479
  });
10415
10480
  var ConfigFieldBridge = custom();
10416
10481
  var PipelineAddonSchemaSchema = object({
@@ -10440,11 +10505,6 @@ var PipelineSchemaSchema = object({
10440
10505
  selectedEngine: PipelineEngineChoiceSchema,
10441
10506
  slots: array(PipelineSlotSchemaSchema).readonly()
10442
10507
  });
10443
- var DetectorOutputSchema = object({
10444
- detections: array(SpatialDetectionSchema).readonly(),
10445
- inferenceMs: number(),
10446
- modelId: string()
10447
- });
10448
10508
  var EngineProvisioningSchema = object({
10449
10509
  runtimeId: _enum([
10450
10510
  "onnx",
@@ -10461,15 +10521,42 @@ var EngineProvisioningSchema = object({
10461
10521
  ]),
10462
10522
  progress: number().optional(),
10463
10523
  error: string().optional(),
10464
- nextRetryAt: number().optional()
10524
+ nextRetryAt: number().optional(),
10525
+ /**
10526
+ * Gate A (config-correctness gate at engine change): human-readable
10527
+ * config issues surfaced EAGERLY when the node's engine changes — model
10528
+ * substitutions ("chose X, running Y") and zero-build steps ("no model
10529
+ * has a <format> build"). Additive/optional: informational only, never
10530
+ * enforced here — `assertEngineReady` (readiness) still gates inference.
10531
+ * Absent/empty when the node-default tree resolves cleanly.
10532
+ */
10533
+ configIssues: array(string()).optional()
10465
10534
  });
10466
10535
  var PipelineStepInputSchema = lazy(() => object({
10467
10536
  addonId: string(),
10468
- modelId: string(),
10537
+ modelId: string().optional(),
10469
10538
  enabled: boolean().default(true),
10470
10539
  children: array(PipelineStepInputSchema).optional(),
10471
10540
  settings: record(string(), unknown()).optional()
10472
10541
  }));
10542
+ var ModelSubstitutionSchema = object({
10543
+ addonId: string(),
10544
+ chosen: string(),
10545
+ running: string(),
10546
+ format: string()
10547
+ });
10548
+ var PipelineValidationIssueSchema = object({
10549
+ addonId: string(),
10550
+ kind: _enum(["unknown-addon", "no-format-build"]),
10551
+ detail: string()
10552
+ });
10553
+ var PipelineValidationResultSchema = object({
10554
+ ok: boolean(),
10555
+ issues: array(PipelineValidationIssueSchema).readonly(),
10556
+ substitutions: array(ModelSubstitutionSchema).readonly(),
10557
+ /** The node's `currentEngine.format` this validation ran against. */
10558
+ format: string()
10559
+ });
10473
10560
  var ReferenceImageEntrySchema = object({
10474
10561
  filename: string(),
10475
10562
  stepIds: array(string()).readonly().optional()
@@ -10540,7 +10627,13 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10540
10627
  })) }), object({ success: literal(true) }), {
10541
10628
  kind: "mutation",
10542
10629
  auth: "admin"
10543
- }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
10630
+ }), method(object({ nodeId: string() }), object({
10631
+ success: literal(true),
10632
+ clearedDevices: number()
10633
+ }), {
10634
+ kind: "mutation",
10635
+ auth: "admin"
10636
+ }), method(_void(), PipelineSchemaSchema), method(_void(), array(PipelineDefaultStepSchema).readonly().nullable()), method(_void(), PipelineConfigBridge), method(_void(), ConfigUISchemaBridge), method(object({ steps: array(PipelineStepInputSchema) }), PipelineValidationResultSchema), method(_void(), array(PipelineTemplateSchema$1).readonly()), method(object({
10544
10637
  name: string(),
10545
10638
  steps: array(PipelineTemplateStepSchema).readonly(),
10546
10639
  engine: PipelineEngineChoiceSchema
@@ -10557,10 +10650,6 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
10557
10650
  modelId: string(),
10558
10651
  format: ModelFormatSchema$1
10559
10652
  }), object({ success: literal(true) }), { kind: "mutation" }), method(object({
10560
- addonId: string(),
10561
- frame: FrameInputSchema,
10562
- config: record(string(), unknown()).optional()
10563
- }), DetectorOutputSchema), method(object({
10564
10653
  engine: PipelineEngineChoiceSchema.optional(),
10565
10654
  steps: array(PipelineStepInputSchema).min(1),
10566
10655
  frame: FrameInputSchema.optional(),
@@ -10800,6 +10889,13 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
10800
10889
  kind: literal("remote-restream"),
10801
10890
  /** The camera's source-owner node (slice 1: always the hub). */
10802
10891
  ownerNodeId: string(),
10892
+ /**
10893
+ * The owner's LAN-reachable host, resolved by the orchestrator from the
10894
+ * per-node `reachableHost` override (Cluster UI). When present the runner
10895
+ * dials THIS host for the owner's restream, in preference to the
10896
+ * `CAMSTACK_HUB_URL`-derived default. Absent → auto-detect fallback.
10897
+ */
10898
+ ownerReachableHost: string().optional(),
10803
10899
  /** Operator override for the owner host the runner dials. */
10804
10900
  hubHostnameOverride: string().optional()
10805
10901
  })]).describe("Per-camera frame-source mode for the runner (P2c)");
@@ -10808,13 +10904,11 @@ var RunnerFrameSourceSchema = discriminatedUnion("kind", [object({ kind: literal
10808
10904
  * specific runner instance via `attachCamera`. Carries everything the
10809
10905
  * runner needs to subscribe to the local broker and execute inference.
10810
10906
  *
10811
- * Stateless-pipeline model: the full pipeline content (`engine`, `steps`,
10812
- * optional `audio`) travels with the attach payload. The runner keeps it
10813
- * in RAM for the lifetime of the attach — on rebalance, edit, or
10814
- * restart the orchestrator re-sends the latest snapshot.
10815
- *
10816
- * `engine`/`steps`/`audio` are optional during the additive migration
10817
- * window; once orchestrator + UI are migrated they become required.
10907
+ * Stateless-pipeline model: the pipeline content (`steps`, optional
10908
+ * `audio`) travels with the attach payload. The runner keeps it in RAM
10909
+ * for the lifetime of the attach — on rebalance, edit, or restart the
10910
+ * orchestrator re-sends the latest snapshot. Engine is NOT carried: it is
10911
+ * node-local, resolved by the executing runner at dispatch time.
10818
10912
  */
10819
10913
  var RunnerCameraConfigSchema = object({
10820
10914
  deviceId: number(),
@@ -10865,14 +10959,11 @@ var RunnerCameraConfigSchema = object({
10865
10959
  */
10866
10960
  motionSources: MotionSourcesSchema.default(["analyzer"]),
10867
10961
  pipelineEnabled: boolean().default(true),
10868
- /** Engine choice for video steps (runtime+backend+format). */
10869
- engine: PipelineEngineChoiceSchema.optional(),
10870
10962
  /** Ordered tree of video steps. Absent → runner skips video detection. */
10871
10963
  steps: array(PipelineStepInputSchema).readonly().optional(),
10872
10964
  /** Audio classification branch. `enabled:false` disables, null skips. */
10873
10965
  audio: object({
10874
- engine: PipelineEngineChoiceSchema,
10875
- modelId: string(),
10966
+ modelId: string().optional(),
10876
10967
  enabled: boolean()
10877
10968
  }).nullable().optional(),
10878
10969
  /**
@@ -12253,7 +12344,9 @@ var AddonPageDeclarationSchema$1 = object({
12253
12344
  icon: string(),
12254
12345
  path: string(),
12255
12346
  remoteName: string(),
12256
- bundle: string()
12347
+ bundle: string(),
12348
+ section: string().optional(),
12349
+ sectionLabel: string().optional()
12257
12350
  });
12258
12351
  var AddonPageInfoSchema = object({
12259
12352
  addonId: string(),
@@ -12293,7 +12386,18 @@ var AddonPageDeclarationSchema = object({
12293
12386
  * the static-file route can compute an mtime-based cache-buster URL
12294
12387
  * without a separate filesystem stat.
12295
12388
  */
12296
- bundle: string()
12389
+ bundle: string(),
12390
+ /**
12391
+ * Sidebar section this page docks into. Well-known ids: `'detection'`,
12392
+ * `'cluster'`, `'administration'` — the page renders inside that group.
12393
+ * Any OTHER string creates (or joins) a custom section rendered after
12394
+ * the built-in groups; its label comes from `sectionLabel` (first
12395
+ * declaration wins), falling back to the id. Absent → the legacy
12396
+ * "Addon Pages" group.
12397
+ */
12398
+ section: string().optional(),
12399
+ /** Display label for a CUSTOM `section` id (ignored for well-known ids). */
12400
+ sectionLabel: string().optional()
12297
12401
  });
12298
12402
  method(_void(), array(AddonPageDeclarationSchema).readonly());
12299
12403
  var AddonHttpRouteSchema = object({
@@ -12509,6 +12613,17 @@ var WidgetMetadataSchema = object({
12509
12613
  deviceContext: boolean().default(false),
12510
12614
  integrationContext: boolean().default(false)
12511
12615
  }),
12616
+ /**
12617
+ * Loadable BEFORE authentication. The normal widget registry listing
12618
+ * (`addon-widgets.listWidgets`) is auth-gated, so a pre-auth surface
12619
+ * (the login page) cannot discover a widget through it. A widget that
12620
+ * declares `preAuth: true` marks itself as safe to mount on a pre-auth
12621
+ * screen — it is surfaced through the PUBLIC `auth.listLoginMethods`
12622
+ * login-method contribution channel (see `login-method.cap.ts`) rather
12623
+ * than the authenticated registry, and its bundle is served by the
12624
+ * public `/api/addon-widgets/:addonId/*` static route. Defaults false.
12625
+ */
12626
+ preAuth: boolean().optional().default(false),
12512
12627
  /** Dashboard placement HINTS (operator can override per instance). */
12513
12628
  defaultSize: WidgetSizeEnum.default("md"),
12514
12629
  allowedSizes: array(WidgetSizeEnum).readonly().default([
@@ -12847,6 +12962,66 @@ method(object({
12847
12962
  password: string()
12848
12963
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
12849
12964
  /**
12965
+ * `login-method` — collection cap through which auth addons contribute
12966
+ * their pre-auth login surfaces to the login page. This is the SINGLE,
12967
+ * generic mechanism that supersedes the dead `auth.listProviders` reader:
12968
+ * every auth addon (OIDC, magic-link, WebAuthn/passkey) registers a
12969
+ * `login-method` provider and the PUBLIC `auth.listLoginMethods`
12970
+ * procedure aggregates them for the unauthenticated login page.
12971
+ *
12972
+ * A contribution is a discriminated union on `kind`:
12973
+ *
12974
+ * - `redirect` — a declarative button. The login page renders a generic
12975
+ * button that navigates to `startUrl` (an addon-owned HTTP route).
12976
+ * Covers OIDC (`/addon/auth-oidc/<id>/start`) and magic-link with
12977
+ * ZERO shell-side JS. A future SSO addon plugs in the same way — the
12978
+ * login page needs NO change.
12979
+ *
12980
+ * - `widget` — a Module-Federation widget the login page mounts (via
12981
+ * `loadRemoteBundle`) for an in-page ceremony. Covers the passkey
12982
+ * login ceremony, which must run `@simplewebauthn/browser` INSIDE the
12983
+ * addon bundle. The referenced widget also declares `preAuth: true` in
12984
+ * its `addon-widgets-source` catalog entry. `auth.listLoginMethods`
12985
+ * stamps a public `bundleUrl` from `addonId` + `bundle`.
12986
+ *
12987
+ * Every contribution carries a `stage`:
12988
+ * - `primary` — shown on the first credentials screen (OIDC /
12989
+ * magic-link buttons; a future usernameless passkey).
12990
+ * - `second-factor` — shown AFTER the password leg, gated on the
12991
+ * returned `factors` (passkey-as-2FA today).
12992
+ *
12993
+ * `mount: skip` — the cap is read server-side by the core auth router
12994
+ * (`registry.getCollection('login-method')`), never mounted as its own
12995
+ * tRPC router.
12996
+ */
12997
+ /** When a login method renders in the two-phase login flow. */
12998
+ var LoginStageEnum = _enum(["primary", "second-factor"]);
12999
+ /** One login-method contribution — redirect button OR pre-auth widget. */
13000
+ var LoginMethodContributionSchema = discriminatedUnion("kind", [object({
13001
+ kind: literal("redirect"),
13002
+ /** Stable id within the login-method set (e.g. `auth-oidc/google`). */
13003
+ id: string(),
13004
+ /** Operator-facing button label. */
13005
+ label: string(),
13006
+ /** lucide-react icon name. */
13007
+ icon: string().optional(),
13008
+ /** Addon-owned HTTP route the button navigates to (GET). */
13009
+ startUrl: string(),
13010
+ stage: LoginStageEnum
13011
+ }), object({
13012
+ kind: literal("widget"),
13013
+ /** Stable id within the login-method set (e.g. `auth-webauthn/passkey-login`). */
13014
+ id: string(),
13015
+ /** Owning addon id — drives the public bundle URL + the MF namespace. */
13016
+ addonId: string(),
13017
+ /** Bundle filename inside the addon's dist dir (`remoteEntry.js`). */
13018
+ bundle: string(),
13019
+ /** MF remote descriptor — `{ remoteName, exposedModule, componentKey }`. */
13020
+ remote: WidgetRemoteSchema,
13021
+ stage: LoginStageEnum
13022
+ })]);
13023
+ method(_void(), array(LoginMethodContributionSchema).readonly());
13024
+ /**
12850
13025
  * Orchestrator-side destination metadata. The orchestrator computes
12851
13026
  * `id = <addonId>:<subId>` from its provider lookup so consumers
12852
13027
  * (admin UI, restore flow) see one canonical key.
@@ -15228,11 +15403,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
15228
15403
  timestamp: number()
15229
15404
  });
15230
15405
  var CameraPipelineConfigSchema = object({
15231
- engine: PipelineEngineChoiceSchema,
15406
+ engine: PipelineEngineChoiceSchema.optional(),
15232
15407
  steps: array(PipelineStepInputSchema).readonly(),
15233
15408
  audio: object({
15234
- engine: PipelineEngineChoiceSchema,
15235
- modelId: string(),
15409
+ engine: PipelineEngineChoiceSchema.optional(),
15410
+ modelId: string().optional(),
15236
15411
  enabled: boolean(),
15237
15412
  settings: record(string(), unknown()).readonly().optional()
15238
15413
  }).nullable().optional()
@@ -15247,7 +15422,7 @@ var PipelineTemplateSchema = object({
15247
15422
  });
15248
15423
  var AgentAddonConfigSchema = object({
15249
15424
  enabled: boolean(),
15250
- modelId: string(),
15425
+ modelId: string().optional(),
15251
15426
  settings: record(string(), unknown()).readonly()
15252
15427
  });
15253
15428
  var AgentPipelineSettingsSchema = object({
@@ -15257,12 +15432,25 @@ var AgentPipelineSettingsSchema = object({
15257
15432
  detectWeight: number().positive().optional(),
15258
15433
  /** Node is eligible to run the detection pipeline (decode + inference). */
15259
15434
  detect: boolean().optional(),
15260
- /** Node is eligible to host decoder sessions. */
15435
+ /**
15436
+ * DEPRECATED AND IGNORED. Decode is always co-located with its frame
15437
+ * consumer, so decode eligibility IS detect eligibility. Kept optional in
15438
+ * the schema ONLY so persisted stores written before the removal still
15439
+ * parse — no code reads it and no write path emits it.
15440
+ */
15261
15441
  decode: boolean().optional(),
15262
15442
  /** Node is eligible to run audio-analyzer sessions. */
15263
15443
  audio: boolean().optional(),
15264
15444
  /** Node is eligible to be the ingest / source-owner (serve the restream). */
15265
- ingest: boolean().optional()
15445
+ ingest: boolean().optional(),
15446
+ /**
15447
+ * Operator override for the LAN host a cross-node decoder dials to reach
15448
+ * THIS node's restream (Cluster UI). Absent → auto-detect: a remote runner
15449
+ * falls back to its `CAMSTACK_HUB_URL`-derived host (the Moleculer address
15450
+ * it already uses to reach the hub). Set this only when the auto-detected
15451
+ * address is wrong (multi-homed host, NAT, custom interface).
15452
+ */
15453
+ reachableHost: string().optional()
15266
15454
  });
15267
15455
  var CameraPipelineForAgentSchema = object({
15268
15456
  steps: array(PipelineStepInputSchema).readonly(),
@@ -15310,25 +15498,6 @@ var PipelineAssignmentSchema = object({
15310
15498
  assignedAt: number()
15311
15499
  });
15312
15500
  /**
15313
- * Decoder placement record. Symmetric to `PipelineAssignmentSchema` but for
15314
- * the decoder-node placement domain (`balanceDecoder` decision: manual pin
15315
- * → co-located with pipeline → capacity).
15316
- */
15317
- var DecoderAssignmentSchema = object({
15318
- deviceId: number(),
15319
- /** Moleculer node id of the decoder provider currently responsible for this camera. */
15320
- decoderNodeId: string(),
15321
- /** True when the assignment was set manually via `assignDecoder`, false when chosen by the balancer. */
15322
- pinned: boolean(),
15323
- /** Why this assignment was made — useful for debugging the decoder balancer. */
15324
- reason: _enum([
15325
- "manual",
15326
- "co-located",
15327
- "capacity",
15328
- "hardware-affinity"
15329
- ])
15330
- });
15331
- /**
15332
15501
  * Per-agent load summary surfaced to the load balancer + dashboards.
15333
15502
  * Aggregated from each runner's `getLocalLoad` cap call.
15334
15503
  */
@@ -15368,6 +15537,15 @@ var GlobalMetricsSchema = object({
15368
15537
  * capability providers.
15369
15538
  */
15370
15539
  var CapabilityBindingsSchema = record(string(), string());
15540
+ /**
15541
+ * The cluster's single camera-source owner (`clusterRoles.ingestNode`) plus
15542
+ * its LAN-reachable host, if one is registered. See `getIngestOwner`.
15543
+ */
15544
+ var IngestOwnerSchema = object({
15545
+ ownerNodeId: string(),
15546
+ reachableHost: string().optional(),
15547
+ configIssue: string().optional()
15548
+ });
15371
15549
  /** Source block — always present; derives from the stream catalog. */
15372
15550
  var CameraSourceStatusSchema = object({ streams: array(object({
15373
15551
  camStreamId: string(),
@@ -15382,6 +15560,14 @@ var CameraAssignmentStatusSchema = object({
15382
15560
  detectionNodeId: string().nullable(),
15383
15561
  decoderNodeId: string().nullable(),
15384
15562
  audioNodeId: string().nullable(),
15563
+ /**
15564
+ * The node that OWNS this camera's physical source pull (dials the RTSP and
15565
+ * hosts the broker/restream) — the cluster ingest owner today
15566
+ * (`clusterRoles.ingestNode`), per-camera once source assignment lands. Lets
15567
+ * the UI show WHERE a camera is sourced without SSH/logs, and is the node the
15568
+ * broker block below was read from (pinned). Nullable only pre-wiring.
15569
+ */
15570
+ sourceNodeId: string().nullable(),
15385
15571
  pinned: object({
15386
15572
  detection: boolean(),
15387
15573
  decoder: boolean(),
@@ -15514,16 +15700,7 @@ method(object({
15514
15700
  }), object({ success: literal(true) }), {
15515
15701
  kind: "mutation",
15516
15702
  auth: "admin"
15517
- }), method(object({
15518
- deviceId: number(),
15519
- nodeId: string()
15520
- }), _void(), {
15521
- kind: "mutation",
15522
- auth: "admin"
15523
- }), method(object({ deviceId: number() }), _void(), {
15524
- kind: "mutation",
15525
- auth: "admin"
15526
- }), method(_void(), array(DecoderAssignmentSchema).readonly()), method(object({
15703
+ }), method(_void(), IngestOwnerSchema), method(object({
15527
15704
  deviceId: number(),
15528
15705
  nodeId: string()
15529
15706
  }), object({ success: literal(true) }), {
@@ -15544,10 +15721,7 @@ method(object({
15544
15721
  nodeId: string(),
15545
15722
  pinned: boolean(),
15546
15723
  assignedAt: number()
15547
- }))), method(object({
15548
- deviceId: number(),
15549
- pipelineNodeId: string().optional()
15550
- }), DecoderAssignmentSchema), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
15724
+ }))), method(object({ agentNodeId: string() }), AgentPipelineSettingsSchema.nullable()), method(_void(), array(object({
15551
15725
  nodeId: string(),
15552
15726
  settings: AgentPipelineSettingsSchema
15553
15727
  })).readonly()), method(object({
@@ -15577,12 +15751,26 @@ method(object({
15577
15751
  }), method(object({
15578
15752
  agentNodeId: string(),
15579
15753
  detect: boolean().nullable().optional(),
15580
- decode: boolean().nullable().optional(),
15581
15754
  audio: boolean().nullable().optional(),
15582
15755
  ingest: boolean().nullable().optional()
15583
15756
  }), object({ success: literal(true) }), {
15584
15757
  kind: "mutation",
15585
15758
  auth: "admin"
15759
+ }), method(object({
15760
+ agentNodeId: string(),
15761
+ reachableHost: string().nullable()
15762
+ }), object({ success: literal(true) }), {
15763
+ kind: "mutation",
15764
+ auth: "admin"
15765
+ }), method(object({ agentNodeId: string() }), object({
15766
+ success: literal(true),
15767
+ /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
15768
+ effectiveModelId: string().nullable(),
15769
+ /** Number of cameras whose node-scoped overrides were cleared. */
15770
+ clearedCameraOverrides: number()
15771
+ }), {
15772
+ kind: "mutation",
15773
+ auth: "admin"
15586
15774
  }), method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()), method(object({
15587
15775
  deviceId: number(),
15588
15776
  addonId: string(),
@@ -15627,22 +15815,131 @@ method(object({
15627
15815
  kind: "mutation",
15628
15816
  auth: "admin"
15629
15817
  });
15630
- var RegisteredStreamSchema = object({
15631
- streamId: string(),
15632
- label: string().optional(),
15633
- codec: string(),
15634
- type: _enum(["video", "audio"]),
15635
- sourceUrl: string()
15818
+ /**
15819
+ * server-management — per-NODE singleton capability for a node's ROOT
15820
+ * package lifecycle (runtime-updatable node packages, phase 2: hub + docker
15821
+ * agents).
15822
+ *
15823
+ * Each node's root package (`@camstack/server` on the hub, `@camstack/agent`
15824
+ * on agents) carries the whole software stack in its npm dep tree, so ONE
15825
+ * version describes the node. Updates install into
15826
+ * `<dataDir>/server-root/versions/<v>/` and apply on restart via the baked
15827
+ * starter (probation boot + auto-rollback to N-1).
15828
+ *
15829
+ * Providers:
15830
+ * - HUB: `ServerUpdateService` behind the `server-provided` mount
15831
+ * (`buildServerProviders` in trpc.router.ts) — the default target for
15832
+ * unpinned calls.
15833
+ * - AGENT: `AgentUpdateService` registered by the agent bootstrap under
15834
+ * the synthetic `agent-runtime` addonId and declared in the agent's
15835
+ * `$hub.registerNode` manifest.
15836
+ *
15837
+ * Node routing: singleton caps get the codegen/runtime-builder `nodeId`
15838
+ * injection on every method — `input.nodeId` (or `nodePin(nodeId)` from the
15839
+ * SDK) routes the call to that node's provider via the standard remote
15840
+ * proxy (`createCapabilityProxy` → `$agent-cap-fwd` → the agent's
15841
+ * in-process provider lookup). No `nodeId` → the hub's own provider.
15842
+ *
15843
+ * Spec: docs/superpowers/specs/2026-07-12-runtime-updatable-node-packages-design.md
15844
+ */
15845
+ /**
15846
+ * Where the running hub's code was loaded from:
15847
+ * - `workspace` — dev checkout (tsx / workspace dist); the starter defers to
15848
+ * plain resolution and runtime updates are refused.
15849
+ * - `baked` — the immutable image seed closure (no data-dir root active).
15850
+ * - `data-root` — the runtime-updatable `<dataDir>/server-root` closure.
15851
+ */
15852
+ var ServerBootModeSchema = _enum([
15853
+ "workspace",
15854
+ "baked",
15855
+ "data-root"
15856
+ ]);
15857
+ /**
15858
+ * Update lifecycle state:
15859
+ * - `idle` / `checking` / `staging` — steady / in-flight registry work.
15860
+ * - `pending-restart` — a version is staged and the node has NOT yet
15861
+ * restarted onto it (still running the OLD version).
15862
+ * - `awaiting-confirmation` — the node HAS restarted onto the staged version
15863
+ * (it is the active probation boot) and is waiting to confirm boot-health.
15864
+ * Apply/rollback are refused in this state and the node must NOT be
15865
+ * manually restarted, or the probation boot auto-rolls-back.
15866
+ */
15867
+ var ServerUpdateStateSchema = _enum([
15868
+ "idle",
15869
+ "checking",
15870
+ "staging",
15871
+ "pending-restart",
15872
+ "awaiting-confirmation"
15873
+ ]);
15874
+ var ServerRollbackInfoSchema = object({
15875
+ /** The version that failed (or was manually rolled back). */
15876
+ fromVersion: string(),
15877
+ /** The version rolled back to; null = the baked seed. */
15878
+ toVersion: string().nullable(),
15879
+ atMs: number(),
15880
+ reason: string()
15636
15881
  });
15637
- var ExposedResourceSchema = object({
15638
- streamId: string(),
15639
- format: string(),
15640
- value: string()
15882
+ var ServerPackageStatusSchema = object({
15883
+ /** Root package name (`@camstack/server` on the hub). */
15884
+ packageName: string(),
15885
+ /** Version of the code the running process ACTUALLY loaded. */
15886
+ runningVersion: string().nullable(),
15887
+ /** Node.js runtime version the node's process runs on (`process.versions.node`). */
15888
+ nodeRuntimeVersion: string().nullable(),
15889
+ /** Active data-dir root version; null when booted from seed/workspace. */
15890
+ activeVersion: string().nullable(),
15891
+ /** N-1 version kept for rollback; null when no previous version exists. */
15892
+ previousVersion: string().nullable(),
15893
+ /** Version of the immutable baked seed closure (image fallback). */
15894
+ seedVersion: string().nullable(),
15895
+ /** Latest registry version from the most recent check (null = never checked). */
15896
+ latestVersion: string().nullable(),
15897
+ updateAvailable: boolean(),
15898
+ bootMode: ServerBootModeSchema,
15899
+ updateState: ServerUpdateStateSchema,
15900
+ /** Version staged + awaiting its probation boot, when one is pending. */
15901
+ pendingVersion: string().nullable(),
15902
+ /** Set when the last freshly-activated version failed its boot health-check. */
15903
+ rolledBack: ServerRollbackInfoSchema.nullable(),
15904
+ /**
15905
+ * True when `server-root/state.json` EXISTS but is unreadable/corrupt — the
15906
+ * hub is running from the baked seed (or workspace) while installed data-dir
15907
+ * versions are being IGNORED. Surfaced as a warning in the UI.
15908
+ */
15909
+ stateFileCorrupt: boolean(),
15910
+ lastCheckedAtMs: number().nullable()
15911
+ });
15912
+ var ServerUpdateCheckResultSchema = object({
15913
+ packageName: string(),
15914
+ runningVersion: string().nullable(),
15915
+ latestVersion: string().nullable(),
15916
+ updateAvailable: boolean(),
15917
+ checkedAtMs: number(),
15918
+ /** Non-null when the registry lookup failed (offline, bad registry, …). */
15919
+ error: string().nullable()
15920
+ });
15921
+ var ServerUpdateActionResultSchema = object({
15922
+ accepted: boolean(),
15923
+ targetVersion: string().nullable(),
15924
+ /** True when a graceful restart was scheduled to apply the change. */
15925
+ restarting: boolean(),
15926
+ message: string()
15927
+ });
15928
+ method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
15929
+ kind: "mutation",
15930
+ auth: "admin"
15931
+ }), method(object({
15932
+ /** Explicit target version; omitted = latest from the registry. */
15933
+ version: string().optional() }), ServerUpdateActionResultSchema, {
15934
+ kind: "mutation",
15935
+ auth: "admin"
15936
+ }), method(_void(), ServerUpdateActionResultSchema, {
15937
+ kind: "mutation",
15938
+ auth: "admin"
15939
+ }), method(_void(), ServerUpdateActionResultSchema, {
15940
+ kind: "mutation",
15941
+ auth: "admin"
15641
15942
  });
15642
- method(object({
15643
- deviceId: number(),
15644
- streams: array(RegisteredStreamSchema).readonly()
15645
- }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(ExposedResourceSchema).readonly());
15646
15943
  /**
15647
15944
  * Query filter for settings-store collections.
15648
15945
  */
@@ -15795,9 +16092,9 @@ method(SendEmailInputSchema, SendEmailResultSchema, {
15795
16092
  /**
15796
16093
  * A single device snapshot returned as base64 JPEG/PNG.
15797
16094
  *
15798
- * Shared with the `snapshot-provider` collection cap the orchestrator
15799
- * receives the same shape from each native provider and from the
15800
- * broker-based fallback.
16095
+ * The `SnapshotAddon` wrapper returns this shape whether the frame came from
16096
+ * the device-native provider (onboard capture) or from the stream-broker
16097
+ * prebuffer fallback.
15801
16098
  */
15802
16099
  var SnapshotImageSchema = object({
15803
16100
  base64: string(),
@@ -15829,10 +16126,6 @@ DeviceType.Camera, method(object({
15829
16126
  kind: "mutation",
15830
16127
  auth: "admin"
15831
16128
  });
15832
- method(object({ deviceId: number() }), boolean()), method(object({
15833
- deviceId: number(),
15834
- streamId: string().optional()
15835
- }), SnapshotImageSchema.nullable());
15836
16129
  /**
15837
16130
  * `sso-bridge` — internal hub-only cap that lets SSO-style auth
15838
16131
  * providers (OIDC, SAML, magic-link, …) mint an HMAC-signed token
@@ -16193,9 +16486,10 @@ method(object({
16193
16486
  auth: "admin"
16194
16487
  });
16195
16488
  /**
16196
- * Optional client-side hints sent at session creation to help the
16197
- * provider pick the best native source. All fields are optional —
16198
- * a viewer that knows nothing still gets a sane default.
16489
+ * Optional client-side hints sent at session creation to help the provider
16490
+ * pick the best native source. All fields optional — a viewer that knows
16491
+ * nothing still gets a sane default. (Relocated from the retired `webrtc`
16492
+ * collection cap; this `webrtc-session` cap is the live signaling surface.)
16199
16493
  */
16200
16494
  var webrtcClientHintsSchema = object({
16201
16495
  viewportWidth: number().int().positive().optional(),
@@ -16206,22 +16500,6 @@ var webrtcClientHintsSchema = object({
16206
16500
  /** Hard tier override; takes precedence over scoring when registered. */
16207
16501
  prefersTier: string().optional()
16208
16502
  }).partial();
16209
- method(object({
16210
- streamId: string(),
16211
- sdpOffer: string()
16212
- }), string(), { kind: "mutation" }), method(object({ streamId: string() }), boolean()), method(object({
16213
- streamId: string(),
16214
- codec: string()
16215
- }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), _void(), { kind: "mutation" }), method(object({
16216
- streamId: string(),
16217
- hints: webrtcClientHintsSchema.optional()
16218
- }), object({
16219
- sessionId: string(),
16220
- sdpOffer: string()
16221
- }), { kind: "mutation" }), method(object({
16222
- sessionId: string(),
16223
- sdpAnswer: string()
16224
- }), _void(), { kind: "mutation" }), method(object({ sessionId: string() }), _void(), { kind: "mutation" }), method(object({ streamId: string() }), boolean());
16225
16503
  /**
16226
16504
  * Discriminated target for a WebRTC session. The client sends this
16227
16505
  * structured object instead of building / parsing brokerId strings;
@@ -17649,6 +17927,16 @@ var TopologyCategorySchema = object({
17649
17927
  healthy: number(),
17650
17928
  addons: array(TopologyCategoryAddonSchema).readonly()
17651
17929
  });
17930
+ /**
17931
+ * The node's runtime-updatable ROOT package (`@camstack/server` on the hub,
17932
+ * `@camstack/agent` on agents) as reported by its `registerNode` manifest —
17933
+ * version visibility for the Server management surface. Nullable: offline
17934
+ * rows and pre-phase-2 nodes report none.
17935
+ */
17936
+ var TopologyRootPackageSchema = object({
17937
+ name: string(),
17938
+ version: string()
17939
+ });
17652
17940
  var TopologyNodeSchema = object({
17653
17941
  id: string(),
17654
17942
  name: string(),
@@ -17672,7 +17960,8 @@ var TopologyNodeSchema = object({
17672
17960
  status: string()
17673
17961
  })).readonly(),
17674
17962
  processes: array(TopologyProcessSchema).readonly(),
17675
- categories: array(TopologyCategorySchema).readonly()
17963
+ categories: array(TopologyCategorySchema).readonly(),
17964
+ rootPackage: TopologyRootPackageSchema.nullable()
17676
17965
  });
17677
17966
  var CapUsageEdgeSchema = object({
17678
17967
  callerAddonId: string(),
@@ -20472,6 +20761,12 @@ Object.freeze({
20472
20761
  addonId: null,
20473
20762
  access: "create"
20474
20763
  },
20764
+ "loginMethod.getLoginMethods": {
20765
+ capName: "login-method",
20766
+ capScope: "system",
20767
+ addonId: null,
20768
+ access: "view"
20769
+ },
20475
20770
  "mediaPlayer.next": {
20476
20771
  capName: "media-player",
20477
20772
  capScope: "device",
@@ -21102,23 +21397,23 @@ Object.freeze({
21102
21397
  addonId: null,
21103
21398
  access: "create"
21104
21399
  },
21105
- "pipelineExecutor.deleteModel": {
21400
+ "pipelineExecutor.clearDeviceOverrides": {
21106
21401
  capName: "pipeline-executor",
21107
21402
  capScope: "system",
21108
21403
  addonId: null,
21109
21404
  access: "delete"
21110
21405
  },
21111
- "pipelineExecutor.deleteTemplate": {
21406
+ "pipelineExecutor.deleteModel": {
21112
21407
  capName: "pipeline-executor",
21113
21408
  capScope: "system",
21114
21409
  addonId: null,
21115
21410
  access: "delete"
21116
21411
  },
21117
- "pipelineExecutor.detect": {
21412
+ "pipelineExecutor.deleteTemplate": {
21118
21413
  capName: "pipeline-executor",
21119
21414
  capScope: "system",
21120
21415
  addonId: null,
21121
- access: "view"
21416
+ access: "delete"
21122
21417
  },
21123
21418
  "pipelineExecutor.downloadModel": {
21124
21419
  capName: "pipeline-executor",
@@ -21312,13 +21607,13 @@ Object.freeze({
21312
21607
  addonId: null,
21313
21608
  access: "create"
21314
21609
  },
21315
- "pipelineOrchestrator.assignAudio": {
21316
- capName: "pipeline-orchestrator",
21610
+ "pipelineExecutor.validatePipeline": {
21611
+ capName: "pipeline-executor",
21317
21612
  capScope: "system",
21318
21613
  addonId: null,
21319
- access: "create"
21614
+ access: "view"
21320
21615
  },
21321
- "pipelineOrchestrator.assignDecoder": {
21616
+ "pipelineOrchestrator.assignAudio": {
21322
21617
  capName: "pipeline-orchestrator",
21323
21618
  capScope: "system",
21324
21619
  addonId: null,
@@ -21402,19 +21697,13 @@ Object.freeze({
21402
21697
  addonId: null,
21403
21698
  access: "view"
21404
21699
  },
21405
- "pipelineOrchestrator.getDecoderAssignment": {
21406
- capName: "pipeline-orchestrator",
21407
- capScope: "system",
21408
- addonId: null,
21409
- access: "view"
21410
- },
21411
- "pipelineOrchestrator.getDecoderAssignments": {
21700
+ "pipelineOrchestrator.getGlobalMetrics": {
21412
21701
  capName: "pipeline-orchestrator",
21413
21702
  capScope: "system",
21414
21703
  addonId: null,
21415
21704
  access: "view"
21416
21705
  },
21417
- "pipelineOrchestrator.getGlobalMetrics": {
21706
+ "pipelineOrchestrator.getIngestOwner": {
21418
21707
  capName: "pipeline-orchestrator",
21419
21708
  capScope: "system",
21420
21709
  addonId: null,
@@ -21456,6 +21745,12 @@ Object.freeze({
21456
21745
  addonId: null,
21457
21746
  access: "delete"
21458
21747
  },
21748
+ "pipelineOrchestrator.resetNodePipelineDefaults": {
21749
+ capName: "pipeline-orchestrator",
21750
+ capScope: "system",
21751
+ addonId: null,
21752
+ access: "delete"
21753
+ },
21459
21754
  "pipelineOrchestrator.resolvePipeline": {
21460
21755
  capName: "pipeline-orchestrator",
21461
21756
  capScope: "system",
@@ -21492,37 +21787,37 @@ Object.freeze({
21492
21787
  addonId: null,
21493
21788
  access: "create"
21494
21789
  },
21495
- "pipelineOrchestrator.setCameraPipelineForAgent": {
21790
+ "pipelineOrchestrator.setAgentReachableHost": {
21496
21791
  capName: "pipeline-orchestrator",
21497
21792
  capScope: "system",
21498
21793
  addonId: null,
21499
21794
  access: "create"
21500
21795
  },
21501
- "pipelineOrchestrator.setCameraStepOverride": {
21796
+ "pipelineOrchestrator.setCameraPipelineForAgent": {
21502
21797
  capName: "pipeline-orchestrator",
21503
21798
  capScope: "system",
21504
21799
  addonId: null,
21505
21800
  access: "create"
21506
21801
  },
21507
- "pipelineOrchestrator.setCameraStepToggle": {
21802
+ "pipelineOrchestrator.setCameraStepOverride": {
21508
21803
  capName: "pipeline-orchestrator",
21509
21804
  capScope: "system",
21510
21805
  addonId: null,
21511
21806
  access: "create"
21512
21807
  },
21513
- "pipelineOrchestrator.setCapabilityBinding": {
21808
+ "pipelineOrchestrator.setCameraStepToggle": {
21514
21809
  capName: "pipeline-orchestrator",
21515
21810
  capScope: "system",
21516
21811
  addonId: null,
21517
21812
  access: "create"
21518
21813
  },
21519
- "pipelineOrchestrator.unassignAudio": {
21814
+ "pipelineOrchestrator.setCapabilityBinding": {
21520
21815
  capName: "pipeline-orchestrator",
21521
21816
  capScope: "system",
21522
21817
  addonId: null,
21523
21818
  access: "create"
21524
21819
  },
21525
- "pipelineOrchestrator.unassignDecoder": {
21820
+ "pipelineOrchestrator.unassignAudio": {
21526
21821
  capName: "pipeline-orchestrator",
21527
21822
  capScope: "system",
21528
21823
  addonId: null,
@@ -21822,33 +22117,45 @@ Object.freeze({
21822
22117
  addonId: null,
21823
22118
  access: "create"
21824
22119
  },
21825
- "restreamer.getExposedResources": {
21826
- capName: "restreamer",
22120
+ "scriptRunner.run": {
22121
+ capName: "script-runner",
22122
+ capScope: "device",
22123
+ addonId: null,
22124
+ access: "create"
22125
+ },
22126
+ "scriptRunner.stop": {
22127
+ capName: "script-runner",
22128
+ capScope: "device",
22129
+ addonId: null,
22130
+ access: "create"
22131
+ },
22132
+ "serverManagement.applyServerUpdate": {
22133
+ capName: "server-management",
21827
22134
  capScope: "system",
21828
22135
  addonId: null,
21829
- access: "view"
22136
+ access: "create"
21830
22137
  },
21831
- "restreamer.registerDevice": {
21832
- capName: "restreamer",
22138
+ "serverManagement.checkServerUpdate": {
22139
+ capName: "server-management",
21833
22140
  capScope: "system",
21834
22141
  addonId: null,
21835
22142
  access: "create"
21836
22143
  },
21837
- "restreamer.unregisterDevice": {
21838
- capName: "restreamer",
22144
+ "serverManagement.getServerPackageStatus": {
22145
+ capName: "server-management",
21839
22146
  capScope: "system",
21840
22147
  addonId: null,
21841
- access: "delete"
22148
+ access: "view"
21842
22149
  },
21843
- "scriptRunner.run": {
21844
- capName: "script-runner",
21845
- capScope: "device",
22150
+ "serverManagement.restartServer": {
22151
+ capName: "server-management",
22152
+ capScope: "system",
21846
22153
  addonId: null,
21847
22154
  access: "create"
21848
22155
  },
21849
- "scriptRunner.stop": {
21850
- capName: "script-runner",
21851
- capScope: "device",
22156
+ "serverManagement.rollbackServerUpdate": {
22157
+ capName: "server-management",
22158
+ capScope: "system",
21852
22159
  addonId: null,
21853
22160
  access: "create"
21854
22161
  },
@@ -21942,18 +22249,6 @@ Object.freeze({
21942
22249
  addonId: null,
21943
22250
  access: "create"
21944
22251
  },
21945
- "snapshotProvider.getSnapshot": {
21946
- capName: "snapshot-provider",
21947
- capScope: "system",
21948
- addonId: null,
21949
- access: "view"
21950
- },
21951
- "snapshotProvider.supportsDevice": {
21952
- capName: "snapshot-provider",
21953
- capScope: "system",
21954
- addonId: null,
21955
- access: "view"
21956
- },
21957
22252
  "ssoBridge.signBridgeToken": {
21958
22253
  capName: "sso-bridge",
21959
22254
  capScope: "system",
@@ -22380,30 +22675,6 @@ Object.freeze({
22380
22675
  addonId: null,
22381
22676
  access: "view"
22382
22677
  },
22383
- "streamingEngine.getStreamUrl": {
22384
- capName: "streaming-engine",
22385
- capScope: "system",
22386
- addonId: null,
22387
- access: "view"
22388
- },
22389
- "streamingEngine.listStreams": {
22390
- capName: "streaming-engine",
22391
- capScope: "system",
22392
- addonId: null,
22393
- access: "view"
22394
- },
22395
- "streamingEngine.registerStream": {
22396
- capName: "streaming-engine",
22397
- capScope: "system",
22398
- addonId: null,
22399
- access: "create"
22400
- },
22401
- "streamingEngine.unregisterStream": {
22402
- capName: "streaming-engine",
22403
- capScope: "system",
22404
- addonId: null,
22405
- access: "delete"
22406
- },
22407
22678
  "streamParams.getConfigSchema": {
22408
22679
  capName: "stream-params",
22409
22680
  capScope: "device",
@@ -22752,6 +23023,18 @@ Object.freeze({
22752
23023
  addonId: null,
22753
23024
  access: "view"
22754
23025
  },
23026
+ "viewerUi.getStaticDir": {
23027
+ capName: "viewer-ui",
23028
+ capScope: "system",
23029
+ addonId: null,
23030
+ access: "view"
23031
+ },
23032
+ "viewerUi.getVersion": {
23033
+ capName: "viewer-ui",
23034
+ capScope: "system",
23035
+ addonId: null,
23036
+ access: "view"
23037
+ },
22755
23038
  "waterHeater.setAway": {
22756
23039
  capName: "water-heater",
22757
23040
  capScope: "device",
@@ -22770,54 +23053,6 @@ Object.freeze({
22770
23053
  addonId: null,
22771
23054
  access: "create"
22772
23055
  },
22773
- "webrtc.closeSession": {
22774
- capName: "webrtc",
22775
- capScope: "system",
22776
- addonId: null,
22777
- access: "create"
22778
- },
22779
- "webrtc.createSession": {
22780
- capName: "webrtc",
22781
- capScope: "system",
22782
- addonId: null,
22783
- access: "create"
22784
- },
22785
- "webrtc.handleAnswer": {
22786
- capName: "webrtc",
22787
- capScope: "system",
22788
- addonId: null,
22789
- access: "create"
22790
- },
22791
- "webrtc.handleOffer": {
22792
- capName: "webrtc",
22793
- capScope: "system",
22794
- addonId: null,
22795
- access: "create"
22796
- },
22797
- "webrtc.hasAdaptiveBitrate": {
22798
- capName: "webrtc",
22799
- capScope: "system",
22800
- addonId: null,
22801
- access: "view"
22802
- },
22803
- "webrtc.registerStream": {
22804
- capName: "webrtc",
22805
- capScope: "system",
22806
- addonId: null,
22807
- access: "create"
22808
- },
22809
- "webrtc.supportsStream": {
22810
- capName: "webrtc",
22811
- capScope: "system",
22812
- addonId: null,
22813
- access: "view"
22814
- },
22815
- "webrtc.unregisterStream": {
22816
- capName: "webrtc",
22817
- capScope: "system",
22818
- addonId: null,
22819
- access: "delete"
22820
- },
22821
23056
  "webrtcSession.addIceCandidate": {
22822
23057
  capName: "webrtc-session",
22823
23058
  capScope: "device",
@@ -23515,276 +23750,6 @@ var FrameDropper = class {
23515
23750
  }
23516
23751
  };
23517
23752
  //#endregion
23518
- //#region src/frame-ring-sink.ts
23519
- /**
23520
- * `DecoderFrameRingSink` — the decoder's shared-memory write side (Phase 5 / D9).
23521
- *
23522
- * When a decoder session is configured with `frameSink: 'shm'`, the decoder
23523
- * **owns** the shared-memory ring segment for that stream: it creates the
23524
- * segment on the first decoded frame (when the output geometry is known),
23525
- * writes every subsequent decoded frame into the ring via a `FrameRingWriter`,
23526
- * and closes + unlinks the segment when the session is destroyed.
23527
- *
23528
- * What leaves the decoder is no longer the pixel `Buffer` — it is a tiny,
23529
- * serialisable `FrameHandle` (`FrameRingWriter.writeFrame`'s return value).
23530
- * Same-host consumers (motion, detection, the WebRTC encoder) open the same
23531
- * segment with a `FrameRingReader` and read the pixels zero-copy.
23532
- *
23533
- * ## Lazy segment creation
23534
- *
23535
- * The segment cannot be sized until the first frame: `slotByteLength` is
23536
- * `width × height × bytesPerPixel`, and the output dimensions are only known
23537
- * once the scaler has produced its first `dstFrame`. So `writeFrame` is a
23538
- * no-op-until-armed: the first call sizes + creates the segment, every later
23539
- * call writes into it.
23540
- *
23541
- * ## Resolution-change decision
23542
- *
23543
- * A live camera stream can change resolution mid-stream (the decoder's scaler
23544
- * is rebuilt on a config toggle, or the source renegotiates). The slot is
23545
- * sized for the **first** frame's geometry. A later frame that no longer fits
23546
- * the slot triggers a **segment re-create**: the old segment is closed +
23547
- * unlinked and a fresh, larger segment is created under a new generation-tagged
23548
- * name. This is simpler and leak-free versus over-allocating slots for a
23549
- * worst-case 4K frame on every stream; resolution changes on a live camera are
23550
- * rare, and a brief gap while consumers re-open the segment is acceptable
23551
- * (latest-wins — a missed frame is correct behaviour).
23552
- */
23553
- /**
23554
- * Per-ring shared-memory budget (MB) — the slot count is derived per-resolution
23555
- * from this budget via {@link deriveSlotCount}, so a 360p stream gets many
23556
- * slots and a 4K stream a few, both inside the same memory footprint.
23557
- *
23558
- * Read ONCE at module load from `CAMSTACK_SHM_RING_BUDGET_MB`; a non-finite or
23559
- * non-positive value falls back to the 16 MB default.
23560
- *
23561
- * The default is deliberately small (16 MB) so many concurrent per-camera rings
23562
- * fit inside a bounded `/dev/shm`. The decoder output is capped at 640px wide, so
23563
- * a slot is ≤ ~920 KB and 16 MB still yields ~17–70 latest-wins slots. A ring
23564
- * segment larger than the container's `/dev/shm` tmpfs backing (Docker default is
23565
- * only 64 MB) faults an **uncatchable SIGBUS** on write past `st_size` — the old
23566
- * 128 MB default overflowed a 64 MB `/dev/shm` and crashed the decoder on 8MP
23567
- * streams. Pair this with a `--shm-size` that scales with concurrent-decode load.
23568
- */
23569
- var RING_BUDGET_MB = (() => {
23570
- const raw = Number(process.env["CAMSTACK_SHM_RING_BUDGET_MB"]);
23571
- return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 16;
23572
- })();
23573
- /** {@link RING_BUDGET_MB} in bytes — the budget passed to `deriveSlotCount`. */
23574
- var RING_BUDGET_BYTES = RING_BUDGET_MB * 1024 * 1024;
23575
- /** A unique, stable shared-memory segment name for a decoder stream.
23576
- *
23577
- * macOS POSIX shm names are capped at ~31 characters (`PSHMNAMLEN`). A
23578
- * `camstack.frames.<deviceId>.<streamId>` scheme overflows that for realistic
23579
- * ids, so the sink uses a short, collision-resistant scheme instead:
23580
- * `csf.<base36 hash>.<gen>`. The hash folds the device id, the session tag
23581
- * and a per-process random salt; the generation suffix makes a re-created
23582
- * segment (resolution change) a distinct name so a stale consumer mapping is
23583
- * never silently reused.
23584
- */
23585
- /**
23586
- * Shared prefix for every decoder shm segment name. Startup orphan reclamation
23587
- * (`purgeOrphanSegments`) keys off this to find segments left behind by a
23588
- * crashed prior instance.
23589
- */
23590
- var SEGMENT_NAME_PREFIX = "csf.";
23591
- function makeSegmentName(seed, generation) {
23592
- let hash = 5381;
23593
- for (let i = 0; i < seed.length; i += 1) hash = (hash << 5) + hash + seed.charCodeAt(i) | 0;
23594
- return `${SEGMENT_NAME_PREFIX}${(hash >>> 0).toString(36)}.${generation}`;
23595
- }
23596
- /**
23597
- * The decoder-side owner of one stream's shared-memory frame ring.
23598
- *
23599
- * Not constructed until a session actually uses the shm sink; the segment
23600
- * itself is created lazily on the first `writeFrame`.
23601
- */
23602
- var DecoderFrameRingSink = class {
23603
- seed;
23604
- logger;
23605
- nodeId;
23606
- segment = null;
23607
- writer = null;
23608
- segmentName = null;
23609
- slotByteLength = 0;
23610
- generation = 0;
23611
- destroyed = false;
23612
- /** Frames committed into the ring across this sink's lifetime (all generations). */
23613
- framesWritten = 0;
23614
- constructor(options) {
23615
- const salt = Math.random().toString(36).slice(2, 8);
23616
- this.seed = `${options.seed}.${salt}`;
23617
- this.logger = options.logger;
23618
- this.nodeId = options.nodeId;
23619
- }
23620
- /** Whether a segment has been created (i.e. at least one frame written). */
23621
- get isArmed() {
23622
- return this.writer !== null;
23623
- }
23624
- /** The current segment name, or `null` before the first frame. */
23625
- get currentSegmentName() {
23626
- return this.segmentName;
23627
- }
23628
- /**
23629
- * Write one decoded frame into the ring and return its `FrameHandle`.
23630
- *
23631
- * On the first call (or after a geometry change that overflows the current
23632
- * slot) the segment is created / re-created sized for this frame. Returns
23633
- * `null` only when the sink has been destroyed.
23634
- *
23635
- * This is the copy-in convenience form (it copies `pixels` into the slot).
23636
- * The decoder's hot path uses the zero-copy {@link beginFrame} /
23637
- * {@link commitFrame} scatter-write pair instead — the scaler produces its
23638
- * packed output directly into the slot, eliminating the write-side memcpy.
23639
- */
23640
- writeFrame(pixels, meta) {
23641
- if (this.destroyed) return null;
23642
- if (this.writer === null || (0, _camstack_shm_ring.computeSlotByteLength)(meta.width, meta.height, meta.format) > this.slotByteLength) this.recreateSegment((0, _camstack_shm_ring.computeSlotByteLength)(meta.width, meta.height, meta.format));
23643
- const writer = this.writer;
23644
- if (writer === null) return null;
23645
- const handle = writer.writeFrame(pixels, meta);
23646
- this.framesWritten += 1;
23647
- return handle;
23648
- }
23649
- /**
23650
- * Reserve a ring slot for a frame of the given geometry — the **zero-copy**
23651
- * scatter-write entry point (Phase 5 / D9 Task 7c).
23652
- *
23653
- * The segment is created / re-created here if this is the first frame or the
23654
- * geometry overflows the current slot capacity, so the slot is correctly
23655
- * sized before the caller fills it. The returned `buffer` is a writable view
23656
- * **directly over the mapped segment** — the node-av scaler scatters its
23657
- * packed output straight into it, with no intermediate copy. The caller MUST
23658
- * call {@link commitFrame} with the returned `slot` once the slot is filled.
23659
- *
23660
- * Returns `null` when the sink is destroyed or the segment cannot be created.
23661
- */
23662
- beginFrame(width, height, format) {
23663
- if (this.destroyed) return null;
23664
- const requiredSlotBytes = (0, _camstack_shm_ring.computeSlotByteLength)(width, height, format);
23665
- if (this.writer === null || requiredSlotBytes > this.slotByteLength) this.recreateSegment(requiredSlotBytes);
23666
- const writer = this.writer;
23667
- if (writer === null) return null;
23668
- const { slot, buffer } = writer.beginFrame();
23669
- return {
23670
- slot,
23671
- buffer
23672
- };
23673
- }
23674
- /**
23675
- * Publish the frame whose slot was reserved by {@link beginFrame} and filled
23676
- * in place by the caller. `slot` MUST be the value from the matching
23677
- * `beginFrame`. Returns the published `FrameHandle`, or `null` if the sink
23678
- * was destroyed (or the segment lost) between begin and commit.
23679
- */
23680
- commitFrame(slot, meta) {
23681
- if (this.destroyed) return null;
23682
- const writer = this.writer;
23683
- if (writer === null) return null;
23684
- const handle = writer.commitFrame(slot, meta);
23685
- this.framesWritten += 1;
23686
- return handle;
23687
- }
23688
- /**
23689
- * Current shm ring usage — `null` until the first frame arms the segment.
23690
- * Surfaced through `decoder.getShmStats` so a downstream consumer can
23691
- * observe ring pressure (slot depth, byte budget, frames written).
23692
- */
23693
- getShmStats() {
23694
- if (this.writer === null) return null;
23695
- return {
23696
- slotCount: this.writer.slotCount,
23697
- slotByteLength: this.slotByteLength,
23698
- segmentBytes: (0, _camstack_shm_ring.computeSegmentSize)(this.writer.slotCount, this.slotByteLength),
23699
- framesWritten: this.framesWritten
23700
- };
23701
- }
23702
- /**
23703
- * Abandon a slot reserved by {@link beginFrame} **without publishing it** —
23704
- * the degenerate-path counterpart of {@link commitFrame}.
23705
- *
23706
- * A caller that reserved a slot but then could not produce valid pixels (no
23707
- * decoded source planes, or the scaler threw) MUST call this instead of
23708
- * `commitFrame`: it closes the open seqlock without advancing `writeIndex`,
23709
- * so no reader ever sees the slot's uninitialised bytes as a real frame, and
23710
- * no `FrameHandle` is handed downstream. `slot` MUST be the value from the
23711
- * matching `beginFrame`. A no-op if the sink was destroyed (or the segment
23712
- * lost) between begin and abort.
23713
- */
23714
- abortFrame(slot) {
23715
- if (this.destroyed) return;
23716
- const writer = this.writer;
23717
- if (writer === null) return;
23718
- writer.abortFrame(slot);
23719
- }
23720
- /** Close + unlink the segment. Idempotent. */
23721
- destroy() {
23722
- if (this.destroyed) return;
23723
- this.destroyed = true;
23724
- this.releaseSegment();
23725
- }
23726
- /**
23727
- * Create a fresh segment sized for at least `slotByteLength` bytes per slot,
23728
- * replacing any prior one. A re-create bumps the generation so the new
23729
- * segment has a distinct name — a consumer holding the old mapping is never
23730
- * silently handed a resized segment.
23731
- */
23732
- recreateSegment(slotByteLength) {
23733
- this.releaseSegment();
23734
- this.generation += 1;
23735
- const name = makeSegmentName(this.seed, this.generation);
23736
- const slotCount = (0, _camstack_shm_ring.deriveSlotCount)(RING_BUDGET_BYTES, slotByteLength);
23737
- if (slotCount === _camstack_shm_ring.MIN_RING_SLOTS && _camstack_shm_ring.MIN_RING_SLOTS * slotByteLength > RING_BUDGET_BYTES) this.logger.warn("decoder shm ring: budget too small for resolution — using MIN slots", { meta: {
23738
- slotByteLength,
23739
- budgetMb: RING_BUDGET_MB
23740
- } });
23741
- const totalBytes = (0, _camstack_shm_ring.computeSegmentSize)(slotCount, slotByteLength);
23742
- try {
23743
- const segment = (0, _camstack_shm_ring.createSegment)(name, totalBytes);
23744
- this.segment = segment;
23745
- this.segmentName = name;
23746
- this.slotByteLength = slotByteLength;
23747
- this.writer = new _camstack_shm_ring.FrameRingWriter(segment.buffer, name, slotCount, slotByteLength, this.nodeId);
23748
- this.logger.info("decoder shm ring: segment created", { meta: {
23749
- segment: name,
23750
- slotCount,
23751
- slotByteLength,
23752
- totalBytes,
23753
- generation: this.generation
23754
- } });
23755
- } catch (err) {
23756
- this.segment = null;
23757
- this.writer = null;
23758
- this.segmentName = null;
23759
- this.slotByteLength = 0;
23760
- this.logger.error("decoder shm ring: segment create failed", { meta: {
23761
- segment: name,
23762
- slotByteLength,
23763
- error: err instanceof Error ? err.message : String(err)
23764
- } });
23765
- }
23766
- }
23767
- /** Unmap + unlink the current segment, if any. */
23768
- releaseSegment() {
23769
- const segment = this.segment;
23770
- if (segment === null) return;
23771
- this.segment = null;
23772
- this.writer = null;
23773
- const name = this.segmentName;
23774
- this.segmentName = null;
23775
- try {
23776
- segment.close();
23777
- segment.unlink();
23778
- this.logger.info("decoder shm ring: segment released", { meta: { segment: name } });
23779
- } catch (err) {
23780
- this.logger.warn("decoder shm ring: segment release failed", { meta: {
23781
- segment: name,
23782
- error: err instanceof Error ? err.message : String(err)
23783
- } });
23784
- }
23785
- }
23786
- };
23787
- //#endregion
23788
23753
  //#region src/frame-stream-splitter.ts
23789
23754
  var FrameStreamSplitter = class {
23790
23755
  frameSize;
@@ -24034,7 +23999,7 @@ var FfmpegDecoderSession = class {
24034
23999
  if (typeof this.config.deviceId === "number") seedParts.push(String(this.config.deviceId));
24035
24000
  if (typeof this.config.tag === "string" && this.config.tag.length > 0) seedParts.push(this.config.tag);
24036
24001
  const seed = seedParts.length > 0 ? seedParts.join(":") : "anon";
24037
- this.frameRingSink = new DecoderFrameRingSink({
24002
+ this.frameRingSink = new _camstack_shm_ring.DecoderFrameRingSink({
24038
24003
  seed,
24039
24004
  logger: this.logger,
24040
24005
  nodeId: this.nodeId
@@ -25755,7 +25720,7 @@ var DecoderFfmpegAddon = class extends BaseAddon {
25755
25720
  return registrations;
25756
25721
  }
25757
25722
  this.ctx.logger.info("ffmpeg decoder addon initialized", { meta: { selectedBackend: backend } });
25758
- const purged = purgeOrphanSegments(SEGMENT_NAME_PREFIX);
25723
+ const purged = purgeOrphanSegments(_camstack_shm_ring.SEGMENT_NAME_PREFIX);
25759
25724
  if (purged.removed > 0) this.ctx.logger.warn("ffmpeg decoder: reclaimed orphaned shm segments at startup", { meta: {
25760
25725
  removed: purged.removed,
25761
25726
  scanned: purged.scanned
@@ -26059,7 +26024,7 @@ var DecoderFfmpegAddon = class extends BaseAddon {
26059
26024
  return {
26060
26025
  sessionId: input.sessionId,
26061
26026
  ...stats,
26062
- budgetMb: RING_BUDGET_MB,
26027
+ budgetMb: _camstack_shm_ring.RING_BUDGET_MB,
26063
26028
  getFrameHits: this.getFrameHits,
26064
26029
  getFrameMisses: this.getFrameMisses
26065
26030
  };