@camstack/addon-decoder-ffmpeg 1.2.25 → 1.2.27

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/index.js +436 -153
  2. package/dist/index.mjs +436 -153
  3. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -5802,6 +5802,13 @@ var BaseAddon = class {
5802
5802
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5803
5803
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5804
5804
  _registeredCapNames = [];
5805
+ /**
5806
+ * True only after `readAddonStore` actually answered. Constructor
5807
+ * defaults look like stored config when the store is down — a forked
5808
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5809
+ * mode, 2026-08-25) is not "the operator chose this".
5810
+ */
5811
+ settingsStoreReady = false;
5805
5812
  /** Default config values. Provided via constructor. */
5806
5813
  defaults;
5807
5814
  constructor(defaults) {
@@ -6202,7 +6209,9 @@ var BaseAddon = class {
6202
6209
  ];
6203
6210
  let lastErr;
6204
6211
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6205
- return await settings.readAddonStore() ?? {};
6212
+ const stored = await settings.readAddonStore() ?? {};
6213
+ this.settingsStoreReady = true;
6214
+ return stored;
6206
6215
  } catch (err) {
6207
6216
  lastErr = err;
6208
6217
  const msg = err instanceof Error ? err.message : String(err);
@@ -6210,6 +6219,7 @@ var BaseAddon = class {
6210
6219
  if (attempt === delaysMs.length) break;
6211
6220
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6212
6221
  }
6222
+ this.settingsStoreReady = false;
6213
6223
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6214
6224
  return {};
6215
6225
  }
@@ -8017,6 +8027,15 @@ var LabelDefinitionSchema = object({
8017
8027
  description: string().optional(),
8018
8028
  icon: string().optional()
8019
8029
  });
8030
+ var ClassMapDefinitionSchema = object({
8031
+ mapping: record(string(), _enum([
8032
+ "person",
8033
+ "vehicle",
8034
+ "animal",
8035
+ "package"
8036
+ ])),
8037
+ preserveOriginal: boolean()
8038
+ });
8020
8039
  var MODEL_FORMATS = [
8021
8040
  "onnx",
8022
8041
  "coreml",
@@ -8100,6 +8119,12 @@ var ModelVariantGroupSchema = object({
8100
8119
  */
8101
8120
  resolution: number().int().positive().optional()
8102
8121
  });
8122
+ var ModelProviderIdSchema = _enum([
8123
+ "camstack",
8124
+ "frigate",
8125
+ "scrypted",
8126
+ "custom"
8127
+ ]);
8103
8128
  var ModelCatalogEntrySchema = object({
8104
8129
  id: string(),
8105
8130
  name: string(),
@@ -8195,7 +8220,19 @@ var ModelCatalogEntrySchema = object({
8195
8220
  * `id` stays the source of truth for resolution/download/persistence; grouping
8196
8221
  * is a presentation overlay resolved back to an `id`.
8197
8222
  */
8198
- group: ModelVariantGroupSchema.optional()
8223
+ group: ModelVariantGroupSchema.optional(),
8224
+ /**
8225
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8226
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8227
+ * persisted before this field existed (`inferModelProvider` fills those).
8228
+ */
8229
+ provider: ModelProviderIdSchema.optional(),
8230
+ /**
8231
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8232
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8233
+ * labels already ARE the CamStack macros (Scrypted identity map).
8234
+ */
8235
+ classMap: ClassMapDefinitionSchema.optional()
8199
8236
  });
8200
8237
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8201
8238
  format: literal("openvino"),
@@ -8224,7 +8261,8 @@ var ModelConvertMetadataSchema = object({
8224
8261
  "ocr",
8225
8262
  "segmentation"
8226
8263
  ]),
8227
- faceAlignment: boolean().optional()
8264
+ faceAlignment: boolean().optional(),
8265
+ classMap: ClassMapDefinitionSchema.optional()
8228
8266
  });
8229
8267
  var ConvertResultSchema = object({
8230
8268
  entry: ModelCatalogEntrySchema,
@@ -11847,6 +11885,27 @@ var LinkedDeviceSchema = object({
11847
11885
  features: array(string()),
11848
11886
  producesTrackedEvents: boolean().optional()
11849
11887
  });
11888
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
11889
+ * The batch answer needs the tag; the single-device answer already has it
11890
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
11891
+ var LinkedDevicesForDeviceSchema = object({
11892
+ deviceId: number(),
11893
+ mode: LinkedDevicesModeSchema,
11894
+ devices: array(LinkedDeviceSchema)
11895
+ });
11896
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
11897
+ * `getAllBindings` all answer in. Declared once: three copies of the same
11898
+ * object literal is exactly how the three drift apart. */
11899
+ var DeviceBindingsForDeviceSchema = object({
11900
+ deviceId: number(),
11901
+ entries: array(object({
11902
+ capName: string(),
11903
+ kind: _enum(["native", "wrapped"]),
11904
+ providerAddonId: string(),
11905
+ providerNodeId: string(),
11906
+ nativeAddonId: string()
11907
+ }))
11908
+ });
11850
11909
  var SavedDeviceRowSchema = object({
11851
11910
  /** Numeric id reserved at allocateDeviceId time. */
11852
11911
  id: number(),
@@ -12072,11 +12131,25 @@ method(object({
12072
12131
  projection: _enum(["full", "slim"]).optional(),
12073
12132
  /** Return only camera devices. Filtering server-side instead of
12074
12133
  * shipping 293 rows to find 12. */
12075
- isCamera: boolean().optional()
12134
+ isCamera: boolean().optional(),
12135
+ /**
12136
+ * Return only these device ids. For the caller that already KNOWS the
12137
+ * handful it wants and needs a field the id-bearing answer does not
12138
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12139
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12140
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12141
+ * refetches on the reconcile interval, on a phone.
12142
+ *
12143
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12144
+ * keys rather than rejecting them (verified against the live hub
12145
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12146
+ * it answers today and the caller filters as it already does.
12147
+ */
12148
+ deviceIds: array(number()).optional()
12076
12149
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12077
12150
  mode: LinkedDevicesModeSchema,
12078
12151
  devices: array(LinkedDeviceSchema)
12079
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12152
+ })), 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({
12080
12153
  deviceId: number(),
12081
12154
  values: record(string(), unknown())
12082
12155
  }), object({ success: literal(true) }), {
@@ -12103,25 +12176,7 @@ method(object({
12103
12176
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12104
12177
  kind: "mutation",
12105
12178
  auth: "admin"
12106
- }), method(object({ deviceId: number() }), object({
12107
- deviceId: number(),
12108
- entries: array(object({
12109
- capName: string(),
12110
- kind: _enum(["native", "wrapped"]),
12111
- providerAddonId: string(),
12112
- providerNodeId: string(),
12113
- nativeAddonId: string()
12114
- }))
12115
- })), method(object({}), array(object({
12116
- deviceId: number(),
12117
- entries: array(object({
12118
- capName: string(),
12119
- kind: _enum(["native", "wrapped"]),
12120
- providerAddonId: string(),
12121
- providerNodeId: string(),
12122
- nativeAddonId: string()
12123
- }))
12124
- }))), method(object({
12179
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12125
12180
  deviceId: number(),
12126
12181
  capName: string(),
12127
12182
  wrapperAddonId: string(),
@@ -14493,12 +14548,15 @@ var NcOccupancyConditionSchema = object({
14493
14548
  * there is no second switch that can disagree with the first and every rule
14494
14549
  * authored before the decision migrates for free (`audioModeOf`):
14495
14550
  *
14496
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14497
- * classifier labels with one of them. No window, no percentage:
14498
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14499
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14500
- * the analyzer's (`classificationMinScore`, per device) a label only
14501
- * reaches this condition if the classifier was already confident enough.
14551
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14552
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14553
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
14554
+ * frames is the wrong question for a classifier that labels 1–3 frames
14555
+ * per episode. The count window is the brake that drops a single-frame
14556
+ * false positive; the rule's own `throttle` cooldown is the other. The
14557
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14558
+ * per device) — a label only reaches this condition if the classifier was
14559
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14502
14560
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14503
14561
  * the condition: at least `hitPercent`% of the samples over
14504
14562
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14525,14 +14583,22 @@ var NcOccupancyConditionSchema = object({
14525
14583
  * an operator who typed `dog` mean the same thing.
14526
14584
  */
14527
14585
  var NcAudioConditionSchema = object({
14528
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14586
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14529
14587
  labels: array(string().min(1)).min(1).optional(),
14530
14588
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14531
14589
  dbThreshold: number().min(-96).max(0).optional(),
14532
14590
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14533
14591
  hitPercent: number().int().min(1).max(100).default(60),
14534
14592
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14535
- samplingSeconds: number().int().min(1).max(300).default(10)
14593
+ samplingSeconds: number().int().min(1).max(300).default(10),
14594
+ /**
14595
+ * LABEL MODE: how many labelled frames must land inside
14596
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14597
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14598
+ */
14599
+ confirmHits: number().int().min(1).max(20).optional(),
14600
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14601
+ confirmWindowSec: number().int().min(1).max(60).optional()
14536
14602
  });
14537
14603
  /**
14538
14604
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -16906,6 +16972,46 @@ var RecentTracksPageSchema = object({
16906
16972
  /** Cursor for the next page, or null when this page is the last. */
16907
16973
  nextCursor: string().nullable()
16908
16974
  });
16975
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
16976
+ var LIST_GROUPS_MAX_LIMIT = 100;
16977
+ var AnalyticsGroupRecordSchema = object({
16978
+ id: string(),
16979
+ deviceId: number().int(),
16980
+ openedAt: number().int(),
16981
+ closedAt: number().int(),
16982
+ timestamp: number().int(),
16983
+ memberCount: number().int(),
16984
+ memberTrackIds: array(string()).readonly(),
16985
+ className: string(),
16986
+ classes: array(string()).readonly(),
16987
+ /** Relative event-media path, or null when the group has no picture yet. */
16988
+ mediaUrl: string().nullable(),
16989
+ singleton: boolean()
16990
+ });
16991
+ var AnalyticsGroupMemberSchema = object({
16992
+ trackId: string(),
16993
+ deviceId: number().int(),
16994
+ className: string(),
16995
+ firstSeen: number().int(),
16996
+ lastSeen: number().int(),
16997
+ mediaUrl: string().nullable()
16998
+ });
16999
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17000
+ var ListGroupsQueryInput = object({
17001
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17002
+ deviceIds: array(number()),
17003
+ /** Window lower bound on `closedAt` (inclusive). */
17004
+ since: number().optional(),
17005
+ /** Window upper bound on `openedAt` (inclusive). */
17006
+ until: number().optional(),
17007
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17008
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17009
+ cursor: string().optional()
17010
+ });
17011
+ var ListGroupsPageSchema = object({
17012
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17013
+ nextCursor: string().nullable()
17014
+ });
16909
17015
  var KeyEventQueryInput = object({
16910
17016
  deviceId: number(),
16911
17017
  /** Window lower bound (track firstSeen ≥ since). */
@@ -16981,7 +17087,9 @@ var TrackCascadeCountsSchema = object({
16981
17087
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
16982
17088
  plates: number().int(),
16983
17089
  /** Per-track CLIP search vectors removed (best-effort). */
16984
- embeddings: number().int()
17090
+ embeddings: number().int(),
17091
+ /** Group membership + group rows removed with their last member (best-effort). */
17092
+ groups: number().int()
16985
17093
  });
16986
17094
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
16987
17095
  var DiskReconcileCountsSchema = object({
@@ -17127,7 +17235,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17127
17235
  * stationary registry). Default false: the timeline lists passages,
17128
17236
  * not parking records (operator decision, 2026-08-15). */
17129
17237
  includeStationary: boolean().optional()
17130
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17238
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17239
+ deviceId: number(),
17240
+ groupId: string().min(1)
17241
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17131
17242
  kind: "mutation",
17132
17243
  auth: "admin"
17133
17244
  }), 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({
@@ -17345,6 +17456,33 @@ var NativeCropRefSchema = object({
17345
17456
  h: number()
17346
17457
  })
17347
17458
  });
17459
+ object({
17460
+ crop: object({
17461
+ left: number(),
17462
+ top: number(),
17463
+ width: number().positive(),
17464
+ height: number().positive()
17465
+ }).optional(),
17466
+ content: object({
17467
+ width: number().int().positive(),
17468
+ height: number().int().positive()
17469
+ }),
17470
+ fit: _enum(["stretch", "contain"]),
17471
+ format: _enum([
17472
+ "rgb",
17473
+ "gray",
17474
+ "jpeg"
17475
+ ])
17476
+ });
17477
+ var FrameRefSchema = object({
17478
+ registryId: string().min(1),
17479
+ id: string().min(1),
17480
+ width: number().int().positive(),
17481
+ height: number().int().positive(),
17482
+ format: _enum(["rgb", "gray"]),
17483
+ timestamp: number(),
17484
+ capturedAt: number().optional()
17485
+ });
17348
17486
  var ModelFormatSchema$1 = _enum([
17349
17487
  "onnx",
17350
17488
  "coreml",
@@ -17410,7 +17548,8 @@ var PipelineModelOptionSchema = object({
17410
17548
  sizeMB: number()
17411
17549
  })),
17412
17550
  group: ModelVariantGroupSchema.optional(),
17413
- legacy: boolean().optional()
17551
+ legacy: boolean().optional(),
17552
+ provider: ModelProviderIdSchema.optional()
17414
17553
  });
17415
17554
  var ConfigFieldBridge = custom();
17416
17555
  var PipelineAddonSchemaSchema = object({
@@ -17589,6 +17728,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17589
17728
  steps: array(PipelineStepInputSchema).min(1),
17590
17729
  frame: FrameInputSchema.optional(),
17591
17730
  /**
17731
+ * Process-local lazy frame. Valid only when caller and provider resolve
17732
+ * in the same execution-group process; split/cross-node callers use
17733
+ * `frame`/`image` inline compatibility instead.
17734
+ */
17735
+ frameRef: FrameRefSchema.optional(),
17736
+ /**
17592
17737
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17593
17738
  * the decoded pixels live in. One more member of the one-of
17594
17739
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -17844,7 +17989,10 @@ var NativeCropResultSchema = object({
17844
17989
  * Which source served this crop, so a quality-sensitive consumer (the native
17845
17990
  * `keyFrame`) can reject a degraded fallback:
17846
17991
  * - `native` — cut from the decode worker's retained NATIVE surface (the
17847
- * quality path).
17992
+ * quality path). A subject-tile serve is also native-resolution and stays
17993
+ * `native` here: the public enum cannot name `tile` without a breaking cap
17994
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
17995
+ * internal crop result (`nativeHits` vs `tileHits`).
17848
17996
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
17849
17997
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
17850
17998
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18335,12 +18483,41 @@ var RunnerLocalLoadSchema = object({
18335
18483
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18336
18484
  * working unchanged when they switch to reading from the runner cap.
18337
18485
  */
18486
+ var FrameLazyCountersSchema = object({
18487
+ framesDecoded: number(),
18488
+ framesAdmitted: number(),
18489
+ framesDroppedPixelFree: number(),
18490
+ viewsMaterialized: number(),
18491
+ viewsSkipped: number(),
18492
+ workerToRunnerBytes: number(),
18493
+ runnerToPoolRawBytes: number(),
18494
+ runnerToPoolJpegBytes: number(),
18495
+ onDemandFullFrameRequests: number(),
18496
+ onDemandCropRequests: number(),
18497
+ nativeHits: number(),
18498
+ nativeMisses: number(),
18499
+ tileHits: number(),
18500
+ tileMisses: number(),
18501
+ fallbackHits: number(),
18502
+ fallbackMisses: number(),
18503
+ retainedWritesAvoided: number(),
18504
+ residentRefs: number(),
18505
+ residentBytes: number(),
18506
+ releases: number(),
18507
+ evictions: number(),
18508
+ staleMisses: number()
18509
+ });
18510
+ var FrameLazyMetricsSchema = object({
18511
+ node: FrameLazyCountersSchema,
18512
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18513
+ });
18338
18514
  var RunnerLocalMetricsSchema = object({
18339
18515
  nodeId: string(),
18340
18516
  activeCameras: number(),
18341
18517
  throttledCameras: number(),
18342
18518
  avgInferenceTimeMs: number(),
18343
- queueDepth: number()
18519
+ queueDepth: number(),
18520
+ frameLazy: FrameLazyMetricsSchema.optional()
18344
18521
  });
18345
18522
  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({
18346
18523
  handle: FrameHandleSchema,
@@ -19640,6 +19817,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19640
19817
  location: StorageLocationSchema,
19641
19818
  relativePath: string()
19642
19819
  }), _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" });
19820
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19821
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19822
+ var ProfileSettingsBagSchema = record(string(), unknown());
19643
19823
  /**
19644
19824
  * A live terminal session hosted by the provider addon. Output and input do
19645
19825
  * NOT flow through the capability — they use the addon data plane
@@ -19669,7 +19849,14 @@ var TerminalSessionInfoSchema = object({
19669
19849
  var TerminalProfileInfoSchema = object({
19670
19850
  profileId: string(),
19671
19851
  label: string(),
19672
- description: string().optional()
19852
+ description: string().optional(),
19853
+ /** Spawn defaults the instance form copies on create. */
19854
+ executable: string().optional(),
19855
+ args: array(string()).readonly().optional(),
19856
+ cwd: string().optional(),
19857
+ environment: array(string()).readonly().optional(),
19858
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
19859
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19673
19860
  });
19674
19861
  /**
19675
19862
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19682,7 +19869,12 @@ var TerminalInstanceInfoSchema = object({
19682
19869
  profileId: string(),
19683
19870
  profileLabel: string(),
19684
19871
  name: string(),
19685
- enabled: boolean()
19872
+ enabled: boolean(),
19873
+ executable: string(),
19874
+ args: array(string()).readonly(),
19875
+ cwd: string(),
19876
+ environment: array(string()).readonly(),
19877
+ profileSettings: ProfileSettingsBagSchema
19686
19878
  });
19687
19879
  var TerminalLegacyCameraSchema = object({
19688
19880
  stableId: string(),
@@ -19712,7 +19904,23 @@ var TerminalOutputBatchSchema = object({
19712
19904
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19713
19905
  targetNodeId: string().min(1),
19714
19906
  profileId: string().min(1),
19715
- name: string().trim().min(1).max(160).optional()
19907
+ name: string().trim().min(1).max(160).optional(),
19908
+ executable: string().max(1024).optional(),
19909
+ args: array(string().max(2048)).max(64).optional(),
19910
+ cwd: string().max(1024).optional(),
19911
+ environment: array(string().max(4096)).max(64).optional(),
19912
+ profileSettings: ProfileSettingsBagSchema.optional()
19913
+ }), TerminalInstanceInfoSchema, {
19914
+ kind: "mutation",
19915
+ auth: "admin"
19916
+ }), method(object({
19917
+ instanceId: string().min(1),
19918
+ name: string().trim().min(1).max(160).optional(),
19919
+ executable: string().max(1024).optional(),
19920
+ args: array(string().max(2048)).max(64).optional(),
19921
+ cwd: string().max(1024).optional(),
19922
+ environment: array(string().max(4096)).max(64).optional(),
19923
+ profileSettings: ProfileSettingsBagSchema.optional()
19716
19924
  }), TerminalInstanceInfoSchema, {
19717
19925
  kind: "mutation",
19718
19926
  auth: "admin"
@@ -19734,7 +19942,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19734
19942
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19735
19943
  profileId: string(),
19736
19944
  cols: number().int().positive(),
19737
- rows: number().int().positive()
19945
+ rows: number().int().positive(),
19946
+ executable: string().max(1024).optional(),
19947
+ args: array(string().max(2048)).max(64).optional(),
19948
+ cwd: string().max(1024).optional(),
19949
+ environment: array(string().max(4096)).max(64).optional()
19738
19950
  }), TerminalSessionInfoSchema, {
19739
19951
  kind: "mutation",
19740
19952
  auth: "admin"
@@ -22516,10 +22728,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
22516
22728
  *
22517
22729
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
22518
22730
  * to receive an ordered list of candidate base URLs it should race
22519
- * on connect — LAN IPv4 first (lowest latency when on same network),
22520
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
22521
- * race them with short timeouts and stick with the winner for the
22522
- * session.
22731
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
22732
+ * when on the same network), then public hostname (if a tunnel is
22733
+ * up). The SDK can race them with short timeouts and stick with the
22734
+ * winner for the session.
22523
22735
  *
22524
22736
  * Why hub-only: agents are not directly addressable by the operator's
22525
22737
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -22674,6 +22886,17 @@ var NotificationEndpointSchema = object({
22674
22886
  /** What the ranking currently resolves to (null when nothing is reachable). */
22675
22887
  resolved: string().nullable()
22676
22888
  });
22889
+ /**
22890
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
22891
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
22892
+ * currently expands to, so the UI can show the effective set either way.
22893
+ */
22894
+ var ViewerEndpointsSchema = object({
22895
+ /** The operator's explicit race set, or empty for AUTO. */
22896
+ baseUrls: array(string()).readonly(),
22897
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
22898
+ resolved: array(string()).readonly()
22899
+ });
22677
22900
  var AllowedAddressesSchema = object({
22678
22901
  /**
22679
22902
  * Allowlist of interface addresses operators have explicitly opted
@@ -22682,6 +22905,20 @@ var AllowedAddressesSchema = object({
22682
22905
  * Network Addresses admin page and persisted by the addon.
22683
22906
  */
22684
22907
  addresses: array(string()).readonly() });
22908
+ var TlsStatusSchema = object({
22909
+ mode: _enum([
22910
+ "generated",
22911
+ "uploaded",
22912
+ "disabled"
22913
+ ]),
22914
+ leafFingerprintSha256: string().nullable(),
22915
+ caFingerprintSha256: string().nullable(),
22916
+ validTo: string().nullable(),
22917
+ sans: array(string()),
22918
+ caCertPem: string().nullable(),
22919
+ reissueError: string().nullable(),
22920
+ restartRequired: boolean()
22921
+ });
22685
22922
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22686
22923
  /**
22687
22924
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -22691,17 +22928,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
22691
22928
  */
22692
22929
  port: number().int().min(1).max(65535).optional(),
22693
22930
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22694
- * candidate. Default `true`. */
22931
+ * candidate. Default `false` — loopback is not a client route. */
22695
22932
  includeLoopback: boolean().optional(),
22696
- /** Skip IPv6 entries. Some legacy clients can't parse them.
22697
- * Default `false`. */
22933
+ /** Skip IPv6 entries. Default `false` the palette includes stable
22934
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
22935
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
22698
22936
  ipv4Only: boolean().optional(),
22699
22937
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
22700
22938
  * Pass `'https'` when the caller is itself loaded over HTTPS
22701
22939
  * to avoid mixed-content blocks in the browser. The public
22702
22940
  * tunnel always emits `https://` regardless. */
22703
22941
  scheme: _enum(["http", "https"]).optional()
22704
- }), 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" });
22942
+ }), 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, {
22943
+ kind: "mutation",
22944
+ auth: "admin"
22945
+ }), method(object({
22946
+ certPem: string().min(1),
22947
+ keyPem: string().min(1),
22948
+ caPem: string().optional()
22949
+ }), TlsStatusSchema, {
22950
+ kind: "mutation",
22951
+ auth: "admin"
22952
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
22953
+ kind: "mutation",
22954
+ auth: "admin"
22955
+ });
22705
22956
  object({
22706
22957
  /** Lifecycle state of the lock. `jammed` means the motor reported
22707
22958
  * failure to reach the target — operator intervention required. */
@@ -23882,7 +24133,12 @@ var PlateInfoSchema = object({
23882
24133
  plateBbox: BoundingBoxSchema.optional(),
23883
24134
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
23884
24135
  keyFrameMediaKey: string().optional(),
23885
- base64: string().optional()
24136
+ base64: string().optional(),
24137
+ /**
24138
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24139
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24140
+ */
24141
+ cropUrl: string().optional()
23886
24142
  });
23887
24143
  var MediaFileLiteSchema = object({
23888
24144
  key: string(),
@@ -27522,6 +27778,12 @@ Object.freeze({
27522
27778
  addonId: null,
27523
27779
  access: "view"
27524
27780
  },
27781
+ "deviceManager.getBindingsBatch": {
27782
+ capName: "device-manager",
27783
+ capScope: "system",
27784
+ addonId: null,
27785
+ access: "view"
27786
+ },
27525
27787
  "deviceManager.getChildren": {
27526
27788
  capName: "device-manager",
27527
27789
  capScope: "system",
@@ -27582,6 +27844,12 @@ Object.freeze({
27582
27844
  addonId: null,
27583
27845
  access: "view"
27584
27846
  },
27847
+ "deviceManager.getLinkedDevicesBatch": {
27848
+ capName: "device-manager",
27849
+ capScope: "system",
27850
+ addonId: null,
27851
+ access: "view"
27852
+ },
27585
27853
  "deviceManager.getRoleDisplayDefaults": {
27586
27854
  capName: "device-manager",
27587
27855
  capScope: "system",
@@ -28464,6 +28732,12 @@ Object.freeze({
28464
28732
  addonId: null,
28465
28733
  access: "create"
28466
28734
  },
28735
+ "localNetwork.downloadCa": {
28736
+ capName: "local-network",
28737
+ capScope: "system",
28738
+ addonId: null,
28739
+ access: "view"
28740
+ },
28467
28741
  "localNetwork.getAllowedAddresses": {
28468
28742
  capName: "local-network",
28469
28743
  capScope: "system",
@@ -28488,18 +28762,42 @@ Object.freeze({
28488
28762
  addonId: null,
28489
28763
  access: "view"
28490
28764
  },
28765
+ "localNetwork.getTlsStatus": {
28766
+ capName: "local-network",
28767
+ capScope: "system",
28768
+ addonId: null,
28769
+ access: "view"
28770
+ },
28771
+ "localNetwork.getViewerEndpoints": {
28772
+ capName: "local-network",
28773
+ capScope: "system",
28774
+ addonId: null,
28775
+ access: "view"
28776
+ },
28491
28777
  "localNetwork.list": {
28492
28778
  capName: "local-network",
28493
28779
  capScope: "system",
28494
28780
  addonId: null,
28495
28781
  access: "view"
28496
28782
  },
28783
+ "localNetwork.regenerateCertificate": {
28784
+ capName: "local-network",
28785
+ capScope: "system",
28786
+ addonId: null,
28787
+ access: "create"
28788
+ },
28497
28789
  "localNetwork.resetAllowlistToBestMatch": {
28498
28790
  capName: "local-network",
28499
28791
  capScope: "system",
28500
28792
  addonId: null,
28501
28793
  access: "delete"
28502
28794
  },
28795
+ "localNetwork.revertToGeneratedCertificate": {
28796
+ capName: "local-network",
28797
+ capScope: "system",
28798
+ addonId: null,
28799
+ access: "create"
28800
+ },
28503
28801
  "localNetwork.setAllowedAddresses": {
28504
28802
  capName: "local-network",
28505
28803
  capScope: "system",
@@ -28512,6 +28810,18 @@ Object.freeze({
28512
28810
  addonId: null,
28513
28811
  access: "create"
28514
28812
  },
28813
+ "localNetwork.setViewerEndpoints": {
28814
+ capName: "local-network",
28815
+ capScope: "system",
28816
+ addonId: null,
28817
+ access: "create"
28818
+ },
28819
+ "localNetwork.uploadCertificate": {
28820
+ capName: "local-network",
28821
+ capScope: "system",
28822
+ addonId: null,
28823
+ access: "create"
28824
+ },
28515
28825
  "lockControl.lock": {
28516
28826
  capName: "lock-control",
28517
28827
  capScope: "device",
@@ -29310,6 +29620,12 @@ Object.freeze({
29310
29620
  addonId: null,
29311
29621
  access: "view"
29312
29622
  },
29623
+ "pipelineAnalytics.getGroup": {
29624
+ capName: "pipeline-analytics",
29625
+ capScope: "device",
29626
+ addonId: null,
29627
+ access: "view"
29628
+ },
29313
29629
  "pipelineAnalytics.getKeyEvents": {
29314
29630
  capName: "pipeline-analytics",
29315
29631
  capScope: "device",
@@ -29394,6 +29710,12 @@ Object.freeze({
29394
29710
  addonId: null,
29395
29711
  access: "view"
29396
29712
  },
29713
+ "pipelineAnalytics.listGroups": {
29714
+ capName: "pipeline-analytics",
29715
+ capScope: "device",
29716
+ addonId: null,
29717
+ access: "view"
29718
+ },
29397
29719
  "pipelineAnalytics.listOpsLog": {
29398
29720
  capName: "pipeline-analytics",
29399
29721
  capScope: "device",
@@ -31392,6 +31714,12 @@ Object.freeze({
31392
31714
  addonId: null,
31393
31715
  access: "create"
31394
31716
  },
31717
+ "terminalSession.updateInstance": {
31718
+ capName: "terminal-session",
31719
+ capScope: "system",
31720
+ addonId: null,
31721
+ access: "create"
31722
+ },
31395
31723
  "terminalSession.writeInput": {
31396
31724
  capName: "terminal-session",
31397
31725
  capScope: "system",
@@ -32169,6 +32497,11 @@ Object.freeze({
32169
32497
  form: "single",
32170
32498
  optional: false
32171
32499
  }],
32500
+ "deviceManager.getBindingsBatch": [{
32501
+ name: "deviceIds",
32502
+ form: "array",
32503
+ optional: false
32504
+ }],
32172
32505
  "deviceManager.getChildren": [{
32173
32506
  name: "parentDeviceId",
32174
32507
  form: "single",
@@ -32214,6 +32547,11 @@ Object.freeze({
32214
32547
  form: "single",
32215
32548
  optional: false
32216
32549
  }],
32550
+ "deviceManager.getLinkedDevicesBatch": [{
32551
+ name: "deviceIds",
32552
+ form: "array",
32553
+ optional: false
32554
+ }],
32217
32555
  "deviceManager.getSettingsSchema": [{
32218
32556
  name: "deviceId",
32219
32557
  form: "single",
@@ -32234,6 +32572,11 @@ Object.freeze({
32234
32572
  form: "single",
32235
32573
  optional: false
32236
32574
  }],
32575
+ "deviceManager.listAll": [{
32576
+ name: "deviceIds",
32577
+ form: "array",
32578
+ optional: true
32579
+ }],
32237
32580
  "deviceManager.loadConfig": [{
32238
32581
  name: "deviceId",
32239
32582
  form: "single",
@@ -32807,6 +33150,11 @@ Object.freeze({
32807
33150
  form: "single",
32808
33151
  optional: false
32809
33152
  }],
33153
+ "pipelineAnalytics.getGroup": [{
33154
+ name: "deviceId",
33155
+ form: "single",
33156
+ optional: false
33157
+ }],
32810
33158
  "pipelineAnalytics.getKeyEvents": [{
32811
33159
  name: "deviceId",
32812
33160
  form: "single",
@@ -32862,6 +33210,11 @@ Object.freeze({
32862
33210
  form: "array",
32863
33211
  optional: false
32864
33212
  }],
33213
+ "pipelineAnalytics.listGroups": [{
33214
+ name: "deviceIds",
33215
+ form: "array",
33216
+ optional: false
33217
+ }],
32865
33218
  "pipelineAnalytics.listOpsLog": [{
32866
33219
  name: "deviceId",
32867
33220
  form: "single",
@@ -33879,6 +34232,35 @@ Object.freeze(Object.fromEntries([{
33879
34232
  }]
33880
34233
  }].map((s) => [s.stepId, s.defaultModelId])));
33881
34234
  string().min(1);
34235
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34236
+ stepId: "face-embedding",
34237
+ key: "minLandmarkFaceSize",
34238
+ label: "Min face size for recognition (detection px)",
34239
+ 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.",
34240
+ type: "slider",
34241
+ min: 0,
34242
+ max: 64,
34243
+ step: 2,
34244
+ default: 24
34245
+ }];
34246
+ function clusterStepSettingKey(stepId, fieldKey) {
34247
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34248
+ }
34249
+ var ClusterSettingNumberSchema = number().finite();
34250
+ function readClusterStepSettings(config) {
34251
+ const out = {};
34252
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
34253
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
34254
+ const value = parsed.success ? parsed.data : field.default;
34255
+ const existing = out[field.stepId] ?? {};
34256
+ out[field.stepId] = {
34257
+ ...existing,
34258
+ [field.key]: value
34259
+ };
34260
+ }
34261
+ return out;
34262
+ }
34263
+ readClusterStepSettings({});
33882
34264
  object({
33883
34265
  /**
33884
34266
  * Fraction of the box's own size added on EACH side before cutting.
@@ -34070,113 +34452,14 @@ var RingBuffer = class {
34070
34452
  * decoder is an explicit opt-in fallback only. A stale/failed settings read at
34071
34453
  * boot therefore resolves to node-av (never leaves the node with no decoder). */
34072
34454
  var DEFAULT_DECODER_BACKEND = "nodeav";
34073
- /** Narrow an unknown settings value to a {@link DecoderBackend}, else `null`. */
34074
- function parseDecoderBackend(value) {
34075
- return value === "ffmpeg" || value === "nodeav" ? value : null;
34076
- }
34077
- /**
34078
- * Normalise a raw kernel node id to the bare node id used for scoping.
34079
- * `localNodeId` can carry a `<node>/<addon>` suffix; the decoder selection is
34080
- * per-NODE, so strip the addon segment. Falls back to `hub`.
34081
- */
34082
- function normalizeDecoderNodeId(rawNodeId) {
34083
- const raw = rawNodeId ?? "hub";
34084
- return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
34085
- }
34086
34455
  //#endregion
34087
34456
  //#region src/shared/decoder-backend.ts
34088
- /** The NEUTRAL addon (pipeline-orchestrator, hub-resident) whose global
34089
- * settings OWN the per-node `backend` selector. Neither decoder addon owns
34090
- * it, so decoder-nodeav / decoder-ffmpeg stay fully independent. */
34091
- var DECODER_OWNER_ADDON_ID = "pipeline-orchestrator";
34092
- function isHydratedField(entry) {
34093
- return typeof entry === "object" && entry !== null && "key" in entry;
34094
- }
34095
- /**
34096
- * Pure selection from an already-read hydrated settings payload: extract the
34097
- * owner's `backend` field value (which the owner projected per-node from its
34098
- * scoped store key) and narrow it. A missing/invalid field or a null payload
34099
- * resolves to {@link DEFAULT_DECODER_BACKEND} — never a bare store key.
34100
- */
34101
- function pickDecoderBackendFromSettings(view) {
34102
- if (view === null) return DEFAULT_DECODER_BACKEND;
34103
- for (const section of view.sections) for (const entry of section.fields) {
34104
- if (!isHydratedField(entry) || entry.key !== "backend") continue;
34105
- return parseDecoderBackend(entry.value) ?? "nodeav";
34106
- }
34107
- return DEFAULT_DECODER_BACKEND;
34108
- }
34109
- /** Missing optional owner fingerprints: ffmpeg may be uninstalled. */
34110
- function isMissingOwnerSettingsError(message) {
34111
- return /not routable/i.test(message) || /provider not available/i.test(message);
34112
- }
34113
- /** Transient transport/settings-store fingerprints worth retrying on. */
34114
- function isTransientSettingsError(message) {
34115
- return /not loaded/i.test(message) || /transport-failed/i.test(message) || /not connected/i.test(message) || /SqliteSettingsBackend not initialized/i.test(message);
34116
- }
34117
34457
  /**
34118
- * Resolve the decoder backend a NON-OWNER addon (`decoder-nodeav`) should run,
34119
- * by reading the OWNER's (`decoder-ffmpeg`) hub-central per-node `backend`.
34120
- *
34121
- * The read routes to the owner addon's child runner; during a simultaneous
34122
- * (re)start the owner may not be up yet → a transient `transport-failed (addon
34123
- * not loaded)`. Immediately defaulting here would ignore an explicit `ffmpeg`
34124
- * selection (the node would silently run the node-av default instead). So retry
34125
- * on the transient fingerprints with a bounded budget (mirrors
34126
- * `BaseAddon.readAddonStoreWithRetry`) until the owner answers; only a
34127
- * persistent failure falls back to {@link DEFAULT_DECODER_BACKEND}.
34128
- *
34129
- * The OWNER addon must NOT call this (it would self-route + deadlock) — it uses
34130
- * {@link resolveOwnDecoderBackend} against its own store instead.
34458
+ * Resolve the decoder backend this addon should run. Owner settings are
34459
+ * ignored: persisted `backend@<nodeId>` rows stay in the store and are not
34460
+ * consulted. The runtime default is always {@link DEFAULT_DECODER_BACKEND}.
34131
34461
  */
34132
- async function resolveDecoderBackend(api, nodeId, logger) {
34133
- if (!api) {
34134
- logger.warn("decoder-backend: no api surface — using default backend", { meta: { default: DEFAULT_DECODER_BACKEND } });
34135
- return DEFAULT_DECODER_BACKEND;
34136
- }
34137
- const normalized = normalizeDecoderNodeId(nodeId);
34138
- const delaysMs = [
34139
- 150,
34140
- 350,
34141
- 600,
34142
- 900,
34143
- 1200
34144
- ];
34145
- let lastErr;
34146
- for (let attempt = 0; attempt <= delaysMs.length; attempt++) {
34147
- try {
34148
- const view = await api.addonSettings.getGlobalSettings.query({
34149
- addonId: DECODER_OWNER_ADDON_ID,
34150
- nodeId: normalized
34151
- });
34152
- if (view !== null) return pickDecoderBackendFromSettings(view);
34153
- lastErr = /* @__PURE__ */ new Error("owner settings unavailable (null)");
34154
- } catch (err) {
34155
- lastErr = err;
34156
- const msg = err instanceof Error ? err.message : String(err);
34157
- if (isTransientSettingsError(msg)) {} else if (isMissingOwnerSettingsError(msg)) {
34158
- logger.warn("decoder-backend: optional owner unavailable — using default backend", { meta: {
34159
- default: DEFAULT_DECODER_BACKEND,
34160
- owner: DECODER_OWNER_ADDON_ID,
34161
- error: msg
34162
- } });
34163
- return DEFAULT_DECODER_BACKEND;
34164
- } else {
34165
- logger.warn("decoder-backend: settings read failed — using default backend", { meta: {
34166
- default: DEFAULT_DECODER_BACKEND,
34167
- error: msg
34168
- } });
34169
- return DEFAULT_DECODER_BACKEND;
34170
- }
34171
- }
34172
- if (attempt === delaysMs.length) break;
34173
- await new Promise((resolve) => setTimeout(resolve, delaysMs[attempt]));
34174
- }
34175
- logger.warn("decoder-backend: owner settings unavailable after retries — using default backend", { meta: {
34176
- default: DEFAULT_DECODER_BACKEND,
34177
- owner: DECODER_OWNER_ADDON_ID,
34178
- error: lastErr instanceof Error ? lastErr.message : String(lastErr)
34179
- } });
34462
+ async function resolveDecoderBackend(_api, _nodeId, _logger) {
34180
34463
  return DEFAULT_DECODER_BACKEND;
34181
34464
  }
34182
34465
  //#endregion