@trillboards/edge-sdk 0.2.40 → 0.2.41
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.
- package/README.md +1 -1
- package/deploy/docker/{Dockerfile.cloud-sense → Dockerfile.connector} +52 -39
- package/deploy/docker/README.md +73 -1
- package/deploy/docker/connector-entrypoint.sh +46 -80
- package/deploy/macos/README.md +1 -1
- package/deploy/macos/test-install-pair.sh +9 -9
- package/deploy/systemd/install.sh +16 -1
- package/deploy/systemd/setup-litertlm.sh +201 -0
- package/deploy/systemd/trillboards-edge.service +8 -4
- package/deploy/systemd/trillboards-litertlm.service +46 -0
- package/deploy/windows/register-task.ps1 +6 -3
- package/deploy/windows/run-connector.ps1 +1 -1
- package/deploy/windows/trillboards-edge-task.vbs +5 -1
- package/dist/CloudSensingManager.d.ts +45 -19
- package/dist/CloudSensingManager.d.ts.map +1 -1
- package/dist/CloudSensingManager.js +89 -46
- package/dist/CloudSensingManager.js.map +1 -1
- package/dist/EdgeAgent.js +1 -1
- package/dist/SensingProfileClient.d.ts +9 -0
- package/dist/SensingProfileClient.d.ts.map +1 -1
- package/dist/SensingProfileClient.js +4 -0
- package/dist/SensingProfileClient.js.map +1 -1
- package/dist/SetupSessionClient.d.ts +0 -1
- package/dist/SetupSessionClient.d.ts.map +1 -1
- package/dist/autoSense.d.ts +42 -31
- package/dist/autoSense.d.ts.map +1 -1
- package/dist/autoSense.js +127 -99
- package/dist/autoSense.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +25 -84
- package/dist/cli.js.map +1 -1
- package/dist/combinedSense.d.ts +1 -3
- package/dist/combinedSense.d.ts.map +1 -1
- package/dist/combinedSense.js +0 -1
- package/dist/combinedSense.js.map +1 -1
- package/dist/config.d.ts +17 -64
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -10
- package/dist/config.js.map +1 -1
- package/dist/deployedCadence.d.ts +37 -75
- package/dist/deployedCadence.d.ts.map +1 -1
- package/dist/deployedCadence.js +45 -105
- package/dist/deployedCadence.js.map +1 -1
- package/dist/deployedCadenceSource.d.ts +0 -2
- package/dist/deployedCadenceSource.d.ts.map +1 -1
- package/dist/deployedCadenceSource.js +0 -1
- package/dist/deployedCadenceSource.js.map +1 -1
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.js +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -8
- package/dist/index.js.map +1 -1
- package/dist/localInferenceSchema.d.ts +19 -0
- package/dist/localInferenceSchema.d.ts.map +1 -1
- package/dist/localInferenceSchema.js +23 -0
- package/dist/localInferenceSchema.js.map +1 -1
- package/dist/localSensing.d.ts +48 -107
- package/dist/localSensing.d.ts.map +1 -1
- package/dist/localSensing.js +80 -171
- package/dist/localSensing.js.map +1 -1
- package/dist/sensingCatchRate.d.ts +246 -0
- package/dist/sensingCatchRate.d.ts.map +1 -0
- package/dist/sensingCatchRate.js +514 -0
- package/dist/sensingCatchRate.js.map +1 -0
- package/dist/sensingRunMode.d.ts +14 -11
- package/dist/sensingRunMode.d.ts.map +1 -1
- package/dist/sensingRunMode.js +9 -92
- package/dist/sensingRunMode.js.map +1 -1
- package/dist/setupMode.d.ts +0 -1
- package/dist/setupMode.d.ts.map +1 -1
- package/dist/setupMode.js +5 -3
- package/dist/setupMode.js.map +1 -1
- package/dist/startupDiscovery.d.ts +43 -0
- package/dist/startupDiscovery.d.ts.map +1 -0
- package/dist/startupDiscovery.js +72 -0
- package/dist/startupDiscovery.js.map +1 -0
- package/package.json +1 -1
- package/dist/CloudEscalationPolicy.d.ts +0 -143
- package/dist/CloudEscalationPolicy.d.ts.map +0 -1
- package/dist/CloudEscalationPolicy.js +0 -137
- package/dist/CloudEscalationPolicy.js.map +0 -1
package/dist/autoSense.d.ts
CHANGED
|
@@ -47,9 +47,16 @@ export declare function probeLiteRtLmHealth(baseUrl?: string, opts?: {
|
|
|
47
47
|
}): Promise<LiteRtHealthResult>;
|
|
48
48
|
/** A timer handle the supervisor sets for its re-probe loop (injectable in tests). */
|
|
49
49
|
export type AutoSenseTimerHandle = ReturnType<typeof setTimeout>;
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Which sensing path the supervisor is currently running.
|
|
52
|
+
*
|
|
53
|
+
* `local` analyzes on-device. `waiting` runs the same manager with inference
|
|
54
|
+
* switched off — cameras open, health reported, nothing analyzed — because a
|
|
55
|
+
* connector that cannot sense yet must still be visibly alive. There is no
|
|
56
|
+
* `cloud`: see the module header.
|
|
57
|
+
*/
|
|
58
|
+
export type SensingMode = 'local' | 'waiting';
|
|
59
|
+
export type AutoSenseState = SensingMode;
|
|
53
60
|
/**
|
|
54
61
|
* Everything the AutoSenseSupervisor needs, all injectable so the supervisor can
|
|
55
62
|
* be unit-tested with a fake probe + fake managers. `runAutoSense` builds this
|
|
@@ -60,8 +67,6 @@ export interface AutoSenseSupervisorDeps {
|
|
|
60
67
|
probe: () => Promise<LiteRtHealthResult>;
|
|
61
68
|
/** Build (NOT start) the local-sensing manager. `null` ⇒ no feeds (fatal). */
|
|
62
69
|
buildLocalManager: () => SensingManager | null;
|
|
63
|
-
/** Build (NOT start) the cloud-sensing manager. `null` ⇒ no feeds (fatal). */
|
|
64
|
-
buildCloudManager: () => SensingManager | null;
|
|
65
70
|
/** Log sink (tests capture it). */
|
|
66
71
|
log: (msg: string) => void;
|
|
67
72
|
/** Sleep between startup re-probes (tests inject a no-op). */
|
|
@@ -80,10 +85,8 @@ export interface AutoSenseSupervisorDeps {
|
|
|
80
85
|
startupBackoffMs: number;
|
|
81
86
|
/** Steady-state re-probe interval (ms), already floored. */
|
|
82
87
|
recheckIntervalMs: number;
|
|
83
|
-
/** Consecutive unhealthy re-probes required before
|
|
88
|
+
/** Consecutive unhealthy re-probes required before the runtime is called unhealthy. */
|
|
84
89
|
unhealthyChecksBeforeFallback: number;
|
|
85
|
-
/** Cloud inference is opt-in because continuous CCTV cloud load is expensive. */
|
|
86
|
-
allowCloudFallback: boolean;
|
|
87
90
|
/** For the initial "probing…" log line. */
|
|
88
91
|
litertLmBaseUrl: string;
|
|
89
92
|
expectedModel: string;
|
|
@@ -95,13 +98,12 @@ export interface AutoSenseSupervisorDeps {
|
|
|
95
98
|
* "restart to re-probe"), it OWNS the sensing lifecycle so the connector always
|
|
96
99
|
* prefers on-device Gemma and switches automatically with NO restart:
|
|
97
100
|
*
|
|
98
|
-
* - startInitial(): bounded startup re-probe → start the
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
+
* - startInitial(): bounded startup re-probe → start the REAL manager with
|
|
102
|
+
* inference ON when litert-lm is healthy, or in `healthOnly` mode when it is
|
|
103
|
+
* not, so a waiting connector still reports its cameras.
|
|
101
104
|
* - beginRechecks(): a steady-state background re-probe loop on recheckIntervalMs.
|
|
102
|
-
* - recheck(): one tick — AUTO-UPGRADE
|
|
103
|
-
* (fixes the fresh-install race with zero restart).
|
|
104
|
-
* installs may also recover local→cloud after K unhealthy probes.
|
|
105
|
+
* - recheck(): one tick — AUTO-UPGRADE waiting→local the instant the model is
|
|
106
|
+
* ready (fixes the fresh-install race with zero restart).
|
|
105
107
|
* - shutdown(): stop the active manager + clear the timer (SIGINT/SIGTERM).
|
|
106
108
|
*
|
|
107
109
|
* At most ONE switch per tick; a re-entrancy guard + the K-debounce mean a
|
|
@@ -112,7 +114,6 @@ export declare class AutoSenseSupervisor {
|
|
|
112
114
|
private mode;
|
|
113
115
|
private active;
|
|
114
116
|
private localManager;
|
|
115
|
-
private cloudManager;
|
|
116
117
|
private consecutiveUnhealthy;
|
|
117
118
|
/**
|
|
118
119
|
* Completed local analyses as of the previous re-probe. `null` = no baseline
|
|
@@ -120,6 +121,14 @@ export declare class AutoSenseSupervisor {
|
|
|
120
121
|
* positive evidence" and leaves the pre-existing behaviour in place.
|
|
121
122
|
*/
|
|
122
123
|
private analysesAtLastRecheck;
|
|
124
|
+
/**
|
|
125
|
+
* Why the connector is not sensing, in the probe's own words. Restated on
|
|
126
|
+
* every re-probe while waiting: a connector that goes quiet after ONE line at
|
|
127
|
+
* boot is how "waiting for a 2.4 GB model" became indistinguishable from
|
|
128
|
+
* "dead" in the first place, and the operator reading the log an hour later
|
|
129
|
+
* needs the reason to still be there.
|
|
130
|
+
*/
|
|
131
|
+
private lastWaitReason;
|
|
123
132
|
private timer;
|
|
124
133
|
private switching;
|
|
125
134
|
private stopped;
|
|
@@ -140,6 +149,7 @@ export declare class AutoSenseSupervisor {
|
|
|
140
149
|
inFlight: number;
|
|
141
150
|
buffered: number;
|
|
142
151
|
};
|
|
152
|
+
/** The one manager, built on first use. Same instance for both states. */
|
|
143
153
|
private getManager;
|
|
144
154
|
/**
|
|
145
155
|
* Initial probe (bounded startup re-probe loop for the fresh-install warmup
|
|
@@ -147,7 +157,13 @@ export declare class AutoSenseSupervisor {
|
|
|
147
157
|
* onNoFeeds) when the chosen mode has no feeds configured.
|
|
148
158
|
*/
|
|
149
159
|
startInitial(): Promise<boolean>;
|
|
150
|
-
/**
|
|
160
|
+
/**
|
|
161
|
+
* Start the manager in the given mode and make it active. False ⇒ no feeds.
|
|
162
|
+
*
|
|
163
|
+
* `waiting` starts the SAME manager with `healthOnly`, so the feeds are
|
|
164
|
+
* validated and the cameras are opened either way — a bad/empty config still
|
|
165
|
+
* fails immediately instead of looking like a model download in progress.
|
|
166
|
+
*/
|
|
151
167
|
private activate;
|
|
152
168
|
/** Start the steady-state background re-probe loop. */
|
|
153
169
|
beginRechecks(): void;
|
|
@@ -161,12 +177,9 @@ export declare class AutoSenseSupervisor {
|
|
|
161
177
|
/**
|
|
162
178
|
* One background re-probe tick.
|
|
163
179
|
* - While WAITING: a healthy probe starts local sensing immediately.
|
|
164
|
-
* - On CLOUD: any healthy probe → AUTO-UPGRADE to local immediately (running
|
|
165
|
-
* on cloud while local is ready is pure waste; the K-debounce on the return
|
|
166
|
-
* path bounds any toggling, so this is safe on the first healthy probe).
|
|
167
180
|
* - On LOCAL: a healthy probe resets the debounce; an unhealthy probe
|
|
168
|
-
* increments it, and only after K CONSECUTIVE unhealthy probes do we
|
|
169
|
-
*
|
|
181
|
+
* increments it, and only after K CONSECUTIVE unhealthy probes do we report
|
|
182
|
+
* the runtime unhealthy — sensing stays put and recovers in place.
|
|
170
183
|
* At most one switch per tick; re-entrancy guarded.
|
|
171
184
|
*
|
|
172
185
|
* ── Why an unanswered probe is not, by itself, a verdict ───────────────────
|
|
@@ -194,11 +207,11 @@ export declare class AutoSenseSupervisor {
|
|
|
194
207
|
* landing; the moment they stop landing the same probe counts, and K of them
|
|
195
208
|
* demote exactly as before.
|
|
196
209
|
*
|
|
197
|
-
* It has to be `localInferences` and not the total analysis count
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
210
|
+
* It has to be `localInferences` and not the total analysis count, and that
|
|
211
|
+
* distinction must be preserved even though the connector no longer has a
|
|
212
|
+
* cloud path that could advance the total on its own: this counter is the
|
|
213
|
+
* supervisor's only proof of life, so anything but a COMPLETED on-device
|
|
214
|
+
* inference advancing it would make the proof self-fulfilling.
|
|
202
215
|
*
|
|
203
216
|
* Deliberately NOT keyed on "we have a request in flight": a serve that
|
|
204
217
|
* ACCEPTS and never answers holds our permit for the provider's whole timeout,
|
|
@@ -216,7 +229,7 @@ export declare class AutoSenseSupervisor {
|
|
|
216
229
|
/** Injection points for runAutoSense (default to the real probe/managers/timers). */
|
|
217
230
|
export interface AutoSenseDeps {
|
|
218
231
|
/**
|
|
219
|
-
* Probe used to decide
|
|
232
|
+
* Probe used to decide sense-vs-wait. Defaults to probeLiteRtLmHealth.
|
|
220
233
|
* Receives the resolved base URL, per-probe timeout, and the model the local
|
|
221
234
|
* path will run (so the probe can require it before declaring local usable).
|
|
222
235
|
*/
|
|
@@ -225,8 +238,6 @@ export interface AutoSenseDeps {
|
|
|
225
238
|
loadConfig?: (configPath: string) => EdgeAgentConfig;
|
|
226
239
|
/** Build (NOT start) the local manager. Defaults to buildLocalSensingManager(config). */
|
|
227
240
|
buildLocalManager?: () => SensingManager | null;
|
|
228
|
-
/** Build (NOT start) the cloud manager. Defaults to buildCloudSensingManager(config). */
|
|
229
|
-
buildCloudManager?: () => SensingManager | null;
|
|
230
241
|
/** Log sink (tests capture it). Defaults to console.log. */
|
|
231
242
|
log?: (msg: string) => void;
|
|
232
243
|
/** Sleep between startup re-probes (tests inject a no-op). Defaults to setTimeout. */
|
|
@@ -264,8 +275,8 @@ export interface AutoSenseDeps {
|
|
|
264
275
|
* genuine config error propagates to main() (prints + exits 1) — the same net
|
|
265
276
|
* result as before, where the chosen run mode re-loaded the config and reported it.
|
|
266
277
|
*
|
|
267
|
-
* The explicit `local-sense`
|
|
268
|
-
*
|
|
278
|
+
* The explicit `local-sense` entrypoint stays non-switching; only `auto-sense`
|
|
279
|
+
* is supervised.
|
|
269
280
|
*/
|
|
270
281
|
export declare function runAutoSense(args: string[], deps?: AutoSenseDeps): Promise<void>;
|
|
271
282
|
/**
|
package/dist/autoSense.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autoSense.d.ts","sourceRoot":"","sources":["../src/autoSense.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAE5D,OAAO,
|
|
1
|
+
{"version":3,"file":"autoSense.d.ts","sourceRoot":"","sources":["../src/autoSense.ts"],"names":[],"mappings":"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"}
|
package/dist/autoSense.js
CHANGED
|
@@ -43,6 +43,7 @@ const config_1 = require("./config");
|
|
|
43
43
|
const LiteRtVlmProvider_1 = require("./LiteRtVlmProvider");
|
|
44
44
|
const sensingRunMode_1 = require("./sensingRunMode");
|
|
45
45
|
const gemmaModelOta_1 = require("./gemmaModelOta");
|
|
46
|
+
const startupDiscovery_1 = require("./startupDiscovery");
|
|
46
47
|
const connectorUpdate_1 = require("./connectorUpdate");
|
|
47
48
|
/**
|
|
48
49
|
* This install's version, read from package.json rather than declared as a
|
|
@@ -61,18 +62,39 @@ function connectorVersion() {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
/**
|
|
64
|
-
* `auto-sense` — the ONE dispatching entrypoint for the BYO-CCTV connector
|
|
65
|
-
*
|
|
66
|
-
*
|
|
65
|
+
* `auto-sense` — the ONE dispatching entrypoint for the BYO-CCTV connector.
|
|
66
|
+
* The connector is LOCAL-INFERENCE ONLY: it runs Gemma on-device via LiteRT-LM
|
|
67
|
+
* and there is no cloud VLM path in it at all.
|
|
67
68
|
*
|
|
68
69
|
* A fresh install may start before its model download finishes, so this probes
|
|
69
|
-
* `litert-lm serve` health with a SHORT timeout and:
|
|
70
|
-
* - healthy →
|
|
71
|
-
* -
|
|
72
|
-
*
|
|
70
|
+
* `litert-lm serve` health with a SHORT timeout and runs one of TWO states:
|
|
71
|
+
* - healthy → `local`: the full on-device sensing path
|
|
72
|
+
* - otherwise → `waiting`: cameras open, video proven, camera health reported
|
|
73
|
+
* on the heartbeat, and NOTHING analyzed or uploaded
|
|
73
74
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
75
|
+
* ── Why there is no third state ────────────────────────────────────────────
|
|
76
|
+
*
|
|
77
|
+
* There used to be: `cloud`, entered when `allowCloudFallback` was set or the
|
|
78
|
+
* local runtime went unhealthy. Measured 2026-08-12 over 255,812 image-bearing
|
|
79
|
+
* cloud VLM calls, that path cost $0.007008 per analyzed frame — ~$605/camera/
|
|
80
|
+
* month at a 30s cadence, so a 16-camera store ran ~$9,688/month against a
|
|
81
|
+
* $99-500 subscription. It is removed rather than rate-limited, which SUPERSEDES
|
|
82
|
+
* the escalation architecture from #11971 ("local Gemma decides *when* to look,
|
|
83
|
+
* the cloud decides *what* it saw") and the frequency-based cost control in
|
|
84
|
+
* plans/cctv-byo-sensing-plan.md:26. Both spent less per frame; neither changes
|
|
85
|
+
* the sign of a per-frame cost inside a flat-rate product.
|
|
86
|
+
*
|
|
87
|
+
* ── Why `waiting` is not idle ──────────────────────────────────────────────
|
|
88
|
+
*
|
|
89
|
+
* `waiting` used to start NO manager, so it emitted no analyze, no camera health
|
|
90
|
+
* and no heartbeat — a connector importing a 2.4 GB model looked exactly like a
|
|
91
|
+
* connector that had been unplugged, on the HAPPY path of a first boot. It now
|
|
92
|
+
* runs the SAME manager in `healthOnly` mode: same feeds, same cameras, same
|
|
93
|
+
* health reporting, zero inference. The portal sees a live connector whose
|
|
94
|
+
* cameras are proven and whose `sensingState` is `unavailable`.
|
|
95
|
+
*
|
|
96
|
+
* It introduces NO new providers and NO duplicated sensing logic — the local run
|
|
97
|
+
* mode is reused verbatim. This module only owns the SELECTION.
|
|
76
98
|
*/
|
|
77
99
|
const DEFAULT_LITERT_LM_BASE_URL = 'http://localhost:9379';
|
|
78
100
|
/** Default model the local-sense path runs (must match config.localSensing.model). */
|
|
@@ -86,10 +108,9 @@ const DEFAULT_PROBE_TIMEOUT_MS = 2000;
|
|
|
86
108
|
/**
|
|
87
109
|
* Startup re-probe budget. A fresh install races litert-lm warmup/model-download
|
|
88
110
|
* (e.g. Windows `setup-litertlm.ps1` runs detached while the scheduled task can
|
|
89
|
-
* start right after pairing). A single probe at boot would
|
|
90
|
-
*
|
|
91
|
-
* re-probe a few times before
|
|
92
|
-
* attempts.
|
|
111
|
+
* start right after pairing). A single probe at boot would drop straight into
|
|
112
|
+
* the waiting state and only leave it on the next re-probe, so at startup we
|
|
113
|
+
* re-probe a few times before settling. Default 4 attempts.
|
|
93
114
|
*/
|
|
94
115
|
const DEFAULT_STARTUP_PROBE_ATTEMPTS = 4;
|
|
95
116
|
/** Delay between startup re-probes (ms). Default 5s → ~15s warmup window. */
|
|
@@ -97,16 +118,16 @@ const DEFAULT_STARTUP_PROBE_BACKOFF_MS = 5000;
|
|
|
97
118
|
/**
|
|
98
119
|
* Steady-state background re-probe interval (ms). AFTER auto-sense picks a mode it
|
|
99
120
|
* keeps probing on this cadence so it can start local inference once a fresh
|
|
100
|
-
* install's litert-lm finishes warming up
|
|
101
|
-
*
|
|
121
|
+
* install's litert-lm finishes warming up, and to restate WHY it is still
|
|
122
|
+
* waiting while it waits. Default 60s.
|
|
102
123
|
*/
|
|
103
124
|
const DEFAULT_HEALTH_RECHECK_INTERVAL_MS = 60000;
|
|
104
125
|
/** Floor for the re-probe interval (ms) so a mis-set tiny value can't hammer the serve. */
|
|
105
126
|
const MIN_HEALTH_RECHECK_INTERVAL_MS = 15000;
|
|
106
127
|
/**
|
|
107
|
-
* Anti-thrash: consecutive UNHEALTHY background re-probes required before
|
|
108
|
-
*
|
|
109
|
-
*
|
|
128
|
+
* Anti-thrash: consecutive UNHEALTHY background re-probes required before the
|
|
129
|
+
* on-device runtime is REPORTED unhealthy (a single flapping bad probe must not
|
|
130
|
+
* raise an alarm). The waiting→local upgrade is immediate. Default 2.
|
|
110
131
|
*/
|
|
111
132
|
const DEFAULT_LOCAL_UNHEALTHY_CHECKS_BEFORE_FALLBACK = 2;
|
|
112
133
|
/**
|
|
@@ -146,10 +167,10 @@ async function probeLiteRtLmHealth(baseUrl = DEFAULT_LITERT_LM_BASE_URL, opts =
|
|
|
146
167
|
// -> ok=false "is up but does not host model ...,gpu
|
|
147
168
|
// (has: gemma-4-E2B-it.litertlm)"
|
|
148
169
|
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
170
|
+
// auto-sense then sets `mode='waiting'`, so a configuration the provider
|
|
171
|
+
// documents as valid would stop the connector analyzing anything. (It no
|
|
172
|
+
// longer goes DARK — waiting runs the cameras in health-only mode — but it
|
|
173
|
+
// still senses nothing, which is just as wrong for a valid config.)
|
|
153
174
|
//
|
|
154
175
|
// The defect is two modules disagreeing about whether `localSensing.model` may
|
|
155
176
|
// carry an accelerator suffix; this makes the probe agree with the component
|
|
@@ -211,7 +232,7 @@ async function probeLiteRtLmHealth(baseUrl = DEFAULT_LITERT_LM_BASE_URL, opts =
|
|
|
211
232
|
};
|
|
212
233
|
}
|
|
213
234
|
// The serve is up with model(s), but if it isn't hosting the one we'll infer
|
|
214
|
-
// against, local would fail every frame — so treat that as unhealthy
|
|
235
|
+
// against, local would fail every frame — so treat that as unhealthy.
|
|
215
236
|
if (expectedModel && !modelIds.includes(expectedModel)) {
|
|
216
237
|
return {
|
|
217
238
|
ok: false,
|
|
@@ -236,13 +257,12 @@ async function probeLiteRtLmHealth(baseUrl = DEFAULT_LITERT_LM_BASE_URL, opts =
|
|
|
236
257
|
* "restart to re-probe"), it OWNS the sensing lifecycle so the connector always
|
|
237
258
|
* prefers on-device Gemma and switches automatically with NO restart:
|
|
238
259
|
*
|
|
239
|
-
* - startInitial(): bounded startup re-probe → start the
|
|
240
|
-
*
|
|
241
|
-
*
|
|
260
|
+
* - startInitial(): bounded startup re-probe → start the REAL manager with
|
|
261
|
+
* inference ON when litert-lm is healthy, or in `healthOnly` mode when it is
|
|
262
|
+
* not, so a waiting connector still reports its cameras.
|
|
242
263
|
* - beginRechecks(): a steady-state background re-probe loop on recheckIntervalMs.
|
|
243
|
-
* - recheck(): one tick — AUTO-UPGRADE
|
|
244
|
-
* (fixes the fresh-install race with zero restart).
|
|
245
|
-
* installs may also recover local→cloud after K unhealthy probes.
|
|
264
|
+
* - recheck(): one tick — AUTO-UPGRADE waiting→local the instant the model is
|
|
265
|
+
* ready (fixes the fresh-install race with zero restart).
|
|
246
266
|
* - shutdown(): stop the active manager + clear the timer (SIGINT/SIGTERM).
|
|
247
267
|
*
|
|
248
268
|
* At most ONE switch per tick; a re-entrancy guard + the K-debounce mean a
|
|
@@ -253,11 +273,12 @@ class AutoSenseSupervisor {
|
|
|
253
273
|
this.deps = deps;
|
|
254
274
|
this.mode = null;
|
|
255
275
|
this.active = null;
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
276
|
+
// Built once and reused across switches: `waiting` and `local` are the SAME
|
|
277
|
+
// manager over the same feeds, differing only in whether inference runs, so a
|
|
278
|
+
// promotion is a stop()→start() cycle on one instance rather than two
|
|
279
|
+
// half-initialised copies of the fleet. `undefined` = not built yet; `null` =
|
|
280
|
+
// built but no feeds (fatal — cached so we don't rebuild).
|
|
259
281
|
this.localManager = undefined;
|
|
260
|
-
this.cloudManager = undefined;
|
|
261
282
|
this.consecutiveUnhealthy = 0;
|
|
262
283
|
/**
|
|
263
284
|
* Completed local analyses as of the previous re-probe. `null` = no baseline
|
|
@@ -265,6 +286,14 @@ class AutoSenseSupervisor {
|
|
|
265
286
|
* positive evidence" and leaves the pre-existing behaviour in place.
|
|
266
287
|
*/
|
|
267
288
|
this.analysesAtLastRecheck = null;
|
|
289
|
+
/**
|
|
290
|
+
* Why the connector is not sensing, in the probe's own words. Restated on
|
|
291
|
+
* every re-probe while waiting: a connector that goes quiet after ONE line at
|
|
292
|
+
* boot is how "waiting for a 2.4 GB model" became indistinguishable from
|
|
293
|
+
* "dead" in the first place, and the operator reading the log an hour later
|
|
294
|
+
* needs the reason to still be there.
|
|
295
|
+
*/
|
|
296
|
+
this.lastWaitReason = null;
|
|
268
297
|
this.timer = null;
|
|
269
298
|
this.switching = false;
|
|
270
299
|
this.stopped = false;
|
|
@@ -295,15 +324,11 @@ class AutoSenseSupervisor {
|
|
|
295
324
|
: 0,
|
|
296
325
|
};
|
|
297
326
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
if (this.cloudManager === undefined)
|
|
305
|
-
this.cloudManager = this.deps.buildCloudManager();
|
|
306
|
-
return this.cloudManager;
|
|
327
|
+
/** The one manager, built on first use. Same instance for both states. */
|
|
328
|
+
getManager() {
|
|
329
|
+
if (this.localManager === undefined)
|
|
330
|
+
this.localManager = this.deps.buildLocalManager();
|
|
331
|
+
return this.localManager;
|
|
307
332
|
}
|
|
308
333
|
/**
|
|
309
334
|
* Initial probe (bounded startup re-probe loop for the fresh-install warmup
|
|
@@ -330,31 +355,27 @@ class AutoSenseSupervisor {
|
|
|
330
355
|
this.deps.log(`[auto-sense] LOCAL on-device sensing selected — ${health.reason}`);
|
|
331
356
|
return this.activate('local');
|
|
332
357
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// Validate feeds before entering the wait loop so a bad/empty config still
|
|
340
|
-
// fails immediately instead of looking like a model download in progress.
|
|
341
|
-
if (!this.getManager('local')) {
|
|
342
|
-
this.deps.onNoFeeds();
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
this.mode = 'waiting';
|
|
346
|
-
this.deps.log(`[auto-sense] LOCAL AI is not ready yet — ${health.reason}. Cloud inference is disabled by ` +
|
|
347
|
-
`default; waiting for LiteRT-LM and re-probing every ${this.deps.recheckIntervalMs}ms.`);
|
|
348
|
-
return true;
|
|
358
|
+
this.deps.log(`[auto-sense] LOCAL AI is not ready yet — ${health.reason}. There is no cloud path: the ` +
|
|
359
|
+
`connector analyzes nothing until the on-device model is up. Starting cameras in ` +
|
|
360
|
+
`HEALTH-ONLY mode so the portal can see this connector is alive and why it is not ` +
|
|
361
|
+
`sensing, and re-probing every ${this.deps.recheckIntervalMs}ms.`);
|
|
362
|
+
this.lastWaitReason = health.reason;
|
|
363
|
+
return this.activate('waiting');
|
|
349
364
|
}
|
|
350
|
-
/**
|
|
365
|
+
/**
|
|
366
|
+
* Start the manager in the given mode and make it active. False ⇒ no feeds.
|
|
367
|
+
*
|
|
368
|
+
* `waiting` starts the SAME manager with `healthOnly`, so the feeds are
|
|
369
|
+
* validated and the cameras are opened either way — a bad/empty config still
|
|
370
|
+
* fails immediately instead of looking like a model download in progress.
|
|
371
|
+
*/
|
|
351
372
|
async activate(mode) {
|
|
352
|
-
const manager = this.getManager(
|
|
373
|
+
const manager = this.getManager();
|
|
353
374
|
if (!manager) {
|
|
354
375
|
this.deps.onNoFeeds();
|
|
355
376
|
return false;
|
|
356
377
|
}
|
|
357
|
-
await manager.start();
|
|
378
|
+
await manager.start(mode === 'waiting' ? { healthOnly: true } : undefined);
|
|
358
379
|
this.active = manager;
|
|
359
380
|
this.mode = mode;
|
|
360
381
|
this.consecutiveUnhealthy = 0;
|
|
@@ -401,12 +422,9 @@ class AutoSenseSupervisor {
|
|
|
401
422
|
/**
|
|
402
423
|
* One background re-probe tick.
|
|
403
424
|
* - While WAITING: a healthy probe starts local sensing immediately.
|
|
404
|
-
* - On CLOUD: any healthy probe → AUTO-UPGRADE to local immediately (running
|
|
405
|
-
* on cloud while local is ready is pure waste; the K-debounce on the return
|
|
406
|
-
* path bounds any toggling, so this is safe on the first healthy probe).
|
|
407
425
|
* - On LOCAL: a healthy probe resets the debounce; an unhealthy probe
|
|
408
|
-
* increments it, and only after K CONSECUTIVE unhealthy probes do we
|
|
409
|
-
*
|
|
426
|
+
* increments it, and only after K CONSECUTIVE unhealthy probes do we report
|
|
427
|
+
* the runtime unhealthy — sensing stays put and recovers in place.
|
|
410
428
|
* At most one switch per tick; re-entrancy guarded.
|
|
411
429
|
*
|
|
412
430
|
* ── Why an unanswered probe is not, by itself, a verdict ───────────────────
|
|
@@ -434,11 +452,11 @@ class AutoSenseSupervisor {
|
|
|
434
452
|
* landing; the moment they stop landing the same probe counts, and K of them
|
|
435
453
|
* demote exactly as before.
|
|
436
454
|
*
|
|
437
|
-
* It has to be `localInferences` and not the total analysis count
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
455
|
+
* It has to be `localInferences` and not the total analysis count, and that
|
|
456
|
+
* distinction must be preserved even though the connector no longer has a
|
|
457
|
+
* cloud path that could advance the total on its own: this counter is the
|
|
458
|
+
* supervisor's only proof of life, so anything but a COMPLETED on-device
|
|
459
|
+
* inference advancing it would make the proof self-fulfilling.
|
|
442
460
|
*
|
|
443
461
|
* Deliberately NOT keyed on "we have a request in flight": a serve that
|
|
444
462
|
* ACCEPTS and never answers holds our permit for the provider's whole timeout,
|
|
@@ -461,14 +479,17 @@ class AutoSenseSupervisor {
|
|
|
461
479
|
if (this.mode === 'waiting') {
|
|
462
480
|
if (health.ok) {
|
|
463
481
|
this.deps.log('[auto-sense] LOCAL AI is ready — starting on-device sensing');
|
|
482
|
+
this.lastWaitReason = null;
|
|
464
483
|
await this.switchTo('local');
|
|
484
|
+
return;
|
|
465
485
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (health.
|
|
470
|
-
this.deps.log(
|
|
471
|
-
|
|
486
|
+
// Still waiting. Restate the cause every tick, and only when it CHANGES
|
|
487
|
+
// say so loudly — an unchanged reason is the normal shape of a long model
|
|
488
|
+
// import, not news.
|
|
489
|
+
if (health.reason !== this.lastWaitReason) {
|
|
490
|
+
this.deps.log(`[auto-sense] still waiting for the on-device model — ${health.reason} ` +
|
|
491
|
+
'(cameras are open and reporting health; nothing is being analyzed or uploaded)');
|
|
492
|
+
this.lastWaitReason = health.reason;
|
|
472
493
|
}
|
|
473
494
|
return;
|
|
474
495
|
}
|
|
@@ -488,19 +509,16 @@ class AutoSenseSupervisor {
|
|
|
488
509
|
}
|
|
489
510
|
this.consecutiveUnhealthy += 1;
|
|
490
511
|
if (this.consecutiveUnhealthy >= this.deps.unhealthyChecksBeforeFallback) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
'Cloud inference remains disabled; local sensing will recover in place.');
|
|
502
|
-
this.consecutiveUnhealthy = 0;
|
|
503
|
-
}
|
|
512
|
+
// Stay on LOCAL and recover in place. Deliberately NOT a demotion to
|
|
513
|
+
// `waiting`: the probe has measured false negatives under exactly the
|
|
514
|
+
// load a healthy 16-camera connector generates (4 of 5 probes timed out
|
|
515
|
+
// during one 11.8 s inference), so tearing down a working sensing run on
|
|
516
|
+
// the strength of an unanswered ping would be the connector's own
|
|
517
|
+
// success switching it off. The loops keep trying and a genuinely dead
|
|
518
|
+
// runtime surfaces as per-frame failures, which are visible.
|
|
519
|
+
this.deps.log(`[auto-sense] LOCAL AI health check failed — ${health.reason}. ` +
|
|
520
|
+
'There is no cloud path; on-device sensing will recover in place.');
|
|
521
|
+
this.consecutiveUnhealthy = 0;
|
|
504
522
|
}
|
|
505
523
|
}
|
|
506
524
|
}
|
|
@@ -508,9 +526,9 @@ class AutoSenseSupervisor {
|
|
|
508
526
|
async switchTo(mode) {
|
|
509
527
|
if (this.mode === mode || this.switching)
|
|
510
528
|
return;
|
|
511
|
-
const next = this.getManager(
|
|
529
|
+
const next = this.getManager();
|
|
512
530
|
if (!next) {
|
|
513
|
-
// Can't happen mid-run (
|
|
531
|
+
// Can't happen mid-run (it is the SAME instance over the SAME feeds), but
|
|
514
532
|
// never tear down the working manager for one we can't start.
|
|
515
533
|
this.deps.log(`[auto-sense] cannot switch to ${mode} sensing (no camera feeds) — staying on ${this.mode}`);
|
|
516
534
|
return;
|
|
@@ -519,7 +537,7 @@ class AutoSenseSupervisor {
|
|
|
519
537
|
try {
|
|
520
538
|
if (this.active)
|
|
521
539
|
await this.active.stop();
|
|
522
|
-
await next.start();
|
|
540
|
+
await next.start(mode === 'waiting' ? { healthOnly: true } : undefined);
|
|
523
541
|
this.active = next;
|
|
524
542
|
this.mode = mode;
|
|
525
543
|
this.consecutiveUnhealthy = 0;
|
|
@@ -575,8 +593,8 @@ function blockForever() {
|
|
|
575
593
|
* genuine config error propagates to main() (prints + exits 1) — the same net
|
|
576
594
|
* result as before, where the chosen run mode re-loaded the config and reported it.
|
|
577
595
|
*
|
|
578
|
-
* The explicit `local-sense`
|
|
579
|
-
*
|
|
596
|
+
* The explicit `local-sense` entrypoint stays non-switching; only `auto-sense`
|
|
597
|
+
* is supervised.
|
|
580
598
|
*/
|
|
581
599
|
async function runAutoSense(args, deps = {}) {
|
|
582
600
|
const log = deps.log ?? ((m) => console.log(m));
|
|
@@ -585,6 +603,13 @@ async function runAutoSense(args, deps = {}) {
|
|
|
585
603
|
const configIdx = args.indexOf('--config');
|
|
586
604
|
const configPath = configIdx >= 0 && args[configIdx + 1] ? args[configIdx + 1] : 'trillboards.config.json';
|
|
587
605
|
const config = load(configPath);
|
|
606
|
+
// Best-effort ONVIF LAN scan, fire-and-forget. This used to run only on
|
|
607
|
+
// `cloud-sense`; deleting that mode left it with zero call sites, which no type
|
|
608
|
+
// checker can see. `auto-sense` is what every platform runs now, so it is the
|
|
609
|
+
// right home. Settled before a no-feeds exit — feeds paired without an rtspUrl
|
|
610
|
+
// resolve to nothing sensable, and the scan is the only way the portal ever
|
|
611
|
+
// learns what is actually on the venue's network.
|
|
612
|
+
const discoveryDone = (0, startupDiscovery_1.kickStartupDiscovery)(config);
|
|
588
613
|
const litertLmBaseUrl = config.localSensing?.litertLmBaseUrl || DEFAULT_LITERT_LM_BASE_URL;
|
|
589
614
|
const probeTimeoutMs = config.localSensing?.healthProbeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
590
615
|
const expectedModel = config.localSensing?.model || DEFAULT_LITERT_LM_MODEL;
|
|
@@ -593,7 +618,6 @@ async function runAutoSense(args, deps = {}) {
|
|
|
593
618
|
const recheckIntervalMs = Math.max(MIN_HEALTH_RECHECK_INTERVAL_MS, config.localSensing?.healthRecheckIntervalMs ?? DEFAULT_HEALTH_RECHECK_INTERVAL_MS);
|
|
594
619
|
const unhealthyChecksBeforeFallback = Math.max(1, config.localSensing?.localUnhealthyChecksBeforeFallback ??
|
|
595
620
|
DEFAULT_LOCAL_UNHEALTHY_CHECKS_BEFORE_FALLBACK);
|
|
596
|
-
const allowCloudFallback = config.localSensing?.allowCloudFallback === true;
|
|
597
621
|
const probe = deps.probe
|
|
598
622
|
? () => deps.probe(litertLmBaseUrl, probeTimeoutMs, expectedModel)
|
|
599
623
|
: () => probeLiteRtLmHealth(litertLmBaseUrl, { timeoutMs: probeTimeoutMs, expectedModel });
|
|
@@ -608,18 +632,22 @@ async function runAutoSense(args, deps = {}) {
|
|
|
608
632
|
const supervisor = new AutoSenseSupervisor({
|
|
609
633
|
probe,
|
|
610
634
|
buildLocalManager: deps.buildLocalManager ?? (() => (0, sensingRunMode_1.buildLocalSensingManager)(config, undefined, { configPath })),
|
|
611
|
-
buildCloudManager: deps.buildCloudManager ?? (() => (0, sensingRunMode_1.buildCloudSensingManager)(config, undefined, { configPath })),
|
|
612
635
|
log,
|
|
613
636
|
sleep,
|
|
614
637
|
setTimer: deps.setTimer ?? ((cb, ms) => setTimeout(cb, ms)),
|
|
615
638
|
clearTimer: deps.clearTimer ?? ((h) => clearTimeout(h)),
|
|
616
|
-
onNoFeeds: deps.onNoFeeds ??
|
|
639
|
+
onNoFeeds: deps.onNoFeeds ??
|
|
640
|
+
(() => {
|
|
641
|
+
// Let the LAN scan finish reporting before we go. Without this the exit
|
|
642
|
+
// races the POST and the operator's "Find my cameras" stays empty in
|
|
643
|
+
// exactly the situation that produced the no-feeds exit.
|
|
644
|
+
void (0, startupDiscovery_1.settleBeforeExit)(discoveryDone).finally(() => process.exit(1));
|
|
645
|
+
}),
|
|
617
646
|
onLocalActivated: startModelOtaOnce,
|
|
618
647
|
startupAttempts,
|
|
619
648
|
startupBackoffMs,
|
|
620
649
|
recheckIntervalMs,
|
|
621
650
|
unhealthyChecksBeforeFallback,
|
|
622
|
-
allowCloudFallback,
|
|
623
651
|
litertLmBaseUrl,
|
|
624
652
|
expectedModel,
|
|
625
653
|
probeTimeoutMs,
|