@camstack/addon-provider-reolink 1.2.57 → 1.2.59

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/addon.js +299 -31
  2. package/dist/addon.mjs +299 -31
  3. package/package.json +3 -2
package/dist/addon.js CHANGED
@@ -26,7 +26,7 @@ let fs_promises = require("fs/promises");
26
26
  fs_promises = require_chunk.__toESM(fs_promises, 1);
27
27
  let node_os = require("node:os");
28
28
  node_os = require_chunk.__toESM(node_os);
29
- //#region ../types/dist/event-category-CIa_iT6b.mjs
29
+ //#region ../types/dist/event-category-BZL-fdNj.mjs
30
30
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
31
31
  EventCategory["SystemBoot"] = "system.boot";
32
32
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -463,7 +463,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
463
463
  EventCategory["MetricsNodeResourcesSnapshot"] = "metrics.node-resources-snapshot";
464
464
  /**
465
465
  * Periodic per-node process-tree snapshot (camstack-related pids
466
- * with ghost / managed / root classification). Emitted ~0.2 Hz by
466
+ * with root / managed / system classification). Emitted ~0.2 Hz by
467
467
  * the metrics-provider addon. Drives the Cluster → Processes tab
468
468
  * without polling `metricsProvider.listNodeProcesses`.
469
469
  */
@@ -11705,6 +11705,19 @@ var SettingsRecordSchema = object({
11705
11705
  data: record(string(), unknown())
11706
11706
  });
11707
11707
  /**
11708
+ * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
11709
+ * optional.
11710
+ *
11711
+ * A separate schema rather than loosening the shared one: every other method
11712
+ * on this cap addresses a row BY its id, and making that field optional
11713
+ * everywhere would turn a forgotten key into a silently generated one on
11714
+ * `update` and `delete` as well.
11715
+ */
11716
+ var BulkRecordSchema = object({
11717
+ id: string().optional(),
11718
+ data: record(string(), unknown())
11719
+ });
11720
+ /**
11708
11721
  * Column declaration for a structured (SQL-backed) collection.
11709
11722
  *
11710
11723
  * Logical types — the backend translates each to the matching SQLite
@@ -11761,6 +11774,10 @@ method(object({
11761
11774
  collection: string(),
11762
11775
  record: SettingsRecordSchema
11763
11776
  }), _void(), { kind: "mutation" }), method(object({
11777
+ namespace: string().optional(),
11778
+ collection: string(),
11779
+ records: array(BulkRecordSchema).readonly()
11780
+ }), object({ inserted: number().int() }), { kind: "mutation" }), method(object({
11764
11781
  namespace: string().optional(),
11765
11782
  collection: string(),
11766
11783
  id: string(),
@@ -11869,6 +11886,13 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
11869
11886
  }), _void(), {
11870
11887
  kind: "mutation",
11871
11888
  auth: "admin"
11889
+ }), method(object({
11890
+ namespace: string().optional(),
11891
+ collection: string(),
11892
+ records: array(BulkRecordSchema).readonly()
11893
+ }), object({ inserted: number().int() }), {
11894
+ kind: "mutation",
11895
+ auth: "admin"
11872
11896
  }), method(object({
11873
11897
  namespace: string().optional(),
11874
11898
  collection: string(),
@@ -13820,6 +13844,68 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13820
13844
  limit: number().optional(),
13821
13845
  tags: record(string(), string()).optional()
13822
13846
  }), array(LogEntrySchema).readonly());
13847
+ var LoadContributionSchema = object({
13848
+ role: _enum([
13849
+ "decode",
13850
+ "transcode",
13851
+ "recording",
13852
+ "streaming",
13853
+ "detection"
13854
+ ]),
13855
+ /**
13856
+ * The NUMERIC device id — the same value every log line carries as
13857
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13858
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
13859
+ * contributor that cannot name its camera must not emit the entry at all,
13860
+ * because an unnamed per-camera entry is indistinguishable from a shared one
13861
+ * and would quietly turn one camera's cost into everybody's.
13862
+ */
13863
+ deviceId: number().int().positive().nullable(),
13864
+ attribution: _enum([
13865
+ "measured",
13866
+ "accounted",
13867
+ "unattributable"
13868
+ ]),
13869
+ /**
13870
+ * What ONE entry is, in the contributor's own words — `615/high`,
13871
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13872
+ * family and inventing a common one would lose the only information that
13873
+ * makes two entries for the same camera distinguishable.
13874
+ */
13875
+ unit: string(),
13876
+ /**
13877
+ * The OS process this cost lives in, when there is one. Present so a
13878
+ * consumer can (a) tell two generations of the same unit apart across a
13879
+ * restart, and (b) subtract claimed processes from the node's process
13880
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13881
+ * process of its own.
13882
+ */
13883
+ pid: number().int().positive().optional(),
13884
+ /**
13885
+ * When this generation started. The pid's incarnation marker: a consumer
13886
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13887
+ * window when this changes, because the counter restarted from zero in a new
13888
+ * process.
13889
+ */
13890
+ startedAtMs: number().optional(),
13891
+ /**
13892
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13893
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
13894
+ * contribution is asked for.
13895
+ *
13896
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
13897
+ * needs a sampler, and a new per-node sampler is the defect half of
13898
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13899
+ * by whoever already keeps a history; a rate cannot be un-averaged.
13900
+ *
13901
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13902
+ * an entry with no process.
13903
+ */
13904
+ cpuSeconds: number().optional(),
13905
+ /** Resident bytes of this unit's process, same source and same rules. */
13906
+ rssBytes: number().optional()
13907
+ });
13908
+ method(_void(), array(LoadContributionSchema).readonly());
13823
13909
  /**
13824
13910
  * `login-method` — collection cap through which auth addons contribute
13825
13911
  * their pre-auth login surfaces to the login page. This is the SINGLE,
@@ -14023,8 +14109,7 @@ var NodeProcessSchema = object({
14023
14109
  classification: _enum([
14024
14110
  "root",
14025
14111
  "managed",
14026
- "system",
14027
- "ghost"
14112
+ "system"
14028
14113
  ]),
14029
14114
  /** `$process` addon binding when `managed`, else null. */
14030
14115
  addonId: string().nullable(),
@@ -14032,22 +14117,39 @@ var NodeProcessSchema = object({
14032
14117
  nodeId: string().nullable(),
14033
14118
  /** Truncated command line. */
14034
14119
  command: string(),
14120
+ /**
14121
+ * `ps pcpu` — CPU averaged over the process's WHOLE LIFETIME, not a rate.
14122
+ * On a runner up for days it barely moves. Fine as a column, useless as a
14123
+ * series: use `cpuMainPercent + cpuGcPercent` for anything time-varying.
14124
+ */
14035
14125
  cpuPercent: number(),
14036
14126
  memoryRssBytes: number(),
14127
+ /**
14128
+ * Instantaneous CPU% of the process's own threads over the last
14129
+ * process-snapshot window, from a `/proc/<pid>/task/*` tick delta.
14130
+ *
14131
+ * `null` = UNKNOWN, never zero: no previous sample yet (first tick after
14132
+ * boot), the pid was recycled, or this node is not Linux.
14133
+ */
14134
+ cpuMainPercent: number().nullable(),
14135
+ /**
14136
+ * Instantaneous CPU% of V8's `V8Worker` platform pool over the same window.
14137
+ *
14138
+ * This is the number that rewrote the 2026-08-27 diagnosis — hub-main 73%,
14139
+ * `stream-broker` 61% (`docs/architecture/load-ledger.md`). A CPU chart that
14140
+ * does not separate it from `cpuMainPercent` shows "busy" where the truth is
14141
+ * "allocating too much".
14142
+ *
14143
+ * Concurrent GC is the dominant tenant of that pool but not the only one
14144
+ * (background compilation runs there too), so it is reported as
14145
+ * "GC / V8 helpers" rather than as pure collection time. `null` has the same
14146
+ * meaning as on `cpuMainPercent`.
14147
+ */
14148
+ cpuGcPercent: number().nullable(),
14149
+ /** Threads seen in the tick scan. `null` under the same conditions. */
14150
+ threadCount: number().nullable(),
14037
14151
  /** Wall-clock uptime (seconds). Parsed from `ps etime`. */
14038
- uptimeSec: number(),
14039
- /** True when ancestor walk reaches `ppid=1` (reparented to init/launchd). */
14040
- orphaned: boolean()
14041
- });
14042
- var KillProcessInputSchema = object({
14043
- pid: number(),
14044
- /** Force = SIGKILL. Default is SIGTERM. */
14045
- force: boolean().optional()
14046
- });
14047
- var KillProcessResultSchema = object({
14048
- success: boolean(),
14049
- reason: string().optional(),
14050
- signal: _enum(["SIGTERM", "SIGKILL"]).optional()
14152
+ uptimeSec: number()
14051
14153
  });
14052
14154
  var DumpHeapSnapshotInputSchema = object({
14053
14155
  /** The addon whose runner should dump a heap snapshot. */
@@ -14060,6 +14162,104 @@ var DumpHeapSnapshotResultSchema = object({
14060
14162
  pid: number().optional(),
14061
14163
  reason: string().optional()
14062
14164
  });
14165
+ /**
14166
+ * One point of one function's series.
14167
+ *
14168
+ * The unsuffixed fields are the bucket's **MAXIMUM**, and that choice is the
14169
+ * point of the whole surface. The two obvious reductions both lie: a mean per
14170
+ * bucket smears a spike away, and taking every Nth sample skips it outright.
14171
+ * Either would give us a tool built to find peaks that does not show peaks.
14172
+ * `...Min` carries the other end, `samples` says how many raw snapshots folded
14173
+ * into the bucket, and a mean stays derivable where it is wanted.
14174
+ *
14175
+ * An UNREDUCED point is a one-sample bucket: `samples === 1` and each `...Min`
14176
+ * equals its unsuffixed twin. Reduced and unreduced are the same shape, so a
14177
+ * caller cannot tell which it received — which is what "one reader" means.
14178
+ */
14179
+ var LoadPointSchema = object({
14180
+ /** Bucket START, or the snapshot's own timestamp when unreduced. */
14181
+ atMs: number(),
14182
+ /** Raw snapshots in this bucket. Never 0 — AN EMPTY BUCKET IS ABSENT. */
14183
+ samples: number().int(),
14184
+ /**
14185
+ * `null` = UNKNOWN and it PROPAGATES: a bucket is null unless every process
14186
+ * of every snapshot in it reported a thread split. A partial sum is a
14187
+ * smaller number that looks exactly as real as a complete one.
14188
+ */
14189
+ cpuMainPercent: number().nullable(),
14190
+ cpuMainPercentMin: number().nullable(),
14191
+ cpuGcPercent: number().nullable(),
14192
+ cpuGcPercentMin: number().nullable(),
14193
+ /** Lifetime-average CPU%, summed. Always known — and never a rate. */
14194
+ cpuLifetimePercent: number(),
14195
+ cpuLifetimePercentMin: number(),
14196
+ memoryRssBytes: number(),
14197
+ memoryRssBytesMin: number(),
14198
+ processCount: number().int(),
14199
+ processCountMin: number().int()
14200
+ });
14201
+ /** One function's series. `key` is an addonId, `__root__` or `__unattributed__`. */
14202
+ var LoadFunctionSeriesSchema = object({
14203
+ key: string(),
14204
+ kind: _enum([
14205
+ "addon",
14206
+ "root",
14207
+ "unattributed"
14208
+ ]),
14209
+ /** Oldest-first. A missing interval is MISSING — never zero-filled. */
14210
+ points: array(LoadPointSchema).readonly()
14211
+ });
14212
+ var NodeLoadSeriesSchema = object({
14213
+ nodeId: string(),
14214
+ /** One entry per function seen in the window, heaviest-first. */
14215
+ series: array(LoadFunctionSeriesSchema).readonly(),
14216
+ /**
14217
+ * Width of one returned bucket, in ms. Equals the sampling cadence when no
14218
+ * reduction was needed — so a caller can always say what one point covers
14219
+ * without having to know whether it was reduced.
14220
+ */
14221
+ bucketMs: number(),
14222
+ /** Raw snapshots that went into this answer, across both tiers. */
14223
+ retainedSamples: number(),
14224
+ /** Oldest snapshot represented, or `null` when nothing is retained. */
14225
+ oldestAtMs: number().nullable(),
14226
+ /** The fixed sampling cadence in force on the cluster, in ms. */
14227
+ cadenceMs: number(),
14228
+ /**
14229
+ * Did the DURABLE tier contribute? `false` means the answer is the hot ring
14230
+ * alone — an agent (which holds no table), or a store that refused.
14231
+ * Reported because "the last hour" and "the last six hours" are different
14232
+ * questions and an operator must not have to guess which was answered.
14233
+ */
14234
+ durable: boolean()
14235
+ });
14236
+ var GetLoadSeriesInputSchema = object({
14237
+ /**
14238
+ * The node whose series is wanted.
14239
+ *
14240
+ * NOT named `nodeId`: the generated cap router strips a top-level
14241
+ * `nodeId` from every method input and uses it to ROUTE the call to
14242
+ * that node's provider (`generated-cap-routers.ts`). A series target
14243
+ * called `nodeId` would silently become a routing pin and never reach
14244
+ * the provider. The hub holds every node it hears from, so the
14245
+ * ordinary call is unpinned — answered by the hub, for any node.
14246
+ */
14247
+ forNodeId: string(),
14248
+ /**
14249
+ * EXCLUSIVE lower bound. A caller passes the newest `atMs` it already
14250
+ * holds and receives only what it is missing, so seeding a live chart
14251
+ * from this method cannot double a point already drawn.
14252
+ */
14253
+ sinceMs: number().optional(),
14254
+ /**
14255
+ * Most points the caller wants PER FUNCTION. The window is reduced to fit,
14256
+ * preserving min and max per bucket.
14257
+ *
14258
+ * Absent means NO reduction — legitimate for a short window and a trap for a
14259
+ * long one, which is why a chart passes its own pixel width.
14260
+ */
14261
+ maxPoints: number().int().positive().optional()
14262
+ });
14063
14263
  var SystemMetricsSchema = object({
14064
14264
  cpuPercent: number(),
14065
14265
  memoryPercent: number(),
@@ -14070,10 +14270,7 @@ var SystemMetricsSchema = object({
14070
14270
  gpuPercent: number().optional(),
14071
14271
  gpuMemoryPercent: number().optional()
14072
14272
  });
14073
- method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(KillProcessInputSchema, KillProcessResultSchema, {
14074
- kind: "mutation",
14075
- auth: "admin"
14076
- }), method(DumpHeapSnapshotInputSchema, DumpHeapSnapshotResultSchema, {
14273
+ method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema), method(DumpHeapSnapshotInputSchema, DumpHeapSnapshotResultSchema, {
14077
14274
  kind: "mutation",
14078
14275
  auth: "admin"
14079
14276
  });
@@ -29792,6 +29989,15 @@ var LoggingSettingsPatchSchema = object({
29792
29989
  * authority over the whole hierarchy and answers for every layer, so the
29793
29990
  * layer selector needs a name the transport does not already own.
29794
29991
  */
29992
+ /**
29993
+ * One contribution, plus WHO reported it.
29994
+ *
29995
+ * The addon and node are added by the hub as it enumerates providers, never by
29996
+ * the contributor: an addon reporting its own identity could report somebody
29997
+ * else's, and the whole point of this surface is that no claim is made by
29998
+ * anyone but its owner.
29999
+ */
30000
+ var ReportedLoadContributionSchema = LoadContributionSchema.extend({ addonId: string() });
29795
30001
  var GetLoggingSettingsInputSchema = object({
29796
30002
  scopeNodeId: string().optional(),
29797
30003
  /**
@@ -29850,7 +30056,7 @@ method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), met
29850
30056
  }), method(_void(), SiteLocationStatusSchema, {
29851
30057
  kind: "mutation",
29852
30058
  auth: "admin"
29853
- }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
30059
+ }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(_void(), array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
29854
30060
  kind: "mutation",
29855
30061
  auth: "admin"
29856
30062
  });
@@ -33041,6 +33247,12 @@ Object.freeze({
33041
33247
  addonId: null,
33042
33248
  access: "create"
33043
33249
  },
33250
+ "dataStoreProvider.insertMany": {
33251
+ capName: "data-store-provider",
33252
+ capScope: "system",
33253
+ addonId: null,
33254
+ access: "create"
33255
+ },
33044
33256
  "dataStoreProvider.isEmpty": {
33045
33257
  capName: "data-store-provider",
33046
33258
  capScope: "system",
@@ -34355,6 +34567,12 @@ Object.freeze({
34355
34567
  addonId: null,
34356
34568
  access: "create"
34357
34569
  },
34570
+ "loadContribution.list": {
34571
+ capName: "load-contribution",
34572
+ capScope: "system",
34573
+ addonId: null,
34574
+ access: "view"
34575
+ },
34358
34576
  "localNetwork.downloadCa": {
34359
34577
  capName: "local-network",
34360
34578
  capScope: "system",
@@ -34655,17 +34873,17 @@ Object.freeze({
34655
34873
  addonId: null,
34656
34874
  access: "view"
34657
34875
  },
34658
- "metricsProvider.getProcessStats": {
34876
+ "metricsProvider.getLoadSeries": {
34659
34877
  capName: "metrics-provider",
34660
34878
  capScope: "system",
34661
34879
  addonId: null,
34662
34880
  access: "view"
34663
34881
  },
34664
- "metricsProvider.killProcess": {
34882
+ "metricsProvider.getProcessStats": {
34665
34883
  capName: "metrics-provider",
34666
34884
  capScope: "system",
34667
34885
  addonId: null,
34668
- access: "create"
34886
+ access: "view"
34669
34887
  },
34670
34888
  "metricsProvider.listAddonInstances": {
34671
34889
  capName: "metrics-provider",
@@ -36677,6 +36895,12 @@ Object.freeze({
36677
36895
  addonId: null,
36678
36896
  access: "create"
36679
36897
  },
36898
+ "settingsStore.insertMany": {
36899
+ capName: "settings-store",
36900
+ capScope: "system",
36901
+ addonId: null,
36902
+ access: "create"
36903
+ },
36680
36904
  "settingsStore.isEmpty": {
36681
36905
  capName: "settings-store",
36682
36906
  capScope: "system",
@@ -37289,6 +37513,12 @@ Object.freeze({
37289
37513
  addonId: null,
37290
37514
  access: "create"
37291
37515
  },
37516
+ "system.getLoadContributions": {
37517
+ capName: "system",
37518
+ capScope: "system",
37519
+ addonId: null,
37520
+ access: "view"
37521
+ },
37292
37522
  "system.getLoggingSettings": {
37293
37523
  capName: "system",
37294
37524
  capScope: "system",
@@ -235991,10 +236221,12 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
235991
236221
  * descriptors served by the `stream-catalog` cap (broker PULLS via
235992
236222
  * `registerStreamCatalogProvider`).
235993
236223
  *
235994
- * Returns `[]` (logging at debug, matching the prior early-returns) on
235995
- * the battery-sleep gate, `ensureApi` failure, or
235996
- * `buildVideoStreamOptions` failure the broker simply sees no
235997
- * descriptors for the device until the next reconcile.
236224
+ * Returns `[]` only when there is genuinely nothing to say: the
236225
+ * battery-sleep gate (logged at debug), or a camera that connected and
236226
+ * reported zero streams. An `ensureApi` / `buildVideoStreamOptions` failure
236227
+ * THROWS `[]` and an error are not the same fact, and the broker needs
236228
+ * the difference to keep the device's existing entry and to schedule its
236229
+ * per-device backoff instead of treating the camera as stream-less.
235998
236230
  *
235999
236231
  * This method only BUILDS descriptors — it never opens an upstream
236000
236232
  * session or touches the broker.
@@ -236122,10 +236354,22 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
236122
236354
  channel: ownChannel,
236123
236355
  onNvr: true
236124
236356
  } : void 0;
236357
+ const readStreamOptions = async (current) => withTimeout(buildOptions ? current.buildVideoStreamOptions(buildOptions) : current.buildVideoStreamOptions(), 1e4, "buildVideoStreamOptions");
236125
236358
  try {
236126
- streamOptions = await withTimeout(buildOptions ? api.buildVideoStreamOptions(buildOptions) : api.buildVideoStreamOptions(), 1e4, "buildVideoStreamOptions");
236359
+ streamOptions = await readStreamOptions(api);
236127
236360
  } catch (err) {
236128
- throw new Error(`buildStreamCatalog: buildVideoStreamOptions failed for device ${this.id}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
236361
+ if (isChild) throw new Error(`buildStreamCatalog: buildVideoStreamOptions failed for device ${this.id}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
236362
+ this.ctx.logger.warn("buildStreamCatalog: stream options read failed — dropping the Baichuan session and retrying once", {
236363
+ tags: { deviceId: this.id },
236364
+ meta: { error: err instanceof Error ? err.message : String(err) }
236365
+ });
236366
+ await this.dropBaichuanSession("stream-catalog-retry");
236367
+ try {
236368
+ streamOptions = await readStreamOptions(await withTimeout(this.ensureApi(), 1e4, "ensureApi"));
236369
+ } catch (retryErr) {
236370
+ throw new Error(`buildStreamCatalog: buildVideoStreamOptions failed for device ${this.id} (after one re-auth retry): ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`, { cause: retryErr });
236371
+ }
236372
+ this.ctx.logger.info("buildStreamCatalog: stream options read recovered after re-auth", { tags: { deviceId: this.id } });
236129
236373
  }
236130
236374
  const channelCount = this.channelCount();
236131
236375
  const desired = /* @__PURE__ */ new Map();
@@ -239007,6 +239251,30 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
239007
239251
  * `awake` push (or a snapshot demand from the operator) will
239008
239252
  * lazily call `ensureApi()` and re-establish the client.
239009
239253
  */
239254
+ /**
239255
+ * Close and forget the cached control-channel client so the NEXT
239256
+ * `ensureApi()` performs a fresh login.
239257
+ *
239258
+ * The one thing `scheduleReconnect` cannot express: an EXPIRED session on a
239259
+ * socket that is still open. No `close`/`error` ever fires, so the reconnect
239260
+ * ladder is never armed, and `ensureApi`'s `if (this.api) return this.api`
239261
+ * keeps handing back a client whose every request answers `responseCode
239262
+ * 400`. Callers that see an application-level rejection they believe is a
239263
+ * dead session call this and retry ONCE — they must never loop on it.
239264
+ *
239265
+ * Same teardown the `disconnectAll` / debug-options paths use: `close()`
239266
+ * best-effort, then null the field. Active RFC 4571 servers are untouched —
239267
+ * each owns a dedicated socket and survives a control-channel reset (see
239268
+ * `scheduleReconnect`).
239269
+ */
239270
+ async dropBaichuanSession(reason) {
239271
+ const current = this.api;
239272
+ this.api = null;
239273
+ if (!current) return;
239274
+ try {
239275
+ await current.close({ reason });
239276
+ } catch {}
239277
+ }
239010
239278
  scheduleReconnect(reason) {
239011
239279
  if (this.reconnectTimer) return;
239012
239280
  if (this.isBattery) {
package/dist/addon.mjs CHANGED
@@ -21,7 +21,7 @@ import netImpl from "net";
21
21
  import { fileURLToPath } from "url";
22
22
  import { mkdir } from "fs/promises";
23
23
  import os from "node:os";
24
- //#region ../types/dist/event-category-CIa_iT6b.mjs
24
+ //#region ../types/dist/event-category-BZL-fdNj.mjs
25
25
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
26
26
  EventCategory["SystemBoot"] = "system.boot";
27
27
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -458,7 +458,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
458
458
  EventCategory["MetricsNodeResourcesSnapshot"] = "metrics.node-resources-snapshot";
459
459
  /**
460
460
  * Periodic per-node process-tree snapshot (camstack-related pids
461
- * with ghost / managed / root classification). Emitted ~0.2 Hz by
461
+ * with root / managed / system classification). Emitted ~0.2 Hz by
462
462
  * the metrics-provider addon. Drives the Cluster → Processes tab
463
463
  * without polling `metricsProvider.listNodeProcesses`.
464
464
  */
@@ -11700,6 +11700,19 @@ var SettingsRecordSchema = object({
11700
11700
  data: record(string(), unknown())
11701
11701
  });
11702
11702
  /**
11703
+ * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
11704
+ * optional.
11705
+ *
11706
+ * A separate schema rather than loosening the shared one: every other method
11707
+ * on this cap addresses a row BY its id, and making that field optional
11708
+ * everywhere would turn a forgotten key into a silently generated one on
11709
+ * `update` and `delete` as well.
11710
+ */
11711
+ var BulkRecordSchema = object({
11712
+ id: string().optional(),
11713
+ data: record(string(), unknown())
11714
+ });
11715
+ /**
11703
11716
  * Column declaration for a structured (SQL-backed) collection.
11704
11717
  *
11705
11718
  * Logical types — the backend translates each to the matching SQLite
@@ -11756,6 +11769,10 @@ method(object({
11756
11769
  collection: string(),
11757
11770
  record: SettingsRecordSchema
11758
11771
  }), _void(), { kind: "mutation" }), method(object({
11772
+ namespace: string().optional(),
11773
+ collection: string(),
11774
+ records: array(BulkRecordSchema).readonly()
11775
+ }), object({ inserted: number().int() }), { kind: "mutation" }), method(object({
11759
11776
  namespace: string().optional(),
11760
11777
  collection: string(),
11761
11778
  id: string(),
@@ -11864,6 +11881,13 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
11864
11881
  }), _void(), {
11865
11882
  kind: "mutation",
11866
11883
  auth: "admin"
11884
+ }), method(object({
11885
+ namespace: string().optional(),
11886
+ collection: string(),
11887
+ records: array(BulkRecordSchema).readonly()
11888
+ }), object({ inserted: number().int() }), {
11889
+ kind: "mutation",
11890
+ auth: "admin"
11867
11891
  }), method(object({
11868
11892
  namespace: string().optional(),
11869
11893
  collection: string(),
@@ -13815,6 +13839,68 @@ method(LogEntrySchema, _void(), { kind: "mutation" }), method(object({
13815
13839
  limit: number().optional(),
13816
13840
  tags: record(string(), string()).optional()
13817
13841
  }), array(LogEntrySchema).readonly());
13842
+ var LoadContributionSchema = object({
13843
+ role: _enum([
13844
+ "decode",
13845
+ "transcode",
13846
+ "recording",
13847
+ "streaming",
13848
+ "detection"
13849
+ ]),
13850
+ /**
13851
+ * The NUMERIC device id — the same value every log line carries as
13852
+ * `tags.deviceId`. `null` means this cost genuinely belongs to no single
13853
+ * camera (a shared pool), NOT that the contributor forgot to look it up: a
13854
+ * contributor that cannot name its camera must not emit the entry at all,
13855
+ * because an unnamed per-camera entry is indistinguishable from a shared one
13856
+ * and would quietly turn one camera's cost into everybody's.
13857
+ */
13858
+ deviceId: number().int().positive().nullable(),
13859
+ attribution: _enum([
13860
+ "measured",
13861
+ "accounted",
13862
+ "unattributable"
13863
+ ]),
13864
+ /**
13865
+ * What ONE entry is, in the contributor's own words — `615/high`,
13866
+ * `617/native`, `cuda:0 shared pool`. Free text because the unit differs per
13867
+ * family and inventing a common one would lose the only information that
13868
+ * makes two entries for the same camera distinguishable.
13869
+ */
13870
+ unit: string(),
13871
+ /**
13872
+ * The OS process this cost lives in, when there is one. Present so a
13873
+ * consumer can (a) tell two generations of the same unit apart across a
13874
+ * restart, and (b) subtract claimed processes from the node's process
13875
+ * snapshot to see what NOBODY claimed. Absent for an entry that owns no
13876
+ * process of its own.
13877
+ */
13878
+ pid: number().int().positive().optional(),
13879
+ /**
13880
+ * When this generation started. The pid's incarnation marker: a consumer
13881
+ * differencing {@link LoadContributionSchema.shape.cpuSeconds} must drop the
13882
+ * window when this changes, because the counter restarted from zero in a new
13883
+ * process.
13884
+ */
13885
+ startedAtMs: number().optional(),
13886
+ /**
13887
+ * CUMULATIVE CPU seconds this unit has consumed since it started — user +
13888
+ * system, read from the child's own `/proc/<pid>/stat` at the moment the
13889
+ * contribution is asked for.
13890
+ *
13891
+ * Cumulative and not a rate on purpose: a rate needs a window, a window
13892
+ * needs a sampler, and a new per-node sampler is the defect half of
13893
+ * `docs/architecture/load-ledger.md` documents. A counter can be differenced
13894
+ * by whoever already keeps a history; a rate cannot be un-averaged.
13895
+ *
13896
+ * Absent — never zero — on a node with no `/proc`, on a read failure, and on
13897
+ * an entry with no process.
13898
+ */
13899
+ cpuSeconds: number().optional(),
13900
+ /** Resident bytes of this unit's process, same source and same rules. */
13901
+ rssBytes: number().optional()
13902
+ });
13903
+ method(_void(), array(LoadContributionSchema).readonly());
13818
13904
  /**
13819
13905
  * `login-method` — collection cap through which auth addons contribute
13820
13906
  * their pre-auth login surfaces to the login page. This is the SINGLE,
@@ -14018,8 +14104,7 @@ var NodeProcessSchema = object({
14018
14104
  classification: _enum([
14019
14105
  "root",
14020
14106
  "managed",
14021
- "system",
14022
- "ghost"
14107
+ "system"
14023
14108
  ]),
14024
14109
  /** `$process` addon binding when `managed`, else null. */
14025
14110
  addonId: string().nullable(),
@@ -14027,22 +14112,39 @@ var NodeProcessSchema = object({
14027
14112
  nodeId: string().nullable(),
14028
14113
  /** Truncated command line. */
14029
14114
  command: string(),
14115
+ /**
14116
+ * `ps pcpu` — CPU averaged over the process's WHOLE LIFETIME, not a rate.
14117
+ * On a runner up for days it barely moves. Fine as a column, useless as a
14118
+ * series: use `cpuMainPercent + cpuGcPercent` for anything time-varying.
14119
+ */
14030
14120
  cpuPercent: number(),
14031
14121
  memoryRssBytes: number(),
14122
+ /**
14123
+ * Instantaneous CPU% of the process's own threads over the last
14124
+ * process-snapshot window, from a `/proc/<pid>/task/*` tick delta.
14125
+ *
14126
+ * `null` = UNKNOWN, never zero: no previous sample yet (first tick after
14127
+ * boot), the pid was recycled, or this node is not Linux.
14128
+ */
14129
+ cpuMainPercent: number().nullable(),
14130
+ /**
14131
+ * Instantaneous CPU% of V8's `V8Worker` platform pool over the same window.
14132
+ *
14133
+ * This is the number that rewrote the 2026-08-27 diagnosis — hub-main 73%,
14134
+ * `stream-broker` 61% (`docs/architecture/load-ledger.md`). A CPU chart that
14135
+ * does not separate it from `cpuMainPercent` shows "busy" where the truth is
14136
+ * "allocating too much".
14137
+ *
14138
+ * Concurrent GC is the dominant tenant of that pool but not the only one
14139
+ * (background compilation runs there too), so it is reported as
14140
+ * "GC / V8 helpers" rather than as pure collection time. `null` has the same
14141
+ * meaning as on `cpuMainPercent`.
14142
+ */
14143
+ cpuGcPercent: number().nullable(),
14144
+ /** Threads seen in the tick scan. `null` under the same conditions. */
14145
+ threadCount: number().nullable(),
14032
14146
  /** Wall-clock uptime (seconds). Parsed from `ps etime`. */
14033
- uptimeSec: number(),
14034
- /** True when ancestor walk reaches `ppid=1` (reparented to init/launchd). */
14035
- orphaned: boolean()
14036
- });
14037
- var KillProcessInputSchema = object({
14038
- pid: number(),
14039
- /** Force = SIGKILL. Default is SIGTERM. */
14040
- force: boolean().optional()
14041
- });
14042
- var KillProcessResultSchema = object({
14043
- success: boolean(),
14044
- reason: string().optional(),
14045
- signal: _enum(["SIGTERM", "SIGKILL"]).optional()
14147
+ uptimeSec: number()
14046
14148
  });
14047
14149
  var DumpHeapSnapshotInputSchema = object({
14048
14150
  /** The addon whose runner should dump a heap snapshot. */
@@ -14055,6 +14157,104 @@ var DumpHeapSnapshotResultSchema = object({
14055
14157
  pid: number().optional(),
14056
14158
  reason: string().optional()
14057
14159
  });
14160
+ /**
14161
+ * One point of one function's series.
14162
+ *
14163
+ * The unsuffixed fields are the bucket's **MAXIMUM**, and that choice is the
14164
+ * point of the whole surface. The two obvious reductions both lie: a mean per
14165
+ * bucket smears a spike away, and taking every Nth sample skips it outright.
14166
+ * Either would give us a tool built to find peaks that does not show peaks.
14167
+ * `...Min` carries the other end, `samples` says how many raw snapshots folded
14168
+ * into the bucket, and a mean stays derivable where it is wanted.
14169
+ *
14170
+ * An UNREDUCED point is a one-sample bucket: `samples === 1` and each `...Min`
14171
+ * equals its unsuffixed twin. Reduced and unreduced are the same shape, so a
14172
+ * caller cannot tell which it received — which is what "one reader" means.
14173
+ */
14174
+ var LoadPointSchema = object({
14175
+ /** Bucket START, or the snapshot's own timestamp when unreduced. */
14176
+ atMs: number(),
14177
+ /** Raw snapshots in this bucket. Never 0 — AN EMPTY BUCKET IS ABSENT. */
14178
+ samples: number().int(),
14179
+ /**
14180
+ * `null` = UNKNOWN and it PROPAGATES: a bucket is null unless every process
14181
+ * of every snapshot in it reported a thread split. A partial sum is a
14182
+ * smaller number that looks exactly as real as a complete one.
14183
+ */
14184
+ cpuMainPercent: number().nullable(),
14185
+ cpuMainPercentMin: number().nullable(),
14186
+ cpuGcPercent: number().nullable(),
14187
+ cpuGcPercentMin: number().nullable(),
14188
+ /** Lifetime-average CPU%, summed. Always known — and never a rate. */
14189
+ cpuLifetimePercent: number(),
14190
+ cpuLifetimePercentMin: number(),
14191
+ memoryRssBytes: number(),
14192
+ memoryRssBytesMin: number(),
14193
+ processCount: number().int(),
14194
+ processCountMin: number().int()
14195
+ });
14196
+ /** One function's series. `key` is an addonId, `__root__` or `__unattributed__`. */
14197
+ var LoadFunctionSeriesSchema = object({
14198
+ key: string(),
14199
+ kind: _enum([
14200
+ "addon",
14201
+ "root",
14202
+ "unattributed"
14203
+ ]),
14204
+ /** Oldest-first. A missing interval is MISSING — never zero-filled. */
14205
+ points: array(LoadPointSchema).readonly()
14206
+ });
14207
+ var NodeLoadSeriesSchema = object({
14208
+ nodeId: string(),
14209
+ /** One entry per function seen in the window, heaviest-first. */
14210
+ series: array(LoadFunctionSeriesSchema).readonly(),
14211
+ /**
14212
+ * Width of one returned bucket, in ms. Equals the sampling cadence when no
14213
+ * reduction was needed — so a caller can always say what one point covers
14214
+ * without having to know whether it was reduced.
14215
+ */
14216
+ bucketMs: number(),
14217
+ /** Raw snapshots that went into this answer, across both tiers. */
14218
+ retainedSamples: number(),
14219
+ /** Oldest snapshot represented, or `null` when nothing is retained. */
14220
+ oldestAtMs: number().nullable(),
14221
+ /** The fixed sampling cadence in force on the cluster, in ms. */
14222
+ cadenceMs: number(),
14223
+ /**
14224
+ * Did the DURABLE tier contribute? `false` means the answer is the hot ring
14225
+ * alone — an agent (which holds no table), or a store that refused.
14226
+ * Reported because "the last hour" and "the last six hours" are different
14227
+ * questions and an operator must not have to guess which was answered.
14228
+ */
14229
+ durable: boolean()
14230
+ });
14231
+ var GetLoadSeriesInputSchema = object({
14232
+ /**
14233
+ * The node whose series is wanted.
14234
+ *
14235
+ * NOT named `nodeId`: the generated cap router strips a top-level
14236
+ * `nodeId` from every method input and uses it to ROUTE the call to
14237
+ * that node's provider (`generated-cap-routers.ts`). A series target
14238
+ * called `nodeId` would silently become a routing pin and never reach
14239
+ * the provider. The hub holds every node it hears from, so the
14240
+ * ordinary call is unpinned — answered by the hub, for any node.
14241
+ */
14242
+ forNodeId: string(),
14243
+ /**
14244
+ * EXCLUSIVE lower bound. A caller passes the newest `atMs` it already
14245
+ * holds and receives only what it is missing, so seeding a live chart
14246
+ * from this method cannot double a point already drawn.
14247
+ */
14248
+ sinceMs: number().optional(),
14249
+ /**
14250
+ * Most points the caller wants PER FUNCTION. The window is reduced to fit,
14251
+ * preserving min and max per bucket.
14252
+ *
14253
+ * Absent means NO reduction — legitimate for a short window and a trap for a
14254
+ * long one, which is why a chart passes its own pixel width.
14255
+ */
14256
+ maxPoints: number().int().positive().optional()
14257
+ });
14058
14258
  var SystemMetricsSchema = object({
14059
14259
  cpuPercent: number(),
14060
14260
  memoryPercent: number(),
@@ -14065,10 +14265,7 @@ var SystemMetricsSchema = object({
14065
14265
  gpuPercent: number().optional(),
14066
14266
  gpuMemoryPercent: number().optional()
14067
14267
  });
14068
- method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(KillProcessInputSchema, KillProcessResultSchema, {
14069
- kind: "mutation",
14070
- auth: "admin"
14071
- }), method(DumpHeapSnapshotInputSchema, DumpHeapSnapshotResultSchema, {
14268
+ method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema), method(DumpHeapSnapshotInputSchema, DumpHeapSnapshotResultSchema, {
14072
14269
  kind: "mutation",
14073
14270
  auth: "admin"
14074
14271
  });
@@ -29787,6 +29984,15 @@ var LoggingSettingsPatchSchema = object({
29787
29984
  * authority over the whole hierarchy and answers for every layer, so the
29788
29985
  * layer selector needs a name the transport does not already own.
29789
29986
  */
29987
+ /**
29988
+ * One contribution, plus WHO reported it.
29989
+ *
29990
+ * The addon and node are added by the hub as it enumerates providers, never by
29991
+ * the contributor: an addon reporting its own identity could report somebody
29992
+ * else's, and the whole point of this surface is that no claim is made by
29993
+ * anyone but its owner.
29994
+ */
29995
+ var ReportedLoadContributionSchema = LoadContributionSchema.extend({ addonId: string() });
29790
29996
  var GetLoggingSettingsInputSchema = object({
29791
29997
  scopeNodeId: string().optional(),
29792
29998
  /**
@@ -29845,7 +30051,7 @@ method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), met
29845
30051
  }), method(_void(), SiteLocationStatusSchema, {
29846
30052
  kind: "mutation",
29847
30053
  auth: "admin"
29848
- }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
30054
+ }), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(_void(), array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
29849
30055
  kind: "mutation",
29850
30056
  auth: "admin"
29851
30057
  });
@@ -33036,6 +33242,12 @@ Object.freeze({
33036
33242
  addonId: null,
33037
33243
  access: "create"
33038
33244
  },
33245
+ "dataStoreProvider.insertMany": {
33246
+ capName: "data-store-provider",
33247
+ capScope: "system",
33248
+ addonId: null,
33249
+ access: "create"
33250
+ },
33039
33251
  "dataStoreProvider.isEmpty": {
33040
33252
  capName: "data-store-provider",
33041
33253
  capScope: "system",
@@ -34350,6 +34562,12 @@ Object.freeze({
34350
34562
  addonId: null,
34351
34563
  access: "create"
34352
34564
  },
34565
+ "loadContribution.list": {
34566
+ capName: "load-contribution",
34567
+ capScope: "system",
34568
+ addonId: null,
34569
+ access: "view"
34570
+ },
34353
34571
  "localNetwork.downloadCa": {
34354
34572
  capName: "local-network",
34355
34573
  capScope: "system",
@@ -34650,17 +34868,17 @@ Object.freeze({
34650
34868
  addonId: null,
34651
34869
  access: "view"
34652
34870
  },
34653
- "metricsProvider.getProcessStats": {
34871
+ "metricsProvider.getLoadSeries": {
34654
34872
  capName: "metrics-provider",
34655
34873
  capScope: "system",
34656
34874
  addonId: null,
34657
34875
  access: "view"
34658
34876
  },
34659
- "metricsProvider.killProcess": {
34877
+ "metricsProvider.getProcessStats": {
34660
34878
  capName: "metrics-provider",
34661
34879
  capScope: "system",
34662
34880
  addonId: null,
34663
- access: "create"
34881
+ access: "view"
34664
34882
  },
34665
34883
  "metricsProvider.listAddonInstances": {
34666
34884
  capName: "metrics-provider",
@@ -36672,6 +36890,12 @@ Object.freeze({
36672
36890
  addonId: null,
36673
36891
  access: "create"
36674
36892
  },
36893
+ "settingsStore.insertMany": {
36894
+ capName: "settings-store",
36895
+ capScope: "system",
36896
+ addonId: null,
36897
+ access: "create"
36898
+ },
36675
36899
  "settingsStore.isEmpty": {
36676
36900
  capName: "settings-store",
36677
36901
  capScope: "system",
@@ -37284,6 +37508,12 @@ Object.freeze({
37284
37508
  addonId: null,
37285
37509
  access: "create"
37286
37510
  },
37511
+ "system.getLoadContributions": {
37512
+ capName: "system",
37513
+ capScope: "system",
37514
+ addonId: null,
37515
+ access: "view"
37516
+ },
37287
37517
  "system.getLoggingSettings": {
37288
37518
  capName: "system",
37289
37519
  capScope: "system",
@@ -235971,10 +236201,12 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
235971
236201
  * descriptors served by the `stream-catalog` cap (broker PULLS via
235972
236202
  * `registerStreamCatalogProvider`).
235973
236203
  *
235974
- * Returns `[]` (logging at debug, matching the prior early-returns) on
235975
- * the battery-sleep gate, `ensureApi` failure, or
235976
- * `buildVideoStreamOptions` failure the broker simply sees no
235977
- * descriptors for the device until the next reconcile.
236204
+ * Returns `[]` only when there is genuinely nothing to say: the
236205
+ * battery-sleep gate (logged at debug), or a camera that connected and
236206
+ * reported zero streams. An `ensureApi` / `buildVideoStreamOptions` failure
236207
+ * THROWS `[]` and an error are not the same fact, and the broker needs
236208
+ * the difference to keep the device's existing entry and to schedule its
236209
+ * per-device backoff instead of treating the camera as stream-less.
235978
236210
  *
235979
236211
  * This method only BUILDS descriptors — it never opens an upstream
235980
236212
  * session or touches the broker.
@@ -236102,10 +236334,22 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
236102
236334
  channel: ownChannel,
236103
236335
  onNvr: true
236104
236336
  } : void 0;
236337
+ const readStreamOptions = async (current) => withTimeout(buildOptions ? current.buildVideoStreamOptions(buildOptions) : current.buildVideoStreamOptions(), 1e4, "buildVideoStreamOptions");
236105
236338
  try {
236106
- streamOptions = await withTimeout(buildOptions ? api.buildVideoStreamOptions(buildOptions) : api.buildVideoStreamOptions(), 1e4, "buildVideoStreamOptions");
236339
+ streamOptions = await readStreamOptions(api);
236107
236340
  } catch (err) {
236108
- throw new Error(`buildStreamCatalog: buildVideoStreamOptions failed for device ${this.id}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
236341
+ if (isChild) throw new Error(`buildStreamCatalog: buildVideoStreamOptions failed for device ${this.id}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
236342
+ this.ctx.logger.warn("buildStreamCatalog: stream options read failed — dropping the Baichuan session and retrying once", {
236343
+ tags: { deviceId: this.id },
236344
+ meta: { error: err instanceof Error ? err.message : String(err) }
236345
+ });
236346
+ await this.dropBaichuanSession("stream-catalog-retry");
236347
+ try {
236348
+ streamOptions = await readStreamOptions(await withTimeout(this.ensureApi(), 1e4, "ensureApi"));
236349
+ } catch (retryErr) {
236350
+ throw new Error(`buildStreamCatalog: buildVideoStreamOptions failed for device ${this.id} (after one re-auth retry): ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`, { cause: retryErr });
236351
+ }
236352
+ this.ctx.logger.info("buildStreamCatalog: stream options read recovered after re-auth", { tags: { deviceId: this.id } });
236109
236353
  }
236110
236354
  const channelCount = this.channelCount();
236111
236355
  const desired = /* @__PURE__ */ new Map();
@@ -238987,6 +239231,30 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
238987
239231
  * `awake` push (or a snapshot demand from the operator) will
238988
239232
  * lazily call `ensureApi()` and re-establish the client.
238989
239233
  */
239234
+ /**
239235
+ * Close and forget the cached control-channel client so the NEXT
239236
+ * `ensureApi()` performs a fresh login.
239237
+ *
239238
+ * The one thing `scheduleReconnect` cannot express: an EXPIRED session on a
239239
+ * socket that is still open. No `close`/`error` ever fires, so the reconnect
239240
+ * ladder is never armed, and `ensureApi`'s `if (this.api) return this.api`
239241
+ * keeps handing back a client whose every request answers `responseCode
239242
+ * 400`. Callers that see an application-level rejection they believe is a
239243
+ * dead session call this and retry ONCE — they must never loop on it.
239244
+ *
239245
+ * Same teardown the `disconnectAll` / debug-options paths use: `close()`
239246
+ * best-effort, then null the field. Active RFC 4571 servers are untouched —
239247
+ * each owns a dedicated socket and survives a control-channel reset (see
239248
+ * `scheduleReconnect`).
239249
+ */
239250
+ async dropBaichuanSession(reason) {
239251
+ const current = this.api;
239252
+ this.api = null;
239253
+ if (!current) return;
239254
+ try {
239255
+ await current.close({ reason });
239256
+ } catch {}
239257
+ }
238990
239258
  scheduleReconnect(reason) {
238991
239259
  if (this.reconnectTimer) return;
238992
239260
  if (this.isBattery) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.57",
3
+ "version": "1.2.59",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",
@@ -44,7 +44,8 @@
44
44
  "mode": "standalone",
45
45
  "execution": {
46
46
  "placement": "hub-only",
47
- "heapProfile": "heavy"
47
+ "heapProfile": "heavy",
48
+ "rssBudgetMb": 1024
48
49
  },
49
50
  "capabilities": [
50
51
  {