@camstack/system 1.2.60 → 1.2.62

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 (58) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  9. package/dist/builtins/console-logging/index.js +1 -1
  10. package/dist/builtins/console-logging/index.mjs +1 -1
  11. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  12. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  13. package/dist/builtins/device-manager/device-aggregation.d.ts +49 -2
  14. package/dist/builtins/device-manager/device-manager.addon.js +60 -5
  15. package/dist/builtins/device-manager/device-manager.addon.mjs +60 -5
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  18. package/dist/builtins/hub-forwarder/index.js +1 -1
  19. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  20. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  21. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  22. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  23. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  24. package/dist/builtins/local-network/local-network.addon.js +1 -1
  25. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  26. package/dist/builtins/loki-logging/index.js +1 -1
  27. package/dist/builtins/loki-logging/index.mjs +1 -1
  28. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  29. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  30. package/dist/builtins/platform-probe/index.js +1 -1
  31. package/dist/builtins/platform-probe/index.mjs +1 -1
  32. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  34. package/dist/builtins/snapshot/index.js +1 -1
  35. package/dist/builtins/snapshot/index.mjs +1 -1
  36. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  38. package/dist/builtins/sqlite-storage/prefix-range.d.ts +50 -0
  39. package/dist/builtins/sqlite-storage/query-bounds.d.ts +116 -0
  40. package/dist/builtins/sqlite-storage/sqlite-pragmas.d.ts +12 -0
  41. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +38 -2
  42. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +281 -19
  43. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +281 -19
  44. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  45. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  46. package/dist/builtins/system-config/system-config.addon.js +1 -1
  47. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  48. package/dist/builtins/winston-logging/index.js +1 -1
  49. package/dist/builtins/winston-logging/index.mjs +1 -1
  50. package/dist/{dist-Cis_iV-u.js → dist-DgPUarcs.js} +636 -3
  51. package/dist/{dist-C5XpOjbI.mjs → dist-DnKpFaP1.mjs} +636 -3
  52. package/dist/index.d.ts +2 -2
  53. package/dist/index.js +337 -12
  54. package/dist/index.mjs +328 -12
  55. package/dist/kernel/addon-residue.d.ts +49 -0
  56. package/dist/kernel/heap-watch.d.ts +48 -1
  57. package/dist/kernel/index.d.ts +2 -0
  58. package/package.json +1 -1
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_fs = require("node:fs");
@@ -1,4 +1,4 @@
1
- import { ft as errMsg, m as addonPagesCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
1
+ import { ft as errMsg, m as addonPagesCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as fs from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_fs = require("node:fs");
@@ -1,4 +1,4 @@
1
- import { ft as errMsg, g as addonWidgetsCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
1
+ import { ft as errMsg, g as addonWidgetsCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as fs from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  //#region src/builtins/alerts/prune-policy.ts
8
8
  /**
9
9
  * Ids older than `cutoffMs`, oldest first.
@@ -1,4 +1,4 @@
1
- import { Et as createEvent, _ as alertsCapability, ft as errMsg, n as AlertSchema, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
1
+ import { Et as createEvent, _ as alertsCapability, ft as errMsg, n as AlertSchema, pt as BaseAddon, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
2
2
  //#region src/builtins/alerts/prune-policy.ts
3
3
  /**
4
4
  * Ids older than `cutoffMs`, oldest first.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs = require("node:fs");
9
9
  let node_fs$1 = require_chunk.__toESM(node_fs, 1);
@@ -1,4 +1,4 @@
1
- import { b as backupCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
1
+ import { b as backupCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
2
2
  import { randomBytes, randomUUID } from "node:crypto";
3
3
  import * as fs from "node:fs";
4
4
  import Kt from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-C5XpOjbI.mjs";
1
+ import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-Cis_iV-u.js");
3
+ const require_dist = require("../../dist-DgPUarcs.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_path = require("node:path");
6
6
  node_path = require_chunk.__toESM(node_path);
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { C as coreBlocksCapability, o as CoreBlockSchema, pt as BaseAddon } from "../../dist-C5XpOjbI.mjs";
2
+ import { C as coreBlocksCapability, o as CoreBlockSchema, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
5
5
  import * as fs from "node:fs/promises";
@@ -88,6 +88,36 @@ export declare function resolveNativeDeviceOwner(deps: AggregationDeps, deviceId
88
88
  addonId: string;
89
89
  nodeId: string;
90
90
  } | null;
91
+ /**
92
+ * How long ONE cap's `getStatus` may take before the aggregate gives up on it
93
+ * and reports `null`.
94
+ *
95
+ * A cap call for a device owned by another node crosses the cluster, where the
96
+ * transport's own ceiling is `REMOTE_CALL_TIMEOUT_MS` — SIXTY seconds. That
97
+ * ceiling is right for a deliberate operator action; it is catastrophic here,
98
+ * because this aggregate is what a camera LIST asks for on every boot, and it
99
+ * has no answer to give until the slowest cap replies.
100
+ *
101
+ * The failure this bounds is not a node that is DOWN — `classifyCapRoute`
102
+ * rejects those immediately with `node-offline`, which the catch below turns
103
+ * into `null` in microseconds. It is a node that is FLAPPING: a crash-looping
104
+ * agent re-registers every few seconds, so the registry believes it is online,
105
+ * the call is routed, and the peer dies mid-flight. Then nothing throws and
106
+ * nothing resolves. On 2026-08-08 little-unraid did exactly that for 22
107
+ * minutes and the viewer showed a loading skeleton the whole time.
108
+ *
109
+ * 3s is chosen against what a HEALTHY remote cap costs (a same-LAN cap call is
110
+ * single-digit ms; the slowest observed live is ~300ms) — so this cannot cut
111
+ * off a working node — and against what a human waits for a camera list.
112
+ */
113
+ export declare const STATUS_CALL_DEADLINE_MS = 3000;
114
+ /** Per-call bound for a status aggregation. Tests pass a small value; nothing
115
+ * in production overrides the default. */
116
+ export interface StatusAggregateOptions {
117
+ /** Deadline for a single cap's `getStatus`. Defaults to
118
+ * {@link STATUS_CALL_DEADLINE_MS}. */
119
+ readonly deadlineMs?: number;
120
+ }
91
121
  /**
92
122
  * Aggregate `status` across every registered cap for a device.
93
123
  *
@@ -99,6 +129,10 @@ export declare function resolveNativeDeviceOwner(deps: AggregationDeps, deviceId
99
129
  * overall aggregate stays usable — a single misbehaving provider
100
130
  * must not blank out a device's entire status view.
101
131
  *
132
+ * A provider that never answers is treated exactly like one that throws: it
133
+ * costs its own cap and nothing else. See {@link STATUS_CALL_DEADLINE_MS} for
134
+ * why a bound is needed at all when the transport already has one.
135
+ *
102
136
  * Returned shape is `Record<capName, unknown | null>`; the client-
103
137
  * side hook tightens this to `CapStatusTypeMap` via the generated
104
138
  * `cap-status-types.ts`.
@@ -106,7 +140,7 @@ export declare function resolveNativeDeviceOwner(deps: AggregationDeps, deviceId
106
140
  export declare function getDeviceStatusAggregate(deps: AggregationDeps, input: {
107
141
  readonly deviceId: number;
108
142
  readonly caps?: readonly string[];
109
- }): Promise<Record<string, unknown | null>>;
143
+ }, options?: StatusAggregateOptions): Promise<Record<string, unknown | null>>;
110
144
  /**
111
145
  * `getDeviceStatusAggregate` for a LIST of devices, in one round-trip.
112
146
  *
@@ -120,11 +154,24 @@ export declare function getDeviceStatusAggregate(deps: AggregationDeps, input: {
120
154
  * A device whose whole aggregation fails still gets an ENTRY (all caps null)
121
155
  * rather than being omitted: a caller that reads a missing key as "not
122
156
  * answered yet" would keep the previous badge on screen forever.
157
+ *
158
+ * ## The response is BOUNDED, and that is load-bearing
159
+ *
160
+ * The windows are walked SEQUENTIALLY, so before the per-cap deadline existed a
161
+ * single unanswerable device did not merely cost its own window — it held every
162
+ * later window behind it, and the call never returned at all. That is the whole
163
+ * mechanism behind "one node offline and the app loads nothing": this is the
164
+ * one call a camera list makes for every card, the client has no timeout of its
165
+ * own, and a tRPC query that never settles is `isLoading` forever.
166
+ *
167
+ * The ceiling is now `ceil(devices / STATUS_BATCH_WINDOW) × STATUS_CALL_DEADLINE_MS`
168
+ * in the worst case (a dead device in every window), and unchanged when every
169
+ * node is healthy. It is never unbounded.
123
170
  */
124
171
  export declare function getDeviceStatusAggregateBatch(deps: AggregationDeps, input: {
125
172
  readonly deviceIds: readonly number[];
126
173
  readonly caps?: readonly string[];
127
- }): Promise<Record<string, Record<string, unknown | null>>>;
174
+ }, options?: StatusAggregateOptions): Promise<Record<string, Record<string, unknown | null>>>;
128
175
  /**
129
176
  * Return the driver-specific device-settings contribution. Hub-local
130
177
  * devices call `getSettingsUISchema()` directly; forked-worker devices
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  let _camstack_types_node = require("@camstack/types/node");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
@@ -1992,6 +1992,30 @@ function resolveNativeDeviceOwner(deps, deviceId) {
1992
1992
  } : null;
1993
1993
  }
1994
1994
  /**
1995
+ * Race `work` against a deadline WITHOUT swallowing its rejection.
1996
+ *
1997
+ * `Promise.race` attaches a handler to every entrant, so a `work` that rejects
1998
+ * after the deadline already fired is still handled — no unhandled rejection,
1999
+ * and no `.catch(() => {})` that would also hide a genuine throw from the
2000
+ * caller's `try`. A rejection propagates out of here exactly as it would
2001
+ * without the race; only the TIMEOUT is turned into a value.
2002
+ */
2003
+ async function withDeadline(work, deadlineMs) {
2004
+ let timer;
2005
+ const deadline = new Promise((resolve) => {
2006
+ timer = setTimeout(() => resolve({ kind: "timed-out" }), deadlineMs);
2007
+ timer.unref?.();
2008
+ });
2009
+ try {
2010
+ return await Promise.race([work.then((value) => ({
2011
+ kind: "value",
2012
+ value
2013
+ })), deadline]);
2014
+ } finally {
2015
+ if (timer !== void 0) clearTimeout(timer);
2016
+ }
2017
+ }
2018
+ /**
1995
2019
  * Aggregate `status` across every registered cap for a device.
1996
2020
  *
1997
2021
  * Walks the supplied cap list (or `CAP_NAMES_WITH_STATUS` when
@@ -2002,11 +2026,16 @@ function resolveNativeDeviceOwner(deps, deviceId) {
2002
2026
  * overall aggregate stays usable — a single misbehaving provider
2003
2027
  * must not blank out a device's entire status view.
2004
2028
  *
2029
+ * A provider that never answers is treated exactly like one that throws: it
2030
+ * costs its own cap and nothing else. See {@link STATUS_CALL_DEADLINE_MS} for
2031
+ * why a bound is needed at all when the transport already has one.
2032
+ *
2005
2033
  * Returned shape is `Record<capName, unknown | null>`; the client-
2006
2034
  * side hook tightens this to `CapStatusTypeMap` via the generated
2007
2035
  * `cap-status-types.ts`.
2008
2036
  */
2009
- async function getDeviceStatusAggregate(deps, input) {
2037
+ async function getDeviceStatusAggregate(deps, input, options) {
2038
+ const deadlineMs = options?.deadlineMs ?? 3e3;
2010
2039
  const capNames = input.caps ?? require_dist.CAP_NAMES_WITH_STATUS;
2011
2040
  const registry = deps.capabilityRegistry;
2012
2041
  const out = {};
@@ -2026,7 +2055,20 @@ async function getDeviceStatusAggregate(deps, input) {
2026
2055
  out[capName] = null;
2027
2056
  return;
2028
2057
  }
2029
- const raw = await provider.getStatus({ deviceId: input.deviceId });
2058
+ const settled = await withDeadline(provider.getStatus({ deviceId: input.deviceId }), deadlineMs);
2059
+ if (settled.kind === "timed-out") {
2060
+ deps.ctx.logger.warn("getDeviceStatusAggregate: cap missed its deadline, dropping", {
2061
+ tags: { deviceId: input.deviceId },
2062
+ meta: {
2063
+ deviceId: input.deviceId,
2064
+ capName,
2065
+ deadlineMs
2066
+ }
2067
+ });
2068
+ out[capName] = null;
2069
+ return;
2070
+ }
2071
+ const raw = settled.value;
2030
2072
  if (raw == null) {
2031
2073
  out[capName] = null;
2032
2074
  return;
@@ -2080,8 +2122,21 @@ var STATUS_BATCH_WINDOW = 10;
2080
2122
  * A device whose whole aggregation fails still gets an ENTRY (all caps null)
2081
2123
  * rather than being omitted: a caller that reads a missing key as "not
2082
2124
  * answered yet" would keep the previous badge on screen forever.
2125
+ *
2126
+ * ## The response is BOUNDED, and that is load-bearing
2127
+ *
2128
+ * The windows are walked SEQUENTIALLY, so before the per-cap deadline existed a
2129
+ * single unanswerable device did not merely cost its own window — it held every
2130
+ * later window behind it, and the call never returned at all. That is the whole
2131
+ * mechanism behind "one node offline and the app loads nothing": this is the
2132
+ * one call a camera list makes for every card, the client has no timeout of its
2133
+ * own, and a tRPC query that never settles is `isLoading` forever.
2134
+ *
2135
+ * The ceiling is now `ceil(devices / STATUS_BATCH_WINDOW) × STATUS_CALL_DEADLINE_MS`
2136
+ * in the worst case (a dead device in every window), and unchanged when every
2137
+ * node is healthy. It is never unbounded.
2083
2138
  */
2084
- async function getDeviceStatusAggregateBatch(deps, input) {
2139
+ async function getDeviceStatusAggregateBatch(deps, input, options) {
2085
2140
  const out = {};
2086
2141
  const ids = [...new Set(input.deviceIds)];
2087
2142
  for (let i = 0; i < ids.length; i += STATUS_BATCH_WINDOW) {
@@ -2090,7 +2145,7 @@ async function getDeviceStatusAggregateBatch(deps, input) {
2090
2145
  try {
2091
2146
  const single = { deviceId };
2092
2147
  if (input.caps !== void 0) single.caps = input.caps;
2093
- out[String(deviceId)] = await getDeviceStatusAggregate(deps, single);
2148
+ out[String(deviceId)] = await getDeviceStatusAggregate(deps, single, options);
2094
2149
  } catch (err) {
2095
2150
  deps.ctx.logger.warn("getDeviceStatusAggregateBatch: device aggregation threw", {
2096
2151
  tags: { deviceId },
@@ -1,4 +1,4 @@
1
- import { D as deviceStateCapability, E as deviceManagerCapability, I as getByPath, M as enumerateSchemaFields, N as evaluateLinkExpression, O as deviceStatusCapability, Rt as sleep, S as buildStreamParamsConfigSchema, St as WELL_KNOWN_TAB_MAP, Y as normalizeUnit, Z as parseStreamParamsFormPatch, _t as DeviceFeature, a as CAP_NAMES_WITH_STATUS, ft as errMsg, j as enumerateItemArrayFields, jt as isDeviceConfigCap, lt as validateExpressionSource, pt as BaseAddon, s as DeviceStatusSchema, st as toExpressionValue, t as ALL_CAPABILITY_DEFINITIONS, tt as setByPath, u as STREAM_PROFILE_META, v as applyTransform, vt as DeviceRole, x as bareAddonId, yt as DeviceType, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
1
+ import { D as deviceStateCapability, E as deviceManagerCapability, I as getByPath, M as enumerateSchemaFields, N as evaluateLinkExpression, O as deviceStatusCapability, Rt as sleep, S as buildStreamParamsConfigSchema, St as WELL_KNOWN_TAB_MAP, Y as normalizeUnit, Z as parseStreamParamsFormPatch, _t as DeviceFeature, a as CAP_NAMES_WITH_STATUS, ft as errMsg, j as enumerateItemArrayFields, jt as isDeviceConfigCap, lt as validateExpressionSource, pt as BaseAddon, s as DeviceStatusSchema, st as toExpressionValue, t as ALL_CAPABILITY_DEFINITIONS, tt as setByPath, u as STREAM_PROFILE_META, v as applyTransform, vt as DeviceRole, x as bareAddonId, yt as DeviceType, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
2
2
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
@@ -1987,6 +1987,30 @@ function resolveNativeDeviceOwner(deps, deviceId) {
1987
1987
  } : null;
1988
1988
  }
1989
1989
  /**
1990
+ * Race `work` against a deadline WITHOUT swallowing its rejection.
1991
+ *
1992
+ * `Promise.race` attaches a handler to every entrant, so a `work` that rejects
1993
+ * after the deadline already fired is still handled — no unhandled rejection,
1994
+ * and no `.catch(() => {})` that would also hide a genuine throw from the
1995
+ * caller's `try`. A rejection propagates out of here exactly as it would
1996
+ * without the race; only the TIMEOUT is turned into a value.
1997
+ */
1998
+ async function withDeadline(work, deadlineMs) {
1999
+ let timer;
2000
+ const deadline = new Promise((resolve) => {
2001
+ timer = setTimeout(() => resolve({ kind: "timed-out" }), deadlineMs);
2002
+ timer.unref?.();
2003
+ });
2004
+ try {
2005
+ return await Promise.race([work.then((value) => ({
2006
+ kind: "value",
2007
+ value
2008
+ })), deadline]);
2009
+ } finally {
2010
+ if (timer !== void 0) clearTimeout(timer);
2011
+ }
2012
+ }
2013
+ /**
1990
2014
  * Aggregate `status` across every registered cap for a device.
1991
2015
  *
1992
2016
  * Walks the supplied cap list (or `CAP_NAMES_WITH_STATUS` when
@@ -1997,11 +2021,16 @@ function resolveNativeDeviceOwner(deps, deviceId) {
1997
2021
  * overall aggregate stays usable — a single misbehaving provider
1998
2022
  * must not blank out a device's entire status view.
1999
2023
  *
2024
+ * A provider that never answers is treated exactly like one that throws: it
2025
+ * costs its own cap and nothing else. See {@link STATUS_CALL_DEADLINE_MS} for
2026
+ * why a bound is needed at all when the transport already has one.
2027
+ *
2000
2028
  * Returned shape is `Record<capName, unknown | null>`; the client-
2001
2029
  * side hook tightens this to `CapStatusTypeMap` via the generated
2002
2030
  * `cap-status-types.ts`.
2003
2031
  */
2004
- async function getDeviceStatusAggregate(deps, input) {
2032
+ async function getDeviceStatusAggregate(deps, input, options) {
2033
+ const deadlineMs = options?.deadlineMs ?? 3e3;
2005
2034
  const capNames = input.caps ?? CAP_NAMES_WITH_STATUS;
2006
2035
  const registry = deps.capabilityRegistry;
2007
2036
  const out = {};
@@ -2021,7 +2050,20 @@ async function getDeviceStatusAggregate(deps, input) {
2021
2050
  out[capName] = null;
2022
2051
  return;
2023
2052
  }
2024
- const raw = await provider.getStatus({ deviceId: input.deviceId });
2053
+ const settled = await withDeadline(provider.getStatus({ deviceId: input.deviceId }), deadlineMs);
2054
+ if (settled.kind === "timed-out") {
2055
+ deps.ctx.logger.warn("getDeviceStatusAggregate: cap missed its deadline, dropping", {
2056
+ tags: { deviceId: input.deviceId },
2057
+ meta: {
2058
+ deviceId: input.deviceId,
2059
+ capName,
2060
+ deadlineMs
2061
+ }
2062
+ });
2063
+ out[capName] = null;
2064
+ return;
2065
+ }
2066
+ const raw = settled.value;
2025
2067
  if (raw == null) {
2026
2068
  out[capName] = null;
2027
2069
  return;
@@ -2075,8 +2117,21 @@ var STATUS_BATCH_WINDOW = 10;
2075
2117
  * A device whose whole aggregation fails still gets an ENTRY (all caps null)
2076
2118
  * rather than being omitted: a caller that reads a missing key as "not
2077
2119
  * answered yet" would keep the previous badge on screen forever.
2120
+ *
2121
+ * ## The response is BOUNDED, and that is load-bearing
2122
+ *
2123
+ * The windows are walked SEQUENTIALLY, so before the per-cap deadline existed a
2124
+ * single unanswerable device did not merely cost its own window — it held every
2125
+ * later window behind it, and the call never returned at all. That is the whole
2126
+ * mechanism behind "one node offline and the app loads nothing": this is the
2127
+ * one call a camera list makes for every card, the client has no timeout of its
2128
+ * own, and a tRPC query that never settles is `isLoading` forever.
2129
+ *
2130
+ * The ceiling is now `ceil(devices / STATUS_BATCH_WINDOW) × STATUS_CALL_DEADLINE_MS`
2131
+ * in the worst case (a dead device in every window), and unchanged when every
2132
+ * node is healthy. It is never unbounded.
2078
2133
  */
2079
- async function getDeviceStatusAggregateBatch(deps, input) {
2134
+ async function getDeviceStatusAggregateBatch(deps, input, options) {
2080
2135
  const out = {};
2081
2136
  const ids = [...new Set(input.deviceIds)];
2082
2137
  for (let i = 0; i < ids.length; i += STATUS_BATCH_WINDOW) {
@@ -2085,7 +2140,7 @@ async function getDeviceStatusAggregateBatch(deps, input) {
2085
2140
  try {
2086
2141
  const single = { deviceId };
2087
2142
  if (input.caps !== void 0) single.caps = input.caps;
2088
- out[String(deviceId)] = await getDeviceStatusAggregate(deps, single);
2143
+ out[String(deviceId)] = await getDeviceStatusAggregate(deps, single, options);
2089
2144
  } catch (err) {
2090
2145
  deps.ctx.logger.warn("getDeviceStatusAggregateBatch: device aggregation threw", {
2091
2146
  tags: { deviceId },
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  //#region src/builtins/doorbell/trigger-engine.ts
8
8
  /**
9
9
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -1,4 +1,4 @@
1
- import { B as isSameAddonId, Et as createEvent, ft as errMsg, k as doorbellCapability, pt as BaseAddon, x as bareAddonId, yt as DeviceType, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
1
+ import { B as isSameAddonId, Et as createEvent, ft as errMsg, k as doorbellCapability, pt as BaseAddon, x as bareAddonId, yt as DeviceType, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
2
2
  //#region src/builtins/doorbell/trigger-engine.ts
3
3
  /**
4
4
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-C5XpOjbI.mjs";
1
+ import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-Cis_iV-u.js");
3
+ const require_dist = require("../../dist-DgPUarcs.js");
4
4
  //#region src/builtins/liveness-monitor/liveness-checks.ts
5
5
  var NO_DEVICES = "liveness:no-devices";
6
6
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -1,4 +1,4 @@
1
- import { Et as createEvent, ft as errMsg, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
1
+ import { Et as createEvent, ft as errMsg, pt as BaseAddon, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
2
2
  //#region src/builtins/liveness-monitor/liveness-checks.ts
3
3
  var NO_DEVICES = "liveness:no-devices";
4
4
  var ALL_OFFLINE = "liveness:all-devices-offline";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  let crypto$1 = require("crypto");
@@ -1,5 +1,5 @@
1
1
  import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
2
- import { ct as userManagementCapability, d as ScopedTokenSchema, p as UserRecordSchema, pt as BaseAddon, r as ApiKeyRecordSchema, y as authProviderCapability } from "../../dist-C5XpOjbI.mjs";
2
+ import { ct as userManagementCapability, d as ScopedTokenSchema, p as UserRecordSchema, pt as BaseAddon, r as ApiKeyRecordSchema, y as authProviderCapability } from "../../dist-DnKpFaP1.mjs";
3
3
  import * as crypto$1 from "node:crypto";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import nodeCrypto from "crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-Cis_iV-u.js");
3
+ const require_dist = require("../../dist-DgPUarcs.js");
4
4
  let node_os = require("node:os");
5
5
  node_os = require_chunk.__toESM(node_os);
6
6
  //#region src/builtins/local-network/local-network.addon.ts
@@ -1,4 +1,4 @@
1
- import { W as localNetworkCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
1
+ import { W as localNetworkCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
2
2
  import * as os from "node:os";
3
3
  //#region src/builtins/local-network/local-network.addon.ts
4
4
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -1,4 +1,4 @@
1
- import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-C5XpOjbI.mjs";
1
+ import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,4 +1,4 @@
1
- import { Et as createEvent, J as metricsProviderCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
1
+ import { Et as createEvent, J as metricsProviderCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile, execFileSync } from "node:child_process";
4
4
  import { promisify } from "node:util";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { A as enumerateInferenceDevices, Ot as emitReadiness, Q as platformProbeCapability, et as scoreRuntimes, ft as errMsg, pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
2
+ import { A as enumerateInferenceDevices, Ot as emitReadiness, Q as platformProbeCapability, et as scoreRuntimes, ft as errMsg, pt as BaseAddon, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import { execFile } from "node:child_process";
5
5
  import { promisify } from "node:util";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { pt as BaseAddon, zt as EventCategory } from "../../dist-C5XpOjbI.mjs";
1
+ import { pt as BaseAddon, zt as EventCategory } from "../../dist-DnKpFaP1.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  //#region src/builtins/snapshot/snapshot-coalescing.ts
9
9
  /**
@@ -1,4 +1,4 @@
1
- import { Mt as nodePin, _t as DeviceFeature, ft as errMsg, i as BatteryStatusSchema, ot as streamQualityLabel, pt as BaseAddon, rt as snapshotCapability, x as bareAddonId, yt as DeviceType } from "../../dist-C5XpOjbI.mjs";
1
+ import { Mt as nodePin, _t as DeviceFeature, ft as errMsg, i as BatteryStatusSchema, ot as streamQualityLabel, pt as BaseAddon, rt as snapshotCapability, x as bareAddonId, yt as DeviceType } from "../../dist-DnKpFaP1.mjs";
2
2
  import { execFile, spawn } from "node:child_process";
3
3
  //#region src/builtins/snapshot/snapshot-coalescing.ts
4
4
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-Cis_iV-u.js");
6
+ const require_dist = require("../../dist-DgPUarcs.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { F as filesystemBrowseCapability, at as storageProviderCapability, pt as BaseAddon } from "../../dist-C5XpOjbI.mjs";
1
+ import { F as filesystemBrowseCapability, at as storageProviderCapability, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";