@camstack/addon-provider-onvif 1.2.25 → 1.2.27

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/addon.js +432 -50
  2. package/dist/addon.mjs +432 -50
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -5804,6 +5804,13 @@ var BaseAddon = class {
5804
5804
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5805
5805
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5806
5806
  _registeredCapNames = [];
5807
+ /**
5808
+ * True only after `readAddonStore` actually answered. Constructor
5809
+ * defaults look like stored config when the store is down — a forked
5810
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5811
+ * mode, 2026-08-25) is not "the operator chose this".
5812
+ */
5813
+ settingsStoreReady = false;
5807
5814
  /** Default config values. Provided via constructor. */
5808
5815
  defaults;
5809
5816
  constructor(defaults) {
@@ -6204,7 +6211,9 @@ var BaseAddon = class {
6204
6211
  ];
6205
6212
  let lastErr;
6206
6213
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6207
- return await settings.readAddonStore() ?? {};
6214
+ const stored = await settings.readAddonStore() ?? {};
6215
+ this.settingsStoreReady = true;
6216
+ return stored;
6208
6217
  } catch (err) {
6209
6218
  lastErr = err;
6210
6219
  const msg = err instanceof Error ? err.message : String(err);
@@ -6212,6 +6221,7 @@ var BaseAddon = class {
6212
6221
  if (attempt === delaysMs.length) break;
6213
6222
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6214
6223
  }
6224
+ this.settingsStoreReady = false;
6215
6225
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6216
6226
  return {};
6217
6227
  }
@@ -8019,6 +8029,15 @@ var LabelDefinitionSchema = object({
8019
8029
  description: string().optional(),
8020
8030
  icon: string().optional()
8021
8031
  });
8032
+ var ClassMapDefinitionSchema = object({
8033
+ mapping: record(string(), _enum([
8034
+ "person",
8035
+ "vehicle",
8036
+ "animal",
8037
+ "package"
8038
+ ])),
8039
+ preserveOriginal: boolean()
8040
+ });
8022
8041
  var MODEL_FORMATS = [
8023
8042
  "onnx",
8024
8043
  "coreml",
@@ -8102,6 +8121,12 @@ var ModelVariantGroupSchema = object({
8102
8121
  */
8103
8122
  resolution: number().int().positive().optional()
8104
8123
  });
8124
+ var ModelProviderIdSchema = _enum([
8125
+ "camstack",
8126
+ "frigate",
8127
+ "scrypted",
8128
+ "custom"
8129
+ ]);
8105
8130
  var ModelCatalogEntrySchema = object({
8106
8131
  id: string(),
8107
8132
  name: string(),
@@ -8197,7 +8222,19 @@ var ModelCatalogEntrySchema = object({
8197
8222
  * `id` stays the source of truth for resolution/download/persistence; grouping
8198
8223
  * is a presentation overlay resolved back to an `id`.
8199
8224
  */
8200
- group: ModelVariantGroupSchema.optional()
8225
+ group: ModelVariantGroupSchema.optional(),
8226
+ /**
8227
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8228
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8229
+ * persisted before this field existed (`inferModelProvider` fills those).
8230
+ */
8231
+ provider: ModelProviderIdSchema.optional(),
8232
+ /**
8233
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8234
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8235
+ * labels already ARE the CamStack macros (Scrypted identity map).
8236
+ */
8237
+ classMap: ClassMapDefinitionSchema.optional()
8201
8238
  });
8202
8239
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8203
8240
  format: literal("openvino"),
@@ -8226,7 +8263,8 @@ var ModelConvertMetadataSchema = object({
8226
8263
  "ocr",
8227
8264
  "segmentation"
8228
8265
  ]),
8229
- faceAlignment: boolean().optional()
8266
+ faceAlignment: boolean().optional(),
8267
+ classMap: ClassMapDefinitionSchema.optional()
8230
8268
  });
8231
8269
  var ConvertResultSchema = object({
8232
8270
  entry: ModelCatalogEntrySchema,
@@ -11774,6 +11812,27 @@ var LinkedDeviceSchema = object({
11774
11812
  features: array(string()),
11775
11813
  producesTrackedEvents: boolean().optional()
11776
11814
  });
11815
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
11816
+ * The batch answer needs the tag; the single-device answer already has it
11817
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
11818
+ var LinkedDevicesForDeviceSchema = object({
11819
+ deviceId: number(),
11820
+ mode: LinkedDevicesModeSchema,
11821
+ devices: array(LinkedDeviceSchema)
11822
+ });
11823
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
11824
+ * `getAllBindings` all answer in. Declared once: three copies of the same
11825
+ * object literal is exactly how the three drift apart. */
11826
+ var DeviceBindingsForDeviceSchema = object({
11827
+ deviceId: number(),
11828
+ entries: array(object({
11829
+ capName: string(),
11830
+ kind: _enum(["native", "wrapped"]),
11831
+ providerAddonId: string(),
11832
+ providerNodeId: string(),
11833
+ nativeAddonId: string()
11834
+ }))
11835
+ });
11777
11836
  var SavedDeviceRowSchema = object({
11778
11837
  /** Numeric id reserved at allocateDeviceId time. */
11779
11838
  id: number(),
@@ -11999,11 +12058,25 @@ method(object({
11999
12058
  projection: _enum(["full", "slim"]).optional(),
12000
12059
  /** Return only camera devices. Filtering server-side instead of
12001
12060
  * shipping 293 rows to find 12. */
12002
- isCamera: boolean().optional()
12061
+ isCamera: boolean().optional(),
12062
+ /**
12063
+ * Return only these device ids. For the caller that already KNOWS the
12064
+ * handful it wants and needs a field the id-bearing answer does not
12065
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12066
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12067
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12068
+ * refetches on the reconcile interval, on a phone.
12069
+ *
12070
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12071
+ * keys rather than rejecting them (verified against the live hub
12072
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12073
+ * it answers today and the caller filters as it already does.
12074
+ */
12075
+ deviceIds: array(number()).optional()
12003
12076
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12004
12077
  mode: LinkedDevicesModeSchema,
12005
12078
  devices: array(LinkedDeviceSchema)
12006
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12079
+ })), method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12007
12080
  deviceId: number(),
12008
12081
  values: record(string(), unknown())
12009
12082
  }), object({ success: literal(true) }), {
@@ -12030,25 +12103,7 @@ method(object({
12030
12103
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12031
12104
  kind: "mutation",
12032
12105
  auth: "admin"
12033
- }), method(object({ deviceId: number() }), object({
12034
- deviceId: number(),
12035
- entries: array(object({
12036
- capName: string(),
12037
- kind: _enum(["native", "wrapped"]),
12038
- providerAddonId: string(),
12039
- providerNodeId: string(),
12040
- nativeAddonId: string()
12041
- }))
12042
- })), method(object({}), array(object({
12043
- deviceId: number(),
12044
- entries: array(object({
12045
- capName: string(),
12046
- kind: _enum(["native", "wrapped"]),
12047
- providerAddonId: string(),
12048
- providerNodeId: string(),
12049
- nativeAddonId: string()
12050
- }))
12051
- }))), method(object({
12106
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12052
12107
  deviceId: number(),
12053
12108
  capName: string(),
12054
12109
  wrapperAddonId: string(),
@@ -14420,12 +14475,15 @@ var NcOccupancyConditionSchema = object({
14420
14475
  * there is no second switch that can disagree with the first and every rule
14421
14476
  * authored before the decision migrates for free (`audioModeOf`):
14422
14477
  *
14423
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14424
- * classifier labels with one of them. No window, no percentage:
14425
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14426
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14427
- * the analyzer's (`classificationMinScore`, per device) a label only
14428
- * reaches this condition if the classifier was already confident enough.
14478
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14479
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14480
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
14481
+ * frames is the wrong question for a classifier that labels 1–3 frames
14482
+ * per episode. The count window is the brake that drops a single-frame
14483
+ * false positive; the rule's own `throttle` cooldown is the other. The
14484
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14485
+ * per device) — a label only reaches this condition if the classifier was
14486
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14429
14487
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14430
14488
  * the condition: at least `hitPercent`% of the samples over
14431
14489
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14452,14 +14510,22 @@ var NcOccupancyConditionSchema = object({
14452
14510
  * an operator who typed `dog` mean the same thing.
14453
14511
  */
14454
14512
  var NcAudioConditionSchema = object({
14455
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14513
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14456
14514
  labels: array(string().min(1)).min(1).optional(),
14457
14515
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14458
14516
  dbThreshold: number().min(-96).max(0).optional(),
14459
14517
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14460
14518
  hitPercent: number().int().min(1).max(100).default(60),
14461
14519
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14462
- samplingSeconds: number().int().min(1).max(300).default(10)
14520
+ samplingSeconds: number().int().min(1).max(300).default(10),
14521
+ /**
14522
+ * LABEL MODE: how many labelled frames must land inside
14523
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14524
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14525
+ */
14526
+ confirmHits: number().int().min(1).max(20).optional(),
14527
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14528
+ confirmWindowSec: number().int().min(1).max(60).optional()
14463
14529
  });
14464
14530
  /**
14465
14531
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -16833,6 +16899,46 @@ var RecentTracksPageSchema = object({
16833
16899
  /** Cursor for the next page, or null when this page is the last. */
16834
16900
  nextCursor: string().nullable()
16835
16901
  });
16902
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
16903
+ var LIST_GROUPS_MAX_LIMIT = 100;
16904
+ var AnalyticsGroupRecordSchema = object({
16905
+ id: string(),
16906
+ deviceId: number().int(),
16907
+ openedAt: number().int(),
16908
+ closedAt: number().int(),
16909
+ timestamp: number().int(),
16910
+ memberCount: number().int(),
16911
+ memberTrackIds: array(string()).readonly(),
16912
+ className: string(),
16913
+ classes: array(string()).readonly(),
16914
+ /** Relative event-media path, or null when the group has no picture yet. */
16915
+ mediaUrl: string().nullable(),
16916
+ singleton: boolean()
16917
+ });
16918
+ var AnalyticsGroupMemberSchema = object({
16919
+ trackId: string(),
16920
+ deviceId: number().int(),
16921
+ className: string(),
16922
+ firstSeen: number().int(),
16923
+ lastSeen: number().int(),
16924
+ mediaUrl: string().nullable()
16925
+ });
16926
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
16927
+ var ListGroupsQueryInput = object({
16928
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
16929
+ deviceIds: array(number()),
16930
+ /** Window lower bound on `closedAt` (inclusive). */
16931
+ since: number().optional(),
16932
+ /** Window upper bound on `openedAt` (inclusive). */
16933
+ until: number().optional(),
16934
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
16935
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
16936
+ cursor: string().optional()
16937
+ });
16938
+ var ListGroupsPageSchema = object({
16939
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
16940
+ nextCursor: string().nullable()
16941
+ });
16836
16942
  var KeyEventQueryInput = object({
16837
16943
  deviceId: number(),
16838
16944
  /** Window lower bound (track firstSeen ≥ since). */
@@ -16908,7 +17014,9 @@ var TrackCascadeCountsSchema = object({
16908
17014
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
16909
17015
  plates: number().int(),
16910
17016
  /** Per-track CLIP search vectors removed (best-effort). */
16911
- embeddings: number().int()
17017
+ embeddings: number().int(),
17018
+ /** Group membership + group rows removed with their last member (best-effort). */
17019
+ groups: number().int()
16912
17020
  });
16913
17021
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
16914
17022
  var DiskReconcileCountsSchema = object({
@@ -17054,7 +17162,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17054
17162
  * stationary registry). Default false: the timeline lists passages,
17055
17163
  * not parking records (operator decision, 2026-08-15). */
17056
17164
  includeStationary: boolean().optional()
17057
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17165
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17166
+ deviceId: number(),
17167
+ groupId: string().min(1)
17168
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17058
17169
  kind: "mutation",
17059
17170
  auth: "admin"
17060
17171
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -17272,6 +17383,33 @@ var NativeCropRefSchema = object({
17272
17383
  h: number()
17273
17384
  })
17274
17385
  });
17386
+ object({
17387
+ crop: object({
17388
+ left: number(),
17389
+ top: number(),
17390
+ width: number().positive(),
17391
+ height: number().positive()
17392
+ }).optional(),
17393
+ content: object({
17394
+ width: number().int().positive(),
17395
+ height: number().int().positive()
17396
+ }),
17397
+ fit: _enum(["stretch", "contain"]),
17398
+ format: _enum([
17399
+ "rgb",
17400
+ "gray",
17401
+ "jpeg"
17402
+ ])
17403
+ });
17404
+ var FrameRefSchema = object({
17405
+ registryId: string().min(1),
17406
+ id: string().min(1),
17407
+ width: number().int().positive(),
17408
+ height: number().int().positive(),
17409
+ format: _enum(["rgb", "gray"]),
17410
+ timestamp: number(),
17411
+ capturedAt: number().optional()
17412
+ });
17275
17413
  var ModelFormatSchema$1 = _enum([
17276
17414
  "onnx",
17277
17415
  "coreml",
@@ -17337,7 +17475,8 @@ var PipelineModelOptionSchema = object({
17337
17475
  sizeMB: number()
17338
17476
  })),
17339
17477
  group: ModelVariantGroupSchema.optional(),
17340
- legacy: boolean().optional()
17478
+ legacy: boolean().optional(),
17479
+ provider: ModelProviderIdSchema.optional()
17341
17480
  });
17342
17481
  var ConfigFieldBridge = custom();
17343
17482
  var PipelineAddonSchemaSchema = object({
@@ -17516,6 +17655,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17516
17655
  steps: array(PipelineStepInputSchema).min(1),
17517
17656
  frame: FrameInputSchema.optional(),
17518
17657
  /**
17658
+ * Process-local lazy frame. Valid only when caller and provider resolve
17659
+ * in the same execution-group process; split/cross-node callers use
17660
+ * `frame`/`image` inline compatibility instead.
17661
+ */
17662
+ frameRef: FrameRefSchema.optional(),
17663
+ /**
17519
17664
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17520
17665
  * the decoded pixels live in. One more member of the one-of
17521
17666
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -17771,7 +17916,10 @@ var NativeCropResultSchema = object({
17771
17916
  * Which source served this crop, so a quality-sensitive consumer (the native
17772
17917
  * `keyFrame`) can reject a degraded fallback:
17773
17918
  * - `native` — cut from the decode worker's retained NATIVE surface (the
17774
- * quality path).
17919
+ * quality path). A subject-tile serve is also native-resolution and stays
17920
+ * `native` here: the public enum cannot name `tile` without a breaking cap
17921
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
17922
+ * internal crop result (`nativeHits` vs `tileHits`).
17775
17923
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
17776
17924
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
17777
17925
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18262,12 +18410,41 @@ var RunnerLocalLoadSchema = object({
18262
18410
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18263
18411
  * working unchanged when they switch to reading from the runner cap.
18264
18412
  */
18413
+ var FrameLazyCountersSchema = object({
18414
+ framesDecoded: number(),
18415
+ framesAdmitted: number(),
18416
+ framesDroppedPixelFree: number(),
18417
+ viewsMaterialized: number(),
18418
+ viewsSkipped: number(),
18419
+ workerToRunnerBytes: number(),
18420
+ runnerToPoolRawBytes: number(),
18421
+ runnerToPoolJpegBytes: number(),
18422
+ onDemandFullFrameRequests: number(),
18423
+ onDemandCropRequests: number(),
18424
+ nativeHits: number(),
18425
+ nativeMisses: number(),
18426
+ tileHits: number(),
18427
+ tileMisses: number(),
18428
+ fallbackHits: number(),
18429
+ fallbackMisses: number(),
18430
+ retainedWritesAvoided: number(),
18431
+ residentRefs: number(),
18432
+ residentBytes: number(),
18433
+ releases: number(),
18434
+ evictions: number(),
18435
+ staleMisses: number()
18436
+ });
18437
+ var FrameLazyMetricsSchema = object({
18438
+ node: FrameLazyCountersSchema,
18439
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18440
+ });
18265
18441
  var RunnerLocalMetricsSchema = object({
18266
18442
  nodeId: string(),
18267
18443
  activeCameras: number(),
18268
18444
  throttledCameras: number(),
18269
18445
  avgInferenceTimeMs: number(),
18270
- queueDepth: number()
18446
+ queueDepth: number(),
18447
+ frameLazy: FrameLazyMetricsSchema.optional()
18271
18448
  });
18272
18449
  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({
18273
18450
  handle: FrameHandleSchema,
@@ -19671,6 +19848,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19671
19848
  location: StorageLocationSchema,
19672
19849
  relativePath: string()
19673
19850
  }), _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" });
19851
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19852
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19853
+ var ProfileSettingsBagSchema = record(string(), unknown());
19674
19854
  /**
19675
19855
  * A live terminal session hosted by the provider addon. Output and input do
19676
19856
  * NOT flow through the capability — they use the addon data plane
@@ -19700,7 +19880,14 @@ var TerminalSessionInfoSchema = object({
19700
19880
  var TerminalProfileInfoSchema = object({
19701
19881
  profileId: string(),
19702
19882
  label: string(),
19703
- description: string().optional()
19883
+ description: string().optional(),
19884
+ /** Spawn defaults the instance form copies on create. */
19885
+ executable: string().optional(),
19886
+ args: array(string()).readonly().optional(),
19887
+ cwd: string().optional(),
19888
+ environment: array(string()).readonly().optional(),
19889
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
19890
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19704
19891
  });
19705
19892
  /**
19706
19893
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19713,7 +19900,12 @@ var TerminalInstanceInfoSchema = object({
19713
19900
  profileId: string(),
19714
19901
  profileLabel: string(),
19715
19902
  name: string(),
19716
- enabled: boolean()
19903
+ enabled: boolean(),
19904
+ executable: string(),
19905
+ args: array(string()).readonly(),
19906
+ cwd: string(),
19907
+ environment: array(string()).readonly(),
19908
+ profileSettings: ProfileSettingsBagSchema
19717
19909
  });
19718
19910
  var TerminalLegacyCameraSchema = object({
19719
19911
  stableId: string(),
@@ -19743,7 +19935,23 @@ var TerminalOutputBatchSchema = object({
19743
19935
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19744
19936
  targetNodeId: string().min(1),
19745
19937
  profileId: string().min(1),
19746
- name: string().trim().min(1).max(160).optional()
19938
+ name: string().trim().min(1).max(160).optional(),
19939
+ executable: string().max(1024).optional(),
19940
+ args: array(string().max(2048)).max(64).optional(),
19941
+ cwd: string().max(1024).optional(),
19942
+ environment: array(string().max(4096)).max(64).optional(),
19943
+ profileSettings: ProfileSettingsBagSchema.optional()
19944
+ }), TerminalInstanceInfoSchema, {
19945
+ kind: "mutation",
19946
+ auth: "admin"
19947
+ }), method(object({
19948
+ instanceId: string().min(1),
19949
+ name: string().trim().min(1).max(160).optional(),
19950
+ executable: string().max(1024).optional(),
19951
+ args: array(string().max(2048)).max(64).optional(),
19952
+ cwd: string().max(1024).optional(),
19953
+ environment: array(string().max(4096)).max(64).optional(),
19954
+ profileSettings: ProfileSettingsBagSchema.optional()
19747
19955
  }), TerminalInstanceInfoSchema, {
19748
19956
  kind: "mutation",
19749
19957
  auth: "admin"
@@ -19765,7 +19973,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19765
19973
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19766
19974
  profileId: string(),
19767
19975
  cols: number().int().positive(),
19768
- rows: number().int().positive()
19976
+ rows: number().int().positive(),
19977
+ executable: string().max(1024).optional(),
19978
+ args: array(string().max(2048)).max(64).optional(),
19979
+ cwd: string().max(1024).optional(),
19980
+ environment: array(string().max(4096)).max(64).optional()
19769
19981
  }), TerminalSessionInfoSchema, {
19770
19982
  kind: "mutation",
19771
19983
  auth: "admin"
@@ -22547,10 +22759,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
22547
22759
  *
22548
22760
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
22549
22761
  * to receive an ordered list of candidate base URLs it should race
22550
- * on connect — LAN IPv4 first (lowest latency when on same network),
22551
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
22552
- * race them with short timeouts and stick with the winner for the
22553
- * session.
22762
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
22763
+ * when on the same network), then public hostname (if a tunnel is
22764
+ * up). The SDK can race them with short timeouts and stick with the
22765
+ * winner for the session.
22554
22766
  *
22555
22767
  * Why hub-only: agents are not directly addressable by the operator's
22556
22768
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -22705,6 +22917,17 @@ var NotificationEndpointSchema = object({
22705
22917
  /** What the ranking currently resolves to (null when nothing is reachable). */
22706
22918
  resolved: string().nullable()
22707
22919
  });
22920
+ /**
22921
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
22922
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
22923
+ * currently expands to, so the UI can show the effective set either way.
22924
+ */
22925
+ var ViewerEndpointsSchema = object({
22926
+ /** The operator's explicit race set, or empty for AUTO. */
22927
+ baseUrls: array(string()).readonly(),
22928
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
22929
+ resolved: array(string()).readonly()
22930
+ });
22708
22931
  var AllowedAddressesSchema = object({
22709
22932
  /**
22710
22933
  * Allowlist of interface addresses operators have explicitly opted
@@ -22713,6 +22936,20 @@ var AllowedAddressesSchema = object({
22713
22936
  * Network Addresses admin page and persisted by the addon.
22714
22937
  */
22715
22938
  addresses: array(string()).readonly() });
22939
+ var TlsStatusSchema = object({
22940
+ mode: _enum([
22941
+ "generated",
22942
+ "uploaded",
22943
+ "disabled"
22944
+ ]),
22945
+ leafFingerprintSha256: string().nullable(),
22946
+ caFingerprintSha256: string().nullable(),
22947
+ validTo: string().nullable(),
22948
+ sans: array(string()),
22949
+ caCertPem: string().nullable(),
22950
+ reissueError: string().nullable(),
22951
+ restartRequired: boolean()
22952
+ });
22716
22953
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22717
22954
  /**
22718
22955
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -22722,17 +22959,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
22722
22959
  */
22723
22960
  port: number().int().min(1).max(65535).optional(),
22724
22961
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22725
- * candidate. Default `true`. */
22962
+ * candidate. Default `false` — loopback is not a client route. */
22726
22963
  includeLoopback: boolean().optional(),
22727
- /** Skip IPv6 entries. Some legacy clients can't parse them.
22728
- * Default `false`. */
22964
+ /** Skip IPv6 entries. Default `false` the palette includes stable
22965
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
22966
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
22729
22967
  ipv4Only: boolean().optional(),
22730
22968
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
22731
22969
  * Pass `'https'` when the caller is itself loaded over HTTPS
22732
22970
  * to avoid mixed-content blocks in the browser. The public
22733
22971
  * tunnel always emits `https://` regardless. */
22734
22972
  scheme: _enum(["http", "https"]).optional()
22735
- }), 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" });
22973
+ }), 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, {
22974
+ kind: "mutation",
22975
+ auth: "admin"
22976
+ }), method(object({
22977
+ certPem: string().min(1),
22978
+ keyPem: string().min(1),
22979
+ caPem: string().optional()
22980
+ }), TlsStatusSchema, {
22981
+ kind: "mutation",
22982
+ auth: "admin"
22983
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
22984
+ kind: "mutation",
22985
+ auth: "admin"
22986
+ });
22736
22987
  object({
22737
22988
  /** Lifecycle state of the lock. `jammed` means the motor reported
22738
22989
  * failure to reach the target — operator intervention required. */
@@ -23913,7 +24164,12 @@ var PlateInfoSchema = object({
23913
24164
  plateBbox: BoundingBoxSchema.optional(),
23914
24165
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
23915
24166
  keyFrameMediaKey: string().optional(),
23916
- base64: string().optional()
24167
+ base64: string().optional(),
24168
+ /**
24169
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24170
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24171
+ */
24172
+ cropUrl: string().optional()
23917
24173
  });
23918
24174
  var MediaFileLiteSchema = object({
23919
24175
  key: string(),
@@ -28005,6 +28261,12 @@ Object.freeze({
28005
28261
  addonId: null,
28006
28262
  access: "view"
28007
28263
  },
28264
+ "deviceManager.getBindingsBatch": {
28265
+ capName: "device-manager",
28266
+ capScope: "system",
28267
+ addonId: null,
28268
+ access: "view"
28269
+ },
28008
28270
  "deviceManager.getChildren": {
28009
28271
  capName: "device-manager",
28010
28272
  capScope: "system",
@@ -28065,6 +28327,12 @@ Object.freeze({
28065
28327
  addonId: null,
28066
28328
  access: "view"
28067
28329
  },
28330
+ "deviceManager.getLinkedDevicesBatch": {
28331
+ capName: "device-manager",
28332
+ capScope: "system",
28333
+ addonId: null,
28334
+ access: "view"
28335
+ },
28068
28336
  "deviceManager.getRoleDisplayDefaults": {
28069
28337
  capName: "device-manager",
28070
28338
  capScope: "system",
@@ -28947,6 +29215,12 @@ Object.freeze({
28947
29215
  addonId: null,
28948
29216
  access: "create"
28949
29217
  },
29218
+ "localNetwork.downloadCa": {
29219
+ capName: "local-network",
29220
+ capScope: "system",
29221
+ addonId: null,
29222
+ access: "view"
29223
+ },
28950
29224
  "localNetwork.getAllowedAddresses": {
28951
29225
  capName: "local-network",
28952
29226
  capScope: "system",
@@ -28971,18 +29245,42 @@ Object.freeze({
28971
29245
  addonId: null,
28972
29246
  access: "view"
28973
29247
  },
29248
+ "localNetwork.getTlsStatus": {
29249
+ capName: "local-network",
29250
+ capScope: "system",
29251
+ addonId: null,
29252
+ access: "view"
29253
+ },
29254
+ "localNetwork.getViewerEndpoints": {
29255
+ capName: "local-network",
29256
+ capScope: "system",
29257
+ addonId: null,
29258
+ access: "view"
29259
+ },
28974
29260
  "localNetwork.list": {
28975
29261
  capName: "local-network",
28976
29262
  capScope: "system",
28977
29263
  addonId: null,
28978
29264
  access: "view"
28979
29265
  },
29266
+ "localNetwork.regenerateCertificate": {
29267
+ capName: "local-network",
29268
+ capScope: "system",
29269
+ addonId: null,
29270
+ access: "create"
29271
+ },
28980
29272
  "localNetwork.resetAllowlistToBestMatch": {
28981
29273
  capName: "local-network",
28982
29274
  capScope: "system",
28983
29275
  addonId: null,
28984
29276
  access: "delete"
28985
29277
  },
29278
+ "localNetwork.revertToGeneratedCertificate": {
29279
+ capName: "local-network",
29280
+ capScope: "system",
29281
+ addonId: null,
29282
+ access: "create"
29283
+ },
28986
29284
  "localNetwork.setAllowedAddresses": {
28987
29285
  capName: "local-network",
28988
29286
  capScope: "system",
@@ -28995,6 +29293,18 @@ Object.freeze({
28995
29293
  addonId: null,
28996
29294
  access: "create"
28997
29295
  },
29296
+ "localNetwork.setViewerEndpoints": {
29297
+ capName: "local-network",
29298
+ capScope: "system",
29299
+ addonId: null,
29300
+ access: "create"
29301
+ },
29302
+ "localNetwork.uploadCertificate": {
29303
+ capName: "local-network",
29304
+ capScope: "system",
29305
+ addonId: null,
29306
+ access: "create"
29307
+ },
28998
29308
  "lockControl.lock": {
28999
29309
  capName: "lock-control",
29000
29310
  capScope: "device",
@@ -29793,6 +30103,12 @@ Object.freeze({
29793
30103
  addonId: null,
29794
30104
  access: "view"
29795
30105
  },
30106
+ "pipelineAnalytics.getGroup": {
30107
+ capName: "pipeline-analytics",
30108
+ capScope: "device",
30109
+ addonId: null,
30110
+ access: "view"
30111
+ },
29796
30112
  "pipelineAnalytics.getKeyEvents": {
29797
30113
  capName: "pipeline-analytics",
29798
30114
  capScope: "device",
@@ -29877,6 +30193,12 @@ Object.freeze({
29877
30193
  addonId: null,
29878
30194
  access: "view"
29879
30195
  },
30196
+ "pipelineAnalytics.listGroups": {
30197
+ capName: "pipeline-analytics",
30198
+ capScope: "device",
30199
+ addonId: null,
30200
+ access: "view"
30201
+ },
29880
30202
  "pipelineAnalytics.listOpsLog": {
29881
30203
  capName: "pipeline-analytics",
29882
30204
  capScope: "device",
@@ -31875,6 +32197,12 @@ Object.freeze({
31875
32197
  addonId: null,
31876
32198
  access: "create"
31877
32199
  },
32200
+ "terminalSession.updateInstance": {
32201
+ capName: "terminal-session",
32202
+ capScope: "system",
32203
+ addonId: null,
32204
+ access: "create"
32205
+ },
31878
32206
  "terminalSession.writeInput": {
31879
32207
  capName: "terminal-session",
31880
32208
  capScope: "system",
@@ -32652,6 +32980,11 @@ Object.freeze({
32652
32980
  form: "single",
32653
32981
  optional: false
32654
32982
  }],
32983
+ "deviceManager.getBindingsBatch": [{
32984
+ name: "deviceIds",
32985
+ form: "array",
32986
+ optional: false
32987
+ }],
32655
32988
  "deviceManager.getChildren": [{
32656
32989
  name: "parentDeviceId",
32657
32990
  form: "single",
@@ -32697,6 +33030,11 @@ Object.freeze({
32697
33030
  form: "single",
32698
33031
  optional: false
32699
33032
  }],
33033
+ "deviceManager.getLinkedDevicesBatch": [{
33034
+ name: "deviceIds",
33035
+ form: "array",
33036
+ optional: false
33037
+ }],
32700
33038
  "deviceManager.getSettingsSchema": [{
32701
33039
  name: "deviceId",
32702
33040
  form: "single",
@@ -32717,6 +33055,11 @@ Object.freeze({
32717
33055
  form: "single",
32718
33056
  optional: false
32719
33057
  }],
33058
+ "deviceManager.listAll": [{
33059
+ name: "deviceIds",
33060
+ form: "array",
33061
+ optional: true
33062
+ }],
32720
33063
  "deviceManager.loadConfig": [{
32721
33064
  name: "deviceId",
32722
33065
  form: "single",
@@ -33290,6 +33633,11 @@ Object.freeze({
33290
33633
  form: "single",
33291
33634
  optional: false
33292
33635
  }],
33636
+ "pipelineAnalytics.getGroup": [{
33637
+ name: "deviceId",
33638
+ form: "single",
33639
+ optional: false
33640
+ }],
33293
33641
  "pipelineAnalytics.getKeyEvents": [{
33294
33642
  name: "deviceId",
33295
33643
  form: "single",
@@ -33345,6 +33693,11 @@ Object.freeze({
33345
33693
  form: "array",
33346
33694
  optional: false
33347
33695
  }],
33696
+ "pipelineAnalytics.listGroups": [{
33697
+ name: "deviceIds",
33698
+ form: "array",
33699
+ optional: false
33700
+ }],
33348
33701
  "pipelineAnalytics.listOpsLog": [{
33349
33702
  name: "deviceId",
33350
33703
  form: "single",
@@ -34362,6 +34715,35 @@ Object.freeze(Object.fromEntries([{
34362
34715
  }]
34363
34716
  }].map((s) => [s.stepId, s.defaultModelId])));
34364
34717
  string().min(1);
34718
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34719
+ stepId: "face-embedding",
34720
+ key: "minLandmarkFaceSize",
34721
+ label: "Min face size for recognition (detection px)",
34722
+ 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.",
34723
+ type: "slider",
34724
+ min: 0,
34725
+ max: 64,
34726
+ step: 2,
34727
+ default: 24
34728
+ }];
34729
+ function clusterStepSettingKey(stepId, fieldKey) {
34730
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34731
+ }
34732
+ var ClusterSettingNumberSchema = number().finite();
34733
+ function readClusterStepSettings(config) {
34734
+ const out = {};
34735
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
34736
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
34737
+ const value = parsed.success ? parsed.data : field.default;
34738
+ const existing = out[field.stepId] ?? {};
34739
+ out[field.stepId] = {
34740
+ ...existing,
34741
+ [field.key]: value
34742
+ };
34743
+ }
34744
+ return out;
34745
+ }
34746
+ readClusterStepSettings({});
34365
34747
  object({
34366
34748
  /**
34367
34749
  * Fraction of the box's own size added on EACH side before cutting.