@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.
@@ -5886,6 +5886,13 @@ var BaseAddon = class {
5886
5886
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5887
5887
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5888
5888
  _registeredCapNames = [];
5889
+ /**
5890
+ * True only after `readAddonStore` actually answered. Constructor
5891
+ * defaults look like stored config when the store is down — a forked
5892
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5893
+ * mode, 2026-08-25) is not "the operator chose this".
5894
+ */
5895
+ settingsStoreReady = false;
5889
5896
  /** Default config values. Provided via constructor. */
5890
5897
  defaults;
5891
5898
  constructor(defaults) {
@@ -6286,7 +6293,9 @@ var BaseAddon = class {
6286
6293
  ];
6287
6294
  let lastErr;
6288
6295
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6289
- return await settings.readAddonStore() ?? {};
6296
+ const stored = await settings.readAddonStore() ?? {};
6297
+ this.settingsStoreReady = true;
6298
+ return stored;
6290
6299
  } catch (err) {
6291
6300
  lastErr = err;
6292
6301
  const msg = err instanceof Error ? err.message : String(err);
@@ -6294,6 +6303,7 @@ var BaseAddon = class {
6294
6303
  if (attempt === delaysMs.length) break;
6295
6304
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6296
6305
  }
6306
+ this.settingsStoreReady = false;
6297
6307
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6298
6308
  return {};
6299
6309
  }
@@ -8740,6 +8750,15 @@ var LabelDefinitionSchema = object({
8740
8750
  description: string().optional(),
8741
8751
  icon: string().optional()
8742
8752
  });
8753
+ var ClassMapDefinitionSchema = object({
8754
+ mapping: record(string(), _enum([
8755
+ "person",
8756
+ "vehicle",
8757
+ "animal",
8758
+ "package"
8759
+ ])),
8760
+ preserveOriginal: boolean()
8761
+ });
8743
8762
  var MODEL_FORMATS = [
8744
8763
  "onnx",
8745
8764
  "coreml",
@@ -8823,6 +8842,12 @@ var ModelVariantGroupSchema = object({
8823
8842
  */
8824
8843
  resolution: number().int().positive().optional()
8825
8844
  });
8845
+ var ModelProviderIdSchema = _enum([
8846
+ "camstack",
8847
+ "frigate",
8848
+ "scrypted",
8849
+ "custom"
8850
+ ]);
8826
8851
  var ModelCatalogEntrySchema = object({
8827
8852
  id: string(),
8828
8853
  name: string(),
@@ -8918,7 +8943,19 @@ var ModelCatalogEntrySchema = object({
8918
8943
  * `id` stays the source of truth for resolution/download/persistence; grouping
8919
8944
  * is a presentation overlay resolved back to an `id`.
8920
8945
  */
8921
- group: ModelVariantGroupSchema.optional()
8946
+ group: ModelVariantGroupSchema.optional(),
8947
+ /**
8948
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8949
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8950
+ * persisted before this field existed (`inferModelProvider` fills those).
8951
+ */
8952
+ provider: ModelProviderIdSchema.optional(),
8953
+ /**
8954
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8955
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8956
+ * labels already ARE the CamStack macros (Scrypted identity map).
8957
+ */
8958
+ classMap: ClassMapDefinitionSchema.optional()
8922
8959
  });
8923
8960
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8924
8961
  format: literal("openvino"),
@@ -8947,7 +8984,8 @@ var ModelConvertMetadataSchema = object({
8947
8984
  "ocr",
8948
8985
  "segmentation"
8949
8986
  ]),
8950
- faceAlignment: boolean().optional()
8987
+ faceAlignment: boolean().optional(),
8988
+ classMap: ClassMapDefinitionSchema.optional()
8951
8989
  });
8952
8990
  var ConvertResultSchema = object({
8953
8991
  entry: ModelCatalogEntrySchema,
@@ -12521,6 +12559,27 @@ var LinkedDeviceSchema = object({
12521
12559
  features: array(string()),
12522
12560
  producesTrackedEvents: boolean().optional()
12523
12561
  });
12562
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12563
+ * The batch answer needs the tag; the single-device answer already has it
12564
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12565
+ var LinkedDevicesForDeviceSchema = object({
12566
+ deviceId: number(),
12567
+ mode: LinkedDevicesModeSchema,
12568
+ devices: array(LinkedDeviceSchema)
12569
+ });
12570
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12571
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12572
+ * object literal is exactly how the three drift apart. */
12573
+ var DeviceBindingsForDeviceSchema = object({
12574
+ deviceId: number(),
12575
+ entries: array(object({
12576
+ capName: string(),
12577
+ kind: _enum(["native", "wrapped"]),
12578
+ providerAddonId: string(),
12579
+ providerNodeId: string(),
12580
+ nativeAddonId: string()
12581
+ }))
12582
+ });
12524
12583
  var SavedDeviceRowSchema = object({
12525
12584
  /** Numeric id reserved at allocateDeviceId time. */
12526
12585
  id: number(),
@@ -12746,11 +12805,25 @@ method(object({
12746
12805
  projection: _enum(["full", "slim"]).optional(),
12747
12806
  /** Return only camera devices. Filtering server-side instead of
12748
12807
  * shipping 293 rows to find 12. */
12749
- isCamera: boolean().optional()
12808
+ isCamera: boolean().optional(),
12809
+ /**
12810
+ * Return only these device ids. For the caller that already KNOWS the
12811
+ * handful it wants and needs a field the id-bearing answer does not
12812
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12813
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12814
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12815
+ * refetches on the reconcile interval, on a phone.
12816
+ *
12817
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12818
+ * keys rather than rejecting them (verified against the live hub
12819
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12820
+ * it answers today and the caller filters as it already does.
12821
+ */
12822
+ deviceIds: array(number()).optional()
12750
12823
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12751
12824
  mode: LinkedDevicesModeSchema,
12752
12825
  devices: array(LinkedDeviceSchema)
12753
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12826
+ })), 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({
12754
12827
  deviceId: number(),
12755
12828
  values: record(string(), unknown())
12756
12829
  }), object({ success: literal(true) }), {
@@ -12777,25 +12850,7 @@ method(object({
12777
12850
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12778
12851
  kind: "mutation",
12779
12852
  auth: "admin"
12780
- }), method(object({ deviceId: number() }), object({
12781
- deviceId: number(),
12782
- entries: array(object({
12783
- capName: string(),
12784
- kind: _enum(["native", "wrapped"]),
12785
- providerAddonId: string(),
12786
- providerNodeId: string(),
12787
- nativeAddonId: string()
12788
- }))
12789
- })), method(object({}), array(object({
12790
- deviceId: number(),
12791
- entries: array(object({
12792
- capName: string(),
12793
- kind: _enum(["native", "wrapped"]),
12794
- providerAddonId: string(),
12795
- providerNodeId: string(),
12796
- nativeAddonId: string()
12797
- }))
12798
- }))), method(object({
12853
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12799
12854
  deviceId: number(),
12800
12855
  capName: string(),
12801
12856
  wrapperAddonId: string(),
@@ -15167,12 +15222,15 @@ var NcOccupancyConditionSchema = object({
15167
15222
  * there is no second switch that can disagree with the first and every rule
15168
15223
  * authored before the decision migrates for free (`audioModeOf`):
15169
15224
  *
15170
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
15171
- * classifier labels with one of them. No window, no percentage:
15172
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
15173
- * `throttle` cooldown is the only brake. The per-label confidence floor is
15174
- * the analyzer's (`classificationMinScore`, per device) a label only
15175
- * reaches this condition if the classifier was already confident enough.
15225
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
15226
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
15227
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
15228
+ * frames is the wrong question for a classifier that labels 1–3 frames
15229
+ * per episode. The count window is the brake that drops a single-frame
15230
+ * false positive; the rule's own `throttle` cooldown is the other. The
15231
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
15232
+ * per device) — a label only reaches this condition if the classifier was
15233
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
15176
15234
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
15177
15235
  * the condition: at least `hitPercent`% of the samples over
15178
15236
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -15199,14 +15257,22 @@ var NcOccupancyConditionSchema = object({
15199
15257
  * an operator who typed `dog` mean the same thing.
15200
15258
  */
15201
15259
  var NcAudioConditionSchema = object({
15202
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
15260
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
15203
15261
  labels: array(string().min(1)).min(1).optional(),
15204
15262
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
15205
15263
  dbThreshold: number().min(-96).max(0).optional(),
15206
15264
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
15207
15265
  hitPercent: number().int().min(1).max(100).default(60),
15208
15266
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
15209
- samplingSeconds: number().int().min(1).max(300).default(10)
15267
+ samplingSeconds: number().int().min(1).max(300).default(10),
15268
+ /**
15269
+ * LABEL MODE: how many labelled frames must land inside
15270
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
15271
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
15272
+ */
15273
+ confirmHits: number().int().min(1).max(20).optional(),
15274
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
15275
+ confirmWindowSec: number().int().min(1).max(60).optional()
15210
15276
  });
15211
15277
  /**
15212
15278
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17580,6 +17646,46 @@ var RecentTracksPageSchema = object({
17580
17646
  /** Cursor for the next page, or null when this page is the last. */
17581
17647
  nextCursor: string().nullable()
17582
17648
  });
17649
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17650
+ var LIST_GROUPS_MAX_LIMIT = 100;
17651
+ var AnalyticsGroupRecordSchema = object({
17652
+ id: string(),
17653
+ deviceId: number().int(),
17654
+ openedAt: number().int(),
17655
+ closedAt: number().int(),
17656
+ timestamp: number().int(),
17657
+ memberCount: number().int(),
17658
+ memberTrackIds: array(string()).readonly(),
17659
+ className: string(),
17660
+ classes: array(string()).readonly(),
17661
+ /** Relative event-media path, or null when the group has no picture yet. */
17662
+ mediaUrl: string().nullable(),
17663
+ singleton: boolean()
17664
+ });
17665
+ var AnalyticsGroupMemberSchema = object({
17666
+ trackId: string(),
17667
+ deviceId: number().int(),
17668
+ className: string(),
17669
+ firstSeen: number().int(),
17670
+ lastSeen: number().int(),
17671
+ mediaUrl: string().nullable()
17672
+ });
17673
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17674
+ var ListGroupsQueryInput = object({
17675
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17676
+ deviceIds: array(number()),
17677
+ /** Window lower bound on `closedAt` (inclusive). */
17678
+ since: number().optional(),
17679
+ /** Window upper bound on `openedAt` (inclusive). */
17680
+ until: number().optional(),
17681
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17682
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17683
+ cursor: string().optional()
17684
+ });
17685
+ var ListGroupsPageSchema = object({
17686
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17687
+ nextCursor: string().nullable()
17688
+ });
17583
17689
  var KeyEventQueryInput = object({
17584
17690
  deviceId: number(),
17585
17691
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17655,7 +17761,9 @@ var TrackCascadeCountsSchema = object({
17655
17761
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17656
17762
  plates: number().int(),
17657
17763
  /** Per-track CLIP search vectors removed (best-effort). */
17658
- embeddings: number().int()
17764
+ embeddings: number().int(),
17765
+ /** Group membership + group rows removed with their last member (best-effort). */
17766
+ groups: number().int()
17659
17767
  });
17660
17768
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17661
17769
  var DiskReconcileCountsSchema = object({
@@ -17801,7 +17909,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17801
17909
  * stationary registry). Default false: the timeline lists passages,
17802
17910
  * not parking records (operator decision, 2026-08-15). */
17803
17911
  includeStationary: boolean().optional()
17804
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17912
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17913
+ deviceId: number(),
17914
+ groupId: string().min(1)
17915
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17805
17916
  kind: "mutation",
17806
17917
  auth: "admin"
17807
17918
  }), 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({
@@ -18019,6 +18130,33 @@ var NativeCropRefSchema = object({
18019
18130
  h: number()
18020
18131
  })
18021
18132
  });
18133
+ object({
18134
+ crop: object({
18135
+ left: number(),
18136
+ top: number(),
18137
+ width: number().positive(),
18138
+ height: number().positive()
18139
+ }).optional(),
18140
+ content: object({
18141
+ width: number().int().positive(),
18142
+ height: number().int().positive()
18143
+ }),
18144
+ fit: _enum(["stretch", "contain"]),
18145
+ format: _enum([
18146
+ "rgb",
18147
+ "gray",
18148
+ "jpeg"
18149
+ ])
18150
+ });
18151
+ var FrameRefSchema = object({
18152
+ registryId: string().min(1),
18153
+ id: string().min(1),
18154
+ width: number().int().positive(),
18155
+ height: number().int().positive(),
18156
+ format: _enum(["rgb", "gray"]),
18157
+ timestamp: number(),
18158
+ capturedAt: number().optional()
18159
+ });
18022
18160
  var ModelFormatSchema$1 = _enum([
18023
18161
  "onnx",
18024
18162
  "coreml",
@@ -18084,7 +18222,8 @@ var PipelineModelOptionSchema = object({
18084
18222
  sizeMB: number()
18085
18223
  })),
18086
18224
  group: ModelVariantGroupSchema.optional(),
18087
- legacy: boolean().optional()
18225
+ legacy: boolean().optional(),
18226
+ provider: ModelProviderIdSchema.optional()
18088
18227
  });
18089
18228
  var ConfigFieldBridge = custom();
18090
18229
  var PipelineAddonSchemaSchema = object({
@@ -18263,6 +18402,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
18263
18402
  steps: array(PipelineStepInputSchema).min(1),
18264
18403
  frame: FrameInputSchema.optional(),
18265
18404
  /**
18405
+ * Process-local lazy frame. Valid only when caller and provider resolve
18406
+ * in the same execution-group process; split/cross-node callers use
18407
+ * `frame`/`image` inline compatibility instead.
18408
+ */
18409
+ frameRef: FrameRefSchema.optional(),
18410
+ /**
18266
18411
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
18267
18412
  * the decoded pixels live in. One more member of the one-of
18268
18413
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18518,7 +18663,10 @@ var NativeCropResultSchema = object({
18518
18663
  * Which source served this crop, so a quality-sensitive consumer (the native
18519
18664
  * `keyFrame`) can reject a degraded fallback:
18520
18665
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18521
- * quality path).
18666
+ * quality path). A subject-tile serve is also native-resolution and stays
18667
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18668
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18669
+ * internal crop result (`nativeHits` vs `tileHits`).
18522
18670
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18523
18671
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18524
18672
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -19009,12 +19157,41 @@ var RunnerLocalLoadSchema = object({
19009
19157
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
19010
19158
  * working unchanged when they switch to reading from the runner cap.
19011
19159
  */
19160
+ var FrameLazyCountersSchema = object({
19161
+ framesDecoded: number(),
19162
+ framesAdmitted: number(),
19163
+ framesDroppedPixelFree: number(),
19164
+ viewsMaterialized: number(),
19165
+ viewsSkipped: number(),
19166
+ workerToRunnerBytes: number(),
19167
+ runnerToPoolRawBytes: number(),
19168
+ runnerToPoolJpegBytes: number(),
19169
+ onDemandFullFrameRequests: number(),
19170
+ onDemandCropRequests: number(),
19171
+ nativeHits: number(),
19172
+ nativeMisses: number(),
19173
+ tileHits: number(),
19174
+ tileMisses: number(),
19175
+ fallbackHits: number(),
19176
+ fallbackMisses: number(),
19177
+ retainedWritesAvoided: number(),
19178
+ residentRefs: number(),
19179
+ residentBytes: number(),
19180
+ releases: number(),
19181
+ evictions: number(),
19182
+ staleMisses: number()
19183
+ });
19184
+ var FrameLazyMetricsSchema = object({
19185
+ node: FrameLazyCountersSchema,
19186
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
19187
+ });
19012
19188
  var RunnerLocalMetricsSchema = object({
19013
19189
  nodeId: string(),
19014
19190
  activeCameras: number(),
19015
19191
  throttledCameras: number(),
19016
19192
  avgInferenceTimeMs: number(),
19017
- queueDepth: number()
19193
+ queueDepth: number(),
19194
+ frameLazy: FrameLazyMetricsSchema.optional()
19018
19195
  });
19019
19196
  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({
19020
19197
  handle: FrameHandleSchema,
@@ -20314,6 +20491,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
20314
20491
  location: StorageLocationSchema,
20315
20492
  relativePath: string()
20316
20493
  }), _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" });
20494
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
20495
+ var ProfileSettingsSchemaBridge = unknown().nullable();
20496
+ var ProfileSettingsBagSchema = record(string(), unknown());
20317
20497
  /**
20318
20498
  * A live terminal session hosted by the provider addon. Output and input do
20319
20499
  * NOT flow through the capability — they use the addon data plane
@@ -20343,7 +20523,14 @@ var TerminalSessionInfoSchema = object({
20343
20523
  var TerminalProfileInfoSchema = object({
20344
20524
  profileId: string(),
20345
20525
  label: string(),
20346
- description: string().optional()
20526
+ description: string().optional(),
20527
+ /** Spawn defaults the instance form copies on create. */
20528
+ executable: string().optional(),
20529
+ args: array(string()).readonly().optional(),
20530
+ cwd: string().optional(),
20531
+ environment: array(string()).readonly().optional(),
20532
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20533
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
20347
20534
  });
20348
20535
  /**
20349
20536
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -20356,7 +20543,12 @@ var TerminalInstanceInfoSchema = object({
20356
20543
  profileId: string(),
20357
20544
  profileLabel: string(),
20358
20545
  name: string(),
20359
- enabled: boolean()
20546
+ enabled: boolean(),
20547
+ executable: string(),
20548
+ args: array(string()).readonly(),
20549
+ cwd: string(),
20550
+ environment: array(string()).readonly(),
20551
+ profileSettings: ProfileSettingsBagSchema
20360
20552
  });
20361
20553
  var TerminalLegacyCameraSchema = object({
20362
20554
  stableId: string(),
@@ -20386,7 +20578,23 @@ var TerminalOutputBatchSchema = object({
20386
20578
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
20387
20579
  targetNodeId: string().min(1),
20388
20580
  profileId: string().min(1),
20389
- name: string().trim().min(1).max(160).optional()
20581
+ name: string().trim().min(1).max(160).optional(),
20582
+ executable: string().max(1024).optional(),
20583
+ args: array(string().max(2048)).max(64).optional(),
20584
+ cwd: string().max(1024).optional(),
20585
+ environment: array(string().max(4096)).max(64).optional(),
20586
+ profileSettings: ProfileSettingsBagSchema.optional()
20587
+ }), TerminalInstanceInfoSchema, {
20588
+ kind: "mutation",
20589
+ auth: "admin"
20590
+ }), method(object({
20591
+ instanceId: string().min(1),
20592
+ name: string().trim().min(1).max(160).optional(),
20593
+ executable: string().max(1024).optional(),
20594
+ args: array(string().max(2048)).max(64).optional(),
20595
+ cwd: string().max(1024).optional(),
20596
+ environment: array(string().max(4096)).max(64).optional(),
20597
+ profileSettings: ProfileSettingsBagSchema.optional()
20390
20598
  }), TerminalInstanceInfoSchema, {
20391
20599
  kind: "mutation",
20392
20600
  auth: "admin"
@@ -20408,7 +20616,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
20408
20616
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
20409
20617
  profileId: string(),
20410
20618
  cols: number().int().positive(),
20411
- rows: number().int().positive()
20619
+ rows: number().int().positive(),
20620
+ executable: string().max(1024).optional(),
20621
+ args: array(string().max(2048)).max(64).optional(),
20622
+ cwd: string().max(1024).optional(),
20623
+ environment: array(string().max(4096)).max(64).optional()
20412
20624
  }), TerminalSessionInfoSchema, {
20413
20625
  kind: "mutation",
20414
20626
  auth: "admin"
@@ -23226,10 +23438,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
23226
23438
  *
23227
23439
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
23228
23440
  * to receive an ordered list of candidate base URLs it should race
23229
- * on connect — LAN IPv4 first (lowest latency when on same network),
23230
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
23231
- * race them with short timeouts and stick with the winner for the
23232
- * session.
23441
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
23442
+ * when on the same network), then public hostname (if a tunnel is
23443
+ * up). The SDK can race them with short timeouts and stick with the
23444
+ * winner for the session.
23233
23445
  *
23234
23446
  * Why hub-only: agents are not directly addressable by the operator's
23235
23447
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -23384,6 +23596,17 @@ var NotificationEndpointSchema = object({
23384
23596
  /** What the ranking currently resolves to (null when nothing is reachable). */
23385
23597
  resolved: string().nullable()
23386
23598
  });
23599
+ /**
23600
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
23601
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
23602
+ * currently expands to, so the UI can show the effective set either way.
23603
+ */
23604
+ var ViewerEndpointsSchema = object({
23605
+ /** The operator's explicit race set, or empty for AUTO. */
23606
+ baseUrls: array(string()).readonly(),
23607
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
23608
+ resolved: array(string()).readonly()
23609
+ });
23387
23610
  var AllowedAddressesSchema = object({
23388
23611
  /**
23389
23612
  * Allowlist of interface addresses operators have explicitly opted
@@ -23392,6 +23615,20 @@ var AllowedAddressesSchema = object({
23392
23615
  * Network Addresses admin page and persisted by the addon.
23393
23616
  */
23394
23617
  addresses: array(string()).readonly() });
23618
+ var TlsStatusSchema = object({
23619
+ mode: _enum([
23620
+ "generated",
23621
+ "uploaded",
23622
+ "disabled"
23623
+ ]),
23624
+ leafFingerprintSha256: string().nullable(),
23625
+ caFingerprintSha256: string().nullable(),
23626
+ validTo: string().nullable(),
23627
+ sans: array(string()),
23628
+ caCertPem: string().nullable(),
23629
+ reissueError: string().nullable(),
23630
+ restartRequired: boolean()
23631
+ });
23395
23632
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
23396
23633
  /**
23397
23634
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -23401,17 +23638,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
23401
23638
  */
23402
23639
  port: number().int().min(1).max(65535).optional(),
23403
23640
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
23404
- * candidate. Default `true`. */
23641
+ * candidate. Default `false` — loopback is not a client route. */
23405
23642
  includeLoopback: boolean().optional(),
23406
- /** Skip IPv6 entries. Some legacy clients can't parse them.
23407
- * Default `false`. */
23643
+ /** Skip IPv6 entries. Default `false` the palette includes stable
23644
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
23645
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
23408
23646
  ipv4Only: boolean().optional(),
23409
23647
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
23410
23648
  * Pass `'https'` when the caller is itself loaded over HTTPS
23411
23649
  * to avoid mixed-content blocks in the browser. The public
23412
23650
  * tunnel always emits `https://` regardless. */
23413
23651
  scheme: _enum(["http", "https"]).optional()
23414
- }), 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" });
23652
+ }), 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, {
23653
+ kind: "mutation",
23654
+ auth: "admin"
23655
+ }), method(object({
23656
+ certPem: string().min(1),
23657
+ keyPem: string().min(1),
23658
+ caPem: string().optional()
23659
+ }), TlsStatusSchema, {
23660
+ kind: "mutation",
23661
+ auth: "admin"
23662
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
23663
+ kind: "mutation",
23664
+ auth: "admin"
23665
+ });
23415
23666
  var LockControlStatusSchema = object({
23416
23667
  /** Lifecycle state of the lock. `jammed` means the motor reported
23417
23668
  * failure to reach the target — operator intervention required. */
@@ -24604,7 +24855,12 @@ var PlateInfoSchema = object({
24604
24855
  plateBbox: BoundingBoxSchema.optional(),
24605
24856
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
24606
24857
  keyFrameMediaKey: string().optional(),
24607
- base64: string().optional()
24858
+ base64: string().optional(),
24859
+ /**
24860
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24861
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24862
+ */
24863
+ cropUrl: string().optional()
24608
24864
  });
24609
24865
  var MediaFileLiteSchema = object({
24610
24866
  key: string(),
@@ -28269,6 +28525,12 @@ Object.freeze({
28269
28525
  addonId: null,
28270
28526
  access: "view"
28271
28527
  },
28528
+ "deviceManager.getBindingsBatch": {
28529
+ capName: "device-manager",
28530
+ capScope: "system",
28531
+ addonId: null,
28532
+ access: "view"
28533
+ },
28272
28534
  "deviceManager.getChildren": {
28273
28535
  capName: "device-manager",
28274
28536
  capScope: "system",
@@ -28329,6 +28591,12 @@ Object.freeze({
28329
28591
  addonId: null,
28330
28592
  access: "view"
28331
28593
  },
28594
+ "deviceManager.getLinkedDevicesBatch": {
28595
+ capName: "device-manager",
28596
+ capScope: "system",
28597
+ addonId: null,
28598
+ access: "view"
28599
+ },
28332
28600
  "deviceManager.getRoleDisplayDefaults": {
28333
28601
  capName: "device-manager",
28334
28602
  capScope: "system",
@@ -29211,6 +29479,12 @@ Object.freeze({
29211
29479
  addonId: null,
29212
29480
  access: "create"
29213
29481
  },
29482
+ "localNetwork.downloadCa": {
29483
+ capName: "local-network",
29484
+ capScope: "system",
29485
+ addonId: null,
29486
+ access: "view"
29487
+ },
29214
29488
  "localNetwork.getAllowedAddresses": {
29215
29489
  capName: "local-network",
29216
29490
  capScope: "system",
@@ -29235,18 +29509,42 @@ Object.freeze({
29235
29509
  addonId: null,
29236
29510
  access: "view"
29237
29511
  },
29512
+ "localNetwork.getTlsStatus": {
29513
+ capName: "local-network",
29514
+ capScope: "system",
29515
+ addonId: null,
29516
+ access: "view"
29517
+ },
29518
+ "localNetwork.getViewerEndpoints": {
29519
+ capName: "local-network",
29520
+ capScope: "system",
29521
+ addonId: null,
29522
+ access: "view"
29523
+ },
29238
29524
  "localNetwork.list": {
29239
29525
  capName: "local-network",
29240
29526
  capScope: "system",
29241
29527
  addonId: null,
29242
29528
  access: "view"
29243
29529
  },
29530
+ "localNetwork.regenerateCertificate": {
29531
+ capName: "local-network",
29532
+ capScope: "system",
29533
+ addonId: null,
29534
+ access: "create"
29535
+ },
29244
29536
  "localNetwork.resetAllowlistToBestMatch": {
29245
29537
  capName: "local-network",
29246
29538
  capScope: "system",
29247
29539
  addonId: null,
29248
29540
  access: "delete"
29249
29541
  },
29542
+ "localNetwork.revertToGeneratedCertificate": {
29543
+ capName: "local-network",
29544
+ capScope: "system",
29545
+ addonId: null,
29546
+ access: "create"
29547
+ },
29250
29548
  "localNetwork.setAllowedAddresses": {
29251
29549
  capName: "local-network",
29252
29550
  capScope: "system",
@@ -29259,6 +29557,18 @@ Object.freeze({
29259
29557
  addonId: null,
29260
29558
  access: "create"
29261
29559
  },
29560
+ "localNetwork.setViewerEndpoints": {
29561
+ capName: "local-network",
29562
+ capScope: "system",
29563
+ addonId: null,
29564
+ access: "create"
29565
+ },
29566
+ "localNetwork.uploadCertificate": {
29567
+ capName: "local-network",
29568
+ capScope: "system",
29569
+ addonId: null,
29570
+ access: "create"
29571
+ },
29262
29572
  "lockControl.lock": {
29263
29573
  capName: "lock-control",
29264
29574
  capScope: "device",
@@ -30057,6 +30367,12 @@ Object.freeze({
30057
30367
  addonId: null,
30058
30368
  access: "view"
30059
30369
  },
30370
+ "pipelineAnalytics.getGroup": {
30371
+ capName: "pipeline-analytics",
30372
+ capScope: "device",
30373
+ addonId: null,
30374
+ access: "view"
30375
+ },
30060
30376
  "pipelineAnalytics.getKeyEvents": {
30061
30377
  capName: "pipeline-analytics",
30062
30378
  capScope: "device",
@@ -30141,6 +30457,12 @@ Object.freeze({
30141
30457
  addonId: null,
30142
30458
  access: "view"
30143
30459
  },
30460
+ "pipelineAnalytics.listGroups": {
30461
+ capName: "pipeline-analytics",
30462
+ capScope: "device",
30463
+ addonId: null,
30464
+ access: "view"
30465
+ },
30144
30466
  "pipelineAnalytics.listOpsLog": {
30145
30467
  capName: "pipeline-analytics",
30146
30468
  capScope: "device",
@@ -32139,6 +32461,12 @@ Object.freeze({
32139
32461
  addonId: null,
32140
32462
  access: "create"
32141
32463
  },
32464
+ "terminalSession.updateInstance": {
32465
+ capName: "terminal-session",
32466
+ capScope: "system",
32467
+ addonId: null,
32468
+ access: "create"
32469
+ },
32142
32470
  "terminalSession.writeInput": {
32143
32471
  capName: "terminal-session",
32144
32472
  capScope: "system",
@@ -32916,6 +33244,11 @@ Object.freeze({
32916
33244
  form: "single",
32917
33245
  optional: false
32918
33246
  }],
33247
+ "deviceManager.getBindingsBatch": [{
33248
+ name: "deviceIds",
33249
+ form: "array",
33250
+ optional: false
33251
+ }],
32919
33252
  "deviceManager.getChildren": [{
32920
33253
  name: "parentDeviceId",
32921
33254
  form: "single",
@@ -32961,6 +33294,11 @@ Object.freeze({
32961
33294
  form: "single",
32962
33295
  optional: false
32963
33296
  }],
33297
+ "deviceManager.getLinkedDevicesBatch": [{
33298
+ name: "deviceIds",
33299
+ form: "array",
33300
+ optional: false
33301
+ }],
32964
33302
  "deviceManager.getSettingsSchema": [{
32965
33303
  name: "deviceId",
32966
33304
  form: "single",
@@ -32981,6 +33319,11 @@ Object.freeze({
32981
33319
  form: "single",
32982
33320
  optional: false
32983
33321
  }],
33322
+ "deviceManager.listAll": [{
33323
+ name: "deviceIds",
33324
+ form: "array",
33325
+ optional: true
33326
+ }],
32984
33327
  "deviceManager.loadConfig": [{
32985
33328
  name: "deviceId",
32986
33329
  form: "single",
@@ -33554,6 +33897,11 @@ Object.freeze({
33554
33897
  form: "single",
33555
33898
  optional: false
33556
33899
  }],
33900
+ "pipelineAnalytics.getGroup": [{
33901
+ name: "deviceId",
33902
+ form: "single",
33903
+ optional: false
33904
+ }],
33557
33905
  "pipelineAnalytics.getKeyEvents": [{
33558
33906
  name: "deviceId",
33559
33907
  form: "single",
@@ -33609,6 +33957,11 @@ Object.freeze({
33609
33957
  form: "array",
33610
33958
  optional: false
33611
33959
  }],
33960
+ "pipelineAnalytics.listGroups": [{
33961
+ name: "deviceIds",
33962
+ form: "array",
33963
+ optional: false
33964
+ }],
33612
33965
  "pipelineAnalytics.listOpsLog": [{
33613
33966
  name: "deviceId",
33614
33967
  form: "single",
@@ -34626,6 +34979,35 @@ Object.freeze(Object.fromEntries([{
34626
34979
  }]
34627
34980
  }].map((s) => [s.stepId, s.defaultModelId])));
34628
34981
  string().min(1);
34982
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34983
+ stepId: "face-embedding",
34984
+ key: "minLandmarkFaceSize",
34985
+ label: "Min face size for recognition (detection px)",
34986
+ 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.",
34987
+ type: "slider",
34988
+ min: 0,
34989
+ max: 64,
34990
+ step: 2,
34991
+ default: 24
34992
+ }];
34993
+ function clusterStepSettingKey(stepId, fieldKey) {
34994
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34995
+ }
34996
+ var ClusterSettingNumberSchema = number().finite();
34997
+ function readClusterStepSettings(config) {
34998
+ const out = {};
34999
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
35000
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
35001
+ const value = parsed.success ? parsed.data : field.default;
35002
+ const existing = out[field.stepId] ?? {};
35003
+ out[field.stepId] = {
35004
+ ...existing,
35005
+ [field.key]: value
35006
+ };
35007
+ }
35008
+ return out;
35009
+ }
35010
+ readClusterStepSettings({});
34629
35011
  object({
34630
35012
  /**
34631
35013
  * Fraction of the box's own size added on EACH side before cutting.