@camstack/addon-tailscale 1.2.26 → 1.2.28

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.
@@ -5800,6 +5800,13 @@ var BaseAddon = class {
5800
5800
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5801
5801
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5802
5802
  _registeredCapNames = [];
5803
+ /**
5804
+ * True only after `readAddonStore` actually answered. Constructor
5805
+ * defaults look like stored config when the store is down — a forked
5806
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5807
+ * mode, 2026-08-25) is not "the operator chose this".
5808
+ */
5809
+ settingsStoreReady = false;
5803
5810
  /** Default config values. Provided via constructor. */
5804
5811
  defaults;
5805
5812
  constructor(defaults) {
@@ -6200,7 +6207,9 @@ var BaseAddon = class {
6200
6207
  ];
6201
6208
  let lastErr;
6202
6209
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6203
- return await settings.readAddonStore() ?? {};
6210
+ const stored = await settings.readAddonStore() ?? {};
6211
+ this.settingsStoreReady = true;
6212
+ return stored;
6204
6213
  } catch (err) {
6205
6214
  lastErr = err;
6206
6215
  const msg = err instanceof Error ? err.message : String(err);
@@ -6208,6 +6217,7 @@ var BaseAddon = class {
6208
6217
  if (attempt === delaysMs.length) break;
6209
6218
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6210
6219
  }
6220
+ this.settingsStoreReady = false;
6211
6221
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6212
6222
  return {};
6213
6223
  }
@@ -8037,6 +8047,15 @@ var LabelDefinitionSchema = object({
8037
8047
  description: string().optional(),
8038
8048
  icon: string().optional()
8039
8049
  });
8050
+ var ClassMapDefinitionSchema = object({
8051
+ mapping: record(string(), _enum([
8052
+ "person",
8053
+ "vehicle",
8054
+ "animal",
8055
+ "package"
8056
+ ])),
8057
+ preserveOriginal: boolean()
8058
+ });
8040
8059
  var MODEL_FORMATS = [
8041
8060
  "onnx",
8042
8061
  "coreml",
@@ -8120,6 +8139,12 @@ var ModelVariantGroupSchema = object({
8120
8139
  */
8121
8140
  resolution: number().int().positive().optional()
8122
8141
  });
8142
+ var ModelProviderIdSchema = _enum([
8143
+ "camstack",
8144
+ "frigate",
8145
+ "scrypted",
8146
+ "custom"
8147
+ ]);
8123
8148
  var ModelCatalogEntrySchema = object({
8124
8149
  id: string(),
8125
8150
  name: string(),
@@ -8215,7 +8240,19 @@ var ModelCatalogEntrySchema = object({
8215
8240
  * `id` stays the source of truth for resolution/download/persistence; grouping
8216
8241
  * is a presentation overlay resolved back to an `id`.
8217
8242
  */
8218
- group: ModelVariantGroupSchema.optional()
8243
+ group: ModelVariantGroupSchema.optional(),
8244
+ /**
8245
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8246
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8247
+ * persisted before this field existed (`inferModelProvider` fills those).
8248
+ */
8249
+ provider: ModelProviderIdSchema.optional(),
8250
+ /**
8251
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8252
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8253
+ * labels already ARE the CamStack macros (Scrypted identity map).
8254
+ */
8255
+ classMap: ClassMapDefinitionSchema.optional()
8219
8256
  });
8220
8257
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8221
8258
  format: literal("openvino"),
@@ -8244,7 +8281,8 @@ var ModelConvertMetadataSchema = object({
8244
8281
  "ocr",
8245
8282
  "segmentation"
8246
8283
  ]),
8247
- faceAlignment: boolean().optional()
8284
+ faceAlignment: boolean().optional(),
8285
+ classMap: ClassMapDefinitionSchema.optional()
8248
8286
  });
8249
8287
  var ConvertResultSchema = object({
8250
8288
  entry: ModelCatalogEntrySchema,
@@ -11737,6 +11775,27 @@ var LinkedDeviceSchema = object({
11737
11775
  features: array(string()),
11738
11776
  producesTrackedEvents: boolean().optional()
11739
11777
  });
11778
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
11779
+ * The batch answer needs the tag; the single-device answer already has it
11780
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
11781
+ var LinkedDevicesForDeviceSchema = object({
11782
+ deviceId: number(),
11783
+ mode: LinkedDevicesModeSchema,
11784
+ devices: array(LinkedDeviceSchema)
11785
+ });
11786
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
11787
+ * `getAllBindings` all answer in. Declared once: three copies of the same
11788
+ * object literal is exactly how the three drift apart. */
11789
+ var DeviceBindingsForDeviceSchema = object({
11790
+ deviceId: number(),
11791
+ entries: array(object({
11792
+ capName: string(),
11793
+ kind: _enum(["native", "wrapped"]),
11794
+ providerAddonId: string(),
11795
+ providerNodeId: string(),
11796
+ nativeAddonId: string()
11797
+ }))
11798
+ });
11740
11799
  var SavedDeviceRowSchema = object({
11741
11800
  /** Numeric id reserved at allocateDeviceId time. */
11742
11801
  id: number(),
@@ -11962,11 +12021,25 @@ method(object({
11962
12021
  projection: _enum(["full", "slim"]).optional(),
11963
12022
  /** Return only camera devices. Filtering server-side instead of
11964
12023
  * shipping 293 rows to find 12. */
11965
- isCamera: boolean().optional()
12024
+ isCamera: boolean().optional(),
12025
+ /**
12026
+ * Return only these device ids. For the caller that already KNOWS the
12027
+ * handful it wants and needs a field the id-bearing answer does not
12028
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12029
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12030
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12031
+ * refetches on the reconcile interval, on a phone.
12032
+ *
12033
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12034
+ * keys rather than rejecting them (verified against the live hub
12035
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12036
+ * it answers today and the caller filters as it already does.
12037
+ */
12038
+ deviceIds: array(number()).optional()
11966
12039
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
11967
12040
  mode: LinkedDevicesModeSchema,
11968
12041
  devices: array(LinkedDeviceSchema)
11969
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12042
+ })), 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({
11970
12043
  deviceId: number(),
11971
12044
  values: record(string(), unknown())
11972
12045
  }), object({ success: literal(true) }), {
@@ -11993,25 +12066,7 @@ method(object({
11993
12066
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
11994
12067
  kind: "mutation",
11995
12068
  auth: "admin"
11996
- }), method(object({ deviceId: number() }), object({
11997
- deviceId: number(),
11998
- entries: array(object({
11999
- capName: string(),
12000
- kind: _enum(["native", "wrapped"]),
12001
- providerAddonId: string(),
12002
- providerNodeId: string(),
12003
- nativeAddonId: string()
12004
- }))
12005
- })), method(object({}), array(object({
12006
- deviceId: number(),
12007
- entries: array(object({
12008
- capName: string(),
12009
- kind: _enum(["native", "wrapped"]),
12010
- providerAddonId: string(),
12011
- providerNodeId: string(),
12012
- nativeAddonId: string()
12013
- }))
12014
- }))), method(object({
12069
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12015
12070
  deviceId: number(),
12016
12071
  capName: string(),
12017
12072
  wrapperAddonId: string(),
@@ -14400,12 +14455,15 @@ var NcOccupancyConditionSchema = object({
14400
14455
  * there is no second switch that can disagree with the first and every rule
14401
14456
  * authored before the decision migrates for free (`audioModeOf`):
14402
14457
  *
14403
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14404
- * classifier labels with one of them. No window, no percentage:
14405
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14406
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14407
- * the analyzer's (`classificationMinScore`, per device) a label only
14408
- * reaches this condition if the classifier was already confident enough.
14458
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14459
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14460
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
14461
+ * frames is the wrong question for a classifier that labels 1–3 frames
14462
+ * per episode. The count window is the brake that drops a single-frame
14463
+ * false positive; the rule's own `throttle` cooldown is the other. The
14464
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14465
+ * per device) — a label only reaches this condition if the classifier was
14466
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14409
14467
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14410
14468
  * the condition: at least `hitPercent`% of the samples over
14411
14469
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14432,14 +14490,22 @@ var NcOccupancyConditionSchema = object({
14432
14490
  * an operator who typed `dog` mean the same thing.
14433
14491
  */
14434
14492
  var NcAudioConditionSchema = object({
14435
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14493
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14436
14494
  labels: array(string().min(1)).min(1).optional(),
14437
14495
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14438
14496
  dbThreshold: number().min(-96).max(0).optional(),
14439
14497
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14440
14498
  hitPercent: number().int().min(1).max(100).default(60),
14441
14499
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14442
- samplingSeconds: number().int().min(1).max(300).default(10)
14500
+ samplingSeconds: number().int().min(1).max(300).default(10),
14501
+ /**
14502
+ * LABEL MODE: how many labelled frames must land inside
14503
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14504
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14505
+ */
14506
+ confirmHits: number().int().min(1).max(20).optional(),
14507
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14508
+ confirmWindowSec: number().int().min(1).max(60).optional()
14443
14509
  });
14444
14510
  /**
14445
14511
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -16813,6 +16879,46 @@ var RecentTracksPageSchema = object({
16813
16879
  /** Cursor for the next page, or null when this page is the last. */
16814
16880
  nextCursor: string().nullable()
16815
16881
  });
16882
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
16883
+ var LIST_GROUPS_MAX_LIMIT = 100;
16884
+ var AnalyticsGroupRecordSchema = object({
16885
+ id: string(),
16886
+ deviceId: number().int(),
16887
+ openedAt: number().int(),
16888
+ closedAt: number().int(),
16889
+ timestamp: number().int(),
16890
+ memberCount: number().int(),
16891
+ memberTrackIds: array(string()).readonly(),
16892
+ className: string(),
16893
+ classes: array(string()).readonly(),
16894
+ /** Relative event-media path, or null when the group has no picture yet. */
16895
+ mediaUrl: string().nullable(),
16896
+ singleton: boolean()
16897
+ });
16898
+ var AnalyticsGroupMemberSchema = object({
16899
+ trackId: string(),
16900
+ deviceId: number().int(),
16901
+ className: string(),
16902
+ firstSeen: number().int(),
16903
+ lastSeen: number().int(),
16904
+ mediaUrl: string().nullable()
16905
+ });
16906
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
16907
+ var ListGroupsQueryInput = object({
16908
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
16909
+ deviceIds: array(number()),
16910
+ /** Window lower bound on `closedAt` (inclusive). */
16911
+ since: number().optional(),
16912
+ /** Window upper bound on `openedAt` (inclusive). */
16913
+ until: number().optional(),
16914
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
16915
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
16916
+ cursor: string().optional()
16917
+ });
16918
+ var ListGroupsPageSchema = object({
16919
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
16920
+ nextCursor: string().nullable()
16921
+ });
16816
16922
  var KeyEventQueryInput = object({
16817
16923
  deviceId: number(),
16818
16924
  /** Window lower bound (track firstSeen ≥ since). */
@@ -16888,7 +16994,9 @@ var TrackCascadeCountsSchema = object({
16888
16994
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
16889
16995
  plates: number().int(),
16890
16996
  /** Per-track CLIP search vectors removed (best-effort). */
16891
- embeddings: number().int()
16997
+ embeddings: number().int(),
16998
+ /** Group membership + group rows removed with their last member (best-effort). */
16999
+ groups: number().int()
16892
17000
  });
16893
17001
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
16894
17002
  var DiskReconcileCountsSchema = object({
@@ -17034,7 +17142,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17034
17142
  * stationary registry). Default false: the timeline lists passages,
17035
17143
  * not parking records (operator decision, 2026-08-15). */
17036
17144
  includeStationary: boolean().optional()
17037
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17145
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17146
+ deviceId: number(),
17147
+ groupId: string().min(1)
17148
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17038
17149
  kind: "mutation",
17039
17150
  auth: "admin"
17040
17151
  }), 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({
@@ -17252,6 +17363,33 @@ var NativeCropRefSchema = object({
17252
17363
  h: number()
17253
17364
  })
17254
17365
  });
17366
+ object({
17367
+ crop: object({
17368
+ left: number(),
17369
+ top: number(),
17370
+ width: number().positive(),
17371
+ height: number().positive()
17372
+ }).optional(),
17373
+ content: object({
17374
+ width: number().int().positive(),
17375
+ height: number().int().positive()
17376
+ }),
17377
+ fit: _enum(["stretch", "contain"]),
17378
+ format: _enum([
17379
+ "rgb",
17380
+ "gray",
17381
+ "jpeg"
17382
+ ])
17383
+ });
17384
+ var FrameRefSchema = object({
17385
+ registryId: string().min(1),
17386
+ id: string().min(1),
17387
+ width: number().int().positive(),
17388
+ height: number().int().positive(),
17389
+ format: _enum(["rgb", "gray"]),
17390
+ timestamp: number(),
17391
+ capturedAt: number().optional()
17392
+ });
17255
17393
  var ModelFormatSchema$1 = _enum([
17256
17394
  "onnx",
17257
17395
  "coreml",
@@ -17317,7 +17455,8 @@ var PipelineModelOptionSchema = object({
17317
17455
  sizeMB: number()
17318
17456
  })),
17319
17457
  group: ModelVariantGroupSchema.optional(),
17320
- legacy: boolean().optional()
17458
+ legacy: boolean().optional(),
17459
+ provider: ModelProviderIdSchema.optional()
17321
17460
  });
17322
17461
  var ConfigFieldBridge = custom();
17323
17462
  var PipelineAddonSchemaSchema = object({
@@ -17496,6 +17635,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17496
17635
  steps: array(PipelineStepInputSchema).min(1),
17497
17636
  frame: FrameInputSchema.optional(),
17498
17637
  /**
17638
+ * Process-local lazy frame. Valid only when caller and provider resolve
17639
+ * in the same execution-group process; split/cross-node callers use
17640
+ * `frame`/`image` inline compatibility instead.
17641
+ */
17642
+ frameRef: FrameRefSchema.optional(),
17643
+ /**
17499
17644
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17500
17645
  * the decoded pixels live in. One more member of the one-of
17501
17646
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -17751,7 +17896,10 @@ var NativeCropResultSchema = object({
17751
17896
  * Which source served this crop, so a quality-sensitive consumer (the native
17752
17897
  * `keyFrame`) can reject a degraded fallback:
17753
17898
  * - `native` — cut from the decode worker's retained NATIVE surface (the
17754
- * quality path).
17899
+ * quality path). A subject-tile serve is also native-resolution and stays
17900
+ * `native` here: the public enum cannot name `tile` without a breaking cap
17901
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
17902
+ * internal crop result (`nativeHits` vs `tileHits`).
17755
17903
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
17756
17904
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
17757
17905
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18242,12 +18390,41 @@ var RunnerLocalLoadSchema = object({
18242
18390
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18243
18391
  * working unchanged when they switch to reading from the runner cap.
18244
18392
  */
18393
+ var FrameLazyCountersSchema = object({
18394
+ framesDecoded: number(),
18395
+ framesAdmitted: number(),
18396
+ framesDroppedPixelFree: number(),
18397
+ viewsMaterialized: number(),
18398
+ viewsSkipped: number(),
18399
+ workerToRunnerBytes: number(),
18400
+ runnerToPoolRawBytes: number(),
18401
+ runnerToPoolJpegBytes: number(),
18402
+ onDemandFullFrameRequests: number(),
18403
+ onDemandCropRequests: number(),
18404
+ nativeHits: number(),
18405
+ nativeMisses: number(),
18406
+ tileHits: number(),
18407
+ tileMisses: number(),
18408
+ fallbackHits: number(),
18409
+ fallbackMisses: number(),
18410
+ retainedWritesAvoided: number(),
18411
+ residentRefs: number(),
18412
+ residentBytes: number(),
18413
+ releases: number(),
18414
+ evictions: number(),
18415
+ staleMisses: number()
18416
+ });
18417
+ var FrameLazyMetricsSchema = object({
18418
+ node: FrameLazyCountersSchema,
18419
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18420
+ });
18245
18421
  var RunnerLocalMetricsSchema = object({
18246
18422
  nodeId: string(),
18247
18423
  activeCameras: number(),
18248
18424
  throttledCameras: number(),
18249
18425
  avgInferenceTimeMs: number(),
18250
- queueDepth: number()
18426
+ queueDepth: number(),
18427
+ frameLazy: FrameLazyMetricsSchema.optional()
18251
18428
  });
18252
18429
  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({
18253
18430
  handle: FrameHandleSchema,
@@ -19547,6 +19724,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19547
19724
  location: StorageLocationSchema,
19548
19725
  relativePath: string()
19549
19726
  }), _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" });
19727
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19728
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19729
+ var ProfileSettingsBagSchema = record(string(), unknown());
19550
19730
  /**
19551
19731
  * A live terminal session hosted by the provider addon. Output and input do
19552
19732
  * NOT flow through the capability — they use the addon data plane
@@ -19576,7 +19756,14 @@ var TerminalSessionInfoSchema = object({
19576
19756
  var TerminalProfileInfoSchema = object({
19577
19757
  profileId: string(),
19578
19758
  label: string(),
19579
- description: string().optional()
19759
+ description: string().optional(),
19760
+ /** Spawn defaults the instance form copies on create. */
19761
+ executable: string().optional(),
19762
+ args: array(string()).readonly().optional(),
19763
+ cwd: string().optional(),
19764
+ environment: array(string()).readonly().optional(),
19765
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
19766
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19580
19767
  });
19581
19768
  /**
19582
19769
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19589,7 +19776,12 @@ var TerminalInstanceInfoSchema = object({
19589
19776
  profileId: string(),
19590
19777
  profileLabel: string(),
19591
19778
  name: string(),
19592
- enabled: boolean()
19779
+ enabled: boolean(),
19780
+ executable: string(),
19781
+ args: array(string()).readonly(),
19782
+ cwd: string(),
19783
+ environment: array(string()).readonly(),
19784
+ profileSettings: ProfileSettingsBagSchema
19593
19785
  });
19594
19786
  var TerminalLegacyCameraSchema = object({
19595
19787
  stableId: string(),
@@ -19619,7 +19811,23 @@ var TerminalOutputBatchSchema = object({
19619
19811
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19620
19812
  targetNodeId: string().min(1),
19621
19813
  profileId: string().min(1),
19622
- name: string().trim().min(1).max(160).optional()
19814
+ name: string().trim().min(1).max(160).optional(),
19815
+ executable: string().max(1024).optional(),
19816
+ args: array(string().max(2048)).max(64).optional(),
19817
+ cwd: string().max(1024).optional(),
19818
+ environment: array(string().max(4096)).max(64).optional(),
19819
+ profileSettings: ProfileSettingsBagSchema.optional()
19820
+ }), TerminalInstanceInfoSchema, {
19821
+ kind: "mutation",
19822
+ auth: "admin"
19823
+ }), method(object({
19824
+ instanceId: string().min(1),
19825
+ name: string().trim().min(1).max(160).optional(),
19826
+ executable: string().max(1024).optional(),
19827
+ args: array(string().max(2048)).max(64).optional(),
19828
+ cwd: string().max(1024).optional(),
19829
+ environment: array(string().max(4096)).max(64).optional(),
19830
+ profileSettings: ProfileSettingsBagSchema.optional()
19623
19831
  }), TerminalInstanceInfoSchema, {
19624
19832
  kind: "mutation",
19625
19833
  auth: "admin"
@@ -19641,7 +19849,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19641
19849
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19642
19850
  profileId: string(),
19643
19851
  cols: number().int().positive(),
19644
- rows: number().int().positive()
19852
+ rows: number().int().positive(),
19853
+ executable: string().max(1024).optional(),
19854
+ args: array(string().max(2048)).max(64).optional(),
19855
+ cwd: string().max(1024).optional(),
19856
+ environment: array(string().max(4096)).max(64).optional()
19645
19857
  }), TerminalSessionInfoSchema, {
19646
19858
  kind: "mutation",
19647
19859
  auth: "admin"
@@ -22423,10 +22635,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
22423
22635
  *
22424
22636
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
22425
22637
  * to receive an ordered list of candidate base URLs it should race
22426
- * on connect — LAN IPv4 first (lowest latency when on same network),
22427
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
22428
- * race them with short timeouts and stick with the winner for the
22429
- * session.
22638
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
22639
+ * when on the same network), then public hostname (if a tunnel is
22640
+ * up). The SDK can race them with short timeouts and stick with the
22641
+ * winner for the session.
22430
22642
  *
22431
22643
  * Why hub-only: agents are not directly addressable by the operator's
22432
22644
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -22581,6 +22793,17 @@ var NotificationEndpointSchema = object({
22581
22793
  /** What the ranking currently resolves to (null when nothing is reachable). */
22582
22794
  resolved: string().nullable()
22583
22795
  });
22796
+ /**
22797
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
22798
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
22799
+ * currently expands to, so the UI can show the effective set either way.
22800
+ */
22801
+ var ViewerEndpointsSchema = object({
22802
+ /** The operator's explicit race set, or empty for AUTO. */
22803
+ baseUrls: array(string()).readonly(),
22804
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
22805
+ resolved: array(string()).readonly()
22806
+ });
22584
22807
  var AllowedAddressesSchema = object({
22585
22808
  /**
22586
22809
  * Allowlist of interface addresses operators have explicitly opted
@@ -22589,6 +22812,20 @@ var AllowedAddressesSchema = object({
22589
22812
  * Network Addresses admin page and persisted by the addon.
22590
22813
  */
22591
22814
  addresses: array(string()).readonly() });
22815
+ var TlsStatusSchema = object({
22816
+ mode: _enum([
22817
+ "generated",
22818
+ "uploaded",
22819
+ "disabled"
22820
+ ]),
22821
+ leafFingerprintSha256: string().nullable(),
22822
+ caFingerprintSha256: string().nullable(),
22823
+ validTo: string().nullable(),
22824
+ sans: array(string()),
22825
+ caCertPem: string().nullable(),
22826
+ reissueError: string().nullable(),
22827
+ restartRequired: boolean()
22828
+ });
22592
22829
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22593
22830
  /**
22594
22831
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -22598,17 +22835,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
22598
22835
  */
22599
22836
  port: number().int().min(1).max(65535).optional(),
22600
22837
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22601
- * candidate. Default `true`. */
22838
+ * candidate. Default `false` — loopback is not a client route. */
22602
22839
  includeLoopback: boolean().optional(),
22603
- /** Skip IPv6 entries. Some legacy clients can't parse them.
22604
- * Default `false`. */
22840
+ /** Skip IPv6 entries. Default `false` the palette includes stable
22841
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
22842
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
22605
22843
  ipv4Only: boolean().optional(),
22606
22844
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
22607
22845
  * Pass `'https'` when the caller is itself loaded over HTTPS
22608
22846
  * to avoid mixed-content blocks in the browser. The public
22609
22847
  * tunnel always emits `https://` regardless. */
22610
22848
  scheme: _enum(["http", "https"]).optional()
22611
- }), 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" });
22849
+ }), 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, {
22850
+ kind: "mutation",
22851
+ auth: "admin"
22852
+ }), method(object({
22853
+ certPem: string().min(1),
22854
+ keyPem: string().min(1),
22855
+ caPem: string().optional()
22856
+ }), TlsStatusSchema, {
22857
+ kind: "mutation",
22858
+ auth: "admin"
22859
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
22860
+ kind: "mutation",
22861
+ auth: "admin"
22862
+ });
22612
22863
  object({
22613
22864
  /** Lifecycle state of the lock. `jammed` means the motor reported
22614
22865
  * failure to reach the target — operator intervention required. */
@@ -23850,7 +24101,12 @@ var PlateInfoSchema = object({
23850
24101
  plateBbox: BoundingBoxSchema.optional(),
23851
24102
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
23852
24103
  keyFrameMediaKey: string().optional(),
23853
- base64: string().optional()
24104
+ base64: string().optional(),
24105
+ /**
24106
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24107
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24108
+ */
24109
+ cropUrl: string().optional()
23854
24110
  });
23855
24111
  var MediaFileLiteSchema = object({
23856
24112
  key: string(),
@@ -27490,6 +27746,12 @@ Object.freeze({
27490
27746
  addonId: null,
27491
27747
  access: "view"
27492
27748
  },
27749
+ "deviceManager.getBindingsBatch": {
27750
+ capName: "device-manager",
27751
+ capScope: "system",
27752
+ addonId: null,
27753
+ access: "view"
27754
+ },
27493
27755
  "deviceManager.getChildren": {
27494
27756
  capName: "device-manager",
27495
27757
  capScope: "system",
@@ -27550,6 +27812,12 @@ Object.freeze({
27550
27812
  addonId: null,
27551
27813
  access: "view"
27552
27814
  },
27815
+ "deviceManager.getLinkedDevicesBatch": {
27816
+ capName: "device-manager",
27817
+ capScope: "system",
27818
+ addonId: null,
27819
+ access: "view"
27820
+ },
27553
27821
  "deviceManager.getRoleDisplayDefaults": {
27554
27822
  capName: "device-manager",
27555
27823
  capScope: "system",
@@ -28432,6 +28700,12 @@ Object.freeze({
28432
28700
  addonId: null,
28433
28701
  access: "create"
28434
28702
  },
28703
+ "localNetwork.downloadCa": {
28704
+ capName: "local-network",
28705
+ capScope: "system",
28706
+ addonId: null,
28707
+ access: "view"
28708
+ },
28435
28709
  "localNetwork.getAllowedAddresses": {
28436
28710
  capName: "local-network",
28437
28711
  capScope: "system",
@@ -28456,18 +28730,42 @@ Object.freeze({
28456
28730
  addonId: null,
28457
28731
  access: "view"
28458
28732
  },
28733
+ "localNetwork.getTlsStatus": {
28734
+ capName: "local-network",
28735
+ capScope: "system",
28736
+ addonId: null,
28737
+ access: "view"
28738
+ },
28739
+ "localNetwork.getViewerEndpoints": {
28740
+ capName: "local-network",
28741
+ capScope: "system",
28742
+ addonId: null,
28743
+ access: "view"
28744
+ },
28459
28745
  "localNetwork.list": {
28460
28746
  capName: "local-network",
28461
28747
  capScope: "system",
28462
28748
  addonId: null,
28463
28749
  access: "view"
28464
28750
  },
28751
+ "localNetwork.regenerateCertificate": {
28752
+ capName: "local-network",
28753
+ capScope: "system",
28754
+ addonId: null,
28755
+ access: "create"
28756
+ },
28465
28757
  "localNetwork.resetAllowlistToBestMatch": {
28466
28758
  capName: "local-network",
28467
28759
  capScope: "system",
28468
28760
  addonId: null,
28469
28761
  access: "delete"
28470
28762
  },
28763
+ "localNetwork.revertToGeneratedCertificate": {
28764
+ capName: "local-network",
28765
+ capScope: "system",
28766
+ addonId: null,
28767
+ access: "create"
28768
+ },
28471
28769
  "localNetwork.setAllowedAddresses": {
28472
28770
  capName: "local-network",
28473
28771
  capScope: "system",
@@ -28480,6 +28778,18 @@ Object.freeze({
28480
28778
  addonId: null,
28481
28779
  access: "create"
28482
28780
  },
28781
+ "localNetwork.setViewerEndpoints": {
28782
+ capName: "local-network",
28783
+ capScope: "system",
28784
+ addonId: null,
28785
+ access: "create"
28786
+ },
28787
+ "localNetwork.uploadCertificate": {
28788
+ capName: "local-network",
28789
+ capScope: "system",
28790
+ addonId: null,
28791
+ access: "create"
28792
+ },
28483
28793
  "lockControl.lock": {
28484
28794
  capName: "lock-control",
28485
28795
  capScope: "device",
@@ -29278,6 +29588,12 @@ Object.freeze({
29278
29588
  addonId: null,
29279
29589
  access: "view"
29280
29590
  },
29591
+ "pipelineAnalytics.getGroup": {
29592
+ capName: "pipeline-analytics",
29593
+ capScope: "device",
29594
+ addonId: null,
29595
+ access: "view"
29596
+ },
29281
29597
  "pipelineAnalytics.getKeyEvents": {
29282
29598
  capName: "pipeline-analytics",
29283
29599
  capScope: "device",
@@ -29362,6 +29678,12 @@ Object.freeze({
29362
29678
  addonId: null,
29363
29679
  access: "view"
29364
29680
  },
29681
+ "pipelineAnalytics.listGroups": {
29682
+ capName: "pipeline-analytics",
29683
+ capScope: "device",
29684
+ addonId: null,
29685
+ access: "view"
29686
+ },
29365
29687
  "pipelineAnalytics.listOpsLog": {
29366
29688
  capName: "pipeline-analytics",
29367
29689
  capScope: "device",
@@ -31360,6 +31682,12 @@ Object.freeze({
31360
31682
  addonId: null,
31361
31683
  access: "create"
31362
31684
  },
31685
+ "terminalSession.updateInstance": {
31686
+ capName: "terminal-session",
31687
+ capScope: "system",
31688
+ addonId: null,
31689
+ access: "create"
31690
+ },
31363
31691
  "terminalSession.writeInput": {
31364
31692
  capName: "terminal-session",
31365
31693
  capScope: "system",
@@ -32137,6 +32465,11 @@ Object.freeze({
32137
32465
  form: "single",
32138
32466
  optional: false
32139
32467
  }],
32468
+ "deviceManager.getBindingsBatch": [{
32469
+ name: "deviceIds",
32470
+ form: "array",
32471
+ optional: false
32472
+ }],
32140
32473
  "deviceManager.getChildren": [{
32141
32474
  name: "parentDeviceId",
32142
32475
  form: "single",
@@ -32182,6 +32515,11 @@ Object.freeze({
32182
32515
  form: "single",
32183
32516
  optional: false
32184
32517
  }],
32518
+ "deviceManager.getLinkedDevicesBatch": [{
32519
+ name: "deviceIds",
32520
+ form: "array",
32521
+ optional: false
32522
+ }],
32185
32523
  "deviceManager.getSettingsSchema": [{
32186
32524
  name: "deviceId",
32187
32525
  form: "single",
@@ -32202,6 +32540,11 @@ Object.freeze({
32202
32540
  form: "single",
32203
32541
  optional: false
32204
32542
  }],
32543
+ "deviceManager.listAll": [{
32544
+ name: "deviceIds",
32545
+ form: "array",
32546
+ optional: true
32547
+ }],
32205
32548
  "deviceManager.loadConfig": [{
32206
32549
  name: "deviceId",
32207
32550
  form: "single",
@@ -32775,6 +33118,11 @@ Object.freeze({
32775
33118
  form: "single",
32776
33119
  optional: false
32777
33120
  }],
33121
+ "pipelineAnalytics.getGroup": [{
33122
+ name: "deviceId",
33123
+ form: "single",
33124
+ optional: false
33125
+ }],
32778
33126
  "pipelineAnalytics.getKeyEvents": [{
32779
33127
  name: "deviceId",
32780
33128
  form: "single",
@@ -32830,6 +33178,11 @@ Object.freeze({
32830
33178
  form: "array",
32831
33179
  optional: false
32832
33180
  }],
33181
+ "pipelineAnalytics.listGroups": [{
33182
+ name: "deviceIds",
33183
+ form: "array",
33184
+ optional: false
33185
+ }],
32833
33186
  "pipelineAnalytics.listOpsLog": [{
32834
33187
  name: "deviceId",
32835
33188
  form: "single",
@@ -33847,6 +34200,35 @@ Object.freeze(Object.fromEntries([{
33847
34200
  }]
33848
34201
  }].map((s) => [s.stepId, s.defaultModelId])));
33849
34202
  string().min(1);
34203
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34204
+ stepId: "face-embedding",
34205
+ key: "minLandmarkFaceSize",
34206
+ label: "Min face size for recognition (detection px)",
34207
+ 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.",
34208
+ type: "slider",
34209
+ min: 0,
34210
+ max: 64,
34211
+ step: 2,
34212
+ default: 24
34213
+ }];
34214
+ function clusterStepSettingKey(stepId, fieldKey) {
34215
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34216
+ }
34217
+ var ClusterSettingNumberSchema = number().finite();
34218
+ function readClusterStepSettings(config) {
34219
+ const out = {};
34220
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
34221
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
34222
+ const value = parsed.success ? parsed.data : field.default;
34223
+ const existing = out[field.stepId] ?? {};
34224
+ out[field.stepId] = {
34225
+ ...existing,
34226
+ [field.key]: value
34227
+ };
34228
+ }
34229
+ return out;
34230
+ }
34231
+ readClusterStepSettings({});
33850
34232
  object({
33851
34233
  /**
33852
34234
  * Fraction of the box's own size added on EACH side before cutting.