@camstack/addon-osd-manager 0.1.19 → 0.1.21

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 (18) hide show
  1. package/dist/{MotionZonesSettings-2bARhPh6.mjs → MotionZonesSettings-C1tuU9vA.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-Cdqm3uIC.mjs → PrivacyMaskSettings-BfYwmnx-.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-C58VvxWO.mjs → SceneMonitorEditor-Fui6aPeX.mjs} +3 -3
  4. package/dist/_stub.js +12 -12
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-SPTPw18n.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-CUYUIYix.mjs} +4 -4
  6. package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BhABwRxy.mjs +26 -0
  7. package/dist/{hostInit-CaN1IxGo.mjs → hostInit-wPjKQ8mT.mjs} +3 -3
  8. package/dist/index.js +506 -57
  9. package/dist/index.mjs +506 -57
  10. package/dist/{player-overlays-CQmBy0Zm.mjs → player-overlays-B_C4YyEi.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-jU_1DnOd.mjs → responsive-BzwsIYSh.mjs} +1 -1
  13. package/dist/{square-BewXHDNm.mjs → square-X_zDrz0l.mjs} +1 -1
  14. package/dist/{trash-2-h2ZuBBnj.mjs → trash-2-BldQMEFN.mjs} +1 -1
  15. package/dist/{use-device-snapshot--wXDROMR.mjs → use-device-snapshot-Bxo1wH26.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-BqFfMnBU.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-DZH4YPzH.mjs} +1 -1
  17. package/package.json +1 -1
  18. package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-KlN_tI2R.mjs +0 -26
package/dist/index.mjs CHANGED
@@ -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
  }
@@ -8091,6 +8101,15 @@ var LabelDefinitionSchema = object({
8091
8101
  description: string().optional(),
8092
8102
  icon: string().optional()
8093
8103
  });
8104
+ var ClassMapDefinitionSchema = object({
8105
+ mapping: record(string(), _enum([
8106
+ "person",
8107
+ "vehicle",
8108
+ "animal",
8109
+ "package"
8110
+ ])),
8111
+ preserveOriginal: boolean()
8112
+ });
8094
8113
  var MODEL_FORMATS = [
8095
8114
  "onnx",
8096
8115
  "coreml",
@@ -8174,6 +8193,12 @@ var ModelVariantGroupSchema = object({
8174
8193
  */
8175
8194
  resolution: number().int().positive().optional()
8176
8195
  });
8196
+ var ModelProviderIdSchema = _enum([
8197
+ "camstack",
8198
+ "frigate",
8199
+ "scrypted",
8200
+ "custom"
8201
+ ]);
8177
8202
  var ModelCatalogEntrySchema = object({
8178
8203
  id: string(),
8179
8204
  name: string(),
@@ -8269,7 +8294,19 @@ var ModelCatalogEntrySchema = object({
8269
8294
  * `id` stays the source of truth for resolution/download/persistence; grouping
8270
8295
  * is a presentation overlay resolved back to an `id`.
8271
8296
  */
8272
- group: ModelVariantGroupSchema.optional()
8297
+ group: ModelVariantGroupSchema.optional(),
8298
+ /**
8299
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8300
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8301
+ * persisted before this field existed (`inferModelProvider` fills those).
8302
+ */
8303
+ provider: ModelProviderIdSchema.optional(),
8304
+ /**
8305
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8306
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8307
+ * labels already ARE the CamStack macros (Scrypted identity map).
8308
+ */
8309
+ classMap: ClassMapDefinitionSchema.optional()
8273
8310
  });
8274
8311
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8275
8312
  format: literal("openvino"),
@@ -8298,7 +8335,8 @@ var ModelConvertMetadataSchema = object({
8298
8335
  "ocr",
8299
8336
  "segmentation"
8300
8337
  ]),
8301
- faceAlignment: boolean().optional()
8338
+ faceAlignment: boolean().optional(),
8339
+ classMap: ClassMapDefinitionSchema.optional()
8302
8340
  });
8303
8341
  var ConvertResultSchema = object({
8304
8342
  entry: ModelCatalogEntrySchema,
@@ -12864,6 +12902,27 @@ var LinkedDeviceSchema = object({
12864
12902
  features: array(string()),
12865
12903
  producesTrackedEvents: boolean().optional()
12866
12904
  });
12905
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12906
+ * The batch answer needs the tag; the single-device answer already has it
12907
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12908
+ var LinkedDevicesForDeviceSchema = object({
12909
+ deviceId: number(),
12910
+ mode: LinkedDevicesModeSchema,
12911
+ devices: array(LinkedDeviceSchema)
12912
+ });
12913
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12914
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12915
+ * object literal is exactly how the three drift apart. */
12916
+ var DeviceBindingsForDeviceSchema = object({
12917
+ deviceId: number(),
12918
+ entries: array(object({
12919
+ capName: string(),
12920
+ kind: _enum(["native", "wrapped"]),
12921
+ providerAddonId: string(),
12922
+ providerNodeId: string(),
12923
+ nativeAddonId: string()
12924
+ }))
12925
+ });
12867
12926
  var SavedDeviceRowSchema = object({
12868
12927
  /** Numeric id reserved at allocateDeviceId time. */
12869
12928
  id: number(),
@@ -13226,7 +13285,21 @@ var deviceManagerCapability = {
13226
13285
  projection: _enum(["full", "slim"]).optional(),
13227
13286
  /** Return only camera devices. Filtering server-side instead of
13228
13287
  * shipping 293 rows to find 12. */
13229
- isCamera: boolean().optional()
13288
+ isCamera: boolean().optional(),
13289
+ /**
13290
+ * Return only these device ids. For the caller that already KNOWS the
13291
+ * handful it wants and needs a field the id-bearing answer does not
13292
+ * carry — the viewer's linked-devices panel joins `type` and `online`
13293
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
13294
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
13295
+ * refetches on the reconcile interval, on a phone.
13296
+ *
13297
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
13298
+ * keys rather than rejecting them (verified against the live hub
13299
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
13300
+ * it answers today and the caller filters as it already does.
13301
+ */
13302
+ deviceIds: array(number()).optional()
13230
13303
  }), array(DeviceInfoSchema)),
13231
13304
  /** Get a single device by numeric deviceId. */
13232
13305
  getDevice: method(object({ deviceId: number() }), DeviceInfoSchema.nullable()),
@@ -13245,6 +13318,23 @@ var deviceManagerCapability = {
13245
13318
  mode: LinkedDevicesModeSchema,
13246
13319
  devices: array(LinkedDeviceSchema)
13247
13320
  })),
13321
+ /**
13322
+ * `getLinkedDevices` for MANY cameras, in one call.
13323
+ *
13324
+ * Not a convenience wrapper — a cost fix. Resolving one camera's linked set
13325
+ * needs the whole fleet as candidates (children ∪ same-location), so the
13326
+ * single-device path runs a full `listAll` per call. Asked per camera that
13327
+ * is N full-fleet sweeps of device-manager's event loop, and they do not
13328
+ * overlap: measured on the live hub 2026-08-25, 30 concurrent
13329
+ * `getLinkedDevices` took 4110 ms wall (median 4060 ms each) to return
13330
+ * 7260 bytes in total. The batch takes ONE sweep and one settings read per
13331
+ * camera.
13332
+ *
13333
+ * A device id that resolves to nothing still gets a row (`devices: []`) —
13334
+ * a caller that asked for thirty and got twenty-eight cannot tell which two
13335
+ * are missing, or that any are.
13336
+ */
13337
+ getLinkedDevicesBatch: method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)),
13248
13338
  /** Get stream sources for a camera device. */
13249
13339
  getStreamSources: method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)),
13250
13340
  /** Get config entries (key + value + description) for a device. */
@@ -13302,16 +13392,22 @@ var deviceManagerCapability = {
13302
13392
  * currently-active provider (native or wrapper) + the underlying native
13303
13393
  * addon id, so consumers can decide routing without re-running discovery.
13304
13394
  */
13305
- getBindings: method(object({ deviceId: number() }), object({
13306
- deviceId: number(),
13307
- entries: array(object({
13308
- capName: string(),
13309
- kind: _enum(["native", "wrapped"]),
13310
- providerAddonId: string(),
13311
- providerNodeId: string(),
13312
- nativeAddonId: string()
13313
- }))
13314
- })),
13395
+ getBindings: method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema),
13396
+ /**
13397
+ * `getBindings` for a NAMED set of devices, in one call.
13398
+ *
13399
+ * Between `getBindings` (one device) and `getAllBindings` (all 988) there
13400
+ * was nothing, so a caller that needs seventy-five either pays
13401
+ * seventy-five round-trips or drags the whole fleet across. Measured on
13402
+ * the live hub 2026-08-25: 75 concurrent `getBindings` = 1211 ms / 118 KB,
13403
+ * `getAllBindings({})` = 511 ms / 679 KB. Neither is the right answer to
13404
+ * "these seventy-five".
13405
+ *
13406
+ * Same resolver, same routing rules, same per-device shape as
13407
+ * `getBindings`; ids are deduped and a device with no bindings answers
13408
+ * `entries: []` rather than dropping out.
13409
+ */
13410
+ getBindingsBatch: method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)),
13315
13411
  /**
13316
13412
  * Return the binding map for every device known to the hub. Used by
13317
13413
  * `SystemManager` warm-boot: a single round-trip resolves the
@@ -13320,16 +13416,7 @@ var deviceManagerCapability = {
13320
13416
  * device add/remove) — clients invalidate via the
13321
13417
  * `capability.binding-changed` event.
13322
13418
  */
13323
- getAllBindings: method(object({}), array(object({
13324
- deviceId: number(),
13325
- entries: array(object({
13326
- capName: string(),
13327
- kind: _enum(["native", "wrapped"]),
13328
- providerAddonId: string(),
13329
- providerNodeId: string(),
13330
- nativeAddonId: string()
13331
- }))
13332
- }))),
13419
+ getAllBindings: method(object({}), array(DeviceBindingsForDeviceSchema)),
13333
13420
  /**
13334
13421
  * Activate (or deactivate) a wrapper addon for a (device, cap) pair.
13335
13422
  * Persists the binding via ctx.settings. active=false clears the wrapper
@@ -16217,12 +16304,15 @@ var NcOccupancyConditionSchema = object({
16217
16304
  * there is no second switch that can disagree with the first and every rule
16218
16305
  * authored before the decision migrates for free (`audioModeOf`):
16219
16306
  *
16220
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
16221
- * classifier labels with one of them. No window, no percentage:
16222
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
16223
- * `throttle` cooldown is the only brake. The per-label confidence floor is
16224
- * the analyzer's (`classificationMinScore`, per device) a label only
16225
- * reaches this condition if the classifier was already confident enough.
16307
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
16308
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
16309
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
16310
+ * frames is the wrong question for a classifier that labels 1–3 frames
16311
+ * per episode. The count window is the brake that drops a single-frame
16312
+ * false positive; the rule's own `throttle` cooldown is the other. The
16313
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
16314
+ * per device) — a label only reaches this condition if the classifier was
16315
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
16226
16316
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
16227
16317
  * the condition: at least `hitPercent`% of the samples over
16228
16318
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -16249,14 +16339,22 @@ var NcOccupancyConditionSchema = object({
16249
16339
  * an operator who typed `dog` mean the same thing.
16250
16340
  */
16251
16341
  var NcAudioConditionSchema = object({
16252
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
16342
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
16253
16343
  labels: array(string().min(1)).min(1).optional(),
16254
16344
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
16255
16345
  dbThreshold: number().min(-96).max(0).optional(),
16256
16346
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
16257
16347
  hitPercent: number().int().min(1).max(100).default(60),
16258
16348
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
16259
- samplingSeconds: number().int().min(1).max(300).default(10)
16349
+ samplingSeconds: number().int().min(1).max(300).default(10),
16350
+ /**
16351
+ * LABEL MODE: how many labelled frames must land inside
16352
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
16353
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
16354
+ */
16355
+ confirmHits: number().int().min(1).max(20).optional(),
16356
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
16357
+ confirmWindowSec: number().int().min(1).max(60).optional()
16260
16358
  });
16261
16359
  /**
16262
16360
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -19181,6 +19279,46 @@ var RecentTracksPageSchema = object({
19181
19279
  /** Cursor for the next page, or null when this page is the last. */
19182
19280
  nextCursor: string().nullable()
19183
19281
  });
19282
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
19283
+ var LIST_GROUPS_MAX_LIMIT = 100;
19284
+ var AnalyticsGroupRecordSchema = object({
19285
+ id: string(),
19286
+ deviceId: number().int(),
19287
+ openedAt: number().int(),
19288
+ closedAt: number().int(),
19289
+ timestamp: number().int(),
19290
+ memberCount: number().int(),
19291
+ memberTrackIds: array(string()).readonly(),
19292
+ className: string(),
19293
+ classes: array(string()).readonly(),
19294
+ /** Relative event-media path, or null when the group has no picture yet. */
19295
+ mediaUrl: string().nullable(),
19296
+ singleton: boolean()
19297
+ });
19298
+ var AnalyticsGroupMemberSchema = object({
19299
+ trackId: string(),
19300
+ deviceId: number().int(),
19301
+ className: string(),
19302
+ firstSeen: number().int(),
19303
+ lastSeen: number().int(),
19304
+ mediaUrl: string().nullable()
19305
+ });
19306
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
19307
+ var ListGroupsQueryInput = object({
19308
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
19309
+ deviceIds: array(number()),
19310
+ /** Window lower bound on `closedAt` (inclusive). */
19311
+ since: number().optional(),
19312
+ /** Window upper bound on `openedAt` (inclusive). */
19313
+ until: number().optional(),
19314
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
19315
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
19316
+ cursor: string().optional()
19317
+ });
19318
+ var ListGroupsPageSchema = object({
19319
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
19320
+ nextCursor: string().nullable()
19321
+ });
19184
19322
  var KeyEventQueryInput = object({
19185
19323
  deviceId: number(),
19186
19324
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19256,7 +19394,9 @@ var TrackCascadeCountsSchema = object({
19256
19394
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
19257
19395
  plates: number().int(),
19258
19396
  /** Per-track CLIP search vectors removed (best-effort). */
19259
- embeddings: number().int()
19397
+ embeddings: number().int(),
19398
+ /** Group membership + group rows removed with their last member (best-effort). */
19399
+ groups: number().int()
19260
19400
  });
19261
19401
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
19262
19402
  var DiskReconcileCountsSchema = object({
@@ -19428,6 +19568,16 @@ var pipelineAnalyticsCapability = {
19428
19568
  * are not included (same contract as `listTracks`).
19429
19569
  */
19430
19570
  listRecentTracks: method(RecentTracksQueryInput, RecentTracksPageSchema),
19571
+ /**
19572
+ * Batched co-moving group listing — the Groups feed. Same merge/cursor
19573
+ * contract as {@link listRecentTracks}. A group is a sealed partition of
19574
+ * one session; `getGroup` is the detail with members.
19575
+ */
19576
+ listGroups: method(ListGroupsQueryInput, ListGroupsPageSchema),
19577
+ getGroup: method(object({
19578
+ deviceId: number(),
19579
+ groupId: string().min(1)
19580
+ }), AnalyticsGroupDetailSchema.nullable()),
19431
19581
  clearTracks: method(object({ deviceId: number() }), _void(), {
19432
19582
  kind: "mutation",
19433
19583
  auth: "admin"
@@ -20021,6 +20171,33 @@ var NativeCropRefSchema = object({
20021
20171
  h: number()
20022
20172
  })
20023
20173
  });
20174
+ object({
20175
+ crop: object({
20176
+ left: number(),
20177
+ top: number(),
20178
+ width: number().positive(),
20179
+ height: number().positive()
20180
+ }).optional(),
20181
+ content: object({
20182
+ width: number().int().positive(),
20183
+ height: number().int().positive()
20184
+ }),
20185
+ fit: _enum(["stretch", "contain"]),
20186
+ format: _enum([
20187
+ "rgb",
20188
+ "gray",
20189
+ "jpeg"
20190
+ ])
20191
+ });
20192
+ var FrameRefSchema = object({
20193
+ registryId: string().min(1),
20194
+ id: string().min(1),
20195
+ width: number().int().positive(),
20196
+ height: number().int().positive(),
20197
+ format: _enum(["rgb", "gray"]),
20198
+ timestamp: number(),
20199
+ capturedAt: number().optional()
20200
+ });
20024
20201
  var ModelFormatSchema$1 = _enum([
20025
20202
  "onnx",
20026
20203
  "coreml",
@@ -20086,7 +20263,8 @@ var PipelineModelOptionSchema = object({
20086
20263
  sizeMB: number()
20087
20264
  })),
20088
20265
  group: ModelVariantGroupSchema.optional(),
20089
- legacy: boolean().optional()
20266
+ legacy: boolean().optional(),
20267
+ provider: ModelProviderIdSchema.optional()
20090
20268
  });
20091
20269
  var ConfigFieldBridge = custom();
20092
20270
  var PipelineAddonSchemaSchema = object({
@@ -20341,7 +20519,7 @@ var pipelineExecutorCapability = {
20341
20519
  * legacy call shape used by existing benchmark code; once all
20342
20520
  * callers pass it explicitly we make it required.
20343
20521
  *
20344
- * Exactly one of `frame`, `frameHandle`, `imageBase64`,
20522
+ * Exactly one of `frame`, `frameRef`, `frameHandle`, `imageBase64`,
20345
20523
  * `referenceImage` must be provided:
20346
20524
  * - `frame`: runtime dispatch path (runner → decoded broker frame).
20347
20525
  * Carries the raw buffer, dimensions, and format; the executor
@@ -20363,6 +20541,12 @@ var pipelineExecutorCapability = {
20363
20541
  steps: array(PipelineStepInputSchema).min(1),
20364
20542
  frame: FrameInputSchema.optional(),
20365
20543
  /**
20544
+ * Process-local lazy frame. Valid only when caller and provider resolve
20545
+ * in the same execution-group process; split/cross-node callers use
20546
+ * `frame`/`image` inline compatibility instead.
20547
+ */
20548
+ frameRef: FrameRefSchema.optional(),
20549
+ /**
20366
20550
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
20367
20551
  * the decoded pixels live in. One more member of the one-of
20368
20552
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -20712,7 +20896,10 @@ var NativeCropResultSchema = object({
20712
20896
  * Which source served this crop, so a quality-sensitive consumer (the native
20713
20897
  * `keyFrame`) can reject a degraded fallback:
20714
20898
  * - `native` — cut from the decode worker's retained NATIVE surface (the
20715
- * quality path).
20899
+ * quality path). A subject-tile serve is also native-resolution and stays
20900
+ * `native` here: the public enum cannot name `tile` without a breaking cap
20901
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
20902
+ * internal crop result (`nativeHits` vs `tileHits`).
20716
20903
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
20717
20904
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
20718
20905
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -21203,12 +21390,41 @@ var RunnerLocalLoadSchema = object({
21203
21390
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
21204
21391
  * working unchanged when they switch to reading from the runner cap.
21205
21392
  */
21393
+ var FrameLazyCountersSchema = object({
21394
+ framesDecoded: number(),
21395
+ framesAdmitted: number(),
21396
+ framesDroppedPixelFree: number(),
21397
+ viewsMaterialized: number(),
21398
+ viewsSkipped: number(),
21399
+ workerToRunnerBytes: number(),
21400
+ runnerToPoolRawBytes: number(),
21401
+ runnerToPoolJpegBytes: number(),
21402
+ onDemandFullFrameRequests: number(),
21403
+ onDemandCropRequests: number(),
21404
+ nativeHits: number(),
21405
+ nativeMisses: number(),
21406
+ tileHits: number(),
21407
+ tileMisses: number(),
21408
+ fallbackHits: number(),
21409
+ fallbackMisses: number(),
21410
+ retainedWritesAvoided: number(),
21411
+ residentRefs: number(),
21412
+ residentBytes: number(),
21413
+ releases: number(),
21414
+ evictions: number(),
21415
+ staleMisses: number()
21416
+ });
21417
+ var FrameLazyMetricsSchema = object({
21418
+ node: FrameLazyCountersSchema,
21419
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
21420
+ });
21206
21421
  var RunnerLocalMetricsSchema = object({
21207
21422
  nodeId: string(),
21208
21423
  activeCameras: number(),
21209
21424
  throttledCameras: number(),
21210
21425
  avgInferenceTimeMs: number(),
21211
- queueDepth: number()
21426
+ queueDepth: number(),
21427
+ frameLazy: FrameLazyMetricsSchema.optional()
21212
21428
  });
21213
21429
  /**
21214
21430
  * Pipeline Runner capability — runtime detection workhorse.
@@ -23240,6 +23456,9 @@ var storageProviderCapability = {
23240
23456
  endDownload: method(EndDownloadInputSchema, _void(), { kind: "mutation" })
23241
23457
  }
23242
23458
  };
23459
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
23460
+ var ProfileSettingsSchemaBridge = unknown().nullable();
23461
+ var ProfileSettingsBagSchema = record(string(), unknown());
23243
23462
  /**
23244
23463
  * A live terminal session hosted by the provider addon. Output and input do
23245
23464
  * NOT flow through the capability — they use the addon data plane
@@ -23269,7 +23488,14 @@ var TerminalSessionInfoSchema = object({
23269
23488
  var TerminalProfileInfoSchema = object({
23270
23489
  profileId: string(),
23271
23490
  label: string(),
23272
- description: string().optional()
23491
+ description: string().optional(),
23492
+ /** Spawn defaults the instance form copies on create. */
23493
+ executable: string().optional(),
23494
+ args: array(string()).readonly().optional(),
23495
+ cwd: string().optional(),
23496
+ environment: array(string()).readonly().optional(),
23497
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
23498
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
23273
23499
  });
23274
23500
  /**
23275
23501
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -23282,7 +23508,12 @@ var TerminalInstanceInfoSchema = object({
23282
23508
  profileId: string(),
23283
23509
  profileLabel: string(),
23284
23510
  name: string(),
23285
- enabled: boolean()
23511
+ enabled: boolean(),
23512
+ executable: string(),
23513
+ args: array(string()).readonly(),
23514
+ cwd: string(),
23515
+ environment: array(string()).readonly(),
23516
+ profileSettings: ProfileSettingsBagSchema
23286
23517
  });
23287
23518
  var TerminalLegacyCameraSchema = object({
23288
23519
  stableId: string(),
@@ -23328,7 +23559,24 @@ var terminalSessionCapability = {
23328
23559
  createInstance: method(object({
23329
23560
  targetNodeId: string().min(1),
23330
23561
  profileId: string().min(1),
23331
- name: string().trim().min(1).max(160).optional()
23562
+ name: string().trim().min(1).max(160).optional(),
23563
+ executable: string().max(1024).optional(),
23564
+ args: array(string().max(2048)).max(64).optional(),
23565
+ cwd: string().max(1024).optional(),
23566
+ environment: array(string().max(4096)).max(64).optional(),
23567
+ profileSettings: ProfileSettingsBagSchema.optional()
23568
+ }), TerminalInstanceInfoSchema, {
23569
+ kind: "mutation",
23570
+ auth: "admin"
23571
+ }),
23572
+ updateInstance: method(object({
23573
+ instanceId: string().min(1),
23574
+ name: string().trim().min(1).max(160).optional(),
23575
+ executable: string().max(1024).optional(),
23576
+ args: array(string().max(2048)).max(64).optional(),
23577
+ cwd: string().max(1024).optional(),
23578
+ environment: array(string().max(4096)).max(64).optional(),
23579
+ profileSettings: ProfileSettingsBagSchema.optional()
23332
23580
  }), TerminalInstanceInfoSchema, {
23333
23581
  kind: "mutation",
23334
23582
  auth: "admin"
@@ -23363,7 +23611,11 @@ var terminalSessionCapability = {
23363
23611
  openSession: method(object({
23364
23612
  profileId: string(),
23365
23613
  cols: number().int().positive(),
23366
- rows: number().int().positive()
23614
+ rows: number().int().positive(),
23615
+ executable: string().max(1024).optional(),
23616
+ args: array(string().max(2048)).max(64).optional(),
23617
+ cwd: string().max(1024).optional(),
23618
+ environment: array(string().max(4096)).max(64).optional()
23367
23619
  }), TerminalSessionInfoSchema, {
23368
23620
  kind: "mutation",
23369
23621
  auth: "admin"
@@ -27741,10 +27993,10 @@ var lawnMowerControlCapability = {
27741
27993
  *
27742
27994
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
27743
27995
  * to receive an ordered list of candidate base URLs it should race
27744
- * on connect — LAN IPv4 first (lowest latency when on same network),
27745
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
27746
- * race them with short timeouts and stick with the winner for the
27747
- * session.
27996
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
27997
+ * when on the same network), then public hostname (if a tunnel is
27998
+ * up). The SDK can race them with short timeouts and stick with the
27999
+ * winner for the session.
27748
28000
  *
27749
28001
  * Why hub-only: agents are not directly addressable by the operator's
27750
28002
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -27899,6 +28151,17 @@ var NotificationEndpointSchema = object({
27899
28151
  /** What the ranking currently resolves to (null when nothing is reachable). */
27900
28152
  resolved: string().nullable()
27901
28153
  });
28154
+ /**
28155
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
28156
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
28157
+ * currently expands to, so the UI can show the effective set either way.
28158
+ */
28159
+ var ViewerEndpointsSchema = object({
28160
+ /** The operator's explicit race set, or empty for AUTO. */
28161
+ baseUrls: array(string()).readonly(),
28162
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
28163
+ resolved: array(string()).readonly()
28164
+ });
27902
28165
  var AllowedAddressesSchema = object({
27903
28166
  /**
27904
28167
  * Allowlist of interface addresses operators have explicitly opted
@@ -27907,6 +28170,20 @@ var AllowedAddressesSchema = object({
27907
28170
  * Network Addresses admin page and persisted by the addon.
27908
28171
  */
27909
28172
  addresses: array(string()).readonly() });
28173
+ var TlsStatusSchema = object({
28174
+ mode: _enum([
28175
+ "generated",
28176
+ "uploaded",
28177
+ "disabled"
28178
+ ]),
28179
+ leafFingerprintSha256: string().nullable(),
28180
+ caFingerprintSha256: string().nullable(),
28181
+ validTo: string().nullable(),
28182
+ sans: array(string()),
28183
+ caCertPem: string().nullable(),
28184
+ reissueError: string().nullable(),
28185
+ restartRequired: boolean()
28186
+ });
27910
28187
  var localNetworkCapability = {
27911
28188
  name: "local-network",
27912
28189
  scope: "system",
@@ -27926,13 +28203,13 @@ var localNetworkCapability = {
27926
28203
  */
27927
28204
  getPreferred: method(_void(), PreferredSchema),
27928
28205
  /**
27929
- * Ordered candidate base URLs the SDK should try on connect.
27930
- * Includes LAN IPs (one per non-internal interface), the public
27931
- * tunnel hostname (when active), and loopback as a last-resort
27932
- * fallback. Filterable by `includeLoopback` / `ipv4Only`.
27933
- * Honours `getAllowedAddresses()` when set addresses outside
27934
- * the allowlist are dropped (the public tunnel + loopback are
27935
- * always included as escape hatches).
28206
+ * Ordered candidate base URLs (the palette the Network tab shows).
28207
+ * Includes LAN IPv4, stable LAN IPv6, the public tunnel, and mesh when
28208
+ * joined. Loopback is off by default. The SDK races the subset from
28209
+ * `getViewerEndpoints`, not this full list — IPv6 stays here because
28210
+ * WebRTC ICE gathers dual-stack regardless of the HTTP race. Honours
28211
+ * `getAllowedAddresses()` when set addresses outside the allowlist
28212
+ * are dropped (the public tunnel is still included as an escape hatch).
27936
28213
  *
27937
28214
  * **The port is the hub's, not the caller's** (D62 — a function's fact
27938
28215
  * belongs to whoever already owns it). This method used to take a `port`
@@ -27963,10 +28240,11 @@ var localNetworkCapability = {
27963
28240
  */
27964
28241
  port: number().int().min(1).max(65535).optional(),
27965
28242
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
27966
- * candidate. Default `true`. */
28243
+ * candidate. Default `false` — loopback is not a client route. */
27967
28244
  includeLoopback: boolean().optional(),
27968
- /** Skip IPv6 entries. Some legacy clients can't parse them.
27969
- * Default `false`. */
28245
+ /** Skip IPv6 entries. Default `false` the palette includes stable
28246
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
28247
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
27970
28248
  ipv4Only: boolean().optional(),
27971
28249
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
27972
28250
  * Pass `'https'` when the caller is itself loaded over HTTPS
@@ -27995,6 +28273,19 @@ var localNetworkCapability = {
27995
28273
  */
27996
28274
  setNotificationEndpoint: method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }),
27997
28275
  /**
28276
+ * The endpoints the SDK / viewer races for API access. Empty `baseUrls`
28277
+ * means AUTO: every LAN IPv4 address plus the public tunnel, never IPv6,
28278
+ * never mesh, never loopback. `resolved` is that set (or the operator's
28279
+ * explicit subset) as it stands right now.
28280
+ */
28281
+ getViewerEndpoints: method(_void(), ViewerEndpointsSchema),
28282
+ /**
28283
+ * Replace the viewer race set. Empty `baseUrls` restores AUTO. Stored
28284
+ * verbatim (not indices) so a temporarily-down tunnel is not silently
28285
+ * dropped from the operator's choice.
28286
+ */
28287
+ setViewerEndpoints: method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }),
28288
+ /**
27998
28289
  * Read the operator's allowlist. Empty = "auto" (no filter). Used
27999
28290
  * by the admin UI's address selector to seed its checkbox state.
28000
28291
  */
@@ -28012,7 +28303,34 @@ var localNetworkCapability = {
28012
28303
  * when the operator wants to wipe their manual edits and start
28013
28304
  * over from the auto-detected best matches.
28014
28305
  */
28015
- resetAllowlistToBestMatch: method(_void(), AllowedAddressesSchema, { kind: "mutation" })
28306
+ resetAllowlistToBestMatch: method(_void(), AllowedAddressesSchema, { kind: "mutation" }),
28307
+ /**
28308
+ * Live TLS material for the Network → Local access certificate card.
28309
+ * LAN HTTP / hostname are addon settings (`globalSettingsSchema`), not
28310
+ * a second store — this query is status, not configuration.
28311
+ */
28312
+ getTlsStatus: method(_void(), TlsStatusSchema),
28313
+ /** Issue a new leaf under the existing local CA. Disabled in uploaded mode. */
28314
+ regenerateCertificate: method(object({ reason: string().optional() }), TlsStatusSchema, {
28315
+ kind: "mutation",
28316
+ auth: "admin"
28317
+ }),
28318
+ /** Replace the served material with operator-supplied PEMs. */
28319
+ uploadCertificate: method(object({
28320
+ certPem: string().min(1),
28321
+ keyPem: string().min(1),
28322
+ caPem: string().optional()
28323
+ }), TlsStatusSchema, {
28324
+ kind: "mutation",
28325
+ auth: "admin"
28326
+ }),
28327
+ /** The local CA PEM, or empty when there is none to download. */
28328
+ downloadCa: method(_void(), object({ pem: string() })),
28329
+ /** Drop uploaded material and return to the generated local CA. */
28330
+ revertToGeneratedCertificate: method(_void(), TlsStatusSchema, {
28331
+ kind: "mutation",
28332
+ auth: "admin"
28333
+ })
28016
28334
  },
28017
28335
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
28018
28336
  mount: { kind: "hub-only" }
@@ -29784,7 +30102,12 @@ var PlateInfoSchema = object({
29784
30102
  plateBbox: BoundingBoxSchema.optional(),
29785
30103
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
29786
30104
  keyFrameMediaKey: string().optional(),
29787
- base64: string().optional()
30105
+ base64: string().optional(),
30106
+ /**
30107
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
30108
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
30109
+ */
30110
+ cropUrl: string().optional()
29788
30111
  });
29789
30112
  var MediaFileLiteSchema = object({
29790
30113
  key: string(),
@@ -34814,6 +35137,12 @@ Object.freeze({
34814
35137
  addonId: null,
34815
35138
  access: "view"
34816
35139
  },
35140
+ "deviceManager.getBindingsBatch": {
35141
+ capName: "device-manager",
35142
+ capScope: "system",
35143
+ addonId: null,
35144
+ access: "view"
35145
+ },
34817
35146
  "deviceManager.getChildren": {
34818
35147
  capName: "device-manager",
34819
35148
  capScope: "system",
@@ -34874,6 +35203,12 @@ Object.freeze({
34874
35203
  addonId: null,
34875
35204
  access: "view"
34876
35205
  },
35206
+ "deviceManager.getLinkedDevicesBatch": {
35207
+ capName: "device-manager",
35208
+ capScope: "system",
35209
+ addonId: null,
35210
+ access: "view"
35211
+ },
34877
35212
  "deviceManager.getRoleDisplayDefaults": {
34878
35213
  capName: "device-manager",
34879
35214
  capScope: "system",
@@ -35756,6 +36091,12 @@ Object.freeze({
35756
36091
  addonId: null,
35757
36092
  access: "create"
35758
36093
  },
36094
+ "localNetwork.downloadCa": {
36095
+ capName: "local-network",
36096
+ capScope: "system",
36097
+ addonId: null,
36098
+ access: "view"
36099
+ },
35759
36100
  "localNetwork.getAllowedAddresses": {
35760
36101
  capName: "local-network",
35761
36102
  capScope: "system",
@@ -35780,18 +36121,42 @@ Object.freeze({
35780
36121
  addonId: null,
35781
36122
  access: "view"
35782
36123
  },
36124
+ "localNetwork.getTlsStatus": {
36125
+ capName: "local-network",
36126
+ capScope: "system",
36127
+ addonId: null,
36128
+ access: "view"
36129
+ },
36130
+ "localNetwork.getViewerEndpoints": {
36131
+ capName: "local-network",
36132
+ capScope: "system",
36133
+ addonId: null,
36134
+ access: "view"
36135
+ },
35783
36136
  "localNetwork.list": {
35784
36137
  capName: "local-network",
35785
36138
  capScope: "system",
35786
36139
  addonId: null,
35787
36140
  access: "view"
35788
36141
  },
36142
+ "localNetwork.regenerateCertificate": {
36143
+ capName: "local-network",
36144
+ capScope: "system",
36145
+ addonId: null,
36146
+ access: "create"
36147
+ },
35789
36148
  "localNetwork.resetAllowlistToBestMatch": {
35790
36149
  capName: "local-network",
35791
36150
  capScope: "system",
35792
36151
  addonId: null,
35793
36152
  access: "delete"
35794
36153
  },
36154
+ "localNetwork.revertToGeneratedCertificate": {
36155
+ capName: "local-network",
36156
+ capScope: "system",
36157
+ addonId: null,
36158
+ access: "create"
36159
+ },
35795
36160
  "localNetwork.setAllowedAddresses": {
35796
36161
  capName: "local-network",
35797
36162
  capScope: "system",
@@ -35804,6 +36169,18 @@ Object.freeze({
35804
36169
  addonId: null,
35805
36170
  access: "create"
35806
36171
  },
36172
+ "localNetwork.setViewerEndpoints": {
36173
+ capName: "local-network",
36174
+ capScope: "system",
36175
+ addonId: null,
36176
+ access: "create"
36177
+ },
36178
+ "localNetwork.uploadCertificate": {
36179
+ capName: "local-network",
36180
+ capScope: "system",
36181
+ addonId: null,
36182
+ access: "create"
36183
+ },
35807
36184
  "lockControl.lock": {
35808
36185
  capName: "lock-control",
35809
36186
  capScope: "device",
@@ -36602,6 +36979,12 @@ Object.freeze({
36602
36979
  addonId: null,
36603
36980
  access: "view"
36604
36981
  },
36982
+ "pipelineAnalytics.getGroup": {
36983
+ capName: "pipeline-analytics",
36984
+ capScope: "device",
36985
+ addonId: null,
36986
+ access: "view"
36987
+ },
36605
36988
  "pipelineAnalytics.getKeyEvents": {
36606
36989
  capName: "pipeline-analytics",
36607
36990
  capScope: "device",
@@ -36686,6 +37069,12 @@ Object.freeze({
36686
37069
  addonId: null,
36687
37070
  access: "view"
36688
37071
  },
37072
+ "pipelineAnalytics.listGroups": {
37073
+ capName: "pipeline-analytics",
37074
+ capScope: "device",
37075
+ addonId: null,
37076
+ access: "view"
37077
+ },
36689
37078
  "pipelineAnalytics.listOpsLog": {
36690
37079
  capName: "pipeline-analytics",
36691
37080
  capScope: "device",
@@ -38684,6 +39073,12 @@ Object.freeze({
38684
39073
  addonId: null,
38685
39074
  access: "create"
38686
39075
  },
39076
+ "terminalSession.updateInstance": {
39077
+ capName: "terminal-session",
39078
+ capScope: "system",
39079
+ addonId: null,
39080
+ access: "create"
39081
+ },
38687
39082
  "terminalSession.writeInput": {
38688
39083
  capName: "terminal-session",
38689
39084
  capScope: "system",
@@ -39461,6 +39856,11 @@ Object.freeze({
39461
39856
  form: "single",
39462
39857
  optional: false
39463
39858
  }],
39859
+ "deviceManager.getBindingsBatch": [{
39860
+ name: "deviceIds",
39861
+ form: "array",
39862
+ optional: false
39863
+ }],
39464
39864
  "deviceManager.getChildren": [{
39465
39865
  name: "parentDeviceId",
39466
39866
  form: "single",
@@ -39506,6 +39906,11 @@ Object.freeze({
39506
39906
  form: "single",
39507
39907
  optional: false
39508
39908
  }],
39909
+ "deviceManager.getLinkedDevicesBatch": [{
39910
+ name: "deviceIds",
39911
+ form: "array",
39912
+ optional: false
39913
+ }],
39509
39914
  "deviceManager.getSettingsSchema": [{
39510
39915
  name: "deviceId",
39511
39916
  form: "single",
@@ -39526,6 +39931,11 @@ Object.freeze({
39526
39931
  form: "single",
39527
39932
  optional: false
39528
39933
  }],
39934
+ "deviceManager.listAll": [{
39935
+ name: "deviceIds",
39936
+ form: "array",
39937
+ optional: true
39938
+ }],
39529
39939
  "deviceManager.loadConfig": [{
39530
39940
  name: "deviceId",
39531
39941
  form: "single",
@@ -40099,6 +40509,11 @@ Object.freeze({
40099
40509
  form: "single",
40100
40510
  optional: false
40101
40511
  }],
40512
+ "pipelineAnalytics.getGroup": [{
40513
+ name: "deviceId",
40514
+ form: "single",
40515
+ optional: false
40516
+ }],
40102
40517
  "pipelineAnalytics.getKeyEvents": [{
40103
40518
  name: "deviceId",
40104
40519
  form: "single",
@@ -40154,6 +40569,11 @@ Object.freeze({
40154
40569
  form: "array",
40155
40570
  optional: false
40156
40571
  }],
40572
+ "pipelineAnalytics.listGroups": [{
40573
+ name: "deviceIds",
40574
+ form: "array",
40575
+ optional: false
40576
+ }],
40157
40577
  "pipelineAnalytics.listOpsLog": [{
40158
40578
  name: "deviceId",
40159
40579
  form: "single",
@@ -41231,6 +41651,35 @@ Object.freeze(Object.fromEntries([{
41231
41651
  }]
41232
41652
  }].map((s) => [s.stepId, s.defaultModelId])));
41233
41653
  string().min(1);
41654
+ var CLUSTER_STEP_SETTING_FIELDS = [{
41655
+ stepId: "face-embedding",
41656
+ key: "minLandmarkFaceSize",
41657
+ label: "Min face size for recognition (detection px)",
41658
+ 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.",
41659
+ type: "slider",
41660
+ min: 0,
41661
+ max: 64,
41662
+ step: 2,
41663
+ default: 24
41664
+ }];
41665
+ function clusterStepSettingKey(stepId, fieldKey) {
41666
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
41667
+ }
41668
+ var ClusterSettingNumberSchema = number().finite();
41669
+ function readClusterStepSettings(config) {
41670
+ const out = {};
41671
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
41672
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
41673
+ const value = parsed.success ? parsed.data : field.default;
41674
+ const existing = out[field.stepId] ?? {};
41675
+ out[field.stepId] = {
41676
+ ...existing,
41677
+ [field.key]: value
41678
+ };
41679
+ }
41680
+ return out;
41681
+ }
41682
+ readClusterStepSettings({});
41234
41683
  object({
41235
41684
  /**
41236
41685
  * Fraction of the box's own size added on EACH side before cutting.