@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.
- package/dist/index.js +490 -525
- package/dist/index.mjs +488 -523
- 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-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
10812
|
-
*
|
|
10813
|
-
*
|
|
10814
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
15631
|
-
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
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
|
|
15638
|
-
|
|
15639
|
-
|
|
15640
|
-
|
|
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
|
-
*
|
|
15799
|
-
*
|
|
15800
|
-
*
|
|
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
|
-
*
|
|
16198
|
-
*
|
|
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.
|
|
21400
|
+
"pipelineExecutor.clearDeviceOverrides": {
|
|
21106
21401
|
capName: "pipeline-executor",
|
|
21107
21402
|
capScope: "system",
|
|
21108
21403
|
addonId: null,
|
|
21109
21404
|
access: "delete"
|
|
21110
21405
|
},
|
|
21111
|
-
"pipelineExecutor.
|
|
21406
|
+
"pipelineExecutor.deleteModel": {
|
|
21112
21407
|
capName: "pipeline-executor",
|
|
21113
21408
|
capScope: "system",
|
|
21114
21409
|
addonId: null,
|
|
21115
21410
|
access: "delete"
|
|
21116
21411
|
},
|
|
21117
|
-
"pipelineExecutor.
|
|
21412
|
+
"pipelineExecutor.deleteTemplate": {
|
|
21118
21413
|
capName: "pipeline-executor",
|
|
21119
21414
|
capScope: "system",
|
|
21120
21415
|
addonId: null,
|
|
21121
|
-
access: "
|
|
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
|
-
"
|
|
21316
|
-
capName: "pipeline-
|
|
21610
|
+
"pipelineExecutor.validatePipeline": {
|
|
21611
|
+
capName: "pipeline-executor",
|
|
21317
21612
|
capScope: "system",
|
|
21318
21613
|
addonId: null,
|
|
21319
|
-
access: "
|
|
21614
|
+
access: "view"
|
|
21320
21615
|
},
|
|
21321
|
-
"pipelineOrchestrator.
|
|
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.
|
|
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.
|
|
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.
|
|
21790
|
+
"pipelineOrchestrator.setAgentReachableHost": {
|
|
21496
21791
|
capName: "pipeline-orchestrator",
|
|
21497
21792
|
capScope: "system",
|
|
21498
21793
|
addonId: null,
|
|
21499
21794
|
access: "create"
|
|
21500
21795
|
},
|
|
21501
|
-
"pipelineOrchestrator.
|
|
21796
|
+
"pipelineOrchestrator.setCameraPipelineForAgent": {
|
|
21502
21797
|
capName: "pipeline-orchestrator",
|
|
21503
21798
|
capScope: "system",
|
|
21504
21799
|
addonId: null,
|
|
21505
21800
|
access: "create"
|
|
21506
21801
|
},
|
|
21507
|
-
"pipelineOrchestrator.
|
|
21802
|
+
"pipelineOrchestrator.setCameraStepOverride": {
|
|
21508
21803
|
capName: "pipeline-orchestrator",
|
|
21509
21804
|
capScope: "system",
|
|
21510
21805
|
addonId: null,
|
|
21511
21806
|
access: "create"
|
|
21512
21807
|
},
|
|
21513
|
-
"pipelineOrchestrator.
|
|
21808
|
+
"pipelineOrchestrator.setCameraStepToggle": {
|
|
21514
21809
|
capName: "pipeline-orchestrator",
|
|
21515
21810
|
capScope: "system",
|
|
21516
21811
|
addonId: null,
|
|
21517
21812
|
access: "create"
|
|
21518
21813
|
},
|
|
21519
|
-
"pipelineOrchestrator.
|
|
21814
|
+
"pipelineOrchestrator.setCapabilityBinding": {
|
|
21520
21815
|
capName: "pipeline-orchestrator",
|
|
21521
21816
|
capScope: "system",
|
|
21522
21817
|
addonId: null,
|
|
21523
21818
|
access: "create"
|
|
21524
21819
|
},
|
|
21525
|
-
"pipelineOrchestrator.
|
|
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
|
-
"
|
|
21826
|
-
capName: "
|
|
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: "
|
|
22136
|
+
access: "create"
|
|
21830
22137
|
},
|
|
21831
|
-
"
|
|
21832
|
-
capName: "
|
|
22138
|
+
"serverManagement.checkServerUpdate": {
|
|
22139
|
+
capName: "server-management",
|
|
21833
22140
|
capScope: "system",
|
|
21834
22141
|
addonId: null,
|
|
21835
22142
|
access: "create"
|
|
21836
22143
|
},
|
|
21837
|
-
"
|
|
21838
|
-
capName: "
|
|
22144
|
+
"serverManagement.getServerPackageStatus": {
|
|
22145
|
+
capName: "server-management",
|
|
21839
22146
|
capScope: "system",
|
|
21840
22147
|
addonId: null,
|
|
21841
|
-
access: "
|
|
22148
|
+
access: "view"
|
|
21842
22149
|
},
|
|
21843
|
-
"
|
|
21844
|
-
capName: "
|
|
21845
|
-
capScope: "
|
|
22150
|
+
"serverManagement.restartServer": {
|
|
22151
|
+
capName: "server-management",
|
|
22152
|
+
capScope: "system",
|
|
21846
22153
|
addonId: null,
|
|
21847
22154
|
access: "create"
|
|
21848
22155
|
},
|
|
21849
|
-
"
|
|
21850
|
-
capName: "
|
|
21851
|
-
capScope: "
|
|
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
|
};
|