@camstack/addon-export-hap 1.2.37 → 1.2.39

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.
@@ -5874,6 +5874,13 @@ var BaseAddon = class {
5874
5874
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5875
5875
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5876
5876
  _registeredCapNames = [];
5877
+ /**
5878
+ * True only after `readAddonStore` actually answered. Constructor
5879
+ * defaults look like stored config when the store is down — a forked
5880
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5881
+ * mode, 2026-08-25) is not "the operator chose this".
5882
+ */
5883
+ settingsStoreReady = false;
5877
5884
  /** Default config values. Provided via constructor. */
5878
5885
  defaults;
5879
5886
  constructor(defaults) {
@@ -6274,7 +6281,9 @@ var BaseAddon = class {
6274
6281
  ];
6275
6282
  let lastErr;
6276
6283
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6277
- return await settings.readAddonStore() ?? {};
6284
+ const stored = await settings.readAddonStore() ?? {};
6285
+ this.settingsStoreReady = true;
6286
+ return stored;
6278
6287
  } catch (err) {
6279
6288
  lastErr = err;
6280
6289
  const msg = err instanceof Error ? err.message : String(err);
@@ -6282,6 +6291,7 @@ var BaseAddon = class {
6282
6291
  if (attempt === delaysMs.length) break;
6283
6292
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6284
6293
  }
6294
+ this.settingsStoreReady = false;
6285
6295
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6286
6296
  return {};
6287
6297
  }
@@ -8728,6 +8738,15 @@ var LabelDefinitionSchema = object({
8728
8738
  description: string().optional(),
8729
8739
  icon: string().optional()
8730
8740
  });
8741
+ var ClassMapDefinitionSchema = object({
8742
+ mapping: record(string(), _enum([
8743
+ "person",
8744
+ "vehicle",
8745
+ "animal",
8746
+ "package"
8747
+ ])),
8748
+ preserveOriginal: boolean()
8749
+ });
8731
8750
  var MODEL_FORMATS = [
8732
8751
  "onnx",
8733
8752
  "coreml",
@@ -8811,6 +8830,12 @@ var ModelVariantGroupSchema = object({
8811
8830
  */
8812
8831
  resolution: number().int().positive().optional()
8813
8832
  });
8833
+ var ModelProviderIdSchema = _enum([
8834
+ "camstack",
8835
+ "frigate",
8836
+ "scrypted",
8837
+ "custom"
8838
+ ]);
8814
8839
  var ModelCatalogEntrySchema = object({
8815
8840
  id: string(),
8816
8841
  name: string(),
@@ -8906,7 +8931,19 @@ var ModelCatalogEntrySchema = object({
8906
8931
  * `id` stays the source of truth for resolution/download/persistence; grouping
8907
8932
  * is a presentation overlay resolved back to an `id`.
8908
8933
  */
8909
- group: ModelVariantGroupSchema.optional()
8934
+ group: ModelVariantGroupSchema.optional(),
8935
+ /**
8936
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8937
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8938
+ * persisted before this field existed (`inferModelProvider` fills those).
8939
+ */
8940
+ provider: ModelProviderIdSchema.optional(),
8941
+ /**
8942
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8943
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8944
+ * labels already ARE the CamStack macros (Scrypted identity map).
8945
+ */
8946
+ classMap: ClassMapDefinitionSchema.optional()
8910
8947
  });
8911
8948
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8912
8949
  format: literal("openvino"),
@@ -8935,7 +8972,8 @@ var ModelConvertMetadataSchema = object({
8935
8972
  "ocr",
8936
8973
  "segmentation"
8937
8974
  ]),
8938
- faceAlignment: boolean().optional()
8975
+ faceAlignment: boolean().optional(),
8976
+ classMap: ClassMapDefinitionSchema.optional()
8939
8977
  });
8940
8978
  var ConvertResultSchema = object({
8941
8979
  entry: ModelCatalogEntrySchema,
@@ -12509,6 +12547,27 @@ var LinkedDeviceSchema = object({
12509
12547
  features: array(string()),
12510
12548
  producesTrackedEvents: boolean().optional()
12511
12549
  });
12550
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12551
+ * The batch answer needs the tag; the single-device answer already has it
12552
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12553
+ var LinkedDevicesForDeviceSchema = object({
12554
+ deviceId: number(),
12555
+ mode: LinkedDevicesModeSchema,
12556
+ devices: array(LinkedDeviceSchema)
12557
+ });
12558
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12559
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12560
+ * object literal is exactly how the three drift apart. */
12561
+ var DeviceBindingsForDeviceSchema = object({
12562
+ deviceId: number(),
12563
+ entries: array(object({
12564
+ capName: string(),
12565
+ kind: _enum(["native", "wrapped"]),
12566
+ providerAddonId: string(),
12567
+ providerNodeId: string(),
12568
+ nativeAddonId: string()
12569
+ }))
12570
+ });
12512
12571
  var SavedDeviceRowSchema = object({
12513
12572
  /** Numeric id reserved at allocateDeviceId time. */
12514
12573
  id: number(),
@@ -12734,11 +12793,25 @@ method(object({
12734
12793
  projection: _enum(["full", "slim"]).optional(),
12735
12794
  /** Return only camera devices. Filtering server-side instead of
12736
12795
  * shipping 293 rows to find 12. */
12737
- isCamera: boolean().optional()
12796
+ isCamera: boolean().optional(),
12797
+ /**
12798
+ * Return only these device ids. For the caller that already KNOWS the
12799
+ * handful it wants and needs a field the id-bearing answer does not
12800
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12801
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12802
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12803
+ * refetches on the reconcile interval, on a phone.
12804
+ *
12805
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12806
+ * keys rather than rejecting them (verified against the live hub
12807
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12808
+ * it answers today and the caller filters as it already does.
12809
+ */
12810
+ deviceIds: array(number()).optional()
12738
12811
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12739
12812
  mode: LinkedDevicesModeSchema,
12740
12813
  devices: array(LinkedDeviceSchema)
12741
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12814
+ })), 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({
12742
12815
  deviceId: number(),
12743
12816
  values: record(string(), unknown())
12744
12817
  }), object({ success: literal(true) }), {
@@ -12765,25 +12838,7 @@ method(object({
12765
12838
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12766
12839
  kind: "mutation",
12767
12840
  auth: "admin"
12768
- }), method(object({ deviceId: number() }), object({
12769
- deviceId: number(),
12770
- entries: array(object({
12771
- capName: string(),
12772
- kind: _enum(["native", "wrapped"]),
12773
- providerAddonId: string(),
12774
- providerNodeId: string(),
12775
- nativeAddonId: string()
12776
- }))
12777
- })), method(object({}), array(object({
12778
- deviceId: number(),
12779
- entries: array(object({
12780
- capName: string(),
12781
- kind: _enum(["native", "wrapped"]),
12782
- providerAddonId: string(),
12783
- providerNodeId: string(),
12784
- nativeAddonId: string()
12785
- }))
12786
- }))), method(object({
12841
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12787
12842
  deviceId: number(),
12788
12843
  capName: string(),
12789
12844
  wrapperAddonId: string(),
@@ -15155,12 +15210,15 @@ var NcOccupancyConditionSchema = object({
15155
15210
  * there is no second switch that can disagree with the first and every rule
15156
15211
  * authored before the decision migrates for free (`audioModeOf`):
15157
15212
  *
15158
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
15159
- * classifier labels with one of them. No window, no percentage:
15160
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
15161
- * `throttle` cooldown is the only brake. The per-label confidence floor is
15162
- * the analyzer's (`classificationMinScore`, per device) a label only
15163
- * reaches this condition if the classifier was already confident enough.
15213
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
15214
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
15215
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
15216
+ * frames is the wrong question for a classifier that labels 1–3 frames
15217
+ * per episode. The count window is the brake that drops a single-frame
15218
+ * false positive; the rule's own `throttle` cooldown is the other. The
15219
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
15220
+ * per device) — a label only reaches this condition if the classifier was
15221
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
15164
15222
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
15165
15223
  * the condition: at least `hitPercent`% of the samples over
15166
15224
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -15187,14 +15245,22 @@ var NcOccupancyConditionSchema = object({
15187
15245
  * an operator who typed `dog` mean the same thing.
15188
15246
  */
15189
15247
  var NcAudioConditionSchema = object({
15190
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
15248
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
15191
15249
  labels: array(string().min(1)).min(1).optional(),
15192
15250
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
15193
15251
  dbThreshold: number().min(-96).max(0).optional(),
15194
15252
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
15195
15253
  hitPercent: number().int().min(1).max(100).default(60),
15196
15254
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
15197
- samplingSeconds: number().int().min(1).max(300).default(10)
15255
+ samplingSeconds: number().int().min(1).max(300).default(10),
15256
+ /**
15257
+ * LABEL MODE: how many labelled frames must land inside
15258
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
15259
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
15260
+ */
15261
+ confirmHits: number().int().min(1).max(20).optional(),
15262
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
15263
+ confirmWindowSec: number().int().min(1).max(60).optional()
15198
15264
  });
15199
15265
  /**
15200
15266
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17568,6 +17634,46 @@ var RecentTracksPageSchema = object({
17568
17634
  /** Cursor for the next page, or null when this page is the last. */
17569
17635
  nextCursor: string().nullable()
17570
17636
  });
17637
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17638
+ var LIST_GROUPS_MAX_LIMIT = 100;
17639
+ var AnalyticsGroupRecordSchema = object({
17640
+ id: string(),
17641
+ deviceId: number().int(),
17642
+ openedAt: number().int(),
17643
+ closedAt: number().int(),
17644
+ timestamp: number().int(),
17645
+ memberCount: number().int(),
17646
+ memberTrackIds: array(string()).readonly(),
17647
+ className: string(),
17648
+ classes: array(string()).readonly(),
17649
+ /** Relative event-media path, or null when the group has no picture yet. */
17650
+ mediaUrl: string().nullable(),
17651
+ singleton: boolean()
17652
+ });
17653
+ var AnalyticsGroupMemberSchema = object({
17654
+ trackId: string(),
17655
+ deviceId: number().int(),
17656
+ className: string(),
17657
+ firstSeen: number().int(),
17658
+ lastSeen: number().int(),
17659
+ mediaUrl: string().nullable()
17660
+ });
17661
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17662
+ var ListGroupsQueryInput = object({
17663
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17664
+ deviceIds: array(number()),
17665
+ /** Window lower bound on `closedAt` (inclusive). */
17666
+ since: number().optional(),
17667
+ /** Window upper bound on `openedAt` (inclusive). */
17668
+ until: number().optional(),
17669
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17670
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17671
+ cursor: string().optional()
17672
+ });
17673
+ var ListGroupsPageSchema = object({
17674
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17675
+ nextCursor: string().nullable()
17676
+ });
17571
17677
  var KeyEventQueryInput = object({
17572
17678
  deviceId: number(),
17573
17679
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17643,7 +17749,9 @@ var TrackCascadeCountsSchema = object({
17643
17749
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17644
17750
  plates: number().int(),
17645
17751
  /** Per-track CLIP search vectors removed (best-effort). */
17646
- embeddings: number().int()
17752
+ embeddings: number().int(),
17753
+ /** Group membership + group rows removed with their last member (best-effort). */
17754
+ groups: number().int()
17647
17755
  });
17648
17756
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17649
17757
  var DiskReconcileCountsSchema = object({
@@ -17789,7 +17897,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17789
17897
  * stationary registry). Default false: the timeline lists passages,
17790
17898
  * not parking records (operator decision, 2026-08-15). */
17791
17899
  includeStationary: boolean().optional()
17792
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17900
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17901
+ deviceId: number(),
17902
+ groupId: string().min(1)
17903
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17793
17904
  kind: "mutation",
17794
17905
  auth: "admin"
17795
17906
  }), 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({
@@ -18007,6 +18118,33 @@ var NativeCropRefSchema = object({
18007
18118
  h: number()
18008
18119
  })
18009
18120
  });
18121
+ object({
18122
+ crop: object({
18123
+ left: number(),
18124
+ top: number(),
18125
+ width: number().positive(),
18126
+ height: number().positive()
18127
+ }).optional(),
18128
+ content: object({
18129
+ width: number().int().positive(),
18130
+ height: number().int().positive()
18131
+ }),
18132
+ fit: _enum(["stretch", "contain"]),
18133
+ format: _enum([
18134
+ "rgb",
18135
+ "gray",
18136
+ "jpeg"
18137
+ ])
18138
+ });
18139
+ var FrameRefSchema = object({
18140
+ registryId: string().min(1),
18141
+ id: string().min(1),
18142
+ width: number().int().positive(),
18143
+ height: number().int().positive(),
18144
+ format: _enum(["rgb", "gray"]),
18145
+ timestamp: number(),
18146
+ capturedAt: number().optional()
18147
+ });
18010
18148
  var ModelFormatSchema$1 = _enum([
18011
18149
  "onnx",
18012
18150
  "coreml",
@@ -18072,7 +18210,8 @@ var PipelineModelOptionSchema = object({
18072
18210
  sizeMB: number()
18073
18211
  })),
18074
18212
  group: ModelVariantGroupSchema.optional(),
18075
- legacy: boolean().optional()
18213
+ legacy: boolean().optional(),
18214
+ provider: ModelProviderIdSchema.optional()
18076
18215
  });
18077
18216
  var ConfigFieldBridge = custom();
18078
18217
  var PipelineAddonSchemaSchema = object({
@@ -18251,6 +18390,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
18251
18390
  steps: array(PipelineStepInputSchema).min(1),
18252
18391
  frame: FrameInputSchema.optional(),
18253
18392
  /**
18393
+ * Process-local lazy frame. Valid only when caller and provider resolve
18394
+ * in the same execution-group process; split/cross-node callers use
18395
+ * `frame`/`image` inline compatibility instead.
18396
+ */
18397
+ frameRef: FrameRefSchema.optional(),
18398
+ /**
18254
18399
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
18255
18400
  * the decoded pixels live in. One more member of the one-of
18256
18401
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18506,7 +18651,10 @@ var NativeCropResultSchema = object({
18506
18651
  * Which source served this crop, so a quality-sensitive consumer (the native
18507
18652
  * `keyFrame`) can reject a degraded fallback:
18508
18653
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18509
- * quality path).
18654
+ * quality path). A subject-tile serve is also native-resolution and stays
18655
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18656
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18657
+ * internal crop result (`nativeHits` vs `tileHits`).
18510
18658
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18511
18659
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18512
18660
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18997,12 +19145,41 @@ var RunnerLocalLoadSchema = object({
18997
19145
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18998
19146
  * working unchanged when they switch to reading from the runner cap.
18999
19147
  */
19148
+ var FrameLazyCountersSchema = object({
19149
+ framesDecoded: number(),
19150
+ framesAdmitted: number(),
19151
+ framesDroppedPixelFree: number(),
19152
+ viewsMaterialized: number(),
19153
+ viewsSkipped: number(),
19154
+ workerToRunnerBytes: number(),
19155
+ runnerToPoolRawBytes: number(),
19156
+ runnerToPoolJpegBytes: number(),
19157
+ onDemandFullFrameRequests: number(),
19158
+ onDemandCropRequests: number(),
19159
+ nativeHits: number(),
19160
+ nativeMisses: number(),
19161
+ tileHits: number(),
19162
+ tileMisses: number(),
19163
+ fallbackHits: number(),
19164
+ fallbackMisses: number(),
19165
+ retainedWritesAvoided: number(),
19166
+ residentRefs: number(),
19167
+ residentBytes: number(),
19168
+ releases: number(),
19169
+ evictions: number(),
19170
+ staleMisses: number()
19171
+ });
19172
+ var FrameLazyMetricsSchema = object({
19173
+ node: FrameLazyCountersSchema,
19174
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
19175
+ });
19000
19176
  var RunnerLocalMetricsSchema = object({
19001
19177
  nodeId: string(),
19002
19178
  activeCameras: number(),
19003
19179
  throttledCameras: number(),
19004
19180
  avgInferenceTimeMs: number(),
19005
- queueDepth: number()
19181
+ queueDepth: number(),
19182
+ frameLazy: FrameLazyMetricsSchema.optional()
19006
19183
  });
19007
19184
  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({
19008
19185
  handle: FrameHandleSchema,
@@ -20302,6 +20479,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
20302
20479
  location: StorageLocationSchema,
20303
20480
  relativePath: string()
20304
20481
  }), _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" });
20482
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
20483
+ var ProfileSettingsSchemaBridge = unknown().nullable();
20484
+ var ProfileSettingsBagSchema = record(string(), unknown());
20305
20485
  /**
20306
20486
  * A live terminal session hosted by the provider addon. Output and input do
20307
20487
  * NOT flow through the capability — they use the addon data plane
@@ -20331,7 +20511,14 @@ var TerminalSessionInfoSchema = object({
20331
20511
  var TerminalProfileInfoSchema = object({
20332
20512
  profileId: string(),
20333
20513
  label: string(),
20334
- description: string().optional()
20514
+ description: string().optional(),
20515
+ /** Spawn defaults the instance form copies on create. */
20516
+ executable: string().optional(),
20517
+ args: array(string()).readonly().optional(),
20518
+ cwd: string().optional(),
20519
+ environment: array(string()).readonly().optional(),
20520
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20521
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
20335
20522
  });
20336
20523
  /**
20337
20524
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -20344,7 +20531,12 @@ var TerminalInstanceInfoSchema = object({
20344
20531
  profileId: string(),
20345
20532
  profileLabel: string(),
20346
20533
  name: string(),
20347
- enabled: boolean()
20534
+ enabled: boolean(),
20535
+ executable: string(),
20536
+ args: array(string()).readonly(),
20537
+ cwd: string(),
20538
+ environment: array(string()).readonly(),
20539
+ profileSettings: ProfileSettingsBagSchema
20348
20540
  });
20349
20541
  var TerminalLegacyCameraSchema = object({
20350
20542
  stableId: string(),
@@ -20374,7 +20566,23 @@ var TerminalOutputBatchSchema = object({
20374
20566
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
20375
20567
  targetNodeId: string().min(1),
20376
20568
  profileId: string().min(1),
20377
- name: string().trim().min(1).max(160).optional()
20569
+ name: string().trim().min(1).max(160).optional(),
20570
+ executable: string().max(1024).optional(),
20571
+ args: array(string().max(2048)).max(64).optional(),
20572
+ cwd: string().max(1024).optional(),
20573
+ environment: array(string().max(4096)).max(64).optional(),
20574
+ profileSettings: ProfileSettingsBagSchema.optional()
20575
+ }), TerminalInstanceInfoSchema, {
20576
+ kind: "mutation",
20577
+ auth: "admin"
20578
+ }), method(object({
20579
+ instanceId: string().min(1),
20580
+ name: string().trim().min(1).max(160).optional(),
20581
+ executable: string().max(1024).optional(),
20582
+ args: array(string().max(2048)).max(64).optional(),
20583
+ cwd: string().max(1024).optional(),
20584
+ environment: array(string().max(4096)).max(64).optional(),
20585
+ profileSettings: ProfileSettingsBagSchema.optional()
20378
20586
  }), TerminalInstanceInfoSchema, {
20379
20587
  kind: "mutation",
20380
20588
  auth: "admin"
@@ -20396,7 +20604,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
20396
20604
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
20397
20605
  profileId: string(),
20398
20606
  cols: number().int().positive(),
20399
- rows: number().int().positive()
20607
+ rows: number().int().positive(),
20608
+ executable: string().max(1024).optional(),
20609
+ args: array(string().max(2048)).max(64).optional(),
20610
+ cwd: string().max(1024).optional(),
20611
+ environment: array(string().max(4096)).max(64).optional()
20400
20612
  }), TerminalSessionInfoSchema, {
20401
20613
  kind: "mutation",
20402
20614
  auth: "admin"
@@ -23214,10 +23426,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
23214
23426
  *
23215
23427
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
23216
23428
  * to receive an ordered list of candidate base URLs it should race
23217
- * on connect — LAN IPv4 first (lowest latency when on same network),
23218
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
23219
- * race them with short timeouts and stick with the winner for the
23220
- * session.
23429
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
23430
+ * when on the same network), then public hostname (if a tunnel is
23431
+ * up). The SDK can race them with short timeouts and stick with the
23432
+ * winner for the session.
23221
23433
  *
23222
23434
  * Why hub-only: agents are not directly addressable by the operator's
23223
23435
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -23372,6 +23584,17 @@ var NotificationEndpointSchema = object({
23372
23584
  /** What the ranking currently resolves to (null when nothing is reachable). */
23373
23585
  resolved: string().nullable()
23374
23586
  });
23587
+ /**
23588
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
23589
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
23590
+ * currently expands to, so the UI can show the effective set either way.
23591
+ */
23592
+ var ViewerEndpointsSchema = object({
23593
+ /** The operator's explicit race set, or empty for AUTO. */
23594
+ baseUrls: array(string()).readonly(),
23595
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
23596
+ resolved: array(string()).readonly()
23597
+ });
23375
23598
  var AllowedAddressesSchema = object({
23376
23599
  /**
23377
23600
  * Allowlist of interface addresses operators have explicitly opted
@@ -23380,6 +23603,20 @@ var AllowedAddressesSchema = object({
23380
23603
  * Network Addresses admin page and persisted by the addon.
23381
23604
  */
23382
23605
  addresses: array(string()).readonly() });
23606
+ var TlsStatusSchema = object({
23607
+ mode: _enum([
23608
+ "generated",
23609
+ "uploaded",
23610
+ "disabled"
23611
+ ]),
23612
+ leafFingerprintSha256: string().nullable(),
23613
+ caFingerprintSha256: string().nullable(),
23614
+ validTo: string().nullable(),
23615
+ sans: array(string()),
23616
+ caCertPem: string().nullable(),
23617
+ reissueError: string().nullable(),
23618
+ restartRequired: boolean()
23619
+ });
23383
23620
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
23384
23621
  /**
23385
23622
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -23389,17 +23626,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
23389
23626
  */
23390
23627
  port: number().int().min(1).max(65535).optional(),
23391
23628
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
23392
- * candidate. Default `true`. */
23629
+ * candidate. Default `false` — loopback is not a client route. */
23393
23630
  includeLoopback: boolean().optional(),
23394
- /** Skip IPv6 entries. Some legacy clients can't parse them.
23395
- * Default `false`. */
23631
+ /** Skip IPv6 entries. Default `false` the palette includes stable
23632
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
23633
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
23396
23634
  ipv4Only: boolean().optional(),
23397
23635
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
23398
23636
  * Pass `'https'` when the caller is itself loaded over HTTPS
23399
23637
  * to avoid mixed-content blocks in the browser. The public
23400
23638
  * tunnel always emits `https://` regardless. */
23401
23639
  scheme: _enum(["http", "https"]).optional()
23402
- }), 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" });
23640
+ }), 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, {
23641
+ kind: "mutation",
23642
+ auth: "admin"
23643
+ }), method(object({
23644
+ certPem: string().min(1),
23645
+ keyPem: string().min(1),
23646
+ caPem: string().optional()
23647
+ }), TlsStatusSchema, {
23648
+ kind: "mutation",
23649
+ auth: "admin"
23650
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
23651
+ kind: "mutation",
23652
+ auth: "admin"
23653
+ });
23403
23654
  var LockControlStatusSchema = object({
23404
23655
  /** Lifecycle state of the lock. `jammed` means the motor reported
23405
23656
  * failure to reach the target — operator intervention required. */
@@ -24592,7 +24843,12 @@ var PlateInfoSchema = object({
24592
24843
  plateBbox: BoundingBoxSchema.optional(),
24593
24844
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
24594
24845
  keyFrameMediaKey: string().optional(),
24595
- base64: string().optional()
24846
+ base64: string().optional(),
24847
+ /**
24848
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24849
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24850
+ */
24851
+ cropUrl: string().optional()
24596
24852
  });
24597
24853
  var MediaFileLiteSchema = object({
24598
24854
  key: string(),
@@ -28257,6 +28513,12 @@ Object.freeze({
28257
28513
  addonId: null,
28258
28514
  access: "view"
28259
28515
  },
28516
+ "deviceManager.getBindingsBatch": {
28517
+ capName: "device-manager",
28518
+ capScope: "system",
28519
+ addonId: null,
28520
+ access: "view"
28521
+ },
28260
28522
  "deviceManager.getChildren": {
28261
28523
  capName: "device-manager",
28262
28524
  capScope: "system",
@@ -28317,6 +28579,12 @@ Object.freeze({
28317
28579
  addonId: null,
28318
28580
  access: "view"
28319
28581
  },
28582
+ "deviceManager.getLinkedDevicesBatch": {
28583
+ capName: "device-manager",
28584
+ capScope: "system",
28585
+ addonId: null,
28586
+ access: "view"
28587
+ },
28320
28588
  "deviceManager.getRoleDisplayDefaults": {
28321
28589
  capName: "device-manager",
28322
28590
  capScope: "system",
@@ -29199,6 +29467,12 @@ Object.freeze({
29199
29467
  addonId: null,
29200
29468
  access: "create"
29201
29469
  },
29470
+ "localNetwork.downloadCa": {
29471
+ capName: "local-network",
29472
+ capScope: "system",
29473
+ addonId: null,
29474
+ access: "view"
29475
+ },
29202
29476
  "localNetwork.getAllowedAddresses": {
29203
29477
  capName: "local-network",
29204
29478
  capScope: "system",
@@ -29223,18 +29497,42 @@ Object.freeze({
29223
29497
  addonId: null,
29224
29498
  access: "view"
29225
29499
  },
29500
+ "localNetwork.getTlsStatus": {
29501
+ capName: "local-network",
29502
+ capScope: "system",
29503
+ addonId: null,
29504
+ access: "view"
29505
+ },
29506
+ "localNetwork.getViewerEndpoints": {
29507
+ capName: "local-network",
29508
+ capScope: "system",
29509
+ addonId: null,
29510
+ access: "view"
29511
+ },
29226
29512
  "localNetwork.list": {
29227
29513
  capName: "local-network",
29228
29514
  capScope: "system",
29229
29515
  addonId: null,
29230
29516
  access: "view"
29231
29517
  },
29518
+ "localNetwork.regenerateCertificate": {
29519
+ capName: "local-network",
29520
+ capScope: "system",
29521
+ addonId: null,
29522
+ access: "create"
29523
+ },
29232
29524
  "localNetwork.resetAllowlistToBestMatch": {
29233
29525
  capName: "local-network",
29234
29526
  capScope: "system",
29235
29527
  addonId: null,
29236
29528
  access: "delete"
29237
29529
  },
29530
+ "localNetwork.revertToGeneratedCertificate": {
29531
+ capName: "local-network",
29532
+ capScope: "system",
29533
+ addonId: null,
29534
+ access: "create"
29535
+ },
29238
29536
  "localNetwork.setAllowedAddresses": {
29239
29537
  capName: "local-network",
29240
29538
  capScope: "system",
@@ -29247,6 +29545,18 @@ Object.freeze({
29247
29545
  addonId: null,
29248
29546
  access: "create"
29249
29547
  },
29548
+ "localNetwork.setViewerEndpoints": {
29549
+ capName: "local-network",
29550
+ capScope: "system",
29551
+ addonId: null,
29552
+ access: "create"
29553
+ },
29554
+ "localNetwork.uploadCertificate": {
29555
+ capName: "local-network",
29556
+ capScope: "system",
29557
+ addonId: null,
29558
+ access: "create"
29559
+ },
29250
29560
  "lockControl.lock": {
29251
29561
  capName: "lock-control",
29252
29562
  capScope: "device",
@@ -30045,6 +30355,12 @@ Object.freeze({
30045
30355
  addonId: null,
30046
30356
  access: "view"
30047
30357
  },
30358
+ "pipelineAnalytics.getGroup": {
30359
+ capName: "pipeline-analytics",
30360
+ capScope: "device",
30361
+ addonId: null,
30362
+ access: "view"
30363
+ },
30048
30364
  "pipelineAnalytics.getKeyEvents": {
30049
30365
  capName: "pipeline-analytics",
30050
30366
  capScope: "device",
@@ -30129,6 +30445,12 @@ Object.freeze({
30129
30445
  addonId: null,
30130
30446
  access: "view"
30131
30447
  },
30448
+ "pipelineAnalytics.listGroups": {
30449
+ capName: "pipeline-analytics",
30450
+ capScope: "device",
30451
+ addonId: null,
30452
+ access: "view"
30453
+ },
30132
30454
  "pipelineAnalytics.listOpsLog": {
30133
30455
  capName: "pipeline-analytics",
30134
30456
  capScope: "device",
@@ -32127,6 +32449,12 @@ Object.freeze({
32127
32449
  addonId: null,
32128
32450
  access: "create"
32129
32451
  },
32452
+ "terminalSession.updateInstance": {
32453
+ capName: "terminal-session",
32454
+ capScope: "system",
32455
+ addonId: null,
32456
+ access: "create"
32457
+ },
32130
32458
  "terminalSession.writeInput": {
32131
32459
  capName: "terminal-session",
32132
32460
  capScope: "system",
@@ -32904,6 +33232,11 @@ Object.freeze({
32904
33232
  form: "single",
32905
33233
  optional: false
32906
33234
  }],
33235
+ "deviceManager.getBindingsBatch": [{
33236
+ name: "deviceIds",
33237
+ form: "array",
33238
+ optional: false
33239
+ }],
32907
33240
  "deviceManager.getChildren": [{
32908
33241
  name: "parentDeviceId",
32909
33242
  form: "single",
@@ -32949,6 +33282,11 @@ Object.freeze({
32949
33282
  form: "single",
32950
33283
  optional: false
32951
33284
  }],
33285
+ "deviceManager.getLinkedDevicesBatch": [{
33286
+ name: "deviceIds",
33287
+ form: "array",
33288
+ optional: false
33289
+ }],
32952
33290
  "deviceManager.getSettingsSchema": [{
32953
33291
  name: "deviceId",
32954
33292
  form: "single",
@@ -32969,6 +33307,11 @@ Object.freeze({
32969
33307
  form: "single",
32970
33308
  optional: false
32971
33309
  }],
33310
+ "deviceManager.listAll": [{
33311
+ name: "deviceIds",
33312
+ form: "array",
33313
+ optional: true
33314
+ }],
32972
33315
  "deviceManager.loadConfig": [{
32973
33316
  name: "deviceId",
32974
33317
  form: "single",
@@ -33542,6 +33885,11 @@ Object.freeze({
33542
33885
  form: "single",
33543
33886
  optional: false
33544
33887
  }],
33888
+ "pipelineAnalytics.getGroup": [{
33889
+ name: "deviceId",
33890
+ form: "single",
33891
+ optional: false
33892
+ }],
33545
33893
  "pipelineAnalytics.getKeyEvents": [{
33546
33894
  name: "deviceId",
33547
33895
  form: "single",
@@ -33597,6 +33945,11 @@ Object.freeze({
33597
33945
  form: "array",
33598
33946
  optional: false
33599
33947
  }],
33948
+ "pipelineAnalytics.listGroups": [{
33949
+ name: "deviceIds",
33950
+ form: "array",
33951
+ optional: false
33952
+ }],
33600
33953
  "pipelineAnalytics.listOpsLog": [{
33601
33954
  name: "deviceId",
33602
33955
  form: "single",
@@ -34614,6 +34967,35 @@ Object.freeze(Object.fromEntries([{
34614
34967
  }]
34615
34968
  }].map((s) => [s.stepId, s.defaultModelId])));
34616
34969
  string().min(1);
34970
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34971
+ stepId: "face-embedding",
34972
+ key: "minLandmarkFaceSize",
34973
+ label: "Min face size for recognition (detection px)",
34974
+ 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.",
34975
+ type: "slider",
34976
+ min: 0,
34977
+ max: 64,
34978
+ step: 2,
34979
+ default: 24
34980
+ }];
34981
+ function clusterStepSettingKey(stepId, fieldKey) {
34982
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34983
+ }
34984
+ var ClusterSettingNumberSchema = number().finite();
34985
+ function readClusterStepSettings(config) {
34986
+ const out = {};
34987
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
34988
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
34989
+ const value = parsed.success ? parsed.data : field.default;
34990
+ const existing = out[field.stepId] ?? {};
34991
+ out[field.stepId] = {
34992
+ ...existing,
34993
+ [field.key]: value
34994
+ };
34995
+ }
34996
+ return out;
34997
+ }
34998
+ readClusterStepSettings({});
34617
34999
  object({
34618
35000
  /**
34619
35001
  * Fraction of the box's own size added on EACH side before cutting.