@camstack/addon-decoder-ffmpeg 1.2.24 → 1.2.26

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 +262 -120
  2. package/dist/index.mjs +262 -120
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ Object.defineProperties(exports, {
4
4
  });
5
5
  let node_crypto = require("node:crypto");
6
6
  let node_child_process = require("node:child_process");
7
- //#region ../types/dist/event-category-XfKNtfCc.mjs
7
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
8
8
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
9
9
  EventCategory["SystemBoot"] = "system.boot";
10
10
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -20,6 +20,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
20
20
  */
21
21
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
22
22
  /**
23
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
24
+ * Emitted only when the material on disk actually changed, so an
25
+ * operator who trusted the old certificate by hand is told rather than
26
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
27
+ *
28
+ * Rule: docs/decisions/adr-0227-*.md
29
+ */
30
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
31
+ /**
23
32
  * A newer addon or server-root package version was found by the
24
33
  * authoritative registry check. Emitted once when any observed
25
34
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8012,6 +8021,15 @@ var LabelDefinitionSchema = object({
8012
8021
  description: string().optional(),
8013
8022
  icon: string().optional()
8014
8023
  });
8024
+ var ClassMapDefinitionSchema = object({
8025
+ mapping: record(string(), _enum([
8026
+ "person",
8027
+ "vehicle",
8028
+ "animal",
8029
+ "package"
8030
+ ])),
8031
+ preserveOriginal: boolean()
8032
+ });
8015
8033
  var MODEL_FORMATS = [
8016
8034
  "onnx",
8017
8035
  "coreml",
@@ -8190,7 +8208,13 @@ var ModelCatalogEntrySchema = object({
8190
8208
  * `id` stays the source of truth for resolution/download/persistence; grouping
8191
8209
  * is a presentation overlay resolved back to an `id`.
8192
8210
  */
8193
- group: ModelVariantGroupSchema.optional()
8211
+ group: ModelVariantGroupSchema.optional(),
8212
+ /**
8213
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8214
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8215
+ * labels already ARE the CamStack macros (Scrypted identity map).
8216
+ */
8217
+ classMap: ClassMapDefinitionSchema.optional()
8194
8218
  });
8195
8219
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8196
8220
  format: literal("openvino"),
@@ -8219,7 +8243,8 @@ var ModelConvertMetadataSchema = object({
8219
8243
  "ocr",
8220
8244
  "segmentation"
8221
8245
  ]),
8222
- faceAlignment: boolean().optional()
8246
+ faceAlignment: boolean().optional(),
8247
+ classMap: ClassMapDefinitionSchema.optional()
8223
8248
  });
8224
8249
  var ConvertResultSchema = object({
8225
8250
  entry: ModelCatalogEntrySchema,
@@ -17340,6 +17365,33 @@ var NativeCropRefSchema = object({
17340
17365
  h: number()
17341
17366
  })
17342
17367
  });
17368
+ object({
17369
+ crop: object({
17370
+ left: number(),
17371
+ top: number(),
17372
+ width: number().positive(),
17373
+ height: number().positive()
17374
+ }).optional(),
17375
+ content: object({
17376
+ width: number().int().positive(),
17377
+ height: number().int().positive()
17378
+ }),
17379
+ fit: _enum(["stretch", "contain"]),
17380
+ format: _enum([
17381
+ "rgb",
17382
+ "gray",
17383
+ "jpeg"
17384
+ ])
17385
+ });
17386
+ var FrameRefSchema = object({
17387
+ registryId: string().min(1),
17388
+ id: string().min(1),
17389
+ width: number().int().positive(),
17390
+ height: number().int().positive(),
17391
+ format: _enum(["rgb", "gray"]),
17392
+ timestamp: number(),
17393
+ capturedAt: number().optional()
17394
+ });
17343
17395
  var ModelFormatSchema$1 = _enum([
17344
17396
  "onnx",
17345
17397
  "coreml",
@@ -17584,6 +17636,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17584
17636
  steps: array(PipelineStepInputSchema).min(1),
17585
17637
  frame: FrameInputSchema.optional(),
17586
17638
  /**
17639
+ * Process-local lazy frame. Valid only when caller and provider resolve
17640
+ * in the same execution-group process; split/cross-node callers use
17641
+ * `frame`/`image` inline compatibility instead.
17642
+ */
17643
+ frameRef: FrameRefSchema.optional(),
17644
+ /**
17587
17645
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17588
17646
  * the decoded pixels live in. One more member of the one-of
17589
17647
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -17839,7 +17897,10 @@ var NativeCropResultSchema = object({
17839
17897
  * Which source served this crop, so a quality-sensitive consumer (the native
17840
17898
  * `keyFrame`) can reject a degraded fallback:
17841
17899
  * - `native` — cut from the decode worker's retained NATIVE surface (the
17842
- * quality path).
17900
+ * quality path). A subject-tile serve is also native-resolution and stays
17901
+ * `native` here: the public enum cannot name `tile` without a breaking cap
17902
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
17903
+ * internal crop result (`nativeHits` vs `tileHits`).
17843
17904
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
17844
17905
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
17845
17906
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18330,12 +18391,41 @@ var RunnerLocalLoadSchema = object({
18330
18391
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18331
18392
  * working unchanged when they switch to reading from the runner cap.
18332
18393
  */
18394
+ var FrameLazyCountersSchema = object({
18395
+ framesDecoded: number(),
18396
+ framesAdmitted: number(),
18397
+ framesDroppedPixelFree: number(),
18398
+ viewsMaterialized: number(),
18399
+ viewsSkipped: number(),
18400
+ workerToRunnerBytes: number(),
18401
+ runnerToPoolRawBytes: number(),
18402
+ runnerToPoolJpegBytes: number(),
18403
+ onDemandFullFrameRequests: number(),
18404
+ onDemandCropRequests: number(),
18405
+ nativeHits: number(),
18406
+ nativeMisses: number(),
18407
+ tileHits: number(),
18408
+ tileMisses: number(),
18409
+ fallbackHits: number(),
18410
+ fallbackMisses: number(),
18411
+ retainedWritesAvoided: number(),
18412
+ residentRefs: number(),
18413
+ residentBytes: number(),
18414
+ releases: number(),
18415
+ evictions: number(),
18416
+ staleMisses: number()
18417
+ });
18418
+ var FrameLazyMetricsSchema = object({
18419
+ node: FrameLazyCountersSchema,
18420
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18421
+ });
18333
18422
  var RunnerLocalMetricsSchema = object({
18334
18423
  nodeId: string(),
18335
18424
  activeCameras: number(),
18336
18425
  throttledCameras: number(),
18337
18426
  avgInferenceTimeMs: number(),
18338
- queueDepth: number()
18427
+ queueDepth: number(),
18428
+ frameLazy: FrameLazyMetricsSchema.optional()
18339
18429
  });
18340
18430
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
18341
18431
  handle: FrameHandleSchema,
@@ -19635,6 +19725,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19635
19725
  location: StorageLocationSchema,
19636
19726
  relativePath: string()
19637
19727
  }), _void(), { kind: "mutation" }), method(object({ location: StorageLocationSchema }), number().nullable()), method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }), method(WriteChunkInputSchema, _void(), { kind: "mutation" }), method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }), method(AbortUploadInputSchema, _void(), { kind: "mutation" }), method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }), method(ReadChunkInputSchema, _instanceof(Uint8Array)), method(EndDownloadInputSchema, _void(), { kind: "mutation" });
19728
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19729
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19730
+ var ProfileSettingsBagSchema = record(string(), unknown());
19638
19731
  /**
19639
19732
  * A live terminal session hosted by the provider addon. Output and input do
19640
19733
  * NOT flow through the capability — they use the addon data plane
@@ -19664,7 +19757,14 @@ var TerminalSessionInfoSchema = object({
19664
19757
  var TerminalProfileInfoSchema = object({
19665
19758
  profileId: string(),
19666
19759
  label: string(),
19667
- description: string().optional()
19760
+ description: string().optional(),
19761
+ /** Spawn defaults the instance form copies on create. */
19762
+ executable: string().optional(),
19763
+ args: array(string()).readonly().optional(),
19764
+ cwd: string().optional(),
19765
+ environment: array(string()).readonly().optional(),
19766
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
19767
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19668
19768
  });
19669
19769
  /**
19670
19770
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19677,7 +19777,12 @@ var TerminalInstanceInfoSchema = object({
19677
19777
  profileId: string(),
19678
19778
  profileLabel: string(),
19679
19779
  name: string(),
19680
- enabled: boolean()
19780
+ enabled: boolean(),
19781
+ executable: string(),
19782
+ args: array(string()).readonly(),
19783
+ cwd: string(),
19784
+ environment: array(string()).readonly(),
19785
+ profileSettings: ProfileSettingsBagSchema
19681
19786
  });
19682
19787
  var TerminalLegacyCameraSchema = object({
19683
19788
  stableId: string(),
@@ -19707,7 +19812,23 @@ var TerminalOutputBatchSchema = object({
19707
19812
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19708
19813
  targetNodeId: string().min(1),
19709
19814
  profileId: string().min(1),
19710
- name: string().trim().min(1).max(160).optional()
19815
+ name: string().trim().min(1).max(160).optional(),
19816
+ executable: string().max(1024).optional(),
19817
+ args: array(string().max(2048)).max(64).optional(),
19818
+ cwd: string().max(1024).optional(),
19819
+ environment: array(string().max(4096)).max(64).optional(),
19820
+ profileSettings: ProfileSettingsBagSchema.optional()
19821
+ }), TerminalInstanceInfoSchema, {
19822
+ kind: "mutation",
19823
+ auth: "admin"
19824
+ }), method(object({
19825
+ instanceId: string().min(1),
19826
+ name: string().trim().min(1).max(160).optional(),
19827
+ executable: string().max(1024).optional(),
19828
+ args: array(string().max(2048)).max(64).optional(),
19829
+ cwd: string().max(1024).optional(),
19830
+ environment: array(string().max(4096)).max(64).optional(),
19831
+ profileSettings: ProfileSettingsBagSchema.optional()
19711
19832
  }), TerminalInstanceInfoSchema, {
19712
19833
  kind: "mutation",
19713
19834
  auth: "admin"
@@ -19729,7 +19850,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19729
19850
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19730
19851
  profileId: string(),
19731
19852
  cols: number().int().positive(),
19732
- rows: number().int().positive()
19853
+ rows: number().int().positive(),
19854
+ executable: string().max(1024).optional(),
19855
+ args: array(string().max(2048)).max(64).optional(),
19856
+ cwd: string().max(1024).optional(),
19857
+ environment: array(string().max(4096)).max(64).optional()
19733
19858
  }), TerminalSessionInfoSchema, {
19734
19859
  kind: "mutation",
19735
19860
  auth: "admin"
@@ -22511,10 +22636,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
22511
22636
  *
22512
22637
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
22513
22638
  * to receive an ordered list of candidate base URLs it should race
22514
- * on connect — LAN IPv4 first (lowest latency when on same network),
22515
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
22516
- * race them with short timeouts and stick with the winner for the
22517
- * session.
22639
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
22640
+ * when on the same network), then public hostname (if a tunnel is
22641
+ * up). The SDK can race them with short timeouts and stick with the
22642
+ * winner for the session.
22518
22643
  *
22519
22644
  * Why hub-only: agents are not directly addressable by the operator's
22520
22645
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -22669,6 +22794,17 @@ var NotificationEndpointSchema = object({
22669
22794
  /** What the ranking currently resolves to (null when nothing is reachable). */
22670
22795
  resolved: string().nullable()
22671
22796
  });
22797
+ /**
22798
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
22799
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
22800
+ * currently expands to, so the UI can show the effective set either way.
22801
+ */
22802
+ var ViewerEndpointsSchema = object({
22803
+ /** The operator's explicit race set, or empty for AUTO. */
22804
+ baseUrls: array(string()).readonly(),
22805
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
22806
+ resolved: array(string()).readonly()
22807
+ });
22672
22808
  var AllowedAddressesSchema = object({
22673
22809
  /**
22674
22810
  * Allowlist of interface addresses operators have explicitly opted
@@ -22677,6 +22813,20 @@ var AllowedAddressesSchema = object({
22677
22813
  * Network Addresses admin page and persisted by the addon.
22678
22814
  */
22679
22815
  addresses: array(string()).readonly() });
22816
+ var TlsStatusSchema = object({
22817
+ mode: _enum([
22818
+ "generated",
22819
+ "uploaded",
22820
+ "disabled"
22821
+ ]),
22822
+ leafFingerprintSha256: string().nullable(),
22823
+ caFingerprintSha256: string().nullable(),
22824
+ validTo: string().nullable(),
22825
+ sans: array(string()),
22826
+ caCertPem: string().nullable(),
22827
+ reissueError: string().nullable(),
22828
+ restartRequired: boolean()
22829
+ });
22680
22830
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22681
22831
  /**
22682
22832
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -22686,17 +22836,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
22686
22836
  */
22687
22837
  port: number().int().min(1).max(65535).optional(),
22688
22838
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22689
- * candidate. Default `true`. */
22839
+ * candidate. Default `false` — loopback is not a client route. */
22690
22840
  includeLoopback: boolean().optional(),
22691
- /** Skip IPv6 entries. Some legacy clients can't parse them.
22692
- * Default `false`. */
22841
+ /** Skip IPv6 entries. Default `false` the palette includes stable
22842
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
22843
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
22693
22844
  ipv4Only: boolean().optional(),
22694
22845
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
22695
22846
  * Pass `'https'` when the caller is itself loaded over HTTPS
22696
22847
  * to avoid mixed-content blocks in the browser. The public
22697
22848
  * tunnel always emits `https://` regardless. */
22698
22849
  scheme: _enum(["http", "https"]).optional()
22699
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
22850
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
22851
+ kind: "mutation",
22852
+ auth: "admin"
22853
+ }), method(object({
22854
+ certPem: string().min(1),
22855
+ keyPem: string().min(1),
22856
+ caPem: string().optional()
22857
+ }), TlsStatusSchema, {
22858
+ kind: "mutation",
22859
+ auth: "admin"
22860
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
22861
+ kind: "mutation",
22862
+ auth: "admin"
22863
+ });
22700
22864
  object({
22701
22865
  /** Lifecycle state of the lock. `jammed` means the motor reported
22702
22866
  * failure to reach the target — operator intervention required. */
@@ -28459,6 +28623,12 @@ Object.freeze({
28459
28623
  addonId: null,
28460
28624
  access: "create"
28461
28625
  },
28626
+ "localNetwork.downloadCa": {
28627
+ capName: "local-network",
28628
+ capScope: "system",
28629
+ addonId: null,
28630
+ access: "view"
28631
+ },
28462
28632
  "localNetwork.getAllowedAddresses": {
28463
28633
  capName: "local-network",
28464
28634
  capScope: "system",
@@ -28483,18 +28653,42 @@ Object.freeze({
28483
28653
  addonId: null,
28484
28654
  access: "view"
28485
28655
  },
28656
+ "localNetwork.getTlsStatus": {
28657
+ capName: "local-network",
28658
+ capScope: "system",
28659
+ addonId: null,
28660
+ access: "view"
28661
+ },
28662
+ "localNetwork.getViewerEndpoints": {
28663
+ capName: "local-network",
28664
+ capScope: "system",
28665
+ addonId: null,
28666
+ access: "view"
28667
+ },
28486
28668
  "localNetwork.list": {
28487
28669
  capName: "local-network",
28488
28670
  capScope: "system",
28489
28671
  addonId: null,
28490
28672
  access: "view"
28491
28673
  },
28674
+ "localNetwork.regenerateCertificate": {
28675
+ capName: "local-network",
28676
+ capScope: "system",
28677
+ addonId: null,
28678
+ access: "create"
28679
+ },
28492
28680
  "localNetwork.resetAllowlistToBestMatch": {
28493
28681
  capName: "local-network",
28494
28682
  capScope: "system",
28495
28683
  addonId: null,
28496
28684
  access: "delete"
28497
28685
  },
28686
+ "localNetwork.revertToGeneratedCertificate": {
28687
+ capName: "local-network",
28688
+ capScope: "system",
28689
+ addonId: null,
28690
+ access: "create"
28691
+ },
28498
28692
  "localNetwork.setAllowedAddresses": {
28499
28693
  capName: "local-network",
28500
28694
  capScope: "system",
@@ -28507,6 +28701,18 @@ Object.freeze({
28507
28701
  addonId: null,
28508
28702
  access: "create"
28509
28703
  },
28704
+ "localNetwork.setViewerEndpoints": {
28705
+ capName: "local-network",
28706
+ capScope: "system",
28707
+ addonId: null,
28708
+ access: "create"
28709
+ },
28710
+ "localNetwork.uploadCertificate": {
28711
+ capName: "local-network",
28712
+ capScope: "system",
28713
+ addonId: null,
28714
+ access: "create"
28715
+ },
28510
28716
  "lockControl.lock": {
28511
28717
  capName: "lock-control",
28512
28718
  capScope: "device",
@@ -31387,6 +31593,12 @@ Object.freeze({
31387
31593
  addonId: null,
31388
31594
  access: "create"
31389
31595
  },
31596
+ "terminalSession.updateInstance": {
31597
+ capName: "terminal-session",
31598
+ capScope: "system",
31599
+ addonId: null,
31600
+ access: "create"
31601
+ },
31390
31602
  "terminalSession.writeInput": {
31391
31603
  capName: "terminal-session",
31392
31604
  capScope: "system",
@@ -33874,6 +34086,35 @@ Object.freeze(Object.fromEntries([{
33874
34086
  }]
33875
34087
  }].map((s) => [s.stepId, s.defaultModelId])));
33876
34088
  string().min(1);
34089
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34090
+ stepId: "face-embedding",
34091
+ key: "minLandmarkFaceSize",
34092
+ label: "Min face size for recognition (detection px)",
34093
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
34094
+ type: "slider",
34095
+ min: 0,
34096
+ max: 64,
34097
+ step: 2,
34098
+ default: 24
34099
+ }];
34100
+ function clusterStepSettingKey(stepId, fieldKey) {
34101
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34102
+ }
34103
+ var ClusterSettingNumberSchema = number().finite();
34104
+ function readClusterStepSettings(config) {
34105
+ const out = {};
34106
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
34107
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
34108
+ const value = parsed.success ? parsed.data : field.default;
34109
+ const existing = out[field.stepId] ?? {};
34110
+ out[field.stepId] = {
34111
+ ...existing,
34112
+ [field.key]: value
34113
+ };
34114
+ }
34115
+ return out;
34116
+ }
34117
+ readClusterStepSettings({});
33877
34118
  object({
33878
34119
  /**
33879
34120
  * Fraction of the box's own size added on EACH side before cutting.
@@ -34065,113 +34306,14 @@ var RingBuffer = class {
34065
34306
  * decoder is an explicit opt-in fallback only. A stale/failed settings read at
34066
34307
  * boot therefore resolves to node-av (never leaves the node with no decoder). */
34067
34308
  var DEFAULT_DECODER_BACKEND = "nodeav";
34068
- /** Narrow an unknown settings value to a {@link DecoderBackend}, else `null`. */
34069
- function parseDecoderBackend(value) {
34070
- return value === "ffmpeg" || value === "nodeav" ? value : null;
34071
- }
34072
- /**
34073
- * Normalise a raw kernel node id to the bare node id used for scoping.
34074
- * `localNodeId` can carry a `<node>/<addon>` suffix; the decoder selection is
34075
- * per-NODE, so strip the addon segment. Falls back to `hub`.
34076
- */
34077
- function normalizeDecoderNodeId(rawNodeId) {
34078
- const raw = rawNodeId ?? "hub";
34079
- return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
34080
- }
34081
34309
  //#endregion
34082
34310
  //#region src/shared/decoder-backend.ts
34083
- /** The NEUTRAL addon (pipeline-orchestrator, hub-resident) whose global
34084
- * settings OWN the per-node `backend` selector. Neither decoder addon owns
34085
- * it, so decoder-nodeav / decoder-ffmpeg stay fully independent. */
34086
- var DECODER_OWNER_ADDON_ID = "pipeline-orchestrator";
34087
- function isHydratedField(entry) {
34088
- return typeof entry === "object" && entry !== null && "key" in entry;
34089
- }
34090
- /**
34091
- * Pure selection from an already-read hydrated settings payload: extract the
34092
- * owner's `backend` field value (which the owner projected per-node from its
34093
- * scoped store key) and narrow it. A missing/invalid field or a null payload
34094
- * resolves to {@link DEFAULT_DECODER_BACKEND} — never a bare store key.
34095
- */
34096
- function pickDecoderBackendFromSettings(view) {
34097
- if (view === null) return DEFAULT_DECODER_BACKEND;
34098
- for (const section of view.sections) for (const entry of section.fields) {
34099
- if (!isHydratedField(entry) || entry.key !== "backend") continue;
34100
- return parseDecoderBackend(entry.value) ?? "nodeav";
34101
- }
34102
- return DEFAULT_DECODER_BACKEND;
34103
- }
34104
- /** Missing optional owner fingerprints: ffmpeg may be uninstalled. */
34105
- function isMissingOwnerSettingsError(message) {
34106
- return /not routable/i.test(message) || /provider not available/i.test(message);
34107
- }
34108
- /** Transient transport/settings-store fingerprints worth retrying on. */
34109
- function isTransientSettingsError(message) {
34110
- return /not loaded/i.test(message) || /transport-failed/i.test(message) || /not connected/i.test(message) || /SqliteSettingsBackend not initialized/i.test(message);
34111
- }
34112
34311
  /**
34113
- * Resolve the decoder backend a NON-OWNER addon (`decoder-nodeav`) should run,
34114
- * by reading the OWNER's (`decoder-ffmpeg`) hub-central per-node `backend`.
34115
- *
34116
- * The read routes to the owner addon's child runner; during a simultaneous
34117
- * (re)start the owner may not be up yet → a transient `transport-failed (addon
34118
- * not loaded)`. Immediately defaulting here would ignore an explicit `ffmpeg`
34119
- * selection (the node would silently run the node-av default instead). So retry
34120
- * on the transient fingerprints with a bounded budget (mirrors
34121
- * `BaseAddon.readAddonStoreWithRetry`) until the owner answers; only a
34122
- * persistent failure falls back to {@link DEFAULT_DECODER_BACKEND}.
34123
- *
34124
- * The OWNER addon must NOT call this (it would self-route + deadlock) — it uses
34125
- * {@link resolveOwnDecoderBackend} against its own store instead.
34312
+ * Resolve the decoder backend this addon should run. Owner settings are
34313
+ * ignored: persisted `backend@<nodeId>` rows stay in the store and are not
34314
+ * consulted. The runtime default is always {@link DEFAULT_DECODER_BACKEND}.
34126
34315
  */
34127
- async function resolveDecoderBackend(api, nodeId, logger) {
34128
- if (!api) {
34129
- logger.warn("decoder-backend: no api surface — using default backend", { meta: { default: DEFAULT_DECODER_BACKEND } });
34130
- return DEFAULT_DECODER_BACKEND;
34131
- }
34132
- const normalized = normalizeDecoderNodeId(nodeId);
34133
- const delaysMs = [
34134
- 150,
34135
- 350,
34136
- 600,
34137
- 900,
34138
- 1200
34139
- ];
34140
- let lastErr;
34141
- for (let attempt = 0; attempt <= delaysMs.length; attempt++) {
34142
- try {
34143
- const view = await api.addonSettings.getGlobalSettings.query({
34144
- addonId: DECODER_OWNER_ADDON_ID,
34145
- nodeId: normalized
34146
- });
34147
- if (view !== null) return pickDecoderBackendFromSettings(view);
34148
- lastErr = /* @__PURE__ */ new Error("owner settings unavailable (null)");
34149
- } catch (err) {
34150
- lastErr = err;
34151
- const msg = err instanceof Error ? err.message : String(err);
34152
- if (isTransientSettingsError(msg)) {} else if (isMissingOwnerSettingsError(msg)) {
34153
- logger.warn("decoder-backend: optional owner unavailable — using default backend", { meta: {
34154
- default: DEFAULT_DECODER_BACKEND,
34155
- owner: DECODER_OWNER_ADDON_ID,
34156
- error: msg
34157
- } });
34158
- return DEFAULT_DECODER_BACKEND;
34159
- } else {
34160
- logger.warn("decoder-backend: settings read failed — using default backend", { meta: {
34161
- default: DEFAULT_DECODER_BACKEND,
34162
- error: msg
34163
- } });
34164
- return DEFAULT_DECODER_BACKEND;
34165
- }
34166
- }
34167
- if (attempt === delaysMs.length) break;
34168
- await new Promise((resolve) => setTimeout(resolve, delaysMs[attempt]));
34169
- }
34170
- logger.warn("decoder-backend: owner settings unavailable after retries — using default backend", { meta: {
34171
- default: DEFAULT_DECODER_BACKEND,
34172
- owner: DECODER_OWNER_ADDON_ID,
34173
- error: lastErr instanceof Error ? lastErr.message : String(lastErr)
34174
- } });
34316
+ async function resolveDecoderBackend(_api, _nodeId, _logger) {
34175
34317
  return DEFAULT_DECODER_BACKEND;
34176
34318
  }
34177
34319
  //#endregion
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { spawn } from "node:child_process";
3
- //#region ../types/dist/event-category-XfKNtfCc.mjs
3
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
4
4
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
5
5
  EventCategory["SystemBoot"] = "system.boot";
6
6
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -16,6 +16,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
16
16
  */
17
17
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
18
18
  /**
19
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
20
+ * Emitted only when the material on disk actually changed, so an
21
+ * operator who trusted the old certificate by hand is told rather than
22
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
23
+ *
24
+ * Rule: docs/decisions/adr-0227-*.md
25
+ */
26
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
27
+ /**
19
28
  * A newer addon or server-root package version was found by the
20
29
  * authoritative registry check. Emitted once when any observed
21
30
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8008,6 +8017,15 @@ var LabelDefinitionSchema = object({
8008
8017
  description: string().optional(),
8009
8018
  icon: string().optional()
8010
8019
  });
8020
+ var ClassMapDefinitionSchema = object({
8021
+ mapping: record(string(), _enum([
8022
+ "person",
8023
+ "vehicle",
8024
+ "animal",
8025
+ "package"
8026
+ ])),
8027
+ preserveOriginal: boolean()
8028
+ });
8011
8029
  var MODEL_FORMATS = [
8012
8030
  "onnx",
8013
8031
  "coreml",
@@ -8186,7 +8204,13 @@ var ModelCatalogEntrySchema = object({
8186
8204
  * `id` stays the source of truth for resolution/download/persistence; grouping
8187
8205
  * is a presentation overlay resolved back to an `id`.
8188
8206
  */
8189
- group: ModelVariantGroupSchema.optional()
8207
+ group: ModelVariantGroupSchema.optional(),
8208
+ /**
8209
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8210
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8211
+ * labels already ARE the CamStack macros (Scrypted identity map).
8212
+ */
8213
+ classMap: ClassMapDefinitionSchema.optional()
8190
8214
  });
8191
8215
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8192
8216
  format: literal("openvino"),
@@ -8215,7 +8239,8 @@ var ModelConvertMetadataSchema = object({
8215
8239
  "ocr",
8216
8240
  "segmentation"
8217
8241
  ]),
8218
- faceAlignment: boolean().optional()
8242
+ faceAlignment: boolean().optional(),
8243
+ classMap: ClassMapDefinitionSchema.optional()
8219
8244
  });
8220
8245
  var ConvertResultSchema = object({
8221
8246
  entry: ModelCatalogEntrySchema,
@@ -17336,6 +17361,33 @@ var NativeCropRefSchema = object({
17336
17361
  h: number()
17337
17362
  })
17338
17363
  });
17364
+ object({
17365
+ crop: object({
17366
+ left: number(),
17367
+ top: number(),
17368
+ width: number().positive(),
17369
+ height: number().positive()
17370
+ }).optional(),
17371
+ content: object({
17372
+ width: number().int().positive(),
17373
+ height: number().int().positive()
17374
+ }),
17375
+ fit: _enum(["stretch", "contain"]),
17376
+ format: _enum([
17377
+ "rgb",
17378
+ "gray",
17379
+ "jpeg"
17380
+ ])
17381
+ });
17382
+ var FrameRefSchema = object({
17383
+ registryId: string().min(1),
17384
+ id: string().min(1),
17385
+ width: number().int().positive(),
17386
+ height: number().int().positive(),
17387
+ format: _enum(["rgb", "gray"]),
17388
+ timestamp: number(),
17389
+ capturedAt: number().optional()
17390
+ });
17339
17391
  var ModelFormatSchema$1 = _enum([
17340
17392
  "onnx",
17341
17393
  "coreml",
@@ -17580,6 +17632,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17580
17632
  steps: array(PipelineStepInputSchema).min(1),
17581
17633
  frame: FrameInputSchema.optional(),
17582
17634
  /**
17635
+ * Process-local lazy frame. Valid only when caller and provider resolve
17636
+ * in the same execution-group process; split/cross-node callers use
17637
+ * `frame`/`image` inline compatibility instead.
17638
+ */
17639
+ frameRef: FrameRefSchema.optional(),
17640
+ /**
17583
17641
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17584
17642
  * the decoded pixels live in. One more member of the one-of
17585
17643
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -17835,7 +17893,10 @@ var NativeCropResultSchema = object({
17835
17893
  * Which source served this crop, so a quality-sensitive consumer (the native
17836
17894
  * `keyFrame`) can reject a degraded fallback:
17837
17895
  * - `native` — cut from the decode worker's retained NATIVE surface (the
17838
- * quality path).
17896
+ * quality path). A subject-tile serve is also native-resolution and stays
17897
+ * `native` here: the public enum cannot name `tile` without a breaking cap
17898
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
17899
+ * internal crop result (`nativeHits` vs `tileHits`).
17839
17900
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
17840
17901
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
17841
17902
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18326,12 +18387,41 @@ var RunnerLocalLoadSchema = object({
18326
18387
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18327
18388
  * working unchanged when they switch to reading from the runner cap.
18328
18389
  */
18390
+ var FrameLazyCountersSchema = object({
18391
+ framesDecoded: number(),
18392
+ framesAdmitted: number(),
18393
+ framesDroppedPixelFree: number(),
18394
+ viewsMaterialized: number(),
18395
+ viewsSkipped: number(),
18396
+ workerToRunnerBytes: number(),
18397
+ runnerToPoolRawBytes: number(),
18398
+ runnerToPoolJpegBytes: number(),
18399
+ onDemandFullFrameRequests: number(),
18400
+ onDemandCropRequests: number(),
18401
+ nativeHits: number(),
18402
+ nativeMisses: number(),
18403
+ tileHits: number(),
18404
+ tileMisses: number(),
18405
+ fallbackHits: number(),
18406
+ fallbackMisses: number(),
18407
+ retainedWritesAvoided: number(),
18408
+ residentRefs: number(),
18409
+ residentBytes: number(),
18410
+ releases: number(),
18411
+ evictions: number(),
18412
+ staleMisses: number()
18413
+ });
18414
+ var FrameLazyMetricsSchema = object({
18415
+ node: FrameLazyCountersSchema,
18416
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18417
+ });
18329
18418
  var RunnerLocalMetricsSchema = object({
18330
18419
  nodeId: string(),
18331
18420
  activeCameras: number(),
18332
18421
  throttledCameras: number(),
18333
18422
  avgInferenceTimeMs: number(),
18334
- queueDepth: number()
18423
+ queueDepth: number(),
18424
+ frameLazy: FrameLazyMetricsSchema.optional()
18335
18425
  });
18336
18426
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
18337
18427
  handle: FrameHandleSchema,
@@ -19631,6 +19721,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19631
19721
  location: StorageLocationSchema,
19632
19722
  relativePath: string()
19633
19723
  }), _void(), { kind: "mutation" }), method(object({ location: StorageLocationSchema }), number().nullable()), method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }), method(WriteChunkInputSchema, _void(), { kind: "mutation" }), method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }), method(AbortUploadInputSchema, _void(), { kind: "mutation" }), method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }), method(ReadChunkInputSchema, _instanceof(Uint8Array)), method(EndDownloadInputSchema, _void(), { kind: "mutation" });
19724
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19725
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19726
+ var ProfileSettingsBagSchema = record(string(), unknown());
19634
19727
  /**
19635
19728
  * A live terminal session hosted by the provider addon. Output and input do
19636
19729
  * NOT flow through the capability — they use the addon data plane
@@ -19660,7 +19753,14 @@ var TerminalSessionInfoSchema = object({
19660
19753
  var TerminalProfileInfoSchema = object({
19661
19754
  profileId: string(),
19662
19755
  label: string(),
19663
- description: string().optional()
19756
+ description: string().optional(),
19757
+ /** Spawn defaults the instance form copies on create. */
19758
+ executable: string().optional(),
19759
+ args: array(string()).readonly().optional(),
19760
+ cwd: string().optional(),
19761
+ environment: array(string()).readonly().optional(),
19762
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
19763
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19664
19764
  });
19665
19765
  /**
19666
19766
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19673,7 +19773,12 @@ var TerminalInstanceInfoSchema = object({
19673
19773
  profileId: string(),
19674
19774
  profileLabel: string(),
19675
19775
  name: string(),
19676
- enabled: boolean()
19776
+ enabled: boolean(),
19777
+ executable: string(),
19778
+ args: array(string()).readonly(),
19779
+ cwd: string(),
19780
+ environment: array(string()).readonly(),
19781
+ profileSettings: ProfileSettingsBagSchema
19677
19782
  });
19678
19783
  var TerminalLegacyCameraSchema = object({
19679
19784
  stableId: string(),
@@ -19703,7 +19808,23 @@ var TerminalOutputBatchSchema = object({
19703
19808
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19704
19809
  targetNodeId: string().min(1),
19705
19810
  profileId: string().min(1),
19706
- name: string().trim().min(1).max(160).optional()
19811
+ name: string().trim().min(1).max(160).optional(),
19812
+ executable: string().max(1024).optional(),
19813
+ args: array(string().max(2048)).max(64).optional(),
19814
+ cwd: string().max(1024).optional(),
19815
+ environment: array(string().max(4096)).max(64).optional(),
19816
+ profileSettings: ProfileSettingsBagSchema.optional()
19817
+ }), TerminalInstanceInfoSchema, {
19818
+ kind: "mutation",
19819
+ auth: "admin"
19820
+ }), method(object({
19821
+ instanceId: string().min(1),
19822
+ name: string().trim().min(1).max(160).optional(),
19823
+ executable: string().max(1024).optional(),
19824
+ args: array(string().max(2048)).max(64).optional(),
19825
+ cwd: string().max(1024).optional(),
19826
+ environment: array(string().max(4096)).max(64).optional(),
19827
+ profileSettings: ProfileSettingsBagSchema.optional()
19707
19828
  }), TerminalInstanceInfoSchema, {
19708
19829
  kind: "mutation",
19709
19830
  auth: "admin"
@@ -19725,7 +19846,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19725
19846
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19726
19847
  profileId: string(),
19727
19848
  cols: number().int().positive(),
19728
- rows: number().int().positive()
19849
+ rows: number().int().positive(),
19850
+ executable: string().max(1024).optional(),
19851
+ args: array(string().max(2048)).max(64).optional(),
19852
+ cwd: string().max(1024).optional(),
19853
+ environment: array(string().max(4096)).max(64).optional()
19729
19854
  }), TerminalSessionInfoSchema, {
19730
19855
  kind: "mutation",
19731
19856
  auth: "admin"
@@ -22507,10 +22632,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
22507
22632
  *
22508
22633
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
22509
22634
  * to receive an ordered list of candidate base URLs it should race
22510
- * on connect — LAN IPv4 first (lowest latency when on same network),
22511
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
22512
- * race them with short timeouts and stick with the winner for the
22513
- * session.
22635
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
22636
+ * when on the same network), then public hostname (if a tunnel is
22637
+ * up). The SDK can race them with short timeouts and stick with the
22638
+ * winner for the session.
22514
22639
  *
22515
22640
  * Why hub-only: agents are not directly addressable by the operator's
22516
22641
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -22665,6 +22790,17 @@ var NotificationEndpointSchema = object({
22665
22790
  /** What the ranking currently resolves to (null when nothing is reachable). */
22666
22791
  resolved: string().nullable()
22667
22792
  });
22793
+ /**
22794
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
22795
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
22796
+ * currently expands to, so the UI can show the effective set either way.
22797
+ */
22798
+ var ViewerEndpointsSchema = object({
22799
+ /** The operator's explicit race set, or empty for AUTO. */
22800
+ baseUrls: array(string()).readonly(),
22801
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
22802
+ resolved: array(string()).readonly()
22803
+ });
22668
22804
  var AllowedAddressesSchema = object({
22669
22805
  /**
22670
22806
  * Allowlist of interface addresses operators have explicitly opted
@@ -22673,6 +22809,20 @@ var AllowedAddressesSchema = object({
22673
22809
  * Network Addresses admin page and persisted by the addon.
22674
22810
  */
22675
22811
  addresses: array(string()).readonly() });
22812
+ var TlsStatusSchema = object({
22813
+ mode: _enum([
22814
+ "generated",
22815
+ "uploaded",
22816
+ "disabled"
22817
+ ]),
22818
+ leafFingerprintSha256: string().nullable(),
22819
+ caFingerprintSha256: string().nullable(),
22820
+ validTo: string().nullable(),
22821
+ sans: array(string()),
22822
+ caCertPem: string().nullable(),
22823
+ reissueError: string().nullable(),
22824
+ restartRequired: boolean()
22825
+ });
22676
22826
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22677
22827
  /**
22678
22828
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -22682,17 +22832,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
22682
22832
  */
22683
22833
  port: number().int().min(1).max(65535).optional(),
22684
22834
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22685
- * candidate. Default `true`. */
22835
+ * candidate. Default `false` — loopback is not a client route. */
22686
22836
  includeLoopback: boolean().optional(),
22687
- /** Skip IPv6 entries. Some legacy clients can't parse them.
22688
- * Default `false`. */
22837
+ /** Skip IPv6 entries. Default `false` the palette includes stable
22838
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
22839
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
22689
22840
  ipv4Only: boolean().optional(),
22690
22841
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
22691
22842
  * Pass `'https'` when the caller is itself loaded over HTTPS
22692
22843
  * to avoid mixed-content blocks in the browser. The public
22693
22844
  * tunnel always emits `https://` regardless. */
22694
22845
  scheme: _enum(["http", "https"]).optional()
22695
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
22846
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
22847
+ kind: "mutation",
22848
+ auth: "admin"
22849
+ }), method(object({
22850
+ certPem: string().min(1),
22851
+ keyPem: string().min(1),
22852
+ caPem: string().optional()
22853
+ }), TlsStatusSchema, {
22854
+ kind: "mutation",
22855
+ auth: "admin"
22856
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
22857
+ kind: "mutation",
22858
+ auth: "admin"
22859
+ });
22696
22860
  object({
22697
22861
  /** Lifecycle state of the lock. `jammed` means the motor reported
22698
22862
  * failure to reach the target — operator intervention required. */
@@ -28455,6 +28619,12 @@ Object.freeze({
28455
28619
  addonId: null,
28456
28620
  access: "create"
28457
28621
  },
28622
+ "localNetwork.downloadCa": {
28623
+ capName: "local-network",
28624
+ capScope: "system",
28625
+ addonId: null,
28626
+ access: "view"
28627
+ },
28458
28628
  "localNetwork.getAllowedAddresses": {
28459
28629
  capName: "local-network",
28460
28630
  capScope: "system",
@@ -28479,18 +28649,42 @@ Object.freeze({
28479
28649
  addonId: null,
28480
28650
  access: "view"
28481
28651
  },
28652
+ "localNetwork.getTlsStatus": {
28653
+ capName: "local-network",
28654
+ capScope: "system",
28655
+ addonId: null,
28656
+ access: "view"
28657
+ },
28658
+ "localNetwork.getViewerEndpoints": {
28659
+ capName: "local-network",
28660
+ capScope: "system",
28661
+ addonId: null,
28662
+ access: "view"
28663
+ },
28482
28664
  "localNetwork.list": {
28483
28665
  capName: "local-network",
28484
28666
  capScope: "system",
28485
28667
  addonId: null,
28486
28668
  access: "view"
28487
28669
  },
28670
+ "localNetwork.regenerateCertificate": {
28671
+ capName: "local-network",
28672
+ capScope: "system",
28673
+ addonId: null,
28674
+ access: "create"
28675
+ },
28488
28676
  "localNetwork.resetAllowlistToBestMatch": {
28489
28677
  capName: "local-network",
28490
28678
  capScope: "system",
28491
28679
  addonId: null,
28492
28680
  access: "delete"
28493
28681
  },
28682
+ "localNetwork.revertToGeneratedCertificate": {
28683
+ capName: "local-network",
28684
+ capScope: "system",
28685
+ addonId: null,
28686
+ access: "create"
28687
+ },
28494
28688
  "localNetwork.setAllowedAddresses": {
28495
28689
  capName: "local-network",
28496
28690
  capScope: "system",
@@ -28503,6 +28697,18 @@ Object.freeze({
28503
28697
  addonId: null,
28504
28698
  access: "create"
28505
28699
  },
28700
+ "localNetwork.setViewerEndpoints": {
28701
+ capName: "local-network",
28702
+ capScope: "system",
28703
+ addonId: null,
28704
+ access: "create"
28705
+ },
28706
+ "localNetwork.uploadCertificate": {
28707
+ capName: "local-network",
28708
+ capScope: "system",
28709
+ addonId: null,
28710
+ access: "create"
28711
+ },
28506
28712
  "lockControl.lock": {
28507
28713
  capName: "lock-control",
28508
28714
  capScope: "device",
@@ -31383,6 +31589,12 @@ Object.freeze({
31383
31589
  addonId: null,
31384
31590
  access: "create"
31385
31591
  },
31592
+ "terminalSession.updateInstance": {
31593
+ capName: "terminal-session",
31594
+ capScope: "system",
31595
+ addonId: null,
31596
+ access: "create"
31597
+ },
31386
31598
  "terminalSession.writeInput": {
31387
31599
  capName: "terminal-session",
31388
31600
  capScope: "system",
@@ -33870,6 +34082,35 @@ Object.freeze(Object.fromEntries([{
33870
34082
  }]
33871
34083
  }].map((s) => [s.stepId, s.defaultModelId])));
33872
34084
  string().min(1);
34085
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34086
+ stepId: "face-embedding",
34087
+ key: "minLandmarkFaceSize",
34088
+ label: "Min face size for recognition (detection px)",
34089
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
34090
+ type: "slider",
34091
+ min: 0,
34092
+ max: 64,
34093
+ step: 2,
34094
+ default: 24
34095
+ }];
34096
+ function clusterStepSettingKey(stepId, fieldKey) {
34097
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34098
+ }
34099
+ var ClusterSettingNumberSchema = number().finite();
34100
+ function readClusterStepSettings(config) {
34101
+ const out = {};
34102
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
34103
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
34104
+ const value = parsed.success ? parsed.data : field.default;
34105
+ const existing = out[field.stepId] ?? {};
34106
+ out[field.stepId] = {
34107
+ ...existing,
34108
+ [field.key]: value
34109
+ };
34110
+ }
34111
+ return out;
34112
+ }
34113
+ readClusterStepSettings({});
33873
34114
  object({
33874
34115
  /**
33875
34116
  * Fraction of the box's own size added on EACH side before cutting.
@@ -34061,113 +34302,14 @@ var RingBuffer = class {
34061
34302
  * decoder is an explicit opt-in fallback only. A stale/failed settings read at
34062
34303
  * boot therefore resolves to node-av (never leaves the node with no decoder). */
34063
34304
  var DEFAULT_DECODER_BACKEND = "nodeav";
34064
- /** Narrow an unknown settings value to a {@link DecoderBackend}, else `null`. */
34065
- function parseDecoderBackend(value) {
34066
- return value === "ffmpeg" || value === "nodeav" ? value : null;
34067
- }
34068
- /**
34069
- * Normalise a raw kernel node id to the bare node id used for scoping.
34070
- * `localNodeId` can carry a `<node>/<addon>` suffix; the decoder selection is
34071
- * per-NODE, so strip the addon segment. Falls back to `hub`.
34072
- */
34073
- function normalizeDecoderNodeId(rawNodeId) {
34074
- const raw = rawNodeId ?? "hub";
34075
- return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
34076
- }
34077
34305
  //#endregion
34078
34306
  //#region src/shared/decoder-backend.ts
34079
- /** The NEUTRAL addon (pipeline-orchestrator, hub-resident) whose global
34080
- * settings OWN the per-node `backend` selector. Neither decoder addon owns
34081
- * it, so decoder-nodeav / decoder-ffmpeg stay fully independent. */
34082
- var DECODER_OWNER_ADDON_ID = "pipeline-orchestrator";
34083
- function isHydratedField(entry) {
34084
- return typeof entry === "object" && entry !== null && "key" in entry;
34085
- }
34086
- /**
34087
- * Pure selection from an already-read hydrated settings payload: extract the
34088
- * owner's `backend` field value (which the owner projected per-node from its
34089
- * scoped store key) and narrow it. A missing/invalid field or a null payload
34090
- * resolves to {@link DEFAULT_DECODER_BACKEND} — never a bare store key.
34091
- */
34092
- function pickDecoderBackendFromSettings(view) {
34093
- if (view === null) return DEFAULT_DECODER_BACKEND;
34094
- for (const section of view.sections) for (const entry of section.fields) {
34095
- if (!isHydratedField(entry) || entry.key !== "backend") continue;
34096
- return parseDecoderBackend(entry.value) ?? "nodeav";
34097
- }
34098
- return DEFAULT_DECODER_BACKEND;
34099
- }
34100
- /** Missing optional owner fingerprints: ffmpeg may be uninstalled. */
34101
- function isMissingOwnerSettingsError(message) {
34102
- return /not routable/i.test(message) || /provider not available/i.test(message);
34103
- }
34104
- /** Transient transport/settings-store fingerprints worth retrying on. */
34105
- function isTransientSettingsError(message) {
34106
- return /not loaded/i.test(message) || /transport-failed/i.test(message) || /not connected/i.test(message) || /SqliteSettingsBackend not initialized/i.test(message);
34107
- }
34108
34307
  /**
34109
- * Resolve the decoder backend a NON-OWNER addon (`decoder-nodeav`) should run,
34110
- * by reading the OWNER's (`decoder-ffmpeg`) hub-central per-node `backend`.
34111
- *
34112
- * The read routes to the owner addon's child runner; during a simultaneous
34113
- * (re)start the owner may not be up yet → a transient `transport-failed (addon
34114
- * not loaded)`. Immediately defaulting here would ignore an explicit `ffmpeg`
34115
- * selection (the node would silently run the node-av default instead). So retry
34116
- * on the transient fingerprints with a bounded budget (mirrors
34117
- * `BaseAddon.readAddonStoreWithRetry`) until the owner answers; only a
34118
- * persistent failure falls back to {@link DEFAULT_DECODER_BACKEND}.
34119
- *
34120
- * The OWNER addon must NOT call this (it would self-route + deadlock) — it uses
34121
- * {@link resolveOwnDecoderBackend} against its own store instead.
34308
+ * Resolve the decoder backend this addon should run. Owner settings are
34309
+ * ignored: persisted `backend@<nodeId>` rows stay in the store and are not
34310
+ * consulted. The runtime default is always {@link DEFAULT_DECODER_BACKEND}.
34122
34311
  */
34123
- async function resolveDecoderBackend(api, nodeId, logger) {
34124
- if (!api) {
34125
- logger.warn("decoder-backend: no api surface — using default backend", { meta: { default: DEFAULT_DECODER_BACKEND } });
34126
- return DEFAULT_DECODER_BACKEND;
34127
- }
34128
- const normalized = normalizeDecoderNodeId(nodeId);
34129
- const delaysMs = [
34130
- 150,
34131
- 350,
34132
- 600,
34133
- 900,
34134
- 1200
34135
- ];
34136
- let lastErr;
34137
- for (let attempt = 0; attempt <= delaysMs.length; attempt++) {
34138
- try {
34139
- const view = await api.addonSettings.getGlobalSettings.query({
34140
- addonId: DECODER_OWNER_ADDON_ID,
34141
- nodeId: normalized
34142
- });
34143
- if (view !== null) return pickDecoderBackendFromSettings(view);
34144
- lastErr = /* @__PURE__ */ new Error("owner settings unavailable (null)");
34145
- } catch (err) {
34146
- lastErr = err;
34147
- const msg = err instanceof Error ? err.message : String(err);
34148
- if (isTransientSettingsError(msg)) {} else if (isMissingOwnerSettingsError(msg)) {
34149
- logger.warn("decoder-backend: optional owner unavailable — using default backend", { meta: {
34150
- default: DEFAULT_DECODER_BACKEND,
34151
- owner: DECODER_OWNER_ADDON_ID,
34152
- error: msg
34153
- } });
34154
- return DEFAULT_DECODER_BACKEND;
34155
- } else {
34156
- logger.warn("decoder-backend: settings read failed — using default backend", { meta: {
34157
- default: DEFAULT_DECODER_BACKEND,
34158
- error: msg
34159
- } });
34160
- return DEFAULT_DECODER_BACKEND;
34161
- }
34162
- }
34163
- if (attempt === delaysMs.length) break;
34164
- await new Promise((resolve) => setTimeout(resolve, delaysMs[attempt]));
34165
- }
34166
- logger.warn("decoder-backend: owner settings unavailable after retries — using default backend", { meta: {
34167
- default: DEFAULT_DECODER_BACKEND,
34168
- owner: DECODER_OWNER_ADDON_ID,
34169
- error: lastErr instanceof Error ? lastErr.message : String(lastErr)
34170
- } });
34312
+ async function resolveDecoderBackend(_api, _nodeId, _logger) {
34171
34313
  return DEFAULT_DECODER_BACKEND;
34172
34314
  }
34173
34315
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-decoder-ffmpeg",
3
- "version": "1.2.24",
3
+ "version": "1.2.26",
4
4
  "description": "Standalone ffmpeg-subprocess decoder fallback addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",