@trillboards/edge-sdk 0.2.4 → 0.2.6

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 (200) hide show
  1. package/README.md +4 -3
  2. package/deploy/docker/Dockerfile.cloud-sense +117 -0
  3. package/deploy/docker/Dockerfile.cpu +30 -21
  4. package/deploy/docker/Dockerfile.cuda +30 -21
  5. package/deploy/docker/Dockerfile.openvino +30 -20
  6. package/deploy/docker/README.md +9 -4
  7. package/deploy/docker/connector-entrypoint.sh +122 -0
  8. package/deploy/helm/README.md +1 -1
  9. package/deploy/k8s/daemonset.yaml +1 -1
  10. package/deploy/macos/README.md +161 -0
  11. package/deploy/macos/TrillboardsSetupApp.m +603 -0
  12. package/deploy/macos/build-macos-pkg.sh +588 -0
  13. package/deploy/macos/com.trillboards.edge.plist +62 -0
  14. package/deploy/macos/com.trillboards.litertlm.plist +45 -0
  15. package/deploy/macos/pair.command +519 -0
  16. package/deploy/macos/scripts/ensure-litertlm.sh +62 -0
  17. package/deploy/macos/scripts/postinstall +153 -0
  18. package/deploy/macos/scripts/setup-litertlm.sh +272 -0
  19. package/deploy/macos/test-install-pair.sh +383 -0
  20. package/deploy/macos/test-setup-litertlm-reinstall.sh +109 -0
  21. package/deploy/systemd/install.sh +25 -15
  22. package/deploy/systemd/trillboards-edge.service +4 -1
  23. package/deploy/windows/README.md +70 -0
  24. package/deploy/windows/build-windows.ps1 +225 -0
  25. package/deploy/windows/bundle-windows-installer.ps1 +153 -0
  26. package/deploy/windows/check-feeds.js +26 -0
  27. package/deploy/windows/e2e/fixtures/person.jpg +0 -0
  28. package/deploy/windows/e2e/test-local-sense.ps1 +185 -0
  29. package/deploy/windows/install-update.ps1 +101 -0
  30. package/deploy/windows/installer.nsi +138 -0
  31. package/deploy/windows/register-litertlm-task.ps1 +42 -0
  32. package/deploy/windows/register-task.ps1 +44 -0
  33. package/deploy/windows/run-connector.ps1 +69 -0
  34. package/deploy/windows/setup-litertlm.ps1 +361 -0
  35. package/deploy/windows/stop-existing.ps1 +72 -0
  36. package/deploy/windows/trillboards-edge-task.vbs +25 -0
  37. package/deploy/windows/trillboards-edge.cmd +33 -0
  38. package/deploy/windows/trillboards-pair.cmd +276 -0
  39. package/deploy/windows/trillboards-setup.ps1 +456 -0
  40. package/deploy/windows/trillboards-setup.vbs +13 -0
  41. package/dist/AudienceAnalyzeClient.d.ts +136 -0
  42. package/dist/AudienceAnalyzeClient.d.ts.map +1 -0
  43. package/dist/AudienceAnalyzeClient.js +246 -0
  44. package/dist/AudienceAnalyzeClient.js.map +1 -0
  45. package/dist/ChannelProber.d.ts +150 -0
  46. package/dist/ChannelProber.d.ts.map +1 -0
  47. package/dist/ChannelProber.js +887 -0
  48. package/dist/ChannelProber.js.map +1 -0
  49. package/dist/CloudEscalationPolicy.d.ts +143 -0
  50. package/dist/CloudEscalationPolicy.d.ts.map +1 -0
  51. package/dist/CloudEscalationPolicy.js +137 -0
  52. package/dist/CloudEscalationPolicy.js.map +1 -0
  53. package/dist/CloudSensingManager.d.ts +533 -0
  54. package/dist/CloudSensingManager.d.ts.map +1 -0
  55. package/dist/CloudSensingManager.js +1254 -0
  56. package/dist/CloudSensingManager.js.map +1 -0
  57. package/dist/DiscoveryReportClient.d.ts +66 -0
  58. package/dist/DiscoveryReportClient.d.ts.map +1 -0
  59. package/dist/DiscoveryReportClient.js +85 -0
  60. package/dist/DiscoveryReportClient.js.map +1 -0
  61. package/dist/EdgeAgent.d.ts +12 -2
  62. package/dist/EdgeAgent.d.ts.map +1 -1
  63. package/dist/EdgeAgent.js +55 -13
  64. package/dist/EdgeAgent.js.map +1 -1
  65. package/dist/LiteRtVlmProvider.d.ts +193 -0
  66. package/dist/LiteRtVlmProvider.d.ts.map +1 -0
  67. package/dist/LiteRtVlmProvider.js +436 -0
  68. package/dist/LiteRtVlmProvider.js.map +1 -0
  69. package/dist/MosaicSource.d.ts +108 -0
  70. package/dist/MosaicSource.d.ts.map +1 -0
  71. package/dist/MosaicSource.js +274 -0
  72. package/dist/MosaicSource.js.map +1 -0
  73. package/dist/OnvifClient.d.ts +83 -0
  74. package/dist/OnvifClient.d.ts.map +1 -0
  75. package/dist/OnvifClient.js +209 -0
  76. package/dist/OnvifClient.js.map +1 -0
  77. package/dist/PairClient.d.ts +106 -0
  78. package/dist/PairClient.d.ts.map +1 -0
  79. package/dist/PairClient.js +186 -0
  80. package/dist/PairClient.js.map +1 -0
  81. package/dist/RecorderAddressRecovery.d.ts +53 -0
  82. package/dist/RecorderAddressRecovery.d.ts.map +1 -0
  83. package/dist/RecorderAddressRecovery.js +359 -0
  84. package/dist/RecorderAddressRecovery.js.map +1 -0
  85. package/dist/SceneChangeDetector.d.ts +291 -0
  86. package/dist/SceneChangeDetector.d.ts.map +1 -0
  87. package/dist/SceneChangeDetector.js +682 -0
  88. package/dist/SceneChangeDetector.js.map +1 -0
  89. package/dist/SensingProfileClient.d.ts +59 -0
  90. package/dist/SensingProfileClient.d.ts.map +1 -0
  91. package/dist/SensingProfileClient.js +78 -0
  92. package/dist/SensingProfileClient.js.map +1 -0
  93. package/dist/SetupSessionClient.d.ts +114 -0
  94. package/dist/SetupSessionClient.d.ts.map +1 -0
  95. package/dist/SetupSessionClient.js +95 -0
  96. package/dist/SetupSessionClient.js.map +1 -0
  97. package/dist/VlmAdmissionGate.d.ts +126 -0
  98. package/dist/VlmAdmissionGate.d.ts.map +1 -0
  99. package/dist/VlmAdmissionGate.js +166 -0
  100. package/dist/VlmAdmissionGate.js.map +1 -0
  101. package/dist/autoSense.d.ts +216 -0
  102. package/dist/autoSense.d.ts.map +1 -0
  103. package/dist/autoSense.js +521 -0
  104. package/dist/autoSense.js.map +1 -0
  105. package/dist/cli.d.ts +27 -1
  106. package/dist/cli.d.ts.map +1 -1
  107. package/dist/cli.js +511 -21
  108. package/dist/cli.js.map +1 -1
  109. package/dist/combinedSense.d.ts +128 -0
  110. package/dist/combinedSense.d.ts.map +1 -0
  111. package/dist/combinedSense.js +261 -0
  112. package/dist/combinedSense.js.map +1 -0
  113. package/dist/config.d.ts +351 -0
  114. package/dist/config.d.ts.map +1 -1
  115. package/dist/config.js +99 -2
  116. package/dist/config.js.map +1 -1
  117. package/dist/connectorCadence.d.ts +135 -0
  118. package/dist/connectorCadence.d.ts.map +1 -0
  119. package/dist/connectorCadence.js +151 -0
  120. package/dist/connectorCadence.js.map +1 -0
  121. package/dist/dataDir.d.ts +33 -0
  122. package/dist/dataDir.d.ts.map +1 -0
  123. package/dist/dataDir.js +76 -0
  124. package/dist/dataDir.js.map +1 -0
  125. package/dist/deployedCadence.d.ts +167 -0
  126. package/dist/deployedCadence.d.ts.map +1 -0
  127. package/dist/deployedCadence.js +209 -0
  128. package/dist/deployedCadence.js.map +1 -0
  129. package/dist/deployedCadenceSource.d.ts +48 -0
  130. package/dist/deployedCadenceSource.d.ts.map +1 -0
  131. package/dist/deployedCadenceSource.js +74 -0
  132. package/dist/deployedCadenceSource.js.map +1 -0
  133. package/dist/diagnose.d.ts +1 -1
  134. package/dist/diagnose.d.ts.map +1 -1
  135. package/dist/diagnose.js +13 -12
  136. package/dist/diagnose.js.map +1 -1
  137. package/dist/discovery.d.ts +131 -0
  138. package/dist/discovery.d.ts.map +1 -0
  139. package/dist/discovery.js +499 -0
  140. package/dist/discovery.js.map +1 -0
  141. package/dist/gemmaModelOta.d.ts +118 -0
  142. package/dist/gemmaModelOta.d.ts.map +1 -0
  143. package/dist/gemmaModelOta.js +291 -0
  144. package/dist/gemmaModelOta.js.map +1 -0
  145. package/dist/index.d.ts +16 -1
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +40 -1
  148. package/dist/index.js.map +1 -1
  149. package/dist/init.d.ts.map +1 -1
  150. package/dist/init.js +41 -18
  151. package/dist/init.js.map +1 -1
  152. package/dist/keepAwake.d.ts +33 -0
  153. package/dist/keepAwake.d.ts.map +1 -0
  154. package/dist/keepAwake.js +54 -0
  155. package/dist/keepAwake.js.map +1 -0
  156. package/dist/legacyRecorderProfile.d.ts +20 -0
  157. package/dist/legacyRecorderProfile.d.ts.map +1 -0
  158. package/dist/legacyRecorderProfile.js +27 -0
  159. package/dist/legacyRecorderProfile.js.map +1 -0
  160. package/dist/localInferenceSchema.d.ts +132 -0
  161. package/dist/localInferenceSchema.d.ts.map +1 -0
  162. package/dist/localInferenceSchema.js +274 -0
  163. package/dist/localInferenceSchema.js.map +1 -0
  164. package/dist/localSenseE2eHarness.d.ts +101 -0
  165. package/dist/localSenseE2eHarness.d.ts.map +1 -0
  166. package/dist/localSenseE2eHarness.js +279 -0
  167. package/dist/localSenseE2eHarness.js.map +1 -0
  168. package/dist/localSensing.d.ts +268 -0
  169. package/dist/localSensing.d.ts.map +1 -0
  170. package/dist/localSensing.js +492 -0
  171. package/dist/localSensing.js.map +1 -0
  172. package/dist/modelIntegrity.d.ts +59 -0
  173. package/dist/modelIntegrity.d.ts.map +1 -0
  174. package/dist/modelIntegrity.js +165 -0
  175. package/dist/modelIntegrity.js.map +1 -0
  176. package/dist/pairVerify.d.ts +100 -0
  177. package/dist/pairVerify.d.ts.map +1 -0
  178. package/dist/pairVerify.js +296 -0
  179. package/dist/pairVerify.js.map +1 -0
  180. package/dist/qrScanner.d.ts +33 -0
  181. package/dist/qrScanner.d.ts.map +1 -0
  182. package/dist/qrScanner.js +103 -0
  183. package/dist/qrScanner.js.map +1 -0
  184. package/dist/runtimePaths.d.ts +10 -0
  185. package/dist/runtimePaths.d.ts.map +1 -0
  186. package/dist/runtimePaths.js +79 -0
  187. package/dist/runtimePaths.js.map +1 -0
  188. package/dist/sensingRunMode.d.ts +68 -0
  189. package/dist/sensingRunMode.d.ts.map +1 -0
  190. package/dist/sensingRunMode.js +346 -0
  191. package/dist/sensingRunMode.js.map +1 -0
  192. package/dist/setupMode.d.ts +98 -0
  193. package/dist/setupMode.d.ts.map +1 -0
  194. package/dist/setupMode.js +828 -0
  195. package/dist/setupMode.js.map +1 -0
  196. package/dist/setupProgress.d.ts +31 -0
  197. package/dist/setupProgress.d.ts.map +1 -0
  198. package/dist/setupProgress.js +173 -0
  199. package/dist/setupProgress.js.map +1 -0
  200. package/package.json +13 -4
package/dist/config.d.ts CHANGED
@@ -1,3 +1,88 @@
1
+ import type { LiteRtAccelerator } from './LiteRtVlmProvider';
2
+ /**
3
+ * A rectangular sub-region (in SOURCE pixels) carving one camera out of a
4
+ * composite ("mosaic"/quad) feed. Field shape mirrors the server-side face-crop
5
+ * geometry; structurally identical to edge-platform-rtsp's CropRegion so it can
6
+ * be handed straight to that package's ffmpeg `buildVideoFilter`. Defined here
7
+ * (not imported) because edge-sdk consumes edge-platform-rtsp only via runtime
8
+ * `require()`, never a typed top-level import.
9
+ */
10
+ export interface MosaicTile {
11
+ /** Left offset of the tile in the source frame (px). */
12
+ x: number;
13
+ /** Top offset of the tile in the source frame (px). */
14
+ y: number;
15
+ /** Tile width in the source frame (px). */
16
+ w: number;
17
+ /** Tile height in the source frame (px). */
18
+ h: number;
19
+ }
20
+ /**
21
+ * Stable local identity for one physical DVR/NVR. The host is deliberately
22
+ * absent: DHCP may change it. The MAC address safely narrows LAN rediscovery;
23
+ * ONVIF serial adds a second identity check when the recorder exposes one.
24
+ * `id` groups all channels so one rediscovery updates the recorder atomically.
25
+ */
26
+ export interface RecorderIdentity {
27
+ /** Opaque local group id shared by every channel on this recorder. */
28
+ id: string;
29
+ /** Physical serial returned by ONVIF GetDeviceInformation, when available. */
30
+ serial?: string;
31
+ /** LAN hardware address fallback for recorders that do not expose ONVIF. */
32
+ mac?: string;
33
+ }
34
+ /**
35
+ * One camera feed in a multi-channel ("cloud sensing") connector.
36
+ *
37
+ * One feed = one screen = one device token = one sensing profile. An NVR's N
38
+ * channels become N of these (each its own RTSP URL + its own token), so a
39
+ * single connector process can run N capture loops and each frame is POSTed
40
+ * under ITS OWN token — the cloud then resolves that screen's profile.
41
+ */
42
+ export interface CameraFeedConfig {
43
+ /** Human-readable label, e.g. "Parking lot", "Register". */
44
+ name?: string;
45
+ /**
46
+ * Stable config-local id for this feed. Optional for ordinary feeds; REQUIRED
47
+ * on a "mosaic source" feed (one composite/quad RTSP stream) so its tiles can
48
+ * reference it via `sourceFeedId` and share its single RTSP pull.
49
+ */
50
+ id?: string;
51
+ /** The Trillboards screen this feed maps to (one feed = one screen). */
52
+ screenId?: string;
53
+ /** Per-feed device token (X-Device-Token). Each feed authenticates as itself. */
54
+ deviceToken: string;
55
+ /** RTSP stream URL for this channel, e.g. rtsp://user:pass@host:554/ch01. */
56
+ rtspUrl: string;
57
+ /** Setup source class. Recorder feeds receive the conservative DVR/NVR profile. */
58
+ sourceType?: 'recorder' | 'direct_url';
59
+ /** Stable server idempotency key for this physical channel. */
60
+ channelKey?: string;
61
+ /** Physical recorder identity used for DHCP-safe runtime rediscovery. */
62
+ recorder?: RecorderIdentity;
63
+ /**
64
+ * Mosaic tiling — carve THIS screen out of a composite ("mosaic"/quad) feed.
65
+ *
66
+ * `sourceFeedId` points at the `id` of the mosaic source feed; this feed then
67
+ * inherits that source's `rtspUrl` (so the operator pastes the credentialed
68
+ * URL exactly once) and the connector pulls the source ONCE, handing each tile
69
+ * a cropped view. `tile` is the sub-rectangle (in SOURCE pixels) for this
70
+ * screen. Omit both for a normal whole-frame feed (unchanged behavior).
71
+ */
72
+ sourceFeedId?: string;
73
+ /** Crop rectangle (source pixels) when this feed is a tile of a mosaic. */
74
+ tile?: MosaicTile;
75
+ /** Capture width in pixels (default: inherits camera defaults / 640). */
76
+ width?: number;
77
+ /** Capture height in pixels (default: inherits camera defaults / 480). */
78
+ height?: number;
79
+ /** Target frames per second (advisory; cadence drives capture). */
80
+ fps?: number;
81
+ /** RTSP transport protocol: 'tcp' (default) or 'udp'. */
82
+ transport?: 'tcp' | 'udp';
83
+ /** Path to ffmpeg binary (default: bundled ffmpeg / inherits camera.ffmpegPath). */
84
+ ffmpegPath?: string;
85
+ }
1
86
  export interface EdgeAgentConfig {
2
87
  apiBaseUrl: string;
3
88
  deviceToken: string;
@@ -12,6 +97,263 @@ export interface EdgeAgentConfig {
12
97
  width?: number;
13
98
  height?: number;
14
99
  fps?: number;
100
+ transport?: 'tcp' | 'udp';
101
+ ffmpegPath?: string;
102
+ };
103
+ cameras?: CameraFeedConfig[];
104
+ cloudSensing?: {
105
+ enabled: boolean;
106
+ /** Capture interval (ms) when NO faces detected. Default: 300000 (5 min). */
107
+ periodicIntervalMs?: number;
108
+ /**
109
+ * Cloud spend per camera per month this connector may commit to when it
110
+ * honours the cadence deployed from the portal. Default 60 USD, which at the
111
+ * measured $0.007008/frame is a ~303 s floor — within 1% of the 300 s every
112
+ * cloud connector already runs, so the default changes no bill.
113
+ *
114
+ * Raise it to buy a faster cadence: 30-second cloud sensing is $605/camera/
115
+ * month at the measured price, which is why the same responsiveness is a
116
+ * LOCAL-mode feature (on-device inference has no per-frame cost). See
117
+ * deployedCadence.ts for the arithmetic and the measurement.
118
+ */
119
+ monthlyBudgetUsdPerCamera?: number;
120
+ /** Capture interval (ms) when faces ARE detected. Default: 30000 (30 sec). */
121
+ faceTriggeredIntervalMs?: number;
122
+ /** JPEG quality for the POSTed frame. Default: 70. */
123
+ jpegQuality?: number;
124
+ /**
125
+ * Skip the POST when no faces are detected (still captures periodically).
126
+ * In the thin cloud-sensing mode there is NO local face detector driving
127
+ * face state, so this defaults to FALSE — the cloud VLM does the inference,
128
+ * so every periodic frame is POSTed. Set true only when an external caller
129
+ * drives setFaceDetected() (e.g. a cheap edge motion gate).
130
+ */
131
+ skipPostWhenNoFaces?: boolean;
132
+ /**
133
+ * RTSP self-recovery backoff (shared by cloud- AND local-sensing). When a
134
+ * camera/feed drops, the connector re-opens it with bounded exponential
135
+ * backoff and retries FOREVER — a transient blip never permanently kills
136
+ * sensing on an unattended 24/7 BYO-CCTV deployment. `reconnectInitialDelayMs`
137
+ * is the first re-open delay (doubles each failure); `reconnectMaxDelayMs`
138
+ * caps it, after which it holds at the cap and keeps retrying. Defaults:
139
+ * 1000 / 30000.
140
+ */
141
+ reconnectInitialDelayMs?: number;
142
+ reconnectMaxDelayMs?: number;
143
+ /**
144
+ * Phase multi-camera capture/reconnect loops apart by feed index so one
145
+ * connector does not stampede a consumer NVR with simultaneous FFmpeg pulls.
146
+ * Defaults in CloudSensingManager: 1000ms per feed, capped at 15000ms.
147
+ */
148
+ feedStaggerMs?: number;
149
+ maxFeedStaggerMs?: number;
150
+ /**
151
+ * Consecutive non-auth RTSP failures tolerated before the connector reports
152
+ * failed camera health to the portal. Auth failures still report
153
+ * immediately. Default: 3.
154
+ */
155
+ cameraHealthFailureGraceCount?: number;
156
+ /**
157
+ * Concurrent analyses allowed across ALL feeds — the global admission gate.
158
+ *
159
+ * Cloud sensing leaves this unset (the cloud VLM is genuinely concurrent,
160
+ * so every feed may be in flight at once — today's behavior). LOCAL sensing
161
+ * defaults it to 1 because `litert-lm serve` is a SINGLE-CONVERSATION
162
+ * server: 16 free-running loops oversubscribe it ~17x, and the surplus does
163
+ * not queue politely — it ages against the provider's 120 s HTTP timeout
164
+ * and burns retries on frames that are already stale. Raise it only with a
165
+ * measurement proving the backend really is concurrent.
166
+ */
167
+ maxConcurrentAnalyses?: number;
168
+ /**
169
+ * Reclaim an admission slot held longer than this (ms). Default 180000.
170
+ * A single lost promise must not blind an unattended 24/7 connector.
171
+ */
172
+ maxAnalysisInFlightMs?: number;
173
+ /**
174
+ * Delay (ms) before a capture the admission gate denied tries again.
175
+ * Default: the derived per-feed stagger step, clamped to 250…5000. The
176
+ * frame itself is DROPPED (a stale frame is worthless) — this only controls
177
+ * how quickly the loop comes back for a freed slot.
178
+ */
179
+ admissionRetryDelayMs?: number;
180
+ /**
181
+ * Cheap motion cascade in front of the model (Frigate's `improved_motion`,
182
+ * ported): every look is decoded to a ~178x100 greyscale frame and only a
183
+ * frame that CHANGED costs an inference. In a real store most frames are an
184
+ * empty aisle, so this is the difference between 16 cameras oversubscribing
185
+ * one Gemma instance and 16 cameras being comfortable.
186
+ *
187
+ * Defaults ON for local sensing, OFF for cloud sensing (cloud has no
188
+ * single-instance ceiling, and tightening its look rate would raise the
189
+ * per-call bill without raising throughput).
190
+ *
191
+ * The gate can only REMOVE analyses: `heartbeatIntervalMs` (default
192
+ * `periodicIntervalMs`) guarantees a still scene still reports — so
193
+ * "nothing is happening" stays distinguishable from "the connector died" —
194
+ * and `minAnalyzeIntervalMs` (default `faceTriggeredIntervalMs`) keeps a
195
+ * busy feed inside the cadence band the config already declares.
196
+ */
197
+ motionGate?: {
198
+ /** Default: true for local sensing, false for cloud sensing. */
199
+ enabled?: boolean;
200
+ /** Look-rate (ms) for a still feed. Default: faceTriggeredIntervalMs. */
201
+ scanIntervalMs?: number;
202
+ /** Guaranteed reporting floor (ms). Default: periodicIntervalMs. */
203
+ heartbeatIntervalMs?: number;
204
+ /** Floor (ms) between motion-triggered analyses. Default: faceTriggeredIntervalMs. */
205
+ minAnalyzeIntervalMs?: number;
206
+ /** Height (px) motion runs at. Frigate default 100. */
207
+ frameHeight?: number;
208
+ /** Per-pixel delta that counts as changed. Frigate default 30. */
209
+ threshold?: number;
210
+ /** Minimum changed-region size in motion-frame px. Frigate default 10. */
211
+ contourArea?: number;
212
+ /** Moving fraction treated as a lighting event, not motion. Default 0.8. */
213
+ lightningThreshold?: number;
214
+ /**
215
+ * Consecutive same-place motion verdicts suppressed before one is let
216
+ * through — a parked car or a swinging sign costs 1/N of a moving one.
217
+ * Frigate re-checks a stationary object every `fps * 10` frames; 10 is
218
+ * that factor. 0 disables suppression.
219
+ */
220
+ stationaryReanalyzeEvery?: number;
221
+ };
222
+ };
223
+ localSensing?: {
224
+ enabled: boolean;
225
+ /** LiteRT-LM `serve` base URL (OpenAI-compatible). Default http://localhost:9379. */
226
+ litertLmBaseUrl?: string;
227
+ /** Model id as imported in `litert-lm list`. Default gemma-4-E2B-it.litertlm. */
228
+ model?: string;
229
+ /**
230
+ * Which litert-lm DELEGATE to run the on-device VLM on. litert-lm advertises
231
+ * its GPU delegate as a separate model id (`<model>,gpu`), and the GPU is
232
+ * several times faster per frame on a capable host.
233
+ * 'auto' (DEFAULT) — read GET /v1/models once and prefer the `,gpu` variant
234
+ * when the serve advertises it; otherwise CPU.
235
+ * 'gpu' — pin the GPU variant (still demotes to CPU if the
236
+ * delegate turns out to be unusable on this host).
237
+ * 'cpu' — pin the CPU delegate; never probe, never demote.
238
+ */
239
+ accelerator?: LiteRtAccelerator;
240
+ /** Capture interval (ms). Default: cloudSensing.periodicIntervalMs / 300000. */
241
+ captureIntervalMs?: number;
242
+ /**
243
+ * Timeout (ms) for each `auto-sense` litert-lm health probe. Short so model
244
+ * readiness checks do not stall the supervisor. Default 2000.
245
+ */
246
+ healthProbeTimeoutMs?: number;
247
+ /**
248
+ * `auto-sense` startup re-probe count. A fresh install can start the connector
249
+ * BEFORE litert-lm finishes warming up / downloading its model; a single boot
250
+ * probe would pick cloud and never re-probe. So auto-sense re-probes up to this
251
+ * many times (with healthProbeStartupBackoffMs between) before entering its
252
+ * local-model waiting state. Default 4.
253
+ */
254
+ healthProbeStartupAttempts?: number;
255
+ /** Delay (ms) between `auto-sense` startup re-probes. Default 5000. */
256
+ healthProbeStartupBackoffMs?: number;
257
+ /**
258
+ * `auto-sense` STEADY-STATE self-healing: interval (ms) between background
259
+ * LiteRT-LM health re-probes AFTER the connector has already picked a mode.
260
+ * This lets `auto-sense` begin on-device inference with NO restart once the
261
+ * ~2.4 GB Gemma download/warmup finishes. When explicit cloud fallback is
262
+ * enabled, the same loop also upgrades CLOUD to LOCAL as soon as possible.
263
+ * Floored at 15000 so a mis-set tiny value cannot hammer the serve. Default
264
+ * 60000.
265
+ */
266
+ healthRecheckIntervalMs?: number;
267
+ /**
268
+ * `auto-sense` anti-thrash debounce: how many CONSECUTIVE unhealthy background
269
+ * re-probes are required before an explicitly enabled LOCAL-to-CLOUD fallback.
270
+ * With the default local-only policy, this controls how often an unhealthy
271
+ * local runtime is reported. Default 2.
272
+ */
273
+ localUnhealthyChecksBeforeFallback?: number;
274
+ /**
275
+ * Per-frame resilience: when on-device inference THROWS for a single frame
276
+ * (e.g. litert-lm dropped mid-run), POST that one frame to the cloud VLM
277
+ * instead of losing it — reusing the same per-feed upload client. Opt-in
278
+ * (default false) so the plain `local-sense` path is unchanged. The
279
+ * cloud-served frame is provenance-gated by the server exactly like any
280
+ * cloud-sense frame.
281
+ */
282
+ cloudFallbackOnError?: boolean;
283
+ /**
284
+ * Allow `auto-sense` to run cloud VLM inference while LiteRT-LM is unavailable.
285
+ * Default: false. CCTV produces continuous, multi-camera load, so cloud
286
+ * inference must be an explicit operator decision rather than a silent cost
287
+ * fallback. The explicit `cloud-sense` command remains available regardless.
288
+ */
289
+ allowCloudFallback?: boolean;
290
+ /**
291
+ * CONFIDENCE-BASED CLOUD ESCALATION — local Gemma stays the always-on gate,
292
+ * and the ONE frame it flags as a candidate is re-analyzed by the cloud VLM
293
+ * before an alert can fire.
294
+ *
295
+ * This is a THIRD, distinct use of the cloud, and the three must not be
296
+ * conflated:
297
+ * - `allowCloudFallback` — the local runtime is UNAVAILABLE, so run the
298
+ * whole mode in the cloud;
299
+ * - `cloudFallbackOnError` — local inference THREW on one frame, so ship
300
+ * that frame to the cloud instead of losing it;
301
+ * - `cloudEscalation` — local inference WORKED, and its judgement on
302
+ * an alert-bearing frame is worth confirming.
303
+ *
304
+ * MEASURED justification (2026-08-12, through the shipped code path against
305
+ * the live litert-lm serve, real 640x480 q70 frames): on-device Gemma called
306
+ * the empty store correctly on every run and got the kitchen frame's SOP
307
+ * booleans wrong on most — while reporting `confidence` 0.95 on the wrong
308
+ * answers. The cloud model was right on every field of every frame. So the
309
+ * escalation trigger is CANDIDACY, not the model's self-reported score.
310
+ *
311
+ * Cost/egress are bounded by construction (novelty, pacing, hourly budget —
312
+ * see CloudEscalationPolicy), and the whole thing is off unless `enabled`.
313
+ * A fully-local deployment (this block absent, or `enabled:false`) behaves
314
+ * exactly as before: lower accuracy on judgement calls, zero egress beyond
315
+ * the evidence photo the operator already opted into.
316
+ */
317
+ cloudEscalation?: {
318
+ /** Master switch. Default false. */
319
+ enabled?: boolean;
320
+ /** Floor (ms) between two escalations on one feed. Default 60000. */
321
+ minIntervalMs?: number;
322
+ /** Hard ceiling per feed per rolling hour. Default 12. 0 disables. */
323
+ maxPerHour?: number;
324
+ /**
325
+ * How long an UNCHANGED verdict stays confirmed before the cloud is asked
326
+ * again (ms). Default 900000 (15 min). This is what keeps a permanently
327
+ * busy camera from escalating every frame.
328
+ */
329
+ reconfirmAfterMs?: number;
330
+ };
331
+ /**
332
+ * Send the analyzed JPEG alongside the `preAnalyzed` result so the server can
333
+ * attach it as the alert evidence photo. PRIVACY-CONSCIOUS: even when true,
334
+ * the frame is attached ONLY for potentially alert-worthy observations — any
335
+ * meaningful signal (a true boolean, a positive count OR gauge, a non-default
336
+ * enum/string, a populated list/distribution); a genuinely empty/uninteresting
337
+ * scene still sends results only. Default: true. Set false to never send a
338
+ * frame from local sensing (results-only).
339
+ */
340
+ attachEvidenceFrame?: boolean;
341
+ /**
342
+ * On-device VLM (Gemma) over-the-air model updates. When the local/auto
343
+ * sensing run mode is active the connector watches modelType `gemma-vlm` via
344
+ * the edge-core ModelManager + GET /v1/federated/models (S3 presigned) and
345
+ * atomically swaps + reloads the litert-lm model when a newer version ships.
346
+ * The model pre-bundled into the installer seeds the installed version so the
347
+ * first check does not re-download it. Default: enabled. Set false to pin the
348
+ * on-device model to whatever is installed (no OTA).
349
+ */
350
+ modelOtaEnabled?: boolean;
351
+ /**
352
+ * Interval (ms) between gemma-vlm OTA version checks. Default: the edge-core
353
+ * ModelManager default (3600000 = 1h). The on-device model changes rarely, so
354
+ * a long cadence is fine.
355
+ */
356
+ modelOtaCheckIntervalMs?: number;
15
357
  };
16
358
  audio?: {
17
359
  enabled: boolean;
@@ -39,6 +381,15 @@ export interface EdgeAgentConfig {
39
381
  statusPort?: number;
40
382
  dataDir?: string;
41
383
  logLevel?: 'debug' | 'info' | 'warn' | 'error';
384
+ /**
385
+ * Whether EdgeAgent installs its OWN SIGINT/SIGTERM handlers (graceful
386
+ * stop + process.exit). Default true — the standalone `start` agent owns its
387
+ * process lifecycle. Set FALSE when EdgeAgent is composed inside an outer
388
+ * supervisor (the `sense` / combined-sense entrypoint) that stops the zoo AND
389
+ * the VLM together, so EdgeAgent's handler can't process.exit() while the VLM
390
+ * manager is still tearing down.
391
+ */
392
+ manageProcessSignals?: boolean;
42
393
  }
43
394
  export declare function loadConfig(configPath: string): EdgeAgentConfig;
44
395
  export declare function resolveConfig(partial: Partial<EdgeAgentConfig>): EdgeAgentConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAE9B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IAGpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAG/B,MAAM,CAAC,EAAE;QACP,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IAGF,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IAGF,MAAM,CAAC,EAAE;QACP,GAAG,EAAE,MAAM,CAAC;QACZ,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,KAAK,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;KAC9D,CAAC;IAGF,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;QACjB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IAGF,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IAGF,SAAS,CAAC,EAAE;QACV,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IAGF,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAChD;AAeD,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAe9D;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,CAiChF"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,CAAC,EAAE,MAAM,CAAC;IACV,uDAAuD;IACvD,CAAC,EAAE,MAAM,CAAC;IACV,2CAA2C;IAC3C,CAAC,EAAE,MAAM,CAAC;IACV,4CAA4C;IAC5C,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,UAAU,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IACvC,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAG9B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IAGpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAI/B,MAAM,CAAC,EAAE;QACP,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IAKF,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAI7B,YAAY,CAAC,EAAE;QACb,OAAO,EAAE,OAAO,CAAC;QACjB,6EAA6E;QAC7E,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B;;;;;;;;;;WAUG;QACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;QACnC,8EAA8E;QAC9E,uBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC,sDAAsD;QACtD,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB;;;;;;WAMG;QACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;QAC9B;;;;;;;;WAQG;QACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B;;;;WAIG;QACH,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B;;;;WAIG;QACH,6BAA6B,CAAC,EAAE,MAAM,CAAC;QACvC;;;;;;;;;;WAUG;QACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B;;;WAGG;QACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B;;;;;WAKG;QACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B;;;;;;;;;;;;;;;;WAgBG;QACH,UAAU,CAAC,EAAE;YACX,gEAAgE;YAChE,OAAO,CAAC,EAAE,OAAO,CAAC;YAClB,yEAAyE;YACzE,cAAc,CAAC,EAAE,MAAM,CAAC;YACxB,oEAAoE;YACpE,mBAAmB,CAAC,EAAE,MAAM,CAAC;YAC7B,sFAAsF;YACtF,oBAAoB,CAAC,EAAE,MAAM,CAAC;YAC9B,uDAAuD;YACvD,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,kEAAkE;YAClE,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,0EAA0E;YAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,4EAA4E;YAC5E,kBAAkB,CAAC,EAAE,MAAM,CAAC;YAC5B;;;;;eAKG;YACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;SACnC,CAAC;KACH,CAAC;IASF,YAAY,CAAC,EAAE;QACb,OAAO,EAAE,OAAO,CAAC;QACjB,qFAAqF;QACrF,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,iFAAiF;QACjF,KAAK,CAAC,EAAE,MAAM,CAAC;QACf;;;;;;;;;WASG;QACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;QAChC,gFAAgF;QAChF,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B;;;WAGG;QACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAC9B;;;;;;WAMG;QACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;QACpC,uEAAuE;QACvE,2BAA2B,CAAC,EAAE,MAAM,CAAC;QACrC;;;;;;;;WAQG;QACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC;;;;;WAKG;QACH,kCAAkC,CAAC,EAAE,MAAM,CAAC;QAC5C;;;;;;;WAOG;QACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAC/B;;;;;WAKG;QACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;WA0BG;QACH,eAAe,CAAC,EAAE;YAChB,oCAAoC;YACpC,OAAO,CAAC,EAAE,OAAO,CAAC;YAClB,qEAAqE;YACrE,aAAa,CAAC,EAAE,MAAM,CAAC;YACvB,sEAAsE;YACtE,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB;;;;eAIG;YACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;SAC3B,CAAC;QACF;;;;;;;;WAQG;QACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;QAC9B;;;;;;;;WAQG;QACH,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B;;;;WAIG;QACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;KAClC,CAAC;IAGF,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IAGF,MAAM,CAAC,EAAE;QACP,GAAG,EAAE,MAAM,CAAC;QACZ,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,KAAK,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;KAC9D,CAAC;IAGF,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;QACjB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IAGF,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IAGF,SAAS,CAAC,EAAE;QACV,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IAGF,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAE/C;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAeD,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAe9D;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,CA4FhF"}
package/dist/config.js CHANGED
@@ -65,6 +65,34 @@ function loadConfig(configPath) {
65
65
  return resolveConfig(parsed);
66
66
  }
67
67
  function resolveConfig(partial) {
68
+ // Normalize multi-channel feeds (if any) up front so we can use them to
69
+ // relax the single-token requirement below.
70
+ //
71
+ // Feeds missing an rtspUrl are SKIPPED (not fatal): in the creds-stay-on-prem
72
+ // pairing mode the server returns cameras WITHOUT an rtspUrl (the operator
73
+ // adds it locally so credentials never leave the venue), and
74
+ // pairedConfigToFile writes those as `rtspUrl: ''`. Dropping them with a
75
+ // warning lets `cloud-sense` run the feeds that ARE configured instead of
76
+ // crashing the entire load. An empty result collapses to `undefined` so the
77
+ // single-camera back-compat path + the deviceToken requirement below still
78
+ // apply.
79
+ // Map each source feed's id → its rtspUrl so a tile (sourceFeedId set, no own
80
+ // rtspUrl) can inherit the credentialed URL the operator pasted once onto the
81
+ // mosaic source. Built from the RAW list so order doesn't matter.
82
+ const idToRtspUrl = new Map();
83
+ if (Array.isArray(partial.cameras)) {
84
+ for (const f of partial.cameras) {
85
+ if (f && typeof f.id === 'string' && f.id && typeof f.rtspUrl === 'string' && f.rtspUrl) {
86
+ idToRtspUrl.set(f.id, f.rtspUrl);
87
+ }
88
+ }
89
+ }
90
+ const normalizedCameras = Array.isArray(partial.cameras)
91
+ ? partial.cameras
92
+ .map((feed, i) => normalizeCameraFeed(feed, i, partial, idToRtspUrl))
93
+ .filter((feed) => feed !== null)
94
+ : undefined;
95
+ const cameras = normalizedCameras && normalizedCameras.length > 0 ? normalizedCameras : undefined;
68
96
  const config = {
69
97
  ...DEFAULTS,
70
98
  ...partial,
@@ -74,9 +102,31 @@ function resolveConfig(partial) {
74
102
  kiosk: { ...DEFAULTS.kiosk, ...partial.kiosk },
75
103
  cloud: { ...DEFAULTS.cloud, ...partial.cloud },
76
104
  federated: { ...DEFAULTS.federated, ...partial.federated },
105
+ cloudSensing: partial.cloudSensing
106
+ ? { ...partial.cloudSensing }
107
+ : undefined,
108
+ localSensing: partial.localSensing
109
+ ? {
110
+ ...partial.localSensing,
111
+ // Privacy-conscious evidence frame defaults ON (the connector still
112
+ // gates it to alert-worthy frames at send time). Explicit false wins.
113
+ attachEvidenceFrame: partial.localSensing.attachEvidenceFrame !== false,
114
+ }
115
+ : undefined,
116
+ cameras,
77
117
  apiBaseUrl: partial.apiBaseUrl || DEFAULTS.apiBaseUrl,
78
118
  deviceToken: partial.deviceToken || '',
79
119
  };
120
+ // Cloud escalation sends a frame off the premises, so the operator's "no frame
121
+ // ever leaves" switch outranks it. LocalSensingClient ENFORCES that; say so
122
+ // here rather than letting the setting look active and do nothing.
123
+ if (config.localSensing?.cloudEscalation?.enabled === true &&
124
+ config.localSensing.attachEvidenceFrame === false) {
125
+ // eslint-disable-next-line no-console
126
+ console.warn('localSensing.cloudEscalation is enabled but localSensing.attachEvidenceFrame is false — ' +
127
+ 'escalation sends the analyzed frame to the cloud VLM, so it stays OFF. ' +
128
+ 'Remove attachEvidenceFrame:false to allow candidate frames to be confirmed in the cloud.');
129
+ }
80
130
  // Auto-detect platform
81
131
  if (!config.platform) {
82
132
  config.platform = process.platform === 'win32' ? 'windows' : 'linux';
@@ -88,10 +138,57 @@ function resolveConfig(partial) {
88
138
  if (config.models && !config.models.yamnetPath) {
89
139
  config.models.yamnetPath = path.join(config.models.dir, 'yamnet.onnx');
90
140
  }
91
- // Validate required fields
92
- if (!config.deviceToken) {
141
+ // Validate required fields. In multi-channel mode each feed carries its own
142
+ // token (validated in normalizeCameraFeed), so the top-level token is
143
+ // optional. Single-camera mode still requires it (back-compat).
144
+ if (!config.deviceToken && (!config.cameras || config.cameras.length === 0)) {
93
145
  throw new Error('deviceToken is required in config. Get one from your Trillboards dashboard.');
94
146
  }
95
147
  return config;
96
148
  }
149
+ /**
150
+ * Validate + fill defaults for a single multi-channel feed. Each feed carries
151
+ * its own deviceToken and rtspUrl (one feed = one screen = one token).
152
+ *
153
+ * Returns `null` (and warns) for a feed with no rtspUrl — the creds-stay-on-prem
154
+ * case, where the server delivered the screen + token but the operator hasn't
155
+ * pasted the RTSP URL into this machine's config yet. Skipping it (rather than
156
+ * throwing) keeps `cloud-sense` running the feeds that ARE ready. A missing
157
+ * deviceToken is still fatal: every server-paired feed carries one, so its
158
+ * absence is a genuine hand-edit error, not the creds-local pattern.
159
+ */
160
+ function normalizeCameraFeed(feed, index, partial, idToRtspUrl) {
161
+ if (!feed || typeof feed !== 'object') {
162
+ throw new Error(`cameras[${index}] must be an object`);
163
+ }
164
+ if (!feed.deviceToken) {
165
+ throw new Error(`cameras[${index}] (${feed.name || 'unnamed'}) is missing deviceToken — each feed authenticates with its own token`);
166
+ }
167
+ // A mosaic tile carries no rtspUrl of its own — it inherits the credentialed
168
+ // URL from its source feed (the composite stream), so the operator pastes the
169
+ // RTSP URL exactly once. This is the ONLY relaxation of the no-rtspUrl skip:
170
+ // a tile with a resolvable `sourceFeedId` survives instead of being dropped.
171
+ const inheritedRtspUrl = !feed.rtspUrl && feed.sourceFeedId ? idToRtspUrl.get(feed.sourceFeedId) : undefined;
172
+ const rtspUrl = feed.rtspUrl || inheritedRtspUrl || '';
173
+ if (!rtspUrl) {
174
+ // eslint-disable-next-line no-console
175
+ console.warn(`cameras[${index}] (${feed.name || 'unnamed'}) has no rtspUrl` +
176
+ (feed.sourceFeedId
177
+ ? ` and its sourceFeedId="${feed.sourceFeedId}" matches no source feed (a feed with that id + an rtspUrl)`
178
+ : '') +
179
+ ' — skipping this feed. ' +
180
+ 'Add its rtspUrl to the config (camera credentials stay on this machine), then restart.');
181
+ return null;
182
+ }
183
+ return {
184
+ ...feed,
185
+ rtspUrl,
186
+ name: feed.name || `camera-${index + 1}`,
187
+ width: feed.width ?? partial.camera?.width,
188
+ height: feed.height ?? partial.camera?.height,
189
+ fps: feed.fps ?? partial.camera?.fps,
190
+ transport: feed.transport ?? partial.camera?.transport,
191
+ ffmpegPath: feed.ffmpegPath ?? partial.camera?.ffmpegPath,
192
+ };
193
+ }
97
194
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}