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