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