@camstack/addon-notifiers 1.2.30 → 1.2.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +432 -50
  2. package/dist/addon.mjs +432 -50
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -5831,6 +5831,13 @@ var BaseAddon = class {
5831
5831
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5832
5832
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5833
5833
  _registeredCapNames = [];
5834
+ /**
5835
+ * True only after `readAddonStore` actually answered. Constructor
5836
+ * defaults look like stored config when the store is down — a forked
5837
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5838
+ * mode, 2026-08-25) is not "the operator chose this".
5839
+ */
5840
+ settingsStoreReady = false;
5834
5841
  /** Default config values. Provided via constructor. */
5835
5842
  defaults;
5836
5843
  constructor(defaults) {
@@ -6231,7 +6238,9 @@ var BaseAddon = class {
6231
6238
  ];
6232
6239
  let lastErr;
6233
6240
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6234
- return await settings.readAddonStore() ?? {};
6241
+ const stored = await settings.readAddonStore() ?? {};
6242
+ this.settingsStoreReady = true;
6243
+ return stored;
6235
6244
  } catch (err) {
6236
6245
  lastErr = err;
6237
6246
  const msg = err instanceof Error ? err.message : String(err);
@@ -6239,6 +6248,7 @@ var BaseAddon = class {
6239
6248
  if (attempt === delaysMs.length) break;
6240
6249
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6241
6250
  }
6251
+ this.settingsStoreReady = false;
6242
6252
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6243
6253
  return {};
6244
6254
  }
@@ -8153,6 +8163,15 @@ var LabelDefinitionSchema = object({
8153
8163
  description: string().optional(),
8154
8164
  icon: string().optional()
8155
8165
  });
8166
+ var ClassMapDefinitionSchema = object({
8167
+ mapping: record(string(), _enum([
8168
+ "person",
8169
+ "vehicle",
8170
+ "animal",
8171
+ "package"
8172
+ ])),
8173
+ preserveOriginal: boolean()
8174
+ });
8156
8175
  var MODEL_FORMATS = [
8157
8176
  "onnx",
8158
8177
  "coreml",
@@ -8236,6 +8255,12 @@ var ModelVariantGroupSchema = object({
8236
8255
  */
8237
8256
  resolution: number().int().positive().optional()
8238
8257
  });
8258
+ var ModelProviderIdSchema = _enum([
8259
+ "camstack",
8260
+ "frigate",
8261
+ "scrypted",
8262
+ "custom"
8263
+ ]);
8239
8264
  var ModelCatalogEntrySchema = object({
8240
8265
  id: string(),
8241
8266
  name: string(),
@@ -8331,7 +8356,19 @@ var ModelCatalogEntrySchema = object({
8331
8356
  * `id` stays the source of truth for resolution/download/persistence; grouping
8332
8357
  * is a presentation overlay resolved back to an `id`.
8333
8358
  */
8334
- group: ModelVariantGroupSchema.optional()
8359
+ group: ModelVariantGroupSchema.optional(),
8360
+ /**
8361
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8362
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8363
+ * persisted before this field existed (`inferModelProvider` fills those).
8364
+ */
8365
+ provider: ModelProviderIdSchema.optional(),
8366
+ /**
8367
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8368
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8369
+ * labels already ARE the CamStack macros (Scrypted identity map).
8370
+ */
8371
+ classMap: ClassMapDefinitionSchema.optional()
8335
8372
  });
8336
8373
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8337
8374
  format: literal("openvino"),
@@ -8360,7 +8397,8 @@ var ModelConvertMetadataSchema = object({
8360
8397
  "ocr",
8361
8398
  "segmentation"
8362
8399
  ]),
8363
- faceAlignment: boolean().optional()
8400
+ faceAlignment: boolean().optional(),
8401
+ classMap: ClassMapDefinitionSchema.optional()
8364
8402
  });
8365
8403
  var ConvertResultSchema = object({
8366
8404
  entry: ModelCatalogEntrySchema,
@@ -11894,6 +11932,27 @@ var LinkedDeviceSchema = object({
11894
11932
  features: array(string()),
11895
11933
  producesTrackedEvents: boolean().optional()
11896
11934
  });
11935
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
11936
+ * The batch answer needs the tag; the single-device answer already has it
11937
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
11938
+ var LinkedDevicesForDeviceSchema = object({
11939
+ deviceId: number(),
11940
+ mode: LinkedDevicesModeSchema,
11941
+ devices: array(LinkedDeviceSchema)
11942
+ });
11943
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
11944
+ * `getAllBindings` all answer in. Declared once: three copies of the same
11945
+ * object literal is exactly how the three drift apart. */
11946
+ var DeviceBindingsForDeviceSchema = object({
11947
+ deviceId: number(),
11948
+ entries: array(object({
11949
+ capName: string(),
11950
+ kind: _enum(["native", "wrapped"]),
11951
+ providerAddonId: string(),
11952
+ providerNodeId: string(),
11953
+ nativeAddonId: string()
11954
+ }))
11955
+ });
11897
11956
  var SavedDeviceRowSchema = object({
11898
11957
  /** Numeric id reserved at allocateDeviceId time. */
11899
11958
  id: number(),
@@ -12119,11 +12178,25 @@ method(object({
12119
12178
  projection: _enum(["full", "slim"]).optional(),
12120
12179
  /** Return only camera devices. Filtering server-side instead of
12121
12180
  * shipping 293 rows to find 12. */
12122
- isCamera: boolean().optional()
12181
+ isCamera: boolean().optional(),
12182
+ /**
12183
+ * Return only these device ids. For the caller that already KNOWS the
12184
+ * handful it wants and needs a field the id-bearing answer does not
12185
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12186
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12187
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12188
+ * refetches on the reconcile interval, on a phone.
12189
+ *
12190
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12191
+ * keys rather than rejecting them (verified against the live hub
12192
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12193
+ * it answers today and the caller filters as it already does.
12194
+ */
12195
+ deviceIds: array(number()).optional()
12123
12196
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12124
12197
  mode: LinkedDevicesModeSchema,
12125
12198
  devices: array(LinkedDeviceSchema)
12126
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12199
+ })), 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({
12127
12200
  deviceId: number(),
12128
12201
  values: record(string(), unknown())
12129
12202
  }), object({ success: literal(true) }), {
@@ -12150,25 +12223,7 @@ method(object({
12150
12223
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12151
12224
  kind: "mutation",
12152
12225
  auth: "admin"
12153
- }), method(object({ deviceId: number() }), object({
12154
- deviceId: number(),
12155
- entries: array(object({
12156
- capName: string(),
12157
- kind: _enum(["native", "wrapped"]),
12158
- providerAddonId: string(),
12159
- providerNodeId: string(),
12160
- nativeAddonId: string()
12161
- }))
12162
- })), method(object({}), array(object({
12163
- deviceId: number(),
12164
- entries: array(object({
12165
- capName: string(),
12166
- kind: _enum(["native", "wrapped"]),
12167
- providerAddonId: string(),
12168
- providerNodeId: string(),
12169
- nativeAddonId: string()
12170
- }))
12171
- }))), method(object({
12226
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12172
12227
  deviceId: number(),
12173
12228
  capName: string(),
12174
12229
  wrapperAddonId: string(),
@@ -14554,12 +14609,15 @@ var NcOccupancyConditionSchema = object({
14554
14609
  * there is no second switch that can disagree with the first and every rule
14555
14610
  * authored before the decision migrates for free (`audioModeOf`):
14556
14611
  *
14557
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14558
- * classifier labels with one of them. No window, no percentage:
14559
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14560
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14561
- * the analyzer's (`classificationMinScore`, per device) a label only
14562
- * reaches this condition if the classifier was already confident enough.
14612
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14613
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14614
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
14615
+ * frames is the wrong question for a classifier that labels 1–3 frames
14616
+ * per episode. The count window is the brake that drops a single-frame
14617
+ * false positive; the rule's own `throttle` cooldown is the other. The
14618
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14619
+ * per device) — a label only reaches this condition if the classifier was
14620
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14563
14621
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14564
14622
  * the condition: at least `hitPercent`% of the samples over
14565
14623
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14586,14 +14644,22 @@ var NcOccupancyConditionSchema = object({
14586
14644
  * an operator who typed `dog` mean the same thing.
14587
14645
  */
14588
14646
  var NcAudioConditionSchema = object({
14589
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14647
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14590
14648
  labels: array(string().min(1)).min(1).optional(),
14591
14649
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14592
14650
  dbThreshold: number().min(-96).max(0).optional(),
14593
14651
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14594
14652
  hitPercent: number().int().min(1).max(100).default(60),
14595
14653
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14596
- samplingSeconds: number().int().min(1).max(300).default(10)
14654
+ samplingSeconds: number().int().min(1).max(300).default(10),
14655
+ /**
14656
+ * LABEL MODE: how many labelled frames must land inside
14657
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14658
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14659
+ */
14660
+ confirmHits: number().int().min(1).max(20).optional(),
14661
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14662
+ confirmWindowSec: number().int().min(1).max(60).optional()
14597
14663
  });
14598
14664
  /**
14599
14665
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -16967,6 +17033,46 @@ var RecentTracksPageSchema = object({
16967
17033
  /** Cursor for the next page, or null when this page is the last. */
16968
17034
  nextCursor: string().nullable()
16969
17035
  });
17036
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17037
+ var LIST_GROUPS_MAX_LIMIT = 100;
17038
+ var AnalyticsGroupRecordSchema = object({
17039
+ id: string(),
17040
+ deviceId: number().int(),
17041
+ openedAt: number().int(),
17042
+ closedAt: number().int(),
17043
+ timestamp: number().int(),
17044
+ memberCount: number().int(),
17045
+ memberTrackIds: array(string()).readonly(),
17046
+ className: string(),
17047
+ classes: array(string()).readonly(),
17048
+ /** Relative event-media path, or null when the group has no picture yet. */
17049
+ mediaUrl: string().nullable(),
17050
+ singleton: boolean()
17051
+ });
17052
+ var AnalyticsGroupMemberSchema = object({
17053
+ trackId: string(),
17054
+ deviceId: number().int(),
17055
+ className: string(),
17056
+ firstSeen: number().int(),
17057
+ lastSeen: number().int(),
17058
+ mediaUrl: string().nullable()
17059
+ });
17060
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17061
+ var ListGroupsQueryInput = object({
17062
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17063
+ deviceIds: array(number()),
17064
+ /** Window lower bound on `closedAt` (inclusive). */
17065
+ since: number().optional(),
17066
+ /** Window upper bound on `openedAt` (inclusive). */
17067
+ until: number().optional(),
17068
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17069
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17070
+ cursor: string().optional()
17071
+ });
17072
+ var ListGroupsPageSchema = object({
17073
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17074
+ nextCursor: string().nullable()
17075
+ });
16970
17076
  var KeyEventQueryInput = object({
16971
17077
  deviceId: number(),
16972
17078
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17042,7 +17148,9 @@ var TrackCascadeCountsSchema = object({
17042
17148
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17043
17149
  plates: number().int(),
17044
17150
  /** Per-track CLIP search vectors removed (best-effort). */
17045
- embeddings: number().int()
17151
+ embeddings: number().int(),
17152
+ /** Group membership + group rows removed with their last member (best-effort). */
17153
+ groups: number().int()
17046
17154
  });
17047
17155
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17048
17156
  var DiskReconcileCountsSchema = object({
@@ -17188,7 +17296,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17188
17296
  * stationary registry). Default false: the timeline lists passages,
17189
17297
  * not parking records (operator decision, 2026-08-15). */
17190
17298
  includeStationary: boolean().optional()
17191
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17299
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17300
+ deviceId: number(),
17301
+ groupId: string().min(1)
17302
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17192
17303
  kind: "mutation",
17193
17304
  auth: "admin"
17194
17305
  }), 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({
@@ -17406,6 +17517,33 @@ var NativeCropRefSchema = object({
17406
17517
  h: number()
17407
17518
  })
17408
17519
  });
17520
+ object({
17521
+ crop: object({
17522
+ left: number(),
17523
+ top: number(),
17524
+ width: number().positive(),
17525
+ height: number().positive()
17526
+ }).optional(),
17527
+ content: object({
17528
+ width: number().int().positive(),
17529
+ height: number().int().positive()
17530
+ }),
17531
+ fit: _enum(["stretch", "contain"]),
17532
+ format: _enum([
17533
+ "rgb",
17534
+ "gray",
17535
+ "jpeg"
17536
+ ])
17537
+ });
17538
+ var FrameRefSchema = object({
17539
+ registryId: string().min(1),
17540
+ id: string().min(1),
17541
+ width: number().int().positive(),
17542
+ height: number().int().positive(),
17543
+ format: _enum(["rgb", "gray"]),
17544
+ timestamp: number(),
17545
+ capturedAt: number().optional()
17546
+ });
17409
17547
  var ModelFormatSchema$1 = _enum([
17410
17548
  "onnx",
17411
17549
  "coreml",
@@ -17471,7 +17609,8 @@ var PipelineModelOptionSchema = object({
17471
17609
  sizeMB: number()
17472
17610
  })),
17473
17611
  group: ModelVariantGroupSchema.optional(),
17474
- legacy: boolean().optional()
17612
+ legacy: boolean().optional(),
17613
+ provider: ModelProviderIdSchema.optional()
17475
17614
  });
17476
17615
  var ConfigFieldBridge = custom();
17477
17616
  var PipelineAddonSchemaSchema = object({
@@ -17650,6 +17789,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17650
17789
  steps: array(PipelineStepInputSchema).min(1),
17651
17790
  frame: FrameInputSchema.optional(),
17652
17791
  /**
17792
+ * Process-local lazy frame. Valid only when caller and provider resolve
17793
+ * in the same execution-group process; split/cross-node callers use
17794
+ * `frame`/`image` inline compatibility instead.
17795
+ */
17796
+ frameRef: FrameRefSchema.optional(),
17797
+ /**
17653
17798
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17654
17799
  * the decoded pixels live in. One more member of the one-of
17655
17800
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -17905,7 +18050,10 @@ var NativeCropResultSchema = object({
17905
18050
  * Which source served this crop, so a quality-sensitive consumer (the native
17906
18051
  * `keyFrame`) can reject a degraded fallback:
17907
18052
  * - `native` — cut from the decode worker's retained NATIVE surface (the
17908
- * quality path).
18053
+ * quality path). A subject-tile serve is also native-resolution and stays
18054
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18055
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18056
+ * internal crop result (`nativeHits` vs `tileHits`).
17909
18057
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
17910
18058
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
17911
18059
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18396,12 +18544,41 @@ var RunnerLocalLoadSchema = object({
18396
18544
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18397
18545
  * working unchanged when they switch to reading from the runner cap.
18398
18546
  */
18547
+ var FrameLazyCountersSchema = object({
18548
+ framesDecoded: number(),
18549
+ framesAdmitted: number(),
18550
+ framesDroppedPixelFree: number(),
18551
+ viewsMaterialized: number(),
18552
+ viewsSkipped: number(),
18553
+ workerToRunnerBytes: number(),
18554
+ runnerToPoolRawBytes: number(),
18555
+ runnerToPoolJpegBytes: number(),
18556
+ onDemandFullFrameRequests: number(),
18557
+ onDemandCropRequests: number(),
18558
+ nativeHits: number(),
18559
+ nativeMisses: number(),
18560
+ tileHits: number(),
18561
+ tileMisses: number(),
18562
+ fallbackHits: number(),
18563
+ fallbackMisses: number(),
18564
+ retainedWritesAvoided: number(),
18565
+ residentRefs: number(),
18566
+ residentBytes: number(),
18567
+ releases: number(),
18568
+ evictions: number(),
18569
+ staleMisses: number()
18570
+ });
18571
+ var FrameLazyMetricsSchema = object({
18572
+ node: FrameLazyCountersSchema,
18573
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18574
+ });
18399
18575
  var RunnerLocalMetricsSchema = object({
18400
18576
  nodeId: string(),
18401
18577
  activeCameras: number(),
18402
18578
  throttledCameras: number(),
18403
18579
  avgInferenceTimeMs: number(),
18404
- queueDepth: number()
18580
+ queueDepth: number(),
18581
+ frameLazy: FrameLazyMetricsSchema.optional()
18405
18582
  });
18406
18583
  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({
18407
18584
  handle: FrameHandleSchema,
@@ -19701,6 +19878,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19701
19878
  location: StorageLocationSchema,
19702
19879
  relativePath: string()
19703
19880
  }), _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" });
19881
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19882
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19883
+ var ProfileSettingsBagSchema = record(string(), unknown());
19704
19884
  /**
19705
19885
  * A live terminal session hosted by the provider addon. Output and input do
19706
19886
  * NOT flow through the capability — they use the addon data plane
@@ -19730,7 +19910,14 @@ var TerminalSessionInfoSchema = object({
19730
19910
  var TerminalProfileInfoSchema = object({
19731
19911
  profileId: string(),
19732
19912
  label: string(),
19733
- description: string().optional()
19913
+ description: string().optional(),
19914
+ /** Spawn defaults the instance form copies on create. */
19915
+ executable: string().optional(),
19916
+ args: array(string()).readonly().optional(),
19917
+ cwd: string().optional(),
19918
+ environment: array(string()).readonly().optional(),
19919
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
19920
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19734
19921
  });
19735
19922
  /**
19736
19923
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19743,7 +19930,12 @@ var TerminalInstanceInfoSchema = object({
19743
19930
  profileId: string(),
19744
19931
  profileLabel: string(),
19745
19932
  name: string(),
19746
- enabled: boolean()
19933
+ enabled: boolean(),
19934
+ executable: string(),
19935
+ args: array(string()).readonly(),
19936
+ cwd: string(),
19937
+ environment: array(string()).readonly(),
19938
+ profileSettings: ProfileSettingsBagSchema
19747
19939
  });
19748
19940
  var TerminalLegacyCameraSchema = object({
19749
19941
  stableId: string(),
@@ -19773,7 +19965,23 @@ var TerminalOutputBatchSchema = object({
19773
19965
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19774
19966
  targetNodeId: string().min(1),
19775
19967
  profileId: string().min(1),
19776
- name: string().trim().min(1).max(160).optional()
19968
+ name: string().trim().min(1).max(160).optional(),
19969
+ executable: string().max(1024).optional(),
19970
+ args: array(string().max(2048)).max(64).optional(),
19971
+ cwd: string().max(1024).optional(),
19972
+ environment: array(string().max(4096)).max(64).optional(),
19973
+ profileSettings: ProfileSettingsBagSchema.optional()
19974
+ }), TerminalInstanceInfoSchema, {
19975
+ kind: "mutation",
19976
+ auth: "admin"
19977
+ }), method(object({
19978
+ instanceId: string().min(1),
19979
+ name: string().trim().min(1).max(160).optional(),
19980
+ executable: string().max(1024).optional(),
19981
+ args: array(string().max(2048)).max(64).optional(),
19982
+ cwd: string().max(1024).optional(),
19983
+ environment: array(string().max(4096)).max(64).optional(),
19984
+ profileSettings: ProfileSettingsBagSchema.optional()
19777
19985
  }), TerminalInstanceInfoSchema, {
19778
19986
  kind: "mutation",
19779
19987
  auth: "admin"
@@ -19795,7 +20003,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19795
20003
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19796
20004
  profileId: string(),
19797
20005
  cols: number().int().positive(),
19798
- rows: number().int().positive()
20006
+ rows: number().int().positive(),
20007
+ executable: string().max(1024).optional(),
20008
+ args: array(string().max(2048)).max(64).optional(),
20009
+ cwd: string().max(1024).optional(),
20010
+ environment: array(string().max(4096)).max(64).optional()
19799
20011
  }), TerminalSessionInfoSchema, {
19800
20012
  kind: "mutation",
19801
20013
  auth: "admin"
@@ -22577,10 +22789,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
22577
22789
  *
22578
22790
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
22579
22791
  * to receive an ordered list of candidate base URLs it should race
22580
- * on connect — LAN IPv4 first (lowest latency when on same network),
22581
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
22582
- * race them with short timeouts and stick with the winner for the
22583
- * session.
22792
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
22793
+ * when on the same network), then public hostname (if a tunnel is
22794
+ * up). The SDK can race them with short timeouts and stick with the
22795
+ * winner for the session.
22584
22796
  *
22585
22797
  * Why hub-only: agents are not directly addressable by the operator's
22586
22798
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -22735,6 +22947,17 @@ var NotificationEndpointSchema = object({
22735
22947
  /** What the ranking currently resolves to (null when nothing is reachable). */
22736
22948
  resolved: string().nullable()
22737
22949
  });
22950
+ /**
22951
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
22952
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
22953
+ * currently expands to, so the UI can show the effective set either way.
22954
+ */
22955
+ var ViewerEndpointsSchema = object({
22956
+ /** The operator's explicit race set, or empty for AUTO. */
22957
+ baseUrls: array(string()).readonly(),
22958
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
22959
+ resolved: array(string()).readonly()
22960
+ });
22738
22961
  var AllowedAddressesSchema = object({
22739
22962
  /**
22740
22963
  * Allowlist of interface addresses operators have explicitly opted
@@ -22743,6 +22966,20 @@ var AllowedAddressesSchema = object({
22743
22966
  * Network Addresses admin page and persisted by the addon.
22744
22967
  */
22745
22968
  addresses: array(string()).readonly() });
22969
+ var TlsStatusSchema = object({
22970
+ mode: _enum([
22971
+ "generated",
22972
+ "uploaded",
22973
+ "disabled"
22974
+ ]),
22975
+ leafFingerprintSha256: string().nullable(),
22976
+ caFingerprintSha256: string().nullable(),
22977
+ validTo: string().nullable(),
22978
+ sans: array(string()),
22979
+ caCertPem: string().nullable(),
22980
+ reissueError: string().nullable(),
22981
+ restartRequired: boolean()
22982
+ });
22746
22983
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22747
22984
  /**
22748
22985
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -22752,17 +22989,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
22752
22989
  */
22753
22990
  port: number().int().min(1).max(65535).optional(),
22754
22991
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22755
- * candidate. Default `true`. */
22992
+ * candidate. Default `false` — loopback is not a client route. */
22756
22993
  includeLoopback: boolean().optional(),
22757
- /** Skip IPv6 entries. Some legacy clients can't parse them.
22758
- * Default `false`. */
22994
+ /** Skip IPv6 entries. Default `false` the palette includes stable
22995
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
22996
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
22759
22997
  ipv4Only: boolean().optional(),
22760
22998
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
22761
22999
  * Pass `'https'` when the caller is itself loaded over HTTPS
22762
23000
  * to avoid mixed-content blocks in the browser. The public
22763
23001
  * tunnel always emits `https://` regardless. */
22764
23002
  scheme: _enum(["http", "https"]).optional()
22765
- }), 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" });
23003
+ }), 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, {
23004
+ kind: "mutation",
23005
+ auth: "admin"
23006
+ }), method(object({
23007
+ certPem: string().min(1),
23008
+ keyPem: string().min(1),
23009
+ caPem: string().optional()
23010
+ }), TlsStatusSchema, {
23011
+ kind: "mutation",
23012
+ auth: "admin"
23013
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
23014
+ kind: "mutation",
23015
+ auth: "admin"
23016
+ });
22766
23017
  object({
22767
23018
  /** Lifecycle state of the lock. `jammed` means the motor reported
22768
23019
  * failure to reach the target — operator intervention required. */
@@ -23943,7 +24194,12 @@ var PlateInfoSchema = object({
23943
24194
  plateBbox: BoundingBoxSchema.optional(),
23944
24195
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
23945
24196
  keyFrameMediaKey: string().optional(),
23946
- base64: string().optional()
24197
+ base64: string().optional(),
24198
+ /**
24199
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24200
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24201
+ */
24202
+ cropUrl: string().optional()
23947
24203
  });
23948
24204
  var MediaFileLiteSchema = object({
23949
24205
  key: string(),
@@ -27583,6 +27839,12 @@ Object.freeze({
27583
27839
  addonId: null,
27584
27840
  access: "view"
27585
27841
  },
27842
+ "deviceManager.getBindingsBatch": {
27843
+ capName: "device-manager",
27844
+ capScope: "system",
27845
+ addonId: null,
27846
+ access: "view"
27847
+ },
27586
27848
  "deviceManager.getChildren": {
27587
27849
  capName: "device-manager",
27588
27850
  capScope: "system",
@@ -27643,6 +27905,12 @@ Object.freeze({
27643
27905
  addonId: null,
27644
27906
  access: "view"
27645
27907
  },
27908
+ "deviceManager.getLinkedDevicesBatch": {
27909
+ capName: "device-manager",
27910
+ capScope: "system",
27911
+ addonId: null,
27912
+ access: "view"
27913
+ },
27646
27914
  "deviceManager.getRoleDisplayDefaults": {
27647
27915
  capName: "device-manager",
27648
27916
  capScope: "system",
@@ -28525,6 +28793,12 @@ Object.freeze({
28525
28793
  addonId: null,
28526
28794
  access: "create"
28527
28795
  },
28796
+ "localNetwork.downloadCa": {
28797
+ capName: "local-network",
28798
+ capScope: "system",
28799
+ addonId: null,
28800
+ access: "view"
28801
+ },
28528
28802
  "localNetwork.getAllowedAddresses": {
28529
28803
  capName: "local-network",
28530
28804
  capScope: "system",
@@ -28549,18 +28823,42 @@ Object.freeze({
28549
28823
  addonId: null,
28550
28824
  access: "view"
28551
28825
  },
28826
+ "localNetwork.getTlsStatus": {
28827
+ capName: "local-network",
28828
+ capScope: "system",
28829
+ addonId: null,
28830
+ access: "view"
28831
+ },
28832
+ "localNetwork.getViewerEndpoints": {
28833
+ capName: "local-network",
28834
+ capScope: "system",
28835
+ addonId: null,
28836
+ access: "view"
28837
+ },
28552
28838
  "localNetwork.list": {
28553
28839
  capName: "local-network",
28554
28840
  capScope: "system",
28555
28841
  addonId: null,
28556
28842
  access: "view"
28557
28843
  },
28844
+ "localNetwork.regenerateCertificate": {
28845
+ capName: "local-network",
28846
+ capScope: "system",
28847
+ addonId: null,
28848
+ access: "create"
28849
+ },
28558
28850
  "localNetwork.resetAllowlistToBestMatch": {
28559
28851
  capName: "local-network",
28560
28852
  capScope: "system",
28561
28853
  addonId: null,
28562
28854
  access: "delete"
28563
28855
  },
28856
+ "localNetwork.revertToGeneratedCertificate": {
28857
+ capName: "local-network",
28858
+ capScope: "system",
28859
+ addonId: null,
28860
+ access: "create"
28861
+ },
28564
28862
  "localNetwork.setAllowedAddresses": {
28565
28863
  capName: "local-network",
28566
28864
  capScope: "system",
@@ -28573,6 +28871,18 @@ Object.freeze({
28573
28871
  addonId: null,
28574
28872
  access: "create"
28575
28873
  },
28874
+ "localNetwork.setViewerEndpoints": {
28875
+ capName: "local-network",
28876
+ capScope: "system",
28877
+ addonId: null,
28878
+ access: "create"
28879
+ },
28880
+ "localNetwork.uploadCertificate": {
28881
+ capName: "local-network",
28882
+ capScope: "system",
28883
+ addonId: null,
28884
+ access: "create"
28885
+ },
28576
28886
  "lockControl.lock": {
28577
28887
  capName: "lock-control",
28578
28888
  capScope: "device",
@@ -29371,6 +29681,12 @@ Object.freeze({
29371
29681
  addonId: null,
29372
29682
  access: "view"
29373
29683
  },
29684
+ "pipelineAnalytics.getGroup": {
29685
+ capName: "pipeline-analytics",
29686
+ capScope: "device",
29687
+ addonId: null,
29688
+ access: "view"
29689
+ },
29374
29690
  "pipelineAnalytics.getKeyEvents": {
29375
29691
  capName: "pipeline-analytics",
29376
29692
  capScope: "device",
@@ -29455,6 +29771,12 @@ Object.freeze({
29455
29771
  addonId: null,
29456
29772
  access: "view"
29457
29773
  },
29774
+ "pipelineAnalytics.listGroups": {
29775
+ capName: "pipeline-analytics",
29776
+ capScope: "device",
29777
+ addonId: null,
29778
+ access: "view"
29779
+ },
29458
29780
  "pipelineAnalytics.listOpsLog": {
29459
29781
  capName: "pipeline-analytics",
29460
29782
  capScope: "device",
@@ -31453,6 +31775,12 @@ Object.freeze({
31453
31775
  addonId: null,
31454
31776
  access: "create"
31455
31777
  },
31778
+ "terminalSession.updateInstance": {
31779
+ capName: "terminal-session",
31780
+ capScope: "system",
31781
+ addonId: null,
31782
+ access: "create"
31783
+ },
31456
31784
  "terminalSession.writeInput": {
31457
31785
  capName: "terminal-session",
31458
31786
  capScope: "system",
@@ -32230,6 +32558,11 @@ Object.freeze({
32230
32558
  form: "single",
32231
32559
  optional: false
32232
32560
  }],
32561
+ "deviceManager.getBindingsBatch": [{
32562
+ name: "deviceIds",
32563
+ form: "array",
32564
+ optional: false
32565
+ }],
32233
32566
  "deviceManager.getChildren": [{
32234
32567
  name: "parentDeviceId",
32235
32568
  form: "single",
@@ -32275,6 +32608,11 @@ Object.freeze({
32275
32608
  form: "single",
32276
32609
  optional: false
32277
32610
  }],
32611
+ "deviceManager.getLinkedDevicesBatch": [{
32612
+ name: "deviceIds",
32613
+ form: "array",
32614
+ optional: false
32615
+ }],
32278
32616
  "deviceManager.getSettingsSchema": [{
32279
32617
  name: "deviceId",
32280
32618
  form: "single",
@@ -32295,6 +32633,11 @@ Object.freeze({
32295
32633
  form: "single",
32296
32634
  optional: false
32297
32635
  }],
32636
+ "deviceManager.listAll": [{
32637
+ name: "deviceIds",
32638
+ form: "array",
32639
+ optional: true
32640
+ }],
32298
32641
  "deviceManager.loadConfig": [{
32299
32642
  name: "deviceId",
32300
32643
  form: "single",
@@ -32868,6 +33211,11 @@ Object.freeze({
32868
33211
  form: "single",
32869
33212
  optional: false
32870
33213
  }],
33214
+ "pipelineAnalytics.getGroup": [{
33215
+ name: "deviceId",
33216
+ form: "single",
33217
+ optional: false
33218
+ }],
32871
33219
  "pipelineAnalytics.getKeyEvents": [{
32872
33220
  name: "deviceId",
32873
33221
  form: "single",
@@ -32923,6 +33271,11 @@ Object.freeze({
32923
33271
  form: "array",
32924
33272
  optional: false
32925
33273
  }],
33274
+ "pipelineAnalytics.listGroups": [{
33275
+ name: "deviceIds",
33276
+ form: "array",
33277
+ optional: false
33278
+ }],
32926
33279
  "pipelineAnalytics.listOpsLog": [{
32927
33280
  name: "deviceId",
32928
33281
  form: "single",
@@ -34147,6 +34500,35 @@ Object.freeze(Object.fromEntries([{
34147
34500
  }]
34148
34501
  }].map((s) => [s.stepId, s.defaultModelId])));
34149
34502
  string().min(1);
34503
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34504
+ stepId: "face-embedding",
34505
+ key: "minLandmarkFaceSize",
34506
+ label: "Min face size for recognition (detection px)",
34507
+ 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.",
34508
+ type: "slider",
34509
+ min: 0,
34510
+ max: 64,
34511
+ step: 2,
34512
+ default: 24
34513
+ }];
34514
+ function clusterStepSettingKey(stepId, fieldKey) {
34515
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34516
+ }
34517
+ var ClusterSettingNumberSchema = number().finite();
34518
+ function readClusterStepSettings(config) {
34519
+ const out = {};
34520
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
34521
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
34522
+ const value = parsed.success ? parsed.data : field.default;
34523
+ const existing = out[field.stepId] ?? {};
34524
+ out[field.stepId] = {
34525
+ ...existing,
34526
+ [field.key]: value
34527
+ };
34528
+ }
34529
+ return out;
34530
+ }
34531
+ readClusterStepSettings({});
34150
34532
  object({
34151
34533
  /**
34152
34534
  * Fraction of the box's own size added on EACH side before cutting.