@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.js CHANGED
@@ -5806,6 +5806,13 @@ var BaseAddon = class {
5806
5806
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5807
5807
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5808
5808
  _registeredCapNames = [];
5809
+ /**
5810
+ * True only after `readAddonStore` actually answered. Constructor
5811
+ * defaults look like stored config when the store is down — a forked
5812
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5813
+ * mode, 2026-08-25) is not "the operator chose this".
5814
+ */
5815
+ settingsStoreReady = false;
5809
5816
  /** Default config values. Provided via constructor. */
5810
5817
  defaults;
5811
5818
  constructor(defaults) {
@@ -6206,7 +6213,9 @@ var BaseAddon = class {
6206
6213
  ];
6207
6214
  let lastErr;
6208
6215
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6209
- return await settings.readAddonStore() ?? {};
6216
+ const stored = await settings.readAddonStore() ?? {};
6217
+ this.settingsStoreReady = true;
6218
+ return stored;
6210
6219
  } catch (err) {
6211
6220
  lastErr = err;
6212
6221
  const msg = err instanceof Error ? err.message : String(err);
@@ -6214,6 +6223,7 @@ var BaseAddon = class {
6214
6223
  if (attempt === delaysMs.length) break;
6215
6224
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6216
6225
  }
6226
+ this.settingsStoreReady = false;
6217
6227
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6218
6228
  return {};
6219
6229
  }
@@ -8021,6 +8031,15 @@ var LabelDefinitionSchema = object({
8021
8031
  description: string().optional(),
8022
8032
  icon: string().optional()
8023
8033
  });
8034
+ var ClassMapDefinitionSchema = object({
8035
+ mapping: record(string(), _enum([
8036
+ "person",
8037
+ "vehicle",
8038
+ "animal",
8039
+ "package"
8040
+ ])),
8041
+ preserveOriginal: boolean()
8042
+ });
8024
8043
  var MODEL_FORMATS = [
8025
8044
  "onnx",
8026
8045
  "coreml",
@@ -8104,6 +8123,12 @@ var ModelVariantGroupSchema = object({
8104
8123
  */
8105
8124
  resolution: number().int().positive().optional()
8106
8125
  });
8126
+ var ModelProviderIdSchema = _enum([
8127
+ "camstack",
8128
+ "frigate",
8129
+ "scrypted",
8130
+ "custom"
8131
+ ]);
8107
8132
  var ModelCatalogEntrySchema = object({
8108
8133
  id: string(),
8109
8134
  name: string(),
@@ -8199,7 +8224,19 @@ var ModelCatalogEntrySchema = object({
8199
8224
  * `id` stays the source of truth for resolution/download/persistence; grouping
8200
8225
  * is a presentation overlay resolved back to an `id`.
8201
8226
  */
8202
- group: ModelVariantGroupSchema.optional()
8227
+ group: ModelVariantGroupSchema.optional(),
8228
+ /**
8229
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8230
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8231
+ * persisted before this field existed (`inferModelProvider` fills those).
8232
+ */
8233
+ provider: ModelProviderIdSchema.optional(),
8234
+ /**
8235
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8236
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8237
+ * labels already ARE the CamStack macros (Scrypted identity map).
8238
+ */
8239
+ classMap: ClassMapDefinitionSchema.optional()
8203
8240
  });
8204
8241
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8205
8242
  format: literal("openvino"),
@@ -8228,7 +8265,8 @@ var ModelConvertMetadataSchema = object({
8228
8265
  "ocr",
8229
8266
  "segmentation"
8230
8267
  ]),
8231
- faceAlignment: boolean().optional()
8268
+ faceAlignment: boolean().optional(),
8269
+ classMap: ClassMapDefinitionSchema.optional()
8232
8270
  });
8233
8271
  var ConvertResultSchema = object({
8234
8272
  entry: ModelCatalogEntrySchema,
@@ -11851,6 +11889,27 @@ var LinkedDeviceSchema = object({
11851
11889
  features: array(string()),
11852
11890
  producesTrackedEvents: boolean().optional()
11853
11891
  });
11892
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
11893
+ * The batch answer needs the tag; the single-device answer already has it
11894
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
11895
+ var LinkedDevicesForDeviceSchema = object({
11896
+ deviceId: number(),
11897
+ mode: LinkedDevicesModeSchema,
11898
+ devices: array(LinkedDeviceSchema)
11899
+ });
11900
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
11901
+ * `getAllBindings` all answer in. Declared once: three copies of the same
11902
+ * object literal is exactly how the three drift apart. */
11903
+ var DeviceBindingsForDeviceSchema = object({
11904
+ deviceId: number(),
11905
+ entries: array(object({
11906
+ capName: string(),
11907
+ kind: _enum(["native", "wrapped"]),
11908
+ providerAddonId: string(),
11909
+ providerNodeId: string(),
11910
+ nativeAddonId: string()
11911
+ }))
11912
+ });
11854
11913
  var SavedDeviceRowSchema = object({
11855
11914
  /** Numeric id reserved at allocateDeviceId time. */
11856
11915
  id: number(),
@@ -12076,11 +12135,25 @@ method(object({
12076
12135
  projection: _enum(["full", "slim"]).optional(),
12077
12136
  /** Return only camera devices. Filtering server-side instead of
12078
12137
  * shipping 293 rows to find 12. */
12079
- isCamera: boolean().optional()
12138
+ isCamera: boolean().optional(),
12139
+ /**
12140
+ * Return only these device ids. For the caller that already KNOWS the
12141
+ * handful it wants and needs a field the id-bearing answer does not
12142
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12143
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12144
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12145
+ * refetches on the reconcile interval, on a phone.
12146
+ *
12147
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12148
+ * keys rather than rejecting them (verified against the live hub
12149
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12150
+ * it answers today and the caller filters as it already does.
12151
+ */
12152
+ deviceIds: array(number()).optional()
12080
12153
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12081
12154
  mode: LinkedDevicesModeSchema,
12082
12155
  devices: array(LinkedDeviceSchema)
12083
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12156
+ })), 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({
12084
12157
  deviceId: number(),
12085
12158
  values: record(string(), unknown())
12086
12159
  }), object({ success: literal(true) }), {
@@ -12107,25 +12180,7 @@ method(object({
12107
12180
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12108
12181
  kind: "mutation",
12109
12182
  auth: "admin"
12110
- }), method(object({ deviceId: number() }), object({
12111
- deviceId: number(),
12112
- entries: array(object({
12113
- capName: string(),
12114
- kind: _enum(["native", "wrapped"]),
12115
- providerAddonId: string(),
12116
- providerNodeId: string(),
12117
- nativeAddonId: string()
12118
- }))
12119
- })), method(object({}), array(object({
12120
- deviceId: number(),
12121
- entries: array(object({
12122
- capName: string(),
12123
- kind: _enum(["native", "wrapped"]),
12124
- providerAddonId: string(),
12125
- providerNodeId: string(),
12126
- nativeAddonId: string()
12127
- }))
12128
- }))), method(object({
12183
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12129
12184
  deviceId: number(),
12130
12185
  capName: string(),
12131
12186
  wrapperAddonId: string(),
@@ -14497,12 +14552,15 @@ var NcOccupancyConditionSchema = object({
14497
14552
  * there is no second switch that can disagree with the first and every rule
14498
14553
  * authored before the decision migrates for free (`audioModeOf`):
14499
14554
  *
14500
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
14501
- * classifier labels with one of them. No window, no percentage:
14502
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
14503
- * `throttle` cooldown is the only brake. The per-label confidence floor is
14504
- * the analyzer's (`classificationMinScore`, per device) a label only
14505
- * reaches this condition if the classifier was already confident enough.
14555
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
14556
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
14557
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
14558
+ * frames is the wrong question for a classifier that labels 1–3 frames
14559
+ * per episode. The count window is the brake that drops a single-frame
14560
+ * false positive; the rule's own `throttle` cooldown is the other. The
14561
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
14562
+ * per device) — a label only reaches this condition if the classifier was
14563
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
14506
14564
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
14507
14565
  * the condition: at least `hitPercent`% of the samples over
14508
14566
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -14529,14 +14587,22 @@ var NcOccupancyConditionSchema = object({
14529
14587
  * an operator who typed `dog` mean the same thing.
14530
14588
  */
14531
14589
  var NcAudioConditionSchema = object({
14532
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
14590
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
14533
14591
  labels: array(string().min(1)).min(1).optional(),
14534
14592
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
14535
14593
  dbThreshold: number().min(-96).max(0).optional(),
14536
14594
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
14537
14595
  hitPercent: number().int().min(1).max(100).default(60),
14538
14596
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
14539
- samplingSeconds: number().int().min(1).max(300).default(10)
14597
+ samplingSeconds: number().int().min(1).max(300).default(10),
14598
+ /**
14599
+ * LABEL MODE: how many labelled frames must land inside
14600
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
14601
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
14602
+ */
14603
+ confirmHits: number().int().min(1).max(20).optional(),
14604
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
14605
+ confirmWindowSec: number().int().min(1).max(60).optional()
14540
14606
  });
14541
14607
  /**
14542
14608
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -16910,6 +16976,46 @@ var RecentTracksPageSchema = object({
16910
16976
  /** Cursor for the next page, or null when this page is the last. */
16911
16977
  nextCursor: string().nullable()
16912
16978
  });
16979
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
16980
+ var LIST_GROUPS_MAX_LIMIT = 100;
16981
+ var AnalyticsGroupRecordSchema = object({
16982
+ id: string(),
16983
+ deviceId: number().int(),
16984
+ openedAt: number().int(),
16985
+ closedAt: number().int(),
16986
+ timestamp: number().int(),
16987
+ memberCount: number().int(),
16988
+ memberTrackIds: array(string()).readonly(),
16989
+ className: string(),
16990
+ classes: array(string()).readonly(),
16991
+ /** Relative event-media path, or null when the group has no picture yet. */
16992
+ mediaUrl: string().nullable(),
16993
+ singleton: boolean()
16994
+ });
16995
+ var AnalyticsGroupMemberSchema = object({
16996
+ trackId: string(),
16997
+ deviceId: number().int(),
16998
+ className: string(),
16999
+ firstSeen: number().int(),
17000
+ lastSeen: number().int(),
17001
+ mediaUrl: string().nullable()
17002
+ });
17003
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17004
+ var ListGroupsQueryInput = object({
17005
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17006
+ deviceIds: array(number()),
17007
+ /** Window lower bound on `closedAt` (inclusive). */
17008
+ since: number().optional(),
17009
+ /** Window upper bound on `openedAt` (inclusive). */
17010
+ until: number().optional(),
17011
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17012
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17013
+ cursor: string().optional()
17014
+ });
17015
+ var ListGroupsPageSchema = object({
17016
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17017
+ nextCursor: string().nullable()
17018
+ });
16913
17019
  var KeyEventQueryInput = object({
16914
17020
  deviceId: number(),
16915
17021
  /** Window lower bound (track firstSeen ≥ since). */
@@ -16985,7 +17091,9 @@ var TrackCascadeCountsSchema = object({
16985
17091
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
16986
17092
  plates: number().int(),
16987
17093
  /** Per-track CLIP search vectors removed (best-effort). */
16988
- embeddings: number().int()
17094
+ embeddings: number().int(),
17095
+ /** Group membership + group rows removed with their last member (best-effort). */
17096
+ groups: number().int()
16989
17097
  });
16990
17098
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
16991
17099
  var DiskReconcileCountsSchema = object({
@@ -17131,7 +17239,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17131
17239
  * stationary registry). Default false: the timeline lists passages,
17132
17240
  * not parking records (operator decision, 2026-08-15). */
17133
17241
  includeStationary: boolean().optional()
17134
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17242
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17243
+ deviceId: number(),
17244
+ groupId: string().min(1)
17245
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17135
17246
  kind: "mutation",
17136
17247
  auth: "admin"
17137
17248
  }), 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({
@@ -17349,6 +17460,33 @@ var NativeCropRefSchema = object({
17349
17460
  h: number()
17350
17461
  })
17351
17462
  });
17463
+ object({
17464
+ crop: object({
17465
+ left: number(),
17466
+ top: number(),
17467
+ width: number().positive(),
17468
+ height: number().positive()
17469
+ }).optional(),
17470
+ content: object({
17471
+ width: number().int().positive(),
17472
+ height: number().int().positive()
17473
+ }),
17474
+ fit: _enum(["stretch", "contain"]),
17475
+ format: _enum([
17476
+ "rgb",
17477
+ "gray",
17478
+ "jpeg"
17479
+ ])
17480
+ });
17481
+ var FrameRefSchema = object({
17482
+ registryId: string().min(1),
17483
+ id: string().min(1),
17484
+ width: number().int().positive(),
17485
+ height: number().int().positive(),
17486
+ format: _enum(["rgb", "gray"]),
17487
+ timestamp: number(),
17488
+ capturedAt: number().optional()
17489
+ });
17352
17490
  var ModelFormatSchema$1 = _enum([
17353
17491
  "onnx",
17354
17492
  "coreml",
@@ -17414,7 +17552,8 @@ var PipelineModelOptionSchema = object({
17414
17552
  sizeMB: number()
17415
17553
  })),
17416
17554
  group: ModelVariantGroupSchema.optional(),
17417
- legacy: boolean().optional()
17555
+ legacy: boolean().optional(),
17556
+ provider: ModelProviderIdSchema.optional()
17418
17557
  });
17419
17558
  var ConfigFieldBridge = custom();
17420
17559
  var PipelineAddonSchemaSchema = object({
@@ -17593,6 +17732,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17593
17732
  steps: array(PipelineStepInputSchema).min(1),
17594
17733
  frame: FrameInputSchema.optional(),
17595
17734
  /**
17735
+ * Process-local lazy frame. Valid only when caller and provider resolve
17736
+ * in the same execution-group process; split/cross-node callers use
17737
+ * `frame`/`image` inline compatibility instead.
17738
+ */
17739
+ frameRef: FrameRefSchema.optional(),
17740
+ /**
17596
17741
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17597
17742
  * the decoded pixels live in. One more member of the one-of
17598
17743
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -17848,7 +17993,10 @@ var NativeCropResultSchema = object({
17848
17993
  * Which source served this crop, so a quality-sensitive consumer (the native
17849
17994
  * `keyFrame`) can reject a degraded fallback:
17850
17995
  * - `native` — cut from the decode worker's retained NATIVE surface (the
17851
- * quality path).
17996
+ * quality path). A subject-tile serve is also native-resolution and stays
17997
+ * `native` here: the public enum cannot name `tile` without a breaking cap
17998
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
17999
+ * internal crop result (`nativeHits` vs `tileHits`).
17852
18000
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
17853
18001
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
17854
18002
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18339,12 +18487,41 @@ var RunnerLocalLoadSchema = object({
18339
18487
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18340
18488
  * working unchanged when they switch to reading from the runner cap.
18341
18489
  */
18490
+ var FrameLazyCountersSchema = object({
18491
+ framesDecoded: number(),
18492
+ framesAdmitted: number(),
18493
+ framesDroppedPixelFree: number(),
18494
+ viewsMaterialized: number(),
18495
+ viewsSkipped: number(),
18496
+ workerToRunnerBytes: number(),
18497
+ runnerToPoolRawBytes: number(),
18498
+ runnerToPoolJpegBytes: number(),
18499
+ onDemandFullFrameRequests: number(),
18500
+ onDemandCropRequests: number(),
18501
+ nativeHits: number(),
18502
+ nativeMisses: number(),
18503
+ tileHits: number(),
18504
+ tileMisses: number(),
18505
+ fallbackHits: number(),
18506
+ fallbackMisses: number(),
18507
+ retainedWritesAvoided: number(),
18508
+ residentRefs: number(),
18509
+ residentBytes: number(),
18510
+ releases: number(),
18511
+ evictions: number(),
18512
+ staleMisses: number()
18513
+ });
18514
+ var FrameLazyMetricsSchema = object({
18515
+ node: FrameLazyCountersSchema,
18516
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18517
+ });
18342
18518
  var RunnerLocalMetricsSchema = object({
18343
18519
  nodeId: string(),
18344
18520
  activeCameras: number(),
18345
18521
  throttledCameras: number(),
18346
18522
  avgInferenceTimeMs: number(),
18347
- queueDepth: number()
18523
+ queueDepth: number(),
18524
+ frameLazy: FrameLazyMetricsSchema.optional()
18348
18525
  });
18349
18526
  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({
18350
18527
  handle: FrameHandleSchema,
@@ -19644,6 +19821,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19644
19821
  location: StorageLocationSchema,
19645
19822
  relativePath: string()
19646
19823
  }), _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" });
19824
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19825
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19826
+ var ProfileSettingsBagSchema = record(string(), unknown());
19647
19827
  /**
19648
19828
  * A live terminal session hosted by the provider addon. Output and input do
19649
19829
  * NOT flow through the capability — they use the addon data plane
@@ -19673,7 +19853,14 @@ var TerminalSessionInfoSchema = object({
19673
19853
  var TerminalProfileInfoSchema = object({
19674
19854
  profileId: string(),
19675
19855
  label: string(),
19676
- description: string().optional()
19856
+ description: string().optional(),
19857
+ /** Spawn defaults the instance form copies on create. */
19858
+ executable: string().optional(),
19859
+ args: array(string()).readonly().optional(),
19860
+ cwd: string().optional(),
19861
+ environment: array(string()).readonly().optional(),
19862
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
19863
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19677
19864
  });
19678
19865
  /**
19679
19866
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19686,7 +19873,12 @@ var TerminalInstanceInfoSchema = object({
19686
19873
  profileId: string(),
19687
19874
  profileLabel: string(),
19688
19875
  name: string(),
19689
- enabled: boolean()
19876
+ enabled: boolean(),
19877
+ executable: string(),
19878
+ args: array(string()).readonly(),
19879
+ cwd: string(),
19880
+ environment: array(string()).readonly(),
19881
+ profileSettings: ProfileSettingsBagSchema
19690
19882
  });
19691
19883
  var TerminalLegacyCameraSchema = object({
19692
19884
  stableId: string(),
@@ -19716,7 +19908,23 @@ var TerminalOutputBatchSchema = object({
19716
19908
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19717
19909
  targetNodeId: string().min(1),
19718
19910
  profileId: string().min(1),
19719
- name: string().trim().min(1).max(160).optional()
19911
+ name: string().trim().min(1).max(160).optional(),
19912
+ executable: string().max(1024).optional(),
19913
+ args: array(string().max(2048)).max(64).optional(),
19914
+ cwd: string().max(1024).optional(),
19915
+ environment: array(string().max(4096)).max(64).optional(),
19916
+ profileSettings: ProfileSettingsBagSchema.optional()
19917
+ }), TerminalInstanceInfoSchema, {
19918
+ kind: "mutation",
19919
+ auth: "admin"
19920
+ }), method(object({
19921
+ instanceId: string().min(1),
19922
+ name: string().trim().min(1).max(160).optional(),
19923
+ executable: string().max(1024).optional(),
19924
+ args: array(string().max(2048)).max(64).optional(),
19925
+ cwd: string().max(1024).optional(),
19926
+ environment: array(string().max(4096)).max(64).optional(),
19927
+ profileSettings: ProfileSettingsBagSchema.optional()
19720
19928
  }), TerminalInstanceInfoSchema, {
19721
19929
  kind: "mutation",
19722
19930
  auth: "admin"
@@ -19738,7 +19946,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19738
19946
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19739
19947
  profileId: string(),
19740
19948
  cols: number().int().positive(),
19741
- rows: number().int().positive()
19949
+ rows: number().int().positive(),
19950
+ executable: string().max(1024).optional(),
19951
+ args: array(string().max(2048)).max(64).optional(),
19952
+ cwd: string().max(1024).optional(),
19953
+ environment: array(string().max(4096)).max(64).optional()
19742
19954
  }), TerminalSessionInfoSchema, {
19743
19955
  kind: "mutation",
19744
19956
  auth: "admin"
@@ -22520,10 +22732,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
22520
22732
  *
22521
22733
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
22522
22734
  * to receive an ordered list of candidate base URLs it should race
22523
- * on connect — LAN IPv4 first (lowest latency when on same network),
22524
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
22525
- * race them with short timeouts and stick with the winner for the
22526
- * session.
22735
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
22736
+ * when on the same network), then public hostname (if a tunnel is
22737
+ * up). The SDK can race them with short timeouts and stick with the
22738
+ * winner for the session.
22527
22739
  *
22528
22740
  * Why hub-only: agents are not directly addressable by the operator's
22529
22741
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -22678,6 +22890,17 @@ var NotificationEndpointSchema = object({
22678
22890
  /** What the ranking currently resolves to (null when nothing is reachable). */
22679
22891
  resolved: string().nullable()
22680
22892
  });
22893
+ /**
22894
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
22895
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
22896
+ * currently expands to, so the UI can show the effective set either way.
22897
+ */
22898
+ var ViewerEndpointsSchema = object({
22899
+ /** The operator's explicit race set, or empty for AUTO. */
22900
+ baseUrls: array(string()).readonly(),
22901
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
22902
+ resolved: array(string()).readonly()
22903
+ });
22681
22904
  var AllowedAddressesSchema = object({
22682
22905
  /**
22683
22906
  * Allowlist of interface addresses operators have explicitly opted
@@ -22686,6 +22909,20 @@ var AllowedAddressesSchema = object({
22686
22909
  * Network Addresses admin page and persisted by the addon.
22687
22910
  */
22688
22911
  addresses: array(string()).readonly() });
22912
+ var TlsStatusSchema = object({
22913
+ mode: _enum([
22914
+ "generated",
22915
+ "uploaded",
22916
+ "disabled"
22917
+ ]),
22918
+ leafFingerprintSha256: string().nullable(),
22919
+ caFingerprintSha256: string().nullable(),
22920
+ validTo: string().nullable(),
22921
+ sans: array(string()),
22922
+ caCertPem: string().nullable(),
22923
+ reissueError: string().nullable(),
22924
+ restartRequired: boolean()
22925
+ });
22689
22926
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22690
22927
  /**
22691
22928
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -22695,17 +22932,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
22695
22932
  */
22696
22933
  port: number().int().min(1).max(65535).optional(),
22697
22934
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22698
- * candidate. Default `true`. */
22935
+ * candidate. Default `false` — loopback is not a client route. */
22699
22936
  includeLoopback: boolean().optional(),
22700
- /** Skip IPv6 entries. Some legacy clients can't parse them.
22701
- * Default `false`. */
22937
+ /** Skip IPv6 entries. Default `false` the palette includes stable
22938
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
22939
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
22702
22940
  ipv4Only: boolean().optional(),
22703
22941
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
22704
22942
  * Pass `'https'` when the caller is itself loaded over HTTPS
22705
22943
  * to avoid mixed-content blocks in the browser. The public
22706
22944
  * tunnel always emits `https://` regardless. */
22707
22945
  scheme: _enum(["http", "https"]).optional()
22708
- }), 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" });
22946
+ }), 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, {
22947
+ kind: "mutation",
22948
+ auth: "admin"
22949
+ }), method(object({
22950
+ certPem: string().min(1),
22951
+ keyPem: string().min(1),
22952
+ caPem: string().optional()
22953
+ }), TlsStatusSchema, {
22954
+ kind: "mutation",
22955
+ auth: "admin"
22956
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
22957
+ kind: "mutation",
22958
+ auth: "admin"
22959
+ });
22709
22960
  object({
22710
22961
  /** Lifecycle state of the lock. `jammed` means the motor reported
22711
22962
  * failure to reach the target — operator intervention required. */
@@ -23886,7 +24137,12 @@ var PlateInfoSchema = object({
23886
24137
  plateBbox: BoundingBoxSchema.optional(),
23887
24138
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
23888
24139
  keyFrameMediaKey: string().optional(),
23889
- base64: string().optional()
24140
+ base64: string().optional(),
24141
+ /**
24142
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24143
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24144
+ */
24145
+ cropUrl: string().optional()
23890
24146
  });
23891
24147
  var MediaFileLiteSchema = object({
23892
24148
  key: string(),
@@ -27526,6 +27782,12 @@ Object.freeze({
27526
27782
  addonId: null,
27527
27783
  access: "view"
27528
27784
  },
27785
+ "deviceManager.getBindingsBatch": {
27786
+ capName: "device-manager",
27787
+ capScope: "system",
27788
+ addonId: null,
27789
+ access: "view"
27790
+ },
27529
27791
  "deviceManager.getChildren": {
27530
27792
  capName: "device-manager",
27531
27793
  capScope: "system",
@@ -27586,6 +27848,12 @@ Object.freeze({
27586
27848
  addonId: null,
27587
27849
  access: "view"
27588
27850
  },
27851
+ "deviceManager.getLinkedDevicesBatch": {
27852
+ capName: "device-manager",
27853
+ capScope: "system",
27854
+ addonId: null,
27855
+ access: "view"
27856
+ },
27589
27857
  "deviceManager.getRoleDisplayDefaults": {
27590
27858
  capName: "device-manager",
27591
27859
  capScope: "system",
@@ -28468,6 +28736,12 @@ Object.freeze({
28468
28736
  addonId: null,
28469
28737
  access: "create"
28470
28738
  },
28739
+ "localNetwork.downloadCa": {
28740
+ capName: "local-network",
28741
+ capScope: "system",
28742
+ addonId: null,
28743
+ access: "view"
28744
+ },
28471
28745
  "localNetwork.getAllowedAddresses": {
28472
28746
  capName: "local-network",
28473
28747
  capScope: "system",
@@ -28492,18 +28766,42 @@ Object.freeze({
28492
28766
  addonId: null,
28493
28767
  access: "view"
28494
28768
  },
28769
+ "localNetwork.getTlsStatus": {
28770
+ capName: "local-network",
28771
+ capScope: "system",
28772
+ addonId: null,
28773
+ access: "view"
28774
+ },
28775
+ "localNetwork.getViewerEndpoints": {
28776
+ capName: "local-network",
28777
+ capScope: "system",
28778
+ addonId: null,
28779
+ access: "view"
28780
+ },
28495
28781
  "localNetwork.list": {
28496
28782
  capName: "local-network",
28497
28783
  capScope: "system",
28498
28784
  addonId: null,
28499
28785
  access: "view"
28500
28786
  },
28787
+ "localNetwork.regenerateCertificate": {
28788
+ capName: "local-network",
28789
+ capScope: "system",
28790
+ addonId: null,
28791
+ access: "create"
28792
+ },
28501
28793
  "localNetwork.resetAllowlistToBestMatch": {
28502
28794
  capName: "local-network",
28503
28795
  capScope: "system",
28504
28796
  addonId: null,
28505
28797
  access: "delete"
28506
28798
  },
28799
+ "localNetwork.revertToGeneratedCertificate": {
28800
+ capName: "local-network",
28801
+ capScope: "system",
28802
+ addonId: null,
28803
+ access: "create"
28804
+ },
28507
28805
  "localNetwork.setAllowedAddresses": {
28508
28806
  capName: "local-network",
28509
28807
  capScope: "system",
@@ -28516,6 +28814,18 @@ Object.freeze({
28516
28814
  addonId: null,
28517
28815
  access: "create"
28518
28816
  },
28817
+ "localNetwork.setViewerEndpoints": {
28818
+ capName: "local-network",
28819
+ capScope: "system",
28820
+ addonId: null,
28821
+ access: "create"
28822
+ },
28823
+ "localNetwork.uploadCertificate": {
28824
+ capName: "local-network",
28825
+ capScope: "system",
28826
+ addonId: null,
28827
+ access: "create"
28828
+ },
28519
28829
  "lockControl.lock": {
28520
28830
  capName: "lock-control",
28521
28831
  capScope: "device",
@@ -29314,6 +29624,12 @@ Object.freeze({
29314
29624
  addonId: null,
29315
29625
  access: "view"
29316
29626
  },
29627
+ "pipelineAnalytics.getGroup": {
29628
+ capName: "pipeline-analytics",
29629
+ capScope: "device",
29630
+ addonId: null,
29631
+ access: "view"
29632
+ },
29317
29633
  "pipelineAnalytics.getKeyEvents": {
29318
29634
  capName: "pipeline-analytics",
29319
29635
  capScope: "device",
@@ -29398,6 +29714,12 @@ Object.freeze({
29398
29714
  addonId: null,
29399
29715
  access: "view"
29400
29716
  },
29717
+ "pipelineAnalytics.listGroups": {
29718
+ capName: "pipeline-analytics",
29719
+ capScope: "device",
29720
+ addonId: null,
29721
+ access: "view"
29722
+ },
29401
29723
  "pipelineAnalytics.listOpsLog": {
29402
29724
  capName: "pipeline-analytics",
29403
29725
  capScope: "device",
@@ -31396,6 +31718,12 @@ Object.freeze({
31396
31718
  addonId: null,
31397
31719
  access: "create"
31398
31720
  },
31721
+ "terminalSession.updateInstance": {
31722
+ capName: "terminal-session",
31723
+ capScope: "system",
31724
+ addonId: null,
31725
+ access: "create"
31726
+ },
31399
31727
  "terminalSession.writeInput": {
31400
31728
  capName: "terminal-session",
31401
31729
  capScope: "system",
@@ -32173,6 +32501,11 @@ Object.freeze({
32173
32501
  form: "single",
32174
32502
  optional: false
32175
32503
  }],
32504
+ "deviceManager.getBindingsBatch": [{
32505
+ name: "deviceIds",
32506
+ form: "array",
32507
+ optional: false
32508
+ }],
32176
32509
  "deviceManager.getChildren": [{
32177
32510
  name: "parentDeviceId",
32178
32511
  form: "single",
@@ -32218,6 +32551,11 @@ Object.freeze({
32218
32551
  form: "single",
32219
32552
  optional: false
32220
32553
  }],
32554
+ "deviceManager.getLinkedDevicesBatch": [{
32555
+ name: "deviceIds",
32556
+ form: "array",
32557
+ optional: false
32558
+ }],
32221
32559
  "deviceManager.getSettingsSchema": [{
32222
32560
  name: "deviceId",
32223
32561
  form: "single",
@@ -32238,6 +32576,11 @@ Object.freeze({
32238
32576
  form: "single",
32239
32577
  optional: false
32240
32578
  }],
32579
+ "deviceManager.listAll": [{
32580
+ name: "deviceIds",
32581
+ form: "array",
32582
+ optional: true
32583
+ }],
32241
32584
  "deviceManager.loadConfig": [{
32242
32585
  name: "deviceId",
32243
32586
  form: "single",
@@ -32811,6 +33154,11 @@ Object.freeze({
32811
33154
  form: "single",
32812
33155
  optional: false
32813
33156
  }],
33157
+ "pipelineAnalytics.getGroup": [{
33158
+ name: "deviceId",
33159
+ form: "single",
33160
+ optional: false
33161
+ }],
32814
33162
  "pipelineAnalytics.getKeyEvents": [{
32815
33163
  name: "deviceId",
32816
33164
  form: "single",
@@ -32866,6 +33214,11 @@ Object.freeze({
32866
33214
  form: "array",
32867
33215
  optional: false
32868
33216
  }],
33217
+ "pipelineAnalytics.listGroups": [{
33218
+ name: "deviceIds",
33219
+ form: "array",
33220
+ optional: false
33221
+ }],
32869
33222
  "pipelineAnalytics.listOpsLog": [{
32870
33223
  name: "deviceId",
32871
33224
  form: "single",
@@ -33883,6 +34236,35 @@ Object.freeze(Object.fromEntries([{
33883
34236
  }]
33884
34237
  }].map((s) => [s.stepId, s.defaultModelId])));
33885
34238
  string().min(1);
34239
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34240
+ stepId: "face-embedding",
34241
+ key: "minLandmarkFaceSize",
34242
+ label: "Min face size for recognition (detection px)",
34243
+ 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.",
34244
+ type: "slider",
34245
+ min: 0,
34246
+ max: 64,
34247
+ step: 2,
34248
+ default: 24
34249
+ }];
34250
+ function clusterStepSettingKey(stepId, fieldKey) {
34251
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34252
+ }
34253
+ var ClusterSettingNumberSchema = number().finite();
34254
+ function readClusterStepSettings(config) {
34255
+ const out = {};
34256
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
34257
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
34258
+ const value = parsed.success ? parsed.data : field.default;
34259
+ const existing = out[field.stepId] ?? {};
34260
+ out[field.stepId] = {
34261
+ ...existing,
34262
+ [field.key]: value
34263
+ };
34264
+ }
34265
+ return out;
34266
+ }
34267
+ readClusterStepSettings({});
33886
34268
  object({
33887
34269
  /**
33888
34270
  * Fraction of the box's own size added on EACH side before cutting.
@@ -34074,113 +34456,14 @@ var RingBuffer = class {
34074
34456
  * decoder is an explicit opt-in fallback only. A stale/failed settings read at
34075
34457
  * boot therefore resolves to node-av (never leaves the node with no decoder). */
34076
34458
  var DEFAULT_DECODER_BACKEND = "nodeav";
34077
- /** Narrow an unknown settings value to a {@link DecoderBackend}, else `null`. */
34078
- function parseDecoderBackend(value) {
34079
- return value === "ffmpeg" || value === "nodeav" ? value : null;
34080
- }
34081
- /**
34082
- * Normalise a raw kernel node id to the bare node id used for scoping.
34083
- * `localNodeId` can carry a `<node>/<addon>` suffix; the decoder selection is
34084
- * per-NODE, so strip the addon segment. Falls back to `hub`.
34085
- */
34086
- function normalizeDecoderNodeId(rawNodeId) {
34087
- const raw = rawNodeId ?? "hub";
34088
- return raw.includes("/") ? raw.split("/")[0] ?? "hub" : raw;
34089
- }
34090
34459
  //#endregion
34091
34460
  //#region src/shared/decoder-backend.ts
34092
- /** The NEUTRAL addon (pipeline-orchestrator, hub-resident) whose global
34093
- * settings OWN the per-node `backend` selector. Neither decoder addon owns
34094
- * it, so decoder-nodeav / decoder-ffmpeg stay fully independent. */
34095
- var DECODER_OWNER_ADDON_ID = "pipeline-orchestrator";
34096
- function isHydratedField(entry) {
34097
- return typeof entry === "object" && entry !== null && "key" in entry;
34098
- }
34099
- /**
34100
- * Pure selection from an already-read hydrated settings payload: extract the
34101
- * owner's `backend` field value (which the owner projected per-node from its
34102
- * scoped store key) and narrow it. A missing/invalid field or a null payload
34103
- * resolves to {@link DEFAULT_DECODER_BACKEND} — never a bare store key.
34104
- */
34105
- function pickDecoderBackendFromSettings(view) {
34106
- if (view === null) return DEFAULT_DECODER_BACKEND;
34107
- for (const section of view.sections) for (const entry of section.fields) {
34108
- if (!isHydratedField(entry) || entry.key !== "backend") continue;
34109
- return parseDecoderBackend(entry.value) ?? "nodeav";
34110
- }
34111
- return DEFAULT_DECODER_BACKEND;
34112
- }
34113
- /** Missing optional owner fingerprints: ffmpeg may be uninstalled. */
34114
- function isMissingOwnerSettingsError(message) {
34115
- return /not routable/i.test(message) || /provider not available/i.test(message);
34116
- }
34117
- /** Transient transport/settings-store fingerprints worth retrying on. */
34118
- function isTransientSettingsError(message) {
34119
- return /not loaded/i.test(message) || /transport-failed/i.test(message) || /not connected/i.test(message) || /SqliteSettingsBackend not initialized/i.test(message);
34120
- }
34121
34461
  /**
34122
- * Resolve the decoder backend a NON-OWNER addon (`decoder-nodeav`) should run,
34123
- * by reading the OWNER's (`decoder-ffmpeg`) hub-central per-node `backend`.
34124
- *
34125
- * The read routes to the owner addon's child runner; during a simultaneous
34126
- * (re)start the owner may not be up yet → a transient `transport-failed (addon
34127
- * not loaded)`. Immediately defaulting here would ignore an explicit `ffmpeg`
34128
- * selection (the node would silently run the node-av default instead). So retry
34129
- * on the transient fingerprints with a bounded budget (mirrors
34130
- * `BaseAddon.readAddonStoreWithRetry`) until the owner answers; only a
34131
- * persistent failure falls back to {@link DEFAULT_DECODER_BACKEND}.
34132
- *
34133
- * The OWNER addon must NOT call this (it would self-route + deadlock) — it uses
34134
- * {@link resolveOwnDecoderBackend} against its own store instead.
34462
+ * Resolve the decoder backend this addon should run. Owner settings are
34463
+ * ignored: persisted `backend@<nodeId>` rows stay in the store and are not
34464
+ * consulted. The runtime default is always {@link DEFAULT_DECODER_BACKEND}.
34135
34465
  */
34136
- async function resolveDecoderBackend(api, nodeId, logger) {
34137
- if (!api) {
34138
- logger.warn("decoder-backend: no api surface — using default backend", { meta: { default: DEFAULT_DECODER_BACKEND } });
34139
- return DEFAULT_DECODER_BACKEND;
34140
- }
34141
- const normalized = normalizeDecoderNodeId(nodeId);
34142
- const delaysMs = [
34143
- 150,
34144
- 350,
34145
- 600,
34146
- 900,
34147
- 1200
34148
- ];
34149
- let lastErr;
34150
- for (let attempt = 0; attempt <= delaysMs.length; attempt++) {
34151
- try {
34152
- const view = await api.addonSettings.getGlobalSettings.query({
34153
- addonId: DECODER_OWNER_ADDON_ID,
34154
- nodeId: normalized
34155
- });
34156
- if (view !== null) return pickDecoderBackendFromSettings(view);
34157
- lastErr = /* @__PURE__ */ new Error("owner settings unavailable (null)");
34158
- } catch (err) {
34159
- lastErr = err;
34160
- const msg = err instanceof Error ? err.message : String(err);
34161
- if (isTransientSettingsError(msg)) {} else if (isMissingOwnerSettingsError(msg)) {
34162
- logger.warn("decoder-backend: optional owner unavailable — using default backend", { meta: {
34163
- default: DEFAULT_DECODER_BACKEND,
34164
- owner: DECODER_OWNER_ADDON_ID,
34165
- error: msg
34166
- } });
34167
- return DEFAULT_DECODER_BACKEND;
34168
- } else {
34169
- logger.warn("decoder-backend: settings read failed — using default backend", { meta: {
34170
- default: DEFAULT_DECODER_BACKEND,
34171
- error: msg
34172
- } });
34173
- return DEFAULT_DECODER_BACKEND;
34174
- }
34175
- }
34176
- if (attempt === delaysMs.length) break;
34177
- await new Promise((resolve) => setTimeout(resolve, delaysMs[attempt]));
34178
- }
34179
- logger.warn("decoder-backend: owner settings unavailable after retries — using default backend", { meta: {
34180
- default: DEFAULT_DECODER_BACKEND,
34181
- owner: DECODER_OWNER_ADDON_ID,
34182
- error: lastErr instanceof Error ? lastErr.message : String(lastErr)
34183
- } });
34466
+ async function resolveDecoderBackend(_api, _nodeId, _logger) {
34184
34467
  return DEFAULT_DECODER_BACKEND;
34185
34468
  }
34186
34469
  //#endregion