@trillboards/edge-sdk 0.2.4 → 0.2.7

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 (204) 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 -5
  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-service.ps1 +6 -1
  30. package/deploy/windows/install-update.ps1 +101 -0
  31. package/deploy/windows/installer.nsi +138 -0
  32. package/deploy/windows/register-litertlm-task.ps1 +42 -0
  33. package/deploy/windows/register-task.ps1 +44 -0
  34. package/deploy/windows/run-connector.ps1 +69 -0
  35. package/deploy/windows/setup-litertlm.ps1 +361 -0
  36. package/deploy/windows/stop-existing.ps1 +72 -0
  37. package/deploy/windows/trillboards-edge-task.vbs +25 -0
  38. package/deploy/windows/trillboards-edge.cmd +33 -0
  39. package/deploy/windows/trillboards-pair.cmd +276 -0
  40. package/deploy/windows/trillboards-setup.ps1 +456 -0
  41. package/deploy/windows/trillboards-setup.vbs +13 -0
  42. package/dist/AudienceAnalyzeClient.d.ts +136 -0
  43. package/dist/AudienceAnalyzeClient.d.ts.map +1 -0
  44. package/dist/AudienceAnalyzeClient.js +246 -0
  45. package/dist/AudienceAnalyzeClient.js.map +1 -0
  46. package/dist/ChannelProber.d.ts +150 -0
  47. package/dist/ChannelProber.d.ts.map +1 -0
  48. package/dist/ChannelProber.js +887 -0
  49. package/dist/ChannelProber.js.map +1 -0
  50. package/dist/CloudEscalationPolicy.d.ts +143 -0
  51. package/dist/CloudEscalationPolicy.d.ts.map +1 -0
  52. package/dist/CloudEscalationPolicy.js +137 -0
  53. package/dist/CloudEscalationPolicy.js.map +1 -0
  54. package/dist/CloudSensingManager.d.ts +557 -0
  55. package/dist/CloudSensingManager.d.ts.map +1 -0
  56. package/dist/CloudSensingManager.js +1267 -0
  57. package/dist/CloudSensingManager.js.map +1 -0
  58. package/dist/DiscoveryReportClient.d.ts +66 -0
  59. package/dist/DiscoveryReportClient.d.ts.map +1 -0
  60. package/dist/DiscoveryReportClient.js +85 -0
  61. package/dist/DiscoveryReportClient.js.map +1 -0
  62. package/dist/EdgeAgent.d.ts +12 -2
  63. package/dist/EdgeAgent.d.ts.map +1 -1
  64. package/dist/EdgeAgent.js +55 -13
  65. package/dist/EdgeAgent.js.map +1 -1
  66. package/dist/LiteRtVlmProvider.d.ts +193 -0
  67. package/dist/LiteRtVlmProvider.d.ts.map +1 -0
  68. package/dist/LiteRtVlmProvider.js +436 -0
  69. package/dist/LiteRtVlmProvider.js.map +1 -0
  70. package/dist/MosaicSource.d.ts +108 -0
  71. package/dist/MosaicSource.d.ts.map +1 -0
  72. package/dist/MosaicSource.js +274 -0
  73. package/dist/MosaicSource.js.map +1 -0
  74. package/dist/OnvifClient.d.ts +83 -0
  75. package/dist/OnvifClient.d.ts.map +1 -0
  76. package/dist/OnvifClient.js +209 -0
  77. package/dist/OnvifClient.js.map +1 -0
  78. package/dist/PairClient.d.ts +106 -0
  79. package/dist/PairClient.d.ts.map +1 -0
  80. package/dist/PairClient.js +186 -0
  81. package/dist/PairClient.js.map +1 -0
  82. package/dist/RecorderAddressRecovery.d.ts +53 -0
  83. package/dist/RecorderAddressRecovery.d.ts.map +1 -0
  84. package/dist/RecorderAddressRecovery.js +359 -0
  85. package/dist/RecorderAddressRecovery.js.map +1 -0
  86. package/dist/SceneChangeDetector.d.ts +291 -0
  87. package/dist/SceneChangeDetector.d.ts.map +1 -0
  88. package/dist/SceneChangeDetector.js +682 -0
  89. package/dist/SceneChangeDetector.js.map +1 -0
  90. package/dist/SensingProfileClient.d.ts +59 -0
  91. package/dist/SensingProfileClient.d.ts.map +1 -0
  92. package/dist/SensingProfileClient.js +78 -0
  93. package/dist/SensingProfileClient.js.map +1 -0
  94. package/dist/SetupSessionClient.d.ts +114 -0
  95. package/dist/SetupSessionClient.d.ts.map +1 -0
  96. package/dist/SetupSessionClient.js +95 -0
  97. package/dist/SetupSessionClient.js.map +1 -0
  98. package/dist/VlmAdmissionGate.d.ts +126 -0
  99. package/dist/VlmAdmissionGate.d.ts.map +1 -0
  100. package/dist/VlmAdmissionGate.js +166 -0
  101. package/dist/VlmAdmissionGate.js.map +1 -0
  102. package/dist/autoSense.d.ts +266 -0
  103. package/dist/autoSense.d.ts.map +1 -0
  104. package/dist/autoSense.js +606 -0
  105. package/dist/autoSense.js.map +1 -0
  106. package/dist/cli.d.ts +27 -1
  107. package/dist/cli.d.ts.map +1 -1
  108. package/dist/cli.js +526 -27
  109. package/dist/cli.js.map +1 -1
  110. package/dist/combinedSense.d.ts +128 -0
  111. package/dist/combinedSense.d.ts.map +1 -0
  112. package/dist/combinedSense.js +261 -0
  113. package/dist/combinedSense.js.map +1 -0
  114. package/dist/config.d.ts +351 -0
  115. package/dist/config.d.ts.map +1 -1
  116. package/dist/config.js +99 -2
  117. package/dist/config.js.map +1 -1
  118. package/dist/connectorCadence.d.ts +135 -0
  119. package/dist/connectorCadence.d.ts.map +1 -0
  120. package/dist/connectorCadence.js +151 -0
  121. package/dist/connectorCadence.js.map +1 -0
  122. package/dist/dataDir.d.ts +33 -0
  123. package/dist/dataDir.d.ts.map +1 -0
  124. package/dist/dataDir.js +76 -0
  125. package/dist/dataDir.js.map +1 -0
  126. package/dist/deployedCadence.d.ts +167 -0
  127. package/dist/deployedCadence.d.ts.map +1 -0
  128. package/dist/deployedCadence.js +209 -0
  129. package/dist/deployedCadence.js.map +1 -0
  130. package/dist/deployedCadenceSource.d.ts +48 -0
  131. package/dist/deployedCadenceSource.d.ts.map +1 -0
  132. package/dist/deployedCadenceSource.js +74 -0
  133. package/dist/deployedCadenceSource.js.map +1 -0
  134. package/dist/diagnose.d.ts +1 -1
  135. package/dist/diagnose.d.ts.map +1 -1
  136. package/dist/diagnose.js +15 -13
  137. package/dist/diagnose.js.map +1 -1
  138. package/dist/discovery.d.ts +131 -0
  139. package/dist/discovery.d.ts.map +1 -0
  140. package/dist/discovery.js +499 -0
  141. package/dist/discovery.js.map +1 -0
  142. package/dist/gemmaModelOta.d.ts +118 -0
  143. package/dist/gemmaModelOta.d.ts.map +1 -0
  144. package/dist/gemmaModelOta.js +291 -0
  145. package/dist/gemmaModelOta.js.map +1 -0
  146. package/dist/index.d.ts +16 -1
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js +40 -1
  149. package/dist/index.js.map +1 -1
  150. package/dist/init.d.ts.map +1 -1
  151. package/dist/init.js +25 -87
  152. package/dist/init.js.map +1 -1
  153. package/dist/keepAwake.d.ts +33 -0
  154. package/dist/keepAwake.d.ts.map +1 -0
  155. package/dist/keepAwake.js +54 -0
  156. package/dist/keepAwake.js.map +1 -0
  157. package/dist/legacyRecorderProfile.d.ts +20 -0
  158. package/dist/legacyRecorderProfile.d.ts.map +1 -0
  159. package/dist/legacyRecorderProfile.js +27 -0
  160. package/dist/legacyRecorderProfile.js.map +1 -0
  161. package/dist/localInferenceSchema.d.ts +132 -0
  162. package/dist/localInferenceSchema.d.ts.map +1 -0
  163. package/dist/localInferenceSchema.js +274 -0
  164. package/dist/localInferenceSchema.js.map +1 -0
  165. package/dist/localSenseE2eHarness.d.ts +101 -0
  166. package/dist/localSenseE2eHarness.d.ts.map +1 -0
  167. package/dist/localSenseE2eHarness.js +279 -0
  168. package/dist/localSenseE2eHarness.js.map +1 -0
  169. package/dist/localSensing.d.ts +284 -0
  170. package/dist/localSensing.d.ts.map +1 -0
  171. package/dist/localSensing.js +512 -0
  172. package/dist/localSensing.js.map +1 -0
  173. package/dist/mcp-server.d.ts.map +1 -1
  174. package/dist/mcp-server.js +3 -1
  175. package/dist/mcp-server.js.map +1 -1
  176. package/dist/modelIntegrity.d.ts +80 -0
  177. package/dist/modelIntegrity.d.ts.map +1 -0
  178. package/dist/modelIntegrity.js +194 -0
  179. package/dist/modelIntegrity.js.map +1 -0
  180. package/dist/pairVerify.d.ts +100 -0
  181. package/dist/pairVerify.d.ts.map +1 -0
  182. package/dist/pairVerify.js +296 -0
  183. package/dist/pairVerify.js.map +1 -0
  184. package/dist/qrScanner.d.ts +33 -0
  185. package/dist/qrScanner.d.ts.map +1 -0
  186. package/dist/qrScanner.js +103 -0
  187. package/dist/qrScanner.js.map +1 -0
  188. package/dist/runtimePaths.d.ts +10 -0
  189. package/dist/runtimePaths.d.ts.map +1 -0
  190. package/dist/runtimePaths.js +79 -0
  191. package/dist/runtimePaths.js.map +1 -0
  192. package/dist/sensingRunMode.d.ts +86 -0
  193. package/dist/sensingRunMode.d.ts.map +1 -0
  194. package/dist/sensingRunMode.js +346 -0
  195. package/dist/sensingRunMode.js.map +1 -0
  196. package/dist/setupMode.d.ts +98 -0
  197. package/dist/setupMode.d.ts.map +1 -0
  198. package/dist/setupMode.js +828 -0
  199. package/dist/setupMode.js.map +1 -0
  200. package/dist/setupProgress.d.ts +31 -0
  201. package/dist/setupProgress.d.ts.map +1 -0
  202. package/dist/setupProgress.js +173 -0
  203. package/dist/setupProgress.js.map +1 -0
  204. package/package.json +13 -4
@@ -0,0 +1,512 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalSensingClient = void 0;
4
+ exports.fieldSetForProfileType = fieldSetForProfileType;
5
+ exports.isAlertWorthy = isAlertWorthy;
6
+ exports.alertSignature = alertSignature;
7
+ exports.isCloudEscalationEnabled = isCloudEscalationEnabled;
8
+ exports.buildLocalSensingLoops = buildLocalSensingLoops;
9
+ const AudienceAnalyzeClient_1 = require("./AudienceAnalyzeClient");
10
+ const LiteRtVlmProvider_1 = require("./LiteRtVlmProvider");
11
+ const SensingProfileClient_1 = require("./SensingProfileClient");
12
+ const CloudSensingManager_1 = require("./CloudSensingManager");
13
+ const localInferenceSchema_1 = require("./localInferenceSchema");
14
+ const CloudEscalationPolicy_1 = require("./CloudEscalationPolicy");
15
+ const legacyRecorderProfile_1 = require("./legacyRecorderProfile");
16
+ /** How long a fetched profile is cached before the connector re-checks for a new alert. */
17
+ const DEFAULT_PROFILE_TTL_MS = 5 * 60 * 1000;
18
+ /**
19
+ * The server's own name for a Sense-Anything profile — one an operator authored
20
+ * from their venue intent, as opposed to a seeded DOOH audience preset.
21
+ * `sensingProfileService.createProfile` defaults `profile_type` to this and
22
+ * derives `profile_origin='custom_intent'` from it; 147 of the 157 active
23
+ * profiles carried it on 2026-08-11.
24
+ */
25
+ const CUSTOM_INTENT_PROFILE_TYPE = 'custom_intent';
26
+ /**
27
+ * Map a deployed profile's SELF-DESCRIBING type to the on-device field set.
28
+ *
29
+ * A Sense-Anything (`custom_intent`) profile asked a specific operational
30
+ * question about the operator's own venue, so the on-device model is asked for
31
+ * that profile's fields plus the presence primitives — NOT the 12 DOOH audience
32
+ * fields, which answer nothing the operator asked and cost 1.09-1.53x the
33
+ * per-frame latency depending on delegate (measured; see localInferenceSchema's
34
+ * header). Every other type — a seeded builtin preset, or NO deployed profile at
35
+ * all — is a screen/audience measurement deployment and keeps the full audience
36
+ * contract.
37
+ *
38
+ * This reads the kind the SERVER already reports rather than an allowlist of
39
+ * profile names maintained on the device.
40
+ */
41
+ function fieldSetForProfileType(profileType) {
42
+ return profileType === CUSTOM_INTENT_PROFILE_TYPE ? 'operator' : 'audience';
43
+ }
44
+ /**
45
+ * Sentinel values the schema uses for an EMPTY scene (see localInferenceSchema):
46
+ * audience_* enums collapse to 'none'/'unclear', crowd_density to 'empty',
47
+ * narratives to '' when no people are present. A custom enum's "nothing here"
48
+ * value typically reads the same ('empty'/'unknown'). Treated as "no signal" so
49
+ * a blank room doesn't ship a frame.
50
+ */
51
+ const EMPTY_SCENE_SENTINELS = new Set(['', 'none', 'unclear', 'empty', 'unknown']);
52
+ /**
53
+ * Keys ignored by the gate — they don't DISCRIMINATE an empty scene from a busy
54
+ * one, so gating on them would attach a frame to every observation (defeating
55
+ * the privacy filter):
56
+ * - source / captured_at: connector-stamped provenance + time (never a signal);
57
+ * - confidence: a meta score the model emits ~always > 0 even for a blank room;
58
+ * - scene_description: free text that is non-empty even when the scene is empty
59
+ * ("an empty room") — not a meaningful route equality target.
60
+ * Discriminative scene fields (person_count, people_present, crowd_density,
61
+ * ad_receptivity_score, the audience_* enums, the narrative) are NOT ignored:
62
+ * each reads its empty-sentinel/0/false for a blank scene, so they correctly
63
+ * contribute no signal when empty but DO when people are present.
64
+ */
65
+ const NON_DISCRIMINATIVE_KEYS = new Set(['source', 'captured_at', 'confidence', 'scene_description']);
66
+ /**
67
+ * Decide whether an on-device result is potentially ALERT-WORTHY — the gate that
68
+ * keeps the evidence frame OFF the wire for an EMPTY/uninteresting scene (privacy
69
+ * + bandwidth) while still attaching it for any observation an operator route
70
+ * could fire on. Sense-Anything routes evaluate gt/gte/lt/lte/eq/neq over
71
+ * numbers (counts AND gauges), booleans, and strings/enums (routeEvaluatorService
72
+ * `compare()`), so the gate cannot be limited to "true boolean / positive count"
73
+ * — a gauge route (`avg_wait_minutes gt 5`) or an enum route
74
+ * (`shelf_state eq "empty"`) would otherwise fire WITHOUT a snapshot.
75
+ *
76
+ * A frame is attached when the result carries ANY meaningful signal:
77
+ * - any boolean field that is `true` (safety_concern, people_present, a custom
78
+ * shelf_empty, …);
79
+ * - any positive finite number — a count (person_count / *_count) OR a gauge
80
+ * (avg_wait_minutes, ad_receptivity_score, …);
81
+ * - any non-empty array (a populated `list` custom field), or non-empty object
82
+ * (a `distribution` field);
83
+ * - any string/enum whose value is NOT an empty-scene sentinel
84
+ * (''/none/unclear/empty/unknown) — e.g. shelf_state='low'.
85
+ * Non-discriminative keys (source, captured_at, confidence, scene_description)
86
+ * are ignored — see NON_DISCRIMINATIVE_KEYS for why.
87
+ *
88
+ * KNOWN BOUNDARY (deliberate privacy tradeoff): a route that alerts on ABSENCE
89
+ * — `person_count lte 0`, `people_present eq false`, `shelf_state eq "empty"` —
90
+ * fires on a genuinely empty scene, which this gate suppresses by design (a
91
+ * blank-room frame is exactly the privacy harm the gate exists to prevent). The
92
+ * server owns the route predicates and can request a frame for those routes;
93
+ * operators who want every frame can disable the gate (attachEvidenceFrame).
94
+ */
95
+ function isAlertWorthy(result) {
96
+ if (!result || typeof result !== 'object')
97
+ return false;
98
+ for (const [key, value] of Object.entries(result)) {
99
+ if (NON_DISCRIMINATIVE_KEYS.has(key))
100
+ continue;
101
+ if (value === true)
102
+ return true;
103
+ if (typeof value === 'number') {
104
+ if (Number.isFinite(value) && value > 0)
105
+ return true;
106
+ continue;
107
+ }
108
+ if (typeof value === 'string') {
109
+ const v = value.trim().toLowerCase();
110
+ if (v.length > 0 && !EMPTY_SCENE_SENTINELS.has(v))
111
+ return true;
112
+ continue;
113
+ }
114
+ if (Array.isArray(value)) {
115
+ if (value.length > 0)
116
+ return true;
117
+ continue;
118
+ }
119
+ if (value && typeof value === 'object') {
120
+ if (Object.keys(value).length > 0)
121
+ return true;
122
+ continue;
123
+ }
124
+ }
125
+ return false;
126
+ }
127
+ /**
128
+ * A stable fingerprint of the ALERT-BEARING state of one result — "has the
129
+ * operator-visible situation changed?" in one comparable string.
130
+ *
131
+ * Built from EXACTLY the keys `isAlertWorthy` judges (NON_DISCRIMINATIVE_KEYS
132
+ * excluded), so the two can never drift: the same fields that decide whether a
133
+ * frame carries signal decide whether that signal is NEW. Keys are sorted so
134
+ * the model's property order cannot manufacture a change, and values are
135
+ * compared as emitted — a `queue_length` of 3 and of 4 are genuinely different
136
+ * facts and an operator route may key on exactly that boundary, so this
137
+ * deliberately does NOT bucket numbers into invented bands.
138
+ *
139
+ * Used by CloudEscalationPolicy's novelty bound: an unchanged signature means a
140
+ * verdict the cloud has already adjudicated, so the frame stays on-premises.
141
+ */
142
+ function alertSignature(result) {
143
+ if (!result || typeof result !== 'object')
144
+ return '';
145
+ const parts = [];
146
+ for (const key of Object.keys(result).sort()) {
147
+ if (NON_DISCRIMINATIVE_KEYS.has(key))
148
+ continue;
149
+ const value = result[key];
150
+ // Arrays/objects (a `list` / `distribution` custom field) serialize
151
+ // structurally; scalars stringify. Undefined is elided so an absent key and
152
+ // an explicitly-undefined key read the same.
153
+ if (value === undefined)
154
+ continue;
155
+ parts.push(`${key}=${typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value)}`);
156
+ }
157
+ return parts.join('|');
158
+ }
159
+ /**
160
+ * Is cloud escalation actually ON for this local-sensing configuration?
161
+ *
162
+ * TWO settings decide it, and this is the ONLY place that combines them:
163
+ * `cloudEscalation.enabled` opts in, and `attachEvidenceFrame: false` — the
164
+ * operator's "no frame ever leaves this machine" switch — outranks it, because
165
+ * escalation sends a frame. `LocalSensingClient` enforces the rule and the
166
+ * startup banner reports it; they must read the same expression or the banner
167
+ * will one day claim an egress posture the client is not running.
168
+ */
169
+ function isCloudEscalationEnabled(options) {
170
+ if (!options)
171
+ return false;
172
+ return options.cloudEscalation?.enabled === true && options.attachEvidenceFrame !== false;
173
+ }
174
+ /**
175
+ * LocalSensingClient — the SensingClient the cadence loop uses in LOCAL mode.
176
+ * It runs the VLM ON-DEVICE (LiteRtVlmProvider — Gemma 4 on LiteRT-LM via
177
+ * plain-completion + client-side schema validation, the real CCTV runtime) and
178
+ * POSTs the STRUCTURED result as `preAnalyzed` (uploadClient.analyzePreAnalyzed),
179
+ * skipping the cloud VLM. Implements the same `analyze({jpeg,captureMode})`
180
+ * shape AudienceAnalyzeClient exposes, so CloudSensingManager runs it UNCHANGED.
181
+ *
182
+ * PROFILE-DRIVEN: before analyzing, it fetches (and caches) the screen's active
183
+ * Sense-Anything profile via SensingProfileClient and rebuilds the constrained
184
+ * schema + prompt to include the operator's CUSTOM fields — the on-device
185
+ * equivalent of the cloud `buildProfileDrivenJsonSchema`. So a deployed alert
186
+ * like "queue_length >= 3" makes Gemma emit `queue_length`, which flows through
187
+ * preAnalyzed → OFV → the route → the webhook, exactly like the cloud VLM. The
188
+ * fetch is best-effort: on any failure the last-good (or default) schema is kept,
189
+ * so on-device sensing NEVER goes silent.
190
+ *
191
+ * The connector OVERRIDES `source='vlm_edge'` and `captured_at` (the actual
192
+ * capture time) — never trusting the model for provenance/time. The SERVER
193
+ * additionally forces provenance `local_connector` on the preAnalyzed ingest,
194
+ * gating local-inferred data out of the sold bidstream while still firing the
195
+ * operator's Sense-Anything routes.
196
+ *
197
+ * ── CLOUD ESCALATION (opt-in, Phase 6d) ─────────────────────────────────────
198
+ * Local Gemma is the always-on gate; the CLOUD is the judge for the frames it
199
+ * flags. When `cloudEscalation.enabled` and the policy admits this frame, the
200
+ * SAME JPEG is POSTed through the SAME upload client's `analyze()` — the cloud
201
+ * path the connector already owns — and the cloud VLM's verdict becomes the
202
+ * observation. Three properties are load-bearing:
203
+ *
204
+ * 1. ONE FRAME, ONE OBSERVATION. An escalated frame does NOT also post its
205
+ * local `preAnalyzed` result. Posting both would double-count the same
206
+ * physical frame and put two verdicts for one moment in the stream —
207
+ * exactly the dual write the codebase forbids. The cloud verdict
208
+ * supersedes; the local one was a sampling decision, not a datum.
209
+ * 2. NO PROVENANCE LAUNDERING. What lands is what the CLOUD inferred from a
210
+ * real frame, so the server's normal cloud provenance is the truthful
211
+ * stamp — identical to `cloud-sense` and to the existing
212
+ * `cloudFallbackOnError` frame. Nothing the local model produced enters
213
+ * that record, so `ingest_source='local_connector'` (which exists to keep
214
+ * operator-forgeable structured input out of the sold bidstream) does not
215
+ * apply and is not evaded: there IS no local structured input on this path.
216
+ * 3. THE PRIVACY GATE IS UNCHANGED. Escalation reuses `isAlertWorthy`, so the
217
+ * set of frames that can leave the premises is exactly the set that
218
+ * already leaves as evidence photos — and `attachEvidenceFrame: false`
219
+ * (the operator's "no frame ever leaves" switch) disables escalation
220
+ * outright. An empty scene never escalates.
221
+ *
222
+ * A failed cloud call falls back to posting the local result, so an escalation
223
+ * can degrade the ACCURACY of one frame but can never LOSE it.
224
+ */
225
+ class LocalSensingClient {
226
+ constructor(localProvider, uploadClient, options = {}) {
227
+ this.localProvider = localProvider;
228
+ this.uploadClient = uploadClient;
229
+ this.lastProfileFetchMs = 0;
230
+ /**
231
+ * Frames the ON-DEVICE runtime actually produced a result for.
232
+ *
233
+ * Counted at the one place that knows — immediately after `localProvider
234
+ * .analyze()` RETURNS — so a `cloudFallbackOnError` frame (local threw, the
235
+ * cloud answered) and an escalated frame are both excluded. `auto-sense`
236
+ * reads this through `getSensingHealth().localInferences` as the only
237
+ * positive proof that a `litert-lm serve` too busy to answer `/v1/models` is
238
+ * busy rather than dead; a counter that a cloud call could advance would make
239
+ * that proof self-fulfilling and mask a genuinely dead runtime forever.
240
+ */
241
+ this.localInferencesCompleted = 0;
242
+ /** The active profile name (for logging / observability). */
243
+ this.activeProfileName = null;
244
+ /**
245
+ * Which field set the active profile resolved to (for logging / observability):
246
+ * 'operator' for a Sense-Anything profile, 'audience' for a DOOH preset or no
247
+ * profile. See fieldSetForProfileType.
248
+ */
249
+ this.activeFieldSet = 'audience';
250
+ this.profileClient = options.profileClient;
251
+ this.profileTtlMs = options.profileTtlMs ?? DEFAULT_PROFILE_TTL_MS;
252
+ this.baseSchema = options.schema ?? (0, localInferenceSchema_1.buildLocalSensingSchema)();
253
+ this.basePrompt = options.prompt ?? (0, localInferenceSchema_1.buildLocalSensingPrompt)();
254
+ this.cachedSchema = this.baseSchema;
255
+ this.cachedPrompt = this.basePrompt;
256
+ this.attachEvidenceFrame = options.attachEvidenceFrame !== false;
257
+ this.cloudFallbackOnError = options.cloudFallbackOnError === true;
258
+ this.feedName = options.feedName || 'local-sense';
259
+ // `attachEvidenceFrame: false` is the operator saying NO frame may ever
260
+ // leave this machine, and escalation sends a frame — isCloudEscalationEnabled
261
+ // is the one expression that resolves the pair (the startup banner reads the
262
+ // same one, so the two can never disagree).
263
+ this.escalationPolicy = new CloudEscalationPolicy_1.CloudEscalationPolicy({
264
+ ...(options.cloudEscalation || {}),
265
+ enabled: isCloudEscalationEnabled({
266
+ attachEvidenceFrame: this.attachEvidenceFrame,
267
+ cloudEscalation: options.cloudEscalation,
268
+ }),
269
+ });
270
+ }
271
+ getEndpoint() {
272
+ return this.uploadClient.getEndpoint();
273
+ }
274
+ reportCameraHealth(cameraHealth) {
275
+ return this.uploadClient.reportCameraHealth(cameraHealth);
276
+ }
277
+ /**
278
+ * Re-fetch the active profile if the cache is stale and rebuild the schema +
279
+ * prompt from its custom fields AND its KIND. Best-effort: a fetch failure
280
+ * keeps the existing schema (never disrupts an in-flight profile).
281
+ *
282
+ * The field set follows the profile's own reported type (fieldSetForProfileType):
283
+ * a Sense-Anything profile gets its declared fields plus the presence
284
+ * primitives; a seeded DOOH preset — or a profile that declares nothing —
285
+ * keeps the full audience contract.
286
+ */
287
+ async refreshProfileIfStale() {
288
+ if (!this.profileClient)
289
+ return;
290
+ const now = Date.now();
291
+ if (now - this.lastProfileFetchMs < this.profileTtlMs)
292
+ return;
293
+ this.lastProfileFetchMs = now;
294
+ const profile = await this.profileClient.fetchProfile();
295
+ if (profile === null)
296
+ return; // transient error — keep last-good schema
297
+ this.activeProfileName = profile.profileName;
298
+ const fieldSet = fieldSetForProfileType(profile.profileType);
299
+ this.activeFieldSet = fieldSet;
300
+ if (fieldSet === 'operator' || profile.fields.length > 0) {
301
+ this.cachedSchema = (0, localInferenceSchema_1.buildLocalSensingSchema)({ fieldSet, extraFields: profile.fields });
302
+ this.cachedPrompt = (0, localInferenceSchema_1.buildLocalSensingPrompt)({ fieldSet, extraFields: profile.fields });
303
+ }
304
+ else {
305
+ // A DOOH/seeded profile with no custom fields (or no active profile at
306
+ // all) → the default audience schema, byte-for-byte unchanged.
307
+ this.cachedSchema = this.baseSchema;
308
+ this.cachedPrompt = this.basePrompt;
309
+ }
310
+ }
311
+ /**
312
+ * What the on-device runtime resolved about this host's accelerator (which
313
+ * litert-lm delegate it selected, and why it is not on the GPU when it is not).
314
+ * Surfaced so an operator/support can see a silent CPU fallback without
315
+ * log-diving. See LiteRtVlmProvider.getAcceleratorHealth.
316
+ */
317
+ getAcceleratorHealth() {
318
+ return this.localProvider.getAcceleratorHealth();
319
+ }
320
+ /**
321
+ * Cloud-escalation counters for this feed — how many candidate frames were
322
+ * adjudicated by the cloud VLM, and why the rest were not. Read by
323
+ * `CloudSensingManager.getSensingHealth()` so escalation shows up on the
324
+ * operator's periodic capacity line: sending frames off-premises must be
325
+ * VISIBLE, not merely documented.
326
+ */
327
+ getEscalationStats() {
328
+ return this.escalationPolicy.getStats();
329
+ }
330
+ /** Frames the on-device runtime produced a result for. See the field docs. */
331
+ getLocalInferenceStats() {
332
+ return { completed: this.localInferencesCompleted };
333
+ }
334
+ async analyze(request) {
335
+ await this.refreshProfileIfStale();
336
+ let structured;
337
+ try {
338
+ structured = await this.localProvider.analyze({
339
+ jpeg: request.jpeg,
340
+ schema: this.cachedSchema,
341
+ prompt: this.cachedPrompt,
342
+ });
343
+ }
344
+ catch (err) {
345
+ if (!this.cloudFallbackOnError)
346
+ throw err;
347
+ // Per-frame degrade: on-device inference threw for THIS frame, so ship it
348
+ // to the cloud VLM instead of dropping it. Reuses the SAME upload client —
349
+ // the cloud path the connector already has — so no new provider/logic. The
350
+ // server provenance-gates a cloud-served frame exactly like cloud-sense.
351
+ // eslint-disable-next-line no-console
352
+ console.warn(`[local-sense] on-device inference failed for a frame (${err.message}) — ` +
353
+ 'falling back to the cloud VLM for this frame');
354
+ return this.uploadClient.analyze({
355
+ jpeg: request.jpeg,
356
+ capturedAt: Date.now(),
357
+ captureMode: request.captureMode,
358
+ });
359
+ }
360
+ // The on-device runtime returned a result: the ONLY event that proves it is
361
+ // alive. Counted here, past every throw and before any cloud path below, so
362
+ // no cloud call can advance it.
363
+ this.localInferencesCompleted += 1;
364
+ // Connector-controlled provenance + time (don't trust the model for these).
365
+ const capturedAtMs = Date.now();
366
+ // PRIVACY-CONSCIOUS evidence frame: attach the SAME JPEG the VLM analyzed
367
+ // (no re-capture) only when enabled AND the observation is potentially
368
+ // alert-worthy. Empty/uninteresting scenes send results only — no frame.
369
+ // Evaluate over the raw model output (not the provenance-stamped `result`,
370
+ // though both yield the same gate since source/captured_at are strings).
371
+ const hasSignal = isAlertWorthy(structured);
372
+ // ESCALATION: a candidate the local model flagged is adjudicated by the
373
+ // cloud VLM BEFORE it can fire an alert. Same gate as the evidence frame,
374
+ // so no frame leaves that would not already have left; the policy adds the
375
+ // novelty/pacing/budget bounds. On success this frame's observation is the
376
+ // CLOUD's — the local result is deliberately not also posted (one frame,
377
+ // one observation).
378
+ const canEscalate = this.escalationPolicy.isEnabled() && hasSignal;
379
+ if (this.escalationPolicy.tryEscalate({
380
+ hasSignal,
381
+ // Only computed when the frame could actually escalate. This runs on
382
+ // every frame of every camera and the common case — escalation off, or
383
+ // an empty scene — needs no signature at all (tryEscalate rejects on
384
+ // `disabled`/`no_signal` before it reads one).
385
+ signature: canEscalate ? alertSignature(structured) : '',
386
+ }).escalate) {
387
+ try {
388
+ const escalated = await this.uploadClient.analyze({
389
+ jpeg: request.jpeg,
390
+ capturedAt: capturedAtMs,
391
+ captureMode: request.captureMode,
392
+ });
393
+ // Rare by construction (bounded per feed per hour), so log every one:
394
+ // an operator must be able to see each frame that left the premises.
395
+ // eslint-disable-next-line no-console
396
+ console.log(`[${this.feedName}] local candidate escalated to the cloud VLM for confirmation ` +
397
+ `→ HTTP ${escalated.statusCode}${escalated.ok ? '' : ' (NOT OK)'}`);
398
+ if (escalated.ok)
399
+ return escalated;
400
+ // A non-2xx cloud verdict is no verdict at all — fall through and ship
401
+ // the local result rather than losing the observation.
402
+ // eslint-disable-next-line no-console
403
+ console.warn(`[${this.feedName}] cloud escalation returned HTTP ${escalated.statusCode} — ` +
404
+ 'keeping the on-device verdict for this frame');
405
+ }
406
+ catch (err) {
407
+ // eslint-disable-next-line no-console
408
+ console.warn(`[${this.feedName}] cloud escalation failed (${err.message}) — ` +
409
+ 'keeping the on-device verdict for this frame');
410
+ }
411
+ }
412
+ const result = {
413
+ ...structured,
414
+ source: 'vlm_edge',
415
+ captured_at: new Date(capturedAtMs).toISOString(),
416
+ };
417
+ const includeFrame = this.attachEvidenceFrame && hasSignal;
418
+ return this.uploadClient.analyzePreAnalyzed(result, {
419
+ capturedAt: capturedAtMs,
420
+ captureMode: request.captureMode,
421
+ ...(includeFrame ? { jpeg: request.jpeg } : {}),
422
+ });
423
+ }
424
+ }
425
+ exports.LocalSensingClient = LocalSensingClient;
426
+ /**
427
+ * Build per-feed capture loops for LOCAL sensing — one RTSP provider + one
428
+ * LocalSensingClient (local VLM + preAnalyzed upload) per feed, each under its
429
+ * OWN device token. Each client fetches its screen's active Sense-Anything
430
+ * profile so the on-device VLM emits that screen's custom fields. Mirrors
431
+ * buildSensingLoops but swaps the cloud client for the local one.
432
+ */
433
+ function buildLocalSensingLoops(config, deps) {
434
+ const feeds = (0, CloudSensingManager_1.resolveFeeds)(config);
435
+ if (feeds.length === 0) {
436
+ throw new Error('buildLocalSensingLoops: no camera feeds configured (set `cameras: [...]` or `camera.rtspUrl`)');
437
+ }
438
+ const litertLmBaseUrl = config.localSensing?.litertLmBaseUrl;
439
+ const model = config.localSensing?.model;
440
+ // Which litert-lm delegate to run. Default 'auto' (resolve the `,gpu` variant
441
+ // from the live model list and prefer it) — see LiteRtVlmProvider.
442
+ const accelerator = config.localSensing?.accelerator;
443
+ // Evidence frame defaults ON; resolveConfig normalizes it, but treat a bare
444
+ // (un-normalized) config or absent localSensing the same way (explicit false
445
+ // is the only opt-out).
446
+ const attachEvidenceFrame = config.localSensing?.attachEvidenceFrame !== false;
447
+ // Per-frame cloud fallback defaults OFF (opt-in) so the plain local-sense path
448
+ // is unchanged. When true, a frame whose on-device inference throws is POSTed
449
+ // to the cloud VLM (same upload client) rather than dropped.
450
+ const cloudFallbackOnError = config.localSensing?.cloudFallbackOnError === true;
451
+ // Cloud escalation defaults OFF. Each feed gets its OWN policy instance so the
452
+ // per-camera budget is per camera (a shared one would let a busy feed spend a
453
+ // quiet feed's confirmations).
454
+ const cloudEscalation = config.localSensing?.cloudEscalation;
455
+ const rtspFactory = deps?.rtspProviderFactory ??
456
+ ((feed) => {
457
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
458
+ const { RTSPCameraProvider } = require('@trillboards/edge-platform-rtsp');
459
+ return new RTSPCameraProvider({
460
+ rtspUrl: feed.rtspUrl,
461
+ width: feed.width,
462
+ height: feed.height,
463
+ fps: feed.fps,
464
+ transport: feed.transport,
465
+ ffmpegPath: feed.ffmpegPath,
466
+ // Honor a mosaic tile's crop in the per-tile-pull fallback path (shared
467
+ // grouping handles the single-pull case). Without this, a mosaic config
468
+ // in local-sense would send the whole composite for each tile.
469
+ crop: feed.crop,
470
+ outputFormat: feed.outputFormat ?? 'jpeg',
471
+ jpegQuality: feed.jpegQuality,
472
+ ...(deps?.streamUrlResolver
473
+ ? {
474
+ streamUrlResolver: (currentUrl, error) => deps.streamUrlResolver(feed, currentUrl, error),
475
+ }
476
+ : {}),
477
+ ...(0, legacyRecorderProfile_1.legacyRecorderRtspOptions)(feed.sourceType),
478
+ });
479
+ });
480
+ const localProviderFactory = deps?.localProviderFactory ??
481
+ (() => new LiteRtVlmProvider_1.LiteRtVlmProvider({ litertLmBaseUrl, model, accelerator }));
482
+ const uploadClientFactory = deps?.uploadClientFactory ??
483
+ ((feed) => new AudienceAnalyzeClient_1.AudienceAnalyzeClient({
484
+ apiBaseUrl: config.apiBaseUrl,
485
+ deviceToken: feed.deviceToken,
486
+ screenId: feed.screenId,
487
+ imageWidth: feed.width,
488
+ imageHeight: feed.height,
489
+ }, deps?.httpPoster));
490
+ // Each feed fetches its OWN screen's active profile (device token binds to one
491
+ // screen). Falls back to the default schema if the profile fetch ever fails.
492
+ const profileClientFactory = deps?.profileClientFactory ??
493
+ ((feed) => new SensingProfileClient_1.SensingProfileClient({ apiBaseUrl: config.apiBaseUrl, deviceToken: feed.deviceToken }, deps?.httpPoster));
494
+ // Mosaic-aware (shared with cloud sensing): collapses tiles of one source onto
495
+ // a single shared pull + crops each tile, so local-sense honors mosaic configs
496
+ // identically to cloud-sense. `sharedSources` lets the VLM attach to the SAME
497
+ // pull the on-device zoo uses (combined-sense), so one RTSP pull feeds both.
498
+ return (0, CloudSensingManager_1.buildTiledCameras)(feeds, rtspFactory, {
499
+ sharedSources: deps?.sharedSources,
500
+ }).map(({ feed, camera }) => ({
501
+ name: feed.name,
502
+ camera,
503
+ client: new LocalSensingClient(localProviderFactory(feed), uploadClientFactory(feed), {
504
+ profileClient: profileClientFactory(feed),
505
+ attachEvidenceFrame,
506
+ cloudFallbackOnError,
507
+ feedName: feed.name,
508
+ ...(cloudEscalation ? { cloudEscalation } : {}),
509
+ }),
510
+ }));
511
+ }
512
+ //# sourceMappingURL=localSensing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localSensing.js","sourceRoot":"","sources":["../src/localSensing.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AA43CH,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAmGnC;AAGD,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AA+3CH,iBAAe,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAmGnC;AAGD,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,CAAC"}
@@ -915,7 +915,9 @@ function checkModels() {
915
915
  return {
916
916
  name: 'models',
917
917
  status: 'warn',
918
- message: `No ONNX models found. Run: trillboards-edge download-models`,
918
+ message: 'No ONNX models found. Trillboards does not distribute blazeface.onnx/yamnet.onnx; ' +
919
+ 'place them in the models directory or fetch from your own origin with: ' +
920
+ 'trillboards-edge download-models --from <url>',
919
921
  details: { searchedDirs: modelDirs.map((d) => path.resolve(d)) },
920
922
  };
921
923
  }