camstack 1.2.36 → 1.2.37

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.
@@ -27017,6 +27017,13 @@ var CameraStatusSchema = external_exports.object({
27017
27017
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
27018
27018
  fetchedAt: external_exports.number()
27019
27019
  });
27020
+ var INFERENCE_DEVICE_EXCLUSION_REASONS = [
27021
+ "disabled",
27022
+ "unavailable",
27023
+ "cannot-host-camera-root",
27024
+ "accelerator-preferred"
27025
+ ];
27026
+ var InferenceDeviceExclusionReasonSchema = external_exports.enum(INFERENCE_DEVICE_EXCLUSION_REASONS);
27020
27027
  var NodeInferenceDeviceSchema = external_exports.object({
27021
27028
  /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
27022
27029
  key: external_exports.string(),
@@ -27047,7 +27054,17 @@ var NodeInferenceDeviceSchema = external_exports.object({
27047
27054
  * available per format; this is the stored selection that becomes the
27048
27055
  * default for EVERY camera landing on this accelerator.
27049
27056
  */
27050
- steps: external_exports.record(external_exports.string(), DeviceStepConfigSchema).optional()
27057
+ steps: external_exports.record(external_exports.string(), DeviceStepConfigSchema).optional(),
27058
+ /**
27059
+ * `null` when the device IS a camera-root candidate on this node; otherwise
27060
+ * the reason the dispatcher drops it. Computed by the SAME
27061
+ * `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
27062
+ * never disagree with the election — deriving it in the UI from
27063
+ * `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
27064
+ * the node's model catalog) and `accelerator-preferred` (needs the node-wide
27065
+ * "an accelerator is serving" predicate).
27066
+ */
27067
+ exclusion: InferenceDeviceExclusionReasonSchema.nullable()
27051
27068
  });
27052
27069
  var NodeInferenceDevicesSchema = external_exports.object({
27053
27070
  nodeId: external_exports.string(),
@@ -32057,7 +32074,35 @@ var ConnectionEndpointSchema = external_exports.object({
32057
32074
  */
32058
32075
  priority: external_exports.number()
32059
32076
  });
32060
- var GetConnectionEndpointsResultSchema = external_exports.object({ endpoints: external_exports.array(ConnectionEndpointSchema).readonly() });
32077
+ var LocalPortSourceEnum = external_exports.enum([
32078
+ "server-config",
32079
+ "server-env",
32080
+ "caller-hint",
32081
+ "default"
32082
+ ]);
32083
+ var AdvertisedLocalPortSchema = external_exports.object({
32084
+ port: external_exports.number().int().min(1).max(65535),
32085
+ source: LocalPortSourceEnum
32086
+ });
32087
+ var GetConnectionEndpointsResultSchema = external_exports.object({
32088
+ endpoints: external_exports.array(ConnectionEndpointSchema).readonly(),
32089
+ /**
32090
+ * The port the hub built the LAN/loopback URLs with, and where that number
32091
+ * came from.
32092
+ *
32093
+ * Returned rather than merely applied, because "the URL is right" and "the
32094
+ * client can KNOW the URL is right" are different properties. A client that
32095
+ * only sees a corrected URL cannot distinguish a hub that fixed the port from
32096
+ * a hub that echoed the port the client sent, so it cannot decide whether to
32097
+ * race the candidate or discard it. With `source` it can: anything but
32098
+ * `caller-hint` is the hub's own socket.
32099
+ *
32100
+ * Absent on hubs predating this field — a client that finds it missing is
32101
+ * talking to an echoing hub and must degrade exactly as it does for
32102
+ * `caller-hint`.
32103
+ */
32104
+ localPort: AdvertisedLocalPortSchema
32105
+ });
32061
32106
  var NotificationEndpointSchema = external_exports.object({
32062
32107
  /** The operator's explicit choice, or null for AUTO. */
32063
32108
  baseUrl: external_exports.string().nullable(),
@@ -32099,10 +32144,35 @@ var localNetworkCapability = {
32099
32144
  * Honours `getAllowedAddresses()` when set — addresses outside
32100
32145
  * the allowlist are dropped (the public tunnel + loopback are
32101
32146
  * always included as escape hatches).
32147
+ *
32148
+ * **The port is the hub's, not the caller's** (D62 — a function's fact
32149
+ * belongs to whoever already owns it). This method used to take a `port`
32150
+ * and echo it onto every LAN and loopback URL it advertised, which made
32151
+ * every client a second authority on a socket only the hub binds. A viewer
32152
+ * configured with a port-less public URL (`https://camstack.example.top`)
32153
+ * infers 443 from the scheme, asks with 443, and was handed
32154
+ * `https://192.168.1.9:443` for a hub listening on 4443. That candidate
32155
+ * cannot ever answer, and losing its race reads in the log exactly like
32156
+ * "the LAN was tried and it was slower" — so an operator sitting on his own
32157
+ * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
32158
+ * dead on every single connect.
32159
+ *
32160
+ * The hub now resolves its own listen port and reports it as `localPort`,
32161
+ * with the SOURCE of the number, so a client can tell a fact from an echo.
32162
+ *
32163
+ * Out of scope on purpose: a LAN URL that is not the hub's own socket (a
32164
+ * reverse proxy in front of it on another port) is not this method's to
32165
+ * invent either — pin it with `setNotificationEndpoint` / a configured
32166
+ * origin, which is an operator statement rather than a guess.
32102
32167
  */
32103
32168
  getConnectionEndpoints: method(external_exports.object({
32104
- /** Local hub HTTP port to use in base URLs. */
32105
- port: external_exports.number().int().min(1).max(65535),
32169
+ /**
32170
+ * LEGACY HINT — do not send from new code. Kept optional so clients
32171
+ * written against the echoing contract keep working; the hub uses it
32172
+ * only when it cannot read its own port, and says so via
32173
+ * `localPort.source === 'caller-hint'`.
32174
+ */
32175
+ port: external_exports.number().int().min(1).max(65535).optional(),
32106
32176
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
32107
32177
  * candidate. Default `true`. */
32108
32178
  includeLoopback: external_exports.boolean().optional(),
@@ -36593,6 +36663,7 @@ var BATTERY_DEVICE_PROFILE = {
36593
36663
  settings: {}
36594
36664
  };
36595
36665
  var BATTERY_UNREACHABLE_AFTER_MS = 360 * 6e4;
36666
+ var OPERATOR_WRITTEN_STALE_MS = 10 * 6e4;
36596
36667
  var METHOD_ACCESS_MAP = Object.freeze({
36597
36668
  "accessories.setChildHidden": {
36598
36669
  capName: "accessories",
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runDiscover
4
- } from "./chunk-V75HN7AE.js";
4
+ } from "./chunk-OR2CMMOE.js";
5
5
  import "./chunk-LMMQX4CK.js";
6
6
 
7
7
  // src/cli.ts
@@ -38,7 +38,7 @@ async function runServe(args) {
38
38
  ...typeof values.data === "string" ? { data: values.data } : {}
39
39
  };
40
40
  Object.assign(process.env, buildServeEnv(opts));
41
- await import("./launcher-OXQ3W5WV.js");
41
+ await import("./launcher-6VDFMPM6.js");
42
42
  }
43
43
 
44
44
  // src/commands/agent.ts
@@ -83,7 +83,7 @@ async function runAgent(args) {
83
83
  ...typeof values.port === "string" ? { port: values.port } : {}
84
84
  };
85
85
  Object.assign(process.env, buildAgentEnv(opts));
86
- await import("./launcher-OXQ3W5WV.js");
86
+ await import("./launcher-6VDFMPM6.js");
87
87
  }
88
88
 
89
89
  // src/commands/setup.ts
@@ -1130,7 +1130,7 @@ function isUnknown(_value) {
1130
1130
  return true;
1131
1131
  }
1132
1132
  async function resolveServerInteractive(presetNamespace) {
1133
- const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-FXX2XNDX.js");
1133
+ const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-Y3C7HOBU.js");
1134
1134
  if (presetNamespace) {
1135
1135
  const spinner4 = clack.spinner();
1136
1136
  spinner4.start(`Discovering hub on LAN (namespace "${presetNamespace}")`);
@@ -5,7 +5,7 @@ import {
5
5
  filterHubNodes,
6
6
  resolveHubFromDiscovered,
7
7
  runDiscover
8
- } from "./chunk-V75HN7AE.js";
8
+ } from "./chunk-OR2CMMOE.js";
9
9
  import "./chunk-LMMQX4CK.js";
10
10
  export {
11
11
  DEFAULT_HUB_HTTPS_PORT,
@@ -23631,9 +23631,9 @@ var require_zod = __commonJS({
23631
23631
  }
23632
23632
  });
23633
23633
 
23634
- // ../system/dist/dist-B2CtwVW2.js
23635
- var require_dist_B2CtwVW2 = __commonJS({
23636
- "../system/dist/dist-B2CtwVW2.js"(exports) {
23634
+ // ../system/dist/dist-C1Aea3c6.js
23635
+ var require_dist_C1Aea3c6 = __commonJS({
23636
+ "../system/dist/dist-C1Aea3c6.js"(exports) {
23637
23637
  "use strict";
23638
23638
  var zod = require_zod();
23639
23639
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -37300,6 +37300,12 @@ var require_dist_B2CtwVW2 = __commonJS({
37300
37300
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
37301
37301
  fetchedAt: zod.z.number()
37302
37302
  });
37303
+ var InferenceDeviceExclusionReasonSchema = zod.z.enum([
37304
+ "disabled",
37305
+ "unavailable",
37306
+ "cannot-host-camera-root",
37307
+ "accelerator-preferred"
37308
+ ]);
37303
37309
  var NodeInferenceDeviceSchema = zod.z.object({
37304
37310
  /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
37305
37311
  key: zod.z.string(),
@@ -37330,7 +37336,17 @@ var require_dist_B2CtwVW2 = __commonJS({
37330
37336
  * available per format; this is the stored selection that becomes the
37331
37337
  * default for EVERY camera landing on this accelerator.
37332
37338
  */
37333
- steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional()
37339
+ steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional(),
37340
+ /**
37341
+ * `null` when the device IS a camera-root candidate on this node; otherwise
37342
+ * the reason the dispatcher drops it. Computed by the SAME
37343
+ * `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
37344
+ * never disagree with the election — deriving it in the UI from
37345
+ * `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
37346
+ * the node's model catalog) and `accelerator-preferred` (needs the node-wide
37347
+ * "an accelerator is serving" predicate).
37348
+ */
37349
+ exclusion: InferenceDeviceExclusionReasonSchema.nullable()
37334
37350
  });
37335
37351
  var NodeInferenceDevicesSchema = zod.z.object({
37336
37352
  nodeId: zod.z.string(),
@@ -42339,7 +42355,35 @@ var require_dist_B2CtwVW2 = __commonJS({
42339
42355
  */
42340
42356
  priority: zod.z.number()
42341
42357
  });
42342
- var GetConnectionEndpointsResultSchema = zod.z.object({ endpoints: zod.z.array(ConnectionEndpointSchema).readonly() });
42358
+ var LocalPortSourceEnum = zod.z.enum([
42359
+ "server-config",
42360
+ "server-env",
42361
+ "caller-hint",
42362
+ "default"
42363
+ ]);
42364
+ var AdvertisedLocalPortSchema = zod.z.object({
42365
+ port: zod.z.number().int().min(1).max(65535),
42366
+ source: LocalPortSourceEnum
42367
+ });
42368
+ var GetConnectionEndpointsResultSchema = zod.z.object({
42369
+ endpoints: zod.z.array(ConnectionEndpointSchema).readonly(),
42370
+ /**
42371
+ * The port the hub built the LAN/loopback URLs with, and where that number
42372
+ * came from.
42373
+ *
42374
+ * Returned rather than merely applied, because "the URL is right" and "the
42375
+ * client can KNOW the URL is right" are different properties. A client that
42376
+ * only sees a corrected URL cannot distinguish a hub that fixed the port from
42377
+ * a hub that echoed the port the client sent, so it cannot decide whether to
42378
+ * race the candidate or discard it. With `source` it can: anything but
42379
+ * `caller-hint` is the hub's own socket.
42380
+ *
42381
+ * Absent on hubs predating this field — a client that finds it missing is
42382
+ * talking to an echoing hub and must degrade exactly as it does for
42383
+ * `caller-hint`.
42384
+ */
42385
+ localPort: AdvertisedLocalPortSchema
42386
+ });
42343
42387
  var NotificationEndpointSchema = zod.z.object({
42344
42388
  /** The operator's explicit choice, or null for AUTO. */
42345
42389
  baseUrl: zod.z.string().nullable(),
@@ -42381,10 +42425,35 @@ var require_dist_B2CtwVW2 = __commonJS({
42381
42425
  * Honours `getAllowedAddresses()` when set — addresses outside
42382
42426
  * the allowlist are dropped (the public tunnel + loopback are
42383
42427
  * always included as escape hatches).
42428
+ *
42429
+ * **The port is the hub's, not the caller's** (D62 — a function's fact
42430
+ * belongs to whoever already owns it). This method used to take a `port`
42431
+ * and echo it onto every LAN and loopback URL it advertised, which made
42432
+ * every client a second authority on a socket only the hub binds. A viewer
42433
+ * configured with a port-less public URL (`https://camstack.example.top`)
42434
+ * infers 443 from the scheme, asks with 443, and was handed
42435
+ * `https://192.168.1.9:443` for a hub listening on 4443. That candidate
42436
+ * cannot ever answer, and losing its race reads in the log exactly like
42437
+ * "the LAN was tried and it was slower" — so an operator sitting on his own
42438
+ * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
42439
+ * dead on every single connect.
42440
+ *
42441
+ * The hub now resolves its own listen port and reports it as `localPort`,
42442
+ * with the SOURCE of the number, so a client can tell a fact from an echo.
42443
+ *
42444
+ * Out of scope on purpose: a LAN URL that is not the hub's own socket (a
42445
+ * reverse proxy in front of it on another port) is not this method's to
42446
+ * invent either — pin it with `setNotificationEndpoint` / a configured
42447
+ * origin, which is an operator statement rather than a guess.
42384
42448
  */
42385
42449
  getConnectionEndpoints: method(zod.z.object({
42386
- /** Local hub HTTP port to use in base URLs. */
42387
- port: zod.z.number().int().min(1).max(65535),
42450
+ /**
42451
+ * LEGACY HINT — do not send from new code. Kept optional so clients
42452
+ * written against the echoing contract keep working; the hub uses it
42453
+ * only when it cannot read its own port, and says so via
42454
+ * `localPort.source === 'caller-hint'`.
42455
+ */
42456
+ port: zod.z.number().int().min(1).max(65535).optional(),
42388
42457
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
42389
42458
  * candidate. Default `true`. */
42390
42459
  includeLoopback: zod.z.boolean().optional(),
@@ -56186,7 +56255,7 @@ var require_alerts_addon = __commonJS({
56186
56255
  [Symbol.toStringTag]: { value: "Module" }
56187
56256
  });
56188
56257
  require_chunk_Cek0wNdY();
56189
- var require_dist10 = require_dist_B2CtwVW2();
56258
+ var require_dist10 = require_dist_C1Aea3c6();
56190
56259
  function selectExpired(alerts, cutoffMs) {
56191
56260
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
56192
56261
  }
@@ -56999,7 +57068,7 @@ var require_console_logging = __commonJS({
56999
57068
  [Symbol.toStringTag]: { value: "Module" }
57000
57069
  });
57001
57070
  require_chunk_Cek0wNdY();
57002
- var require_dist10 = require_dist_B2CtwVW2();
57071
+ var require_dist10 = require_dist_C1Aea3c6();
57003
57072
  var require_formatter = require_formatter_DqAKDlvN();
57004
57073
  var LEVEL_RANK = {
57005
57074
  debug: 0,
@@ -57093,7 +57162,7 @@ var require_core_blocks_addon = __commonJS({
57093
57162
  "use strict";
57094
57163
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
57095
57164
  var require_chunk = require_chunk_Cek0wNdY();
57096
- var require_dist10 = require_dist_B2CtwVW2();
57165
+ var require_dist10 = require_dist_C1Aea3c6();
57097
57166
  var node_crypto = __require("crypto");
57098
57167
  var node_fs_promises = __require("fs/promises");
57099
57168
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -57990,11 +58059,11 @@ var require_core_blocks = __commonJS({
57990
58059
  }
57991
58060
  });
57992
58061
 
57993
- // ../system/dist/retired-settings-keys-CYDGGkyM.js
57994
- var require_retired_settings_keys_CYDGGkyM = __commonJS({
57995
- "../system/dist/retired-settings-keys-CYDGGkyM.js"(exports) {
58062
+ // ../system/dist/retired-settings-keys-B52KsNz8.js
58063
+ var require_retired_settings_keys_B52KsNz8 = __commonJS({
58064
+ "../system/dist/retired-settings-keys-B52KsNz8.js"(exports) {
57996
58065
  "use strict";
57997
- var require_dist10 = require_dist_B2CtwVW2();
58066
+ var require_dist10 = require_dist_C1Aea3c6();
57998
58067
  function settingsStoreIsAuthoritativeHere(env) {
57999
58068
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
58000
58069
  return raw === "" || raw === "hub";
@@ -60037,8 +60106,8 @@ var require_device_manager_addon = __commonJS({
60037
60106
  [Symbol.toStringTag]: { value: "Module" }
60038
60107
  });
60039
60108
  require_chunk_Cek0wNdY();
60040
- var require_dist10 = require_dist_B2CtwVW2();
60041
- var require_retired_settings_keys = require_retired_settings_keys_CYDGGkyM();
60109
+ var require_dist10 = require_dist_C1Aea3c6();
60110
+ var require_retired_settings_keys = require_retired_settings_keys_B52KsNz8();
60042
60111
  var node_crypto = __require("crypto");
60043
60112
  var _camstack_types_node = require_node();
60044
60113
  var JOB_HISTORY = 20;
@@ -64365,7 +64434,7 @@ var require_hub_forwarder = __commonJS({
64365
64434
  [Symbol.toStringTag]: { value: "Module" }
64366
64435
  });
64367
64436
  require_chunk_Cek0wNdY();
64368
- var require_dist10 = require_dist_B2CtwVW2();
64437
+ var require_dist10 = require_dist_C1Aea3c6();
64369
64438
  var require_formatter = require_formatter_DqAKDlvN();
64370
64439
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
64371
64440
  var HubForwarderDestination = class {
@@ -64502,7 +64571,7 @@ var require_liveness_monitor_addon = __commonJS({
64502
64571
  "use strict";
64503
64572
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
64504
64573
  require_chunk_Cek0wNdY();
64505
- var require_dist10 = require_dist_B2CtwVW2();
64574
+ var require_dist10 = require_dist_C1Aea3c6();
64506
64575
  var NO_DEVICES = "liveness:no-devices";
64507
64576
  var ALL_OFFLINE = "liveness:all-devices-offline";
64508
64577
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -64692,7 +64761,7 @@ var require_local_auth_addon = __commonJS({
64692
64761
  [Symbol.toStringTag]: { value: "Module" }
64693
64762
  });
64694
64763
  var require_chunk = require_chunk_Cek0wNdY();
64695
- var require_dist10 = require_dist_B2CtwVW2();
64764
+ var require_dist10 = require_dist_C1Aea3c6();
64696
64765
  var node_crypto = __require("crypto");
64697
64766
  node_crypto = require_chunk.__toESM(node_crypto);
64698
64767
  var crypto$1 = __require("crypto");
@@ -72376,7 +72445,7 @@ var require_loki_logging = __commonJS({
72376
72445
  [Symbol.toStringTag]: { value: "Module" }
72377
72446
  });
72378
72447
  require_chunk_Cek0wNdY();
72379
- var require_dist10 = require_dist_B2CtwVW2();
72448
+ var require_dist10 = require_dist_C1Aea3c6();
72380
72449
  function sanitizeLabelName(raw) {
72381
72450
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
72382
72451
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -72941,7 +73010,7 @@ var require_native_metrics_addon = __commonJS({
72941
73010
  [Symbol.toStringTag]: { value: "Module" }
72942
73011
  });
72943
73012
  var require_chunk = require_chunk_Cek0wNdY();
72944
- var require_dist10 = require_dist_B2CtwVW2();
73013
+ var require_dist10 = require_dist_C1Aea3c6();
72945
73014
  var node_child_process = __require("child_process");
72946
73015
  var node_util = __require("util");
72947
73016
  var node_os = __require("os");
@@ -73883,7 +73952,7 @@ var require_filesystem_storage_addon = __commonJS({
73883
73952
  [Symbol.toStringTag]: { value: "Module" }
73884
73953
  });
73885
73954
  var require_chunk = require_chunk_Cek0wNdY();
73886
- var require_dist10 = require_dist_B2CtwVW2();
73955
+ var require_dist10 = require_dist_C1Aea3c6();
73887
73956
  var node_crypto = __require("crypto");
73888
73957
  var node_fs_promises = __require("fs/promises");
73889
73958
  var node_path = __require("path");
@@ -74999,8 +75068,8 @@ var require_sqlite_settings_addon = __commonJS({
74999
75068
  [Symbol.toStringTag]: { value: "Module" }
75000
75069
  });
75001
75070
  var require_chunk = require_chunk_Cek0wNdY();
75002
- var require_dist10 = require_dist_B2CtwVW2();
75003
- var require_retired_settings_keys = require_retired_settings_keys_CYDGGkyM();
75071
+ var require_dist10 = require_dist_C1Aea3c6();
75072
+ var require_retired_settings_keys = require_retired_settings_keys_B52KsNz8();
75004
75073
  var node_crypto = __require("crypto");
75005
75074
  var node_fs = __require("fs");
75006
75075
  var node_module = __require("module");
@@ -76544,7 +76613,7 @@ var require_storage_orchestrator_addon = __commonJS({
76544
76613
  [Symbol.toStringTag]: { value: "Module" }
76545
76614
  });
76546
76615
  var require_chunk = require_chunk_Cek0wNdY();
76547
- var require_dist10 = require_dist_B2CtwVW2();
76616
+ var require_dist10 = require_dist_C1Aea3c6();
76548
76617
  var node_crypto = __require("crypto");
76549
76618
  var node_fs_promises = __require("fs/promises");
76550
76619
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -78423,7 +78492,7 @@ var require_system_config_addon = __commonJS({
78423
78492
  [Symbol.toStringTag]: { value: "Module" }
78424
78493
  });
78425
78494
  require_chunk_Cek0wNdY();
78426
- var require_dist10 = require_dist_B2CtwVW2();
78495
+ var require_dist10 = require_dist_C1Aea3c6();
78427
78496
  var SECTION_TITLES = {
78428
78497
  server: "Server",
78429
78498
  auth: "Authentication"
@@ -96484,7 +96553,7 @@ var require_winston_logging = __commonJS({
96484
96553
  [Symbol.toStringTag]: { value: "Module" }
96485
96554
  });
96486
96555
  var require_chunk = require_chunk_Cek0wNdY();
96487
- var require_dist10 = require_dist_B2CtwVW2();
96556
+ var require_dist10 = require_dist_C1Aea3c6();
96488
96557
  var require_formatter = require_formatter_DqAKDlvN();
96489
96558
  var node_path = __require("path");
96490
96559
  node_path = require_chunk.__toESM(node_path);
@@ -118760,7 +118829,7 @@ var require_dist3 = __commonJS({
118760
118829
  "use strict";
118761
118830
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
118762
118831
  var require_chunk = require_chunk_Cek0wNdY();
118763
- var require_dist10 = require_dist_B2CtwVW2();
118832
+ var require_dist10 = require_dist_C1Aea3c6();
118764
118833
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
118765
118834
  require_alerts();
118766
118835
  var require_formatter = require_formatter_DqAKDlvN();
@@ -252258,6 +252327,13 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252258
252327
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
252259
252328
  fetchedAt: zod.z.number()
252260
252329
  });
252330
+ var INFERENCE_DEVICE_EXCLUSION_REASONS = [
252331
+ "disabled",
252332
+ "unavailable",
252333
+ "cannot-host-camera-root",
252334
+ "accelerator-preferred"
252335
+ ];
252336
+ var InferenceDeviceExclusionReasonSchema = zod.z.enum(INFERENCE_DEVICE_EXCLUSION_REASONS);
252261
252337
  var NodeInferenceDeviceSchema = zod.z.object({
252262
252338
  /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
252263
252339
  key: zod.z.string(),
@@ -252288,7 +252364,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252288
252364
  * available per format; this is the stored selection that becomes the
252289
252365
  * default for EVERY camera landing on this accelerator.
252290
252366
  */
252291
- steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional()
252367
+ steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional(),
252368
+ /**
252369
+ * `null` when the device IS a camera-root candidate on this node; otherwise
252370
+ * the reason the dispatcher drops it. Computed by the SAME
252371
+ * `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
252372
+ * never disagree with the election — deriving it in the UI from
252373
+ * `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
252374
+ * the node's model catalog) and `accelerator-preferred` (needs the node-wide
252375
+ * "an accelerator is serving" predicate).
252376
+ */
252377
+ exclusion: InferenceDeviceExclusionReasonSchema.nullable()
252292
252378
  });
252293
252379
  var NodeInferenceDevicesSchema = zod.z.object({
252294
252380
  nodeId: zod.z.string(),
@@ -257320,7 +257406,35 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
257320
257406
  */
257321
257407
  priority: zod.z.number()
257322
257408
  });
257323
- var GetConnectionEndpointsResultSchema = zod.z.object({ endpoints: zod.z.array(ConnectionEndpointSchema).readonly() });
257409
+ var LocalPortSourceEnum = zod.z.enum([
257410
+ "server-config",
257411
+ "server-env",
257412
+ "caller-hint",
257413
+ "default"
257414
+ ]);
257415
+ var AdvertisedLocalPortSchema = zod.z.object({
257416
+ port: zod.z.number().int().min(1).max(65535),
257417
+ source: LocalPortSourceEnum
257418
+ });
257419
+ var GetConnectionEndpointsResultSchema = zod.z.object({
257420
+ endpoints: zod.z.array(ConnectionEndpointSchema).readonly(),
257421
+ /**
257422
+ * The port the hub built the LAN/loopback URLs with, and where that number
257423
+ * came from.
257424
+ *
257425
+ * Returned rather than merely applied, because "the URL is right" and "the
257426
+ * client can KNOW the URL is right" are different properties. A client that
257427
+ * only sees a corrected URL cannot distinguish a hub that fixed the port from
257428
+ * a hub that echoed the port the client sent, so it cannot decide whether to
257429
+ * race the candidate or discard it. With `source` it can: anything but
257430
+ * `caller-hint` is the hub's own socket.
257431
+ *
257432
+ * Absent on hubs predating this field — a client that finds it missing is
257433
+ * talking to an echoing hub and must degrade exactly as it does for
257434
+ * `caller-hint`.
257435
+ */
257436
+ localPort: AdvertisedLocalPortSchema
257437
+ });
257324
257438
  var NotificationEndpointSchema = zod.z.object({
257325
257439
  /** The operator's explicit choice, or null for AUTO. */
257326
257440
  baseUrl: zod.z.string().nullable(),
@@ -257362,10 +257476,35 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
257362
257476
  * Honours `getAllowedAddresses()` when set — addresses outside
257363
257477
  * the allowlist are dropped (the public tunnel + loopback are
257364
257478
  * always included as escape hatches).
257479
+ *
257480
+ * **The port is the hub's, not the caller's** (D62 — a function's fact
257481
+ * belongs to whoever already owns it). This method used to take a `port`
257482
+ * and echo it onto every LAN and loopback URL it advertised, which made
257483
+ * every client a second authority on a socket only the hub binds. A viewer
257484
+ * configured with a port-less public URL (`https://camstack.example.top`)
257485
+ * infers 443 from the scheme, asks with 443, and was handed
257486
+ * `https://192.168.1.9:443` for a hub listening on 4443. That candidate
257487
+ * cannot ever answer, and losing its race reads in the log exactly like
257488
+ * "the LAN was tried and it was slower" — so an operator sitting on his own
257489
+ * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
257490
+ * dead on every single connect.
257491
+ *
257492
+ * The hub now resolves its own listen port and reports it as `localPort`,
257493
+ * with the SOURCE of the number, so a client can tell a fact from an echo.
257494
+ *
257495
+ * Out of scope on purpose: a LAN URL that is not the hub's own socket (a
257496
+ * reverse proxy in front of it on another port) is not this method's to
257497
+ * invent either — pin it with `setNotificationEndpoint` / a configured
257498
+ * origin, which is an operator statement rather than a guess.
257365
257499
  */
257366
257500
  getConnectionEndpoints: require_sleep.method(zod.z.object({
257367
- /** Local hub HTTP port to use in base URLs. */
257368
- port: zod.z.number().int().min(1).max(65535),
257501
+ /**
257502
+ * LEGACY HINT — do not send from new code. Kept optional so clients
257503
+ * written against the echoing contract keep working; the hub uses it
257504
+ * only when it cannot read its own port, and says so via
257505
+ * `localPort.source === 'caller-hint'`.
257506
+ */
257507
+ port: zod.z.number().int().min(1).max(65535).optional(),
257369
257508
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
257370
257509
  * candidate. Default `true`. */
257371
257510
  includeLoopback: zod.z.boolean().optional(),
@@ -263609,10 +263748,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263609
263748
  } };
263610
263749
  }
263611
263750
  var LAST_FETCHED_FIELD = "lastFetchedAt";
263751
+ var RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS = 6e4;
263752
+ var OPERATOR_WRITTEN_STALE_MS = 10 * 6e4;
263612
263753
  function createRuntimeStateBridge(params) {
263613
263754
  const { runtimeState, cap, ownDeviceId, refresh, staleMs, empty, logger } = params;
263614
263755
  const missCooldownMs = params.refreshMissCooldownMs ?? 6e4;
263756
+ const staleRead = params.staleRead ?? "await-refresh";
263615
263757
  let missCooldownUntil = 0;
263758
+ let refreshInFlight = null;
263616
263759
  const readFetchedAt = () => {
263617
263760
  const value = runtimeState.getCapState(cap.name)?.[LAST_FETCHED_FIELD];
263618
263761
  return typeof value === "number" ? value : 0;
@@ -263627,14 +263770,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263627
263770
  }
263628
263771
  });
263629
263772
  };
263630
- const ensureFresh = async () => {
263631
- const slice = runtimeState.getCapState(cap.name);
263632
- const fetchedAt = readFetchedAt();
263633
- if (slice && Date.now() - fetchedAt <= staleMs) {
263634
- missCooldownUntil = 0;
263635
- return;
263636
- }
263637
- if (Date.now() < missCooldownUntil) return;
263773
+ const runRefresh = async (fetchedAt) => {
263638
263774
  try {
263639
263775
  await refresh();
263640
263776
  } catch (err) {
@@ -263650,6 +263786,34 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
263650
263786
  }
263651
263787
  openMissCooldown(void 0);
263652
263788
  };
263789
+ const startRefresh = (fetchedAt) => {
263790
+ const existing = refreshInFlight;
263791
+ if (existing !== null) return existing;
263792
+ const started = runRefresh(fetchedAt).catch(() => void 0).finally(() => {
263793
+ refreshInFlight = null;
263794
+ });
263795
+ refreshInFlight = started;
263796
+ return started;
263797
+ };
263798
+ const ensureFresh = async () => {
263799
+ const slice = runtimeState.getCapState(cap.name);
263800
+ const fetchedAt = readFetchedAt();
263801
+ if (slice && Date.now() - fetchedAt <= staleMs) {
263802
+ missCooldownUntil = 0;
263803
+ return;
263804
+ }
263805
+ if (Date.now() < missCooldownUntil) return;
263806
+ if (staleRead === "serve-and-revalidate") {
263807
+ if (refreshInFlight !== null) return;
263808
+ if (slice && fetchedAt > 0) {
263809
+ startRefresh(fetchedAt);
263810
+ return;
263811
+ }
263812
+ await startRefresh(fetchedAt);
263813
+ return;
263814
+ }
263815
+ await runRefresh(fetchedAt);
263816
+ };
263653
263817
  const projectStatus = () => {
263654
263818
  const slice = runtimeState.getCapState(cap.name);
263655
263819
  if (!slice) return empty();
@@ -277087,6 +277251,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
277087
277251
  exports.HumidifierStatusSchema = HumidifierStatusSchema;
277088
277252
  exports.HumiditySensorStatusSchema = HumiditySensorStatusSchema;
277089
277253
  exports.HvacModeSchema = HvacModeSchema;
277254
+ exports.INFERENCE_DEVICE_EXCLUSION_REASONS = INFERENCE_DEVICE_EXCLUSION_REASONS;
277090
277255
  exports.ImageContractSchema = ImageContractSchema;
277091
277256
  exports.ImageContractStateSchema = ImageContractStateSchema;
277092
277257
  exports.ImageRotateSchema = ImageRotateSchema;
@@ -277094,6 +277259,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
277094
277259
  exports.ImageSettingsPatchSchema = ImageSettingsPatchSchema;
277095
277260
  exports.ImageSettingsStatusSchema = ImageSettingsStatusSchema;
277096
277261
  exports.ImageStatusSchema = ImageStatusSchema;
277262
+ exports.InferenceDeviceExclusionReasonSchema = InferenceDeviceExclusionReasonSchema;
277097
277263
  exports.IngestOwnerSchema = IngestOwnerSchema;
277098
277264
  exports.InstalledPackageSchema = InstalledPackageSchema;
277099
277265
  exports.IntegrationLiteSchema = IntegrationLiteSchema;
@@ -277306,6 +277472,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
277306
277472
  exports.NotificationSchema = NotificationSchema;
277307
277473
  exports.NotifierStatusSchema = NotifierStatusSchema;
277308
277474
  exports.NumericSensorStatusSchema = NumericSensorStatusSchema;
277475
+ exports.OPERATOR_WRITTEN_STALE_MS = OPERATOR_WRITTEN_STALE_MS;
277309
277476
  exports.OPS_LOG_DEFAULT_LIMIT = OPS_LOG_DEFAULT_LIMIT;
277310
277477
  exports.OPS_LOG_RING_DEFAULT_MAX = OPS_LOG_RING_DEFAULT_MAX;
277311
277478
  exports.OauthIntegrationDescriptorSchema = OauthIntegrationDescriptorSchema;
@@ -277388,6 +277555,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
277388
277555
  exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
277389
277556
  exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
277390
277557
  exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
277558
+ exports.RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS = RUNTIME_STATE_REFRESH_MISS_COOLDOWN_MS;
277391
277559
  exports.RUNTIME_TO_FORMAT = RUNTIME_TO_FORMAT;
277392
277560
  exports.RawStateResultSchema = require_sleep.RawStateResultSchema;
277393
277561
  exports.ReadGopBytesResultSchema = ReadGopBytesResultSchema;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "1.2.36",
3
+ "version": "1.2.37",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",