@trillboards/edge-sdk 0.2.81 → 0.2.84
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/deploy/detector/README.md +108 -21
- package/deploy/detector/bench/verify_against_reference.py +6 -1
- package/deploy/detector/pyproject.toml +1 -1
- package/deploy/detector/tests/test_loader.py +188 -0
- package/deploy/detector/tests/test_modelpath.py +48 -3
- package/deploy/detector/tests/test_registry.py +880 -0
- package/deploy/detector/tests/test_server.py +163 -7
- package/deploy/detector/tests/test_session.py +29 -10
- package/deploy/detector/trillboards_detector/__init__.py +1 -1
- package/deploy/detector/trillboards_detector/cli.py +49 -2
- package/deploy/detector/trillboards_detector/detections.py +98 -0
- package/deploy/detector/trillboards_detector/errors.py +38 -0
- package/deploy/detector/trillboards_detector/labels.py +239 -24
- package/deploy/detector/trillboards_detector/loader.py +186 -148
- package/deploy/detector/trillboards_detector/modelpath.py +32 -0
- package/deploy/detector/trillboards_detector/registry.py +836 -0
- package/deploy/detector/trillboards_detector/server.py +59 -40
- package/deploy/detector/trillboards_detector/session.py +84 -55
- package/dist/EdgeAgent.js +1 -1
- package/dist/SensingProfileClient.d.ts +1 -1
- package/dist/SensingProfileClient.d.ts.map +1 -1
- package/dist/SensingProfileClient.js +20 -0
- package/dist/SensingProfileClient.js.map +1 -1
- package/dist/autoSense.d.ts +6 -5
- package/dist/autoSense.d.ts.map +1 -1
- package/dist/autoSense.js +12 -2
- package/dist/autoSense.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -6
- package/dist/cli.js.map +1 -1
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.js +1 -1
- package/dist/modelOta.d.ts +274 -0
- package/dist/modelOta.d.ts.map +1 -0
- package/dist/modelOta.js +492 -0
- package/dist/modelOta.js.map +1 -0
- package/dist/sensingRunMode.d.ts +21 -0
- package/dist/sensingRunMode.d.ts.map +1 -1
- package/dist/sensingRunMode.js +32 -7
- package/dist/sensingRunMode.js.map +1 -1
- package/package.json +1 -1
package/dist/modelOta.js
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* modelOta — deliver EVERY model the server granted this connector, not just one.
|
|
4
|
+
*
|
|
5
|
+
* ── The gap this closes ────────────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* The connector's whole model-delivery surface was ONE hardcoded `modelType`:
|
|
8
|
+
* `cli.ts` called `startGemmaModelOta(config)` and nothing else. No writer of
|
|
9
|
+
* `models/<id>/model.onnx` existed anywhere — `modelpath.py` only READS that
|
|
10
|
+
* path. So `rfdetr_medium` could be registry-active with a live 10% canary,
|
|
11
|
+
* three installers reporting healthy and `devices_succeeded = 0`, while the
|
|
12
|
+
* detector service 503'd forever waiting for an artifact nothing was sending.
|
|
13
|
+
* That is the `yolov8_nano` shape (offered everywhere, loadable nowhere) with
|
|
14
|
+
* the delivery leg missing instead of the producer leg.
|
|
15
|
+
*
|
|
16
|
+
* The SERVER side was already generic — `getGlobalVlmModelDescriptor` resolves
|
|
17
|
+
* any `modelType` through `resolveGlobalOtaRegistryId`, which accepts a registry
|
|
18
|
+
* `model_id` directly and gates non-alias types through the device manifest,
|
|
19
|
+
* failing closed. Nothing there needed changing. The missing half was here.
|
|
20
|
+
*
|
|
21
|
+
* ── Shape ──────────────────────────────────────────────────────────────────
|
|
22
|
+
*
|
|
23
|
+
* `startModelOta(config, models)` composes two INDEPENDENT watchers:
|
|
24
|
+
*
|
|
25
|
+
* 1. the VLM watcher — `startGemmaModelOta`, called unchanged. It keeps its
|
|
26
|
+
* own gating (`modelOtaEnabled`, the refusal to overwrite a custom
|
|
27
|
+
* litert-lm model), its own litert-lm STORE path, its own XNNPACK cache
|
|
28
|
+
* clear and its own `litert-lm serve` restart. This module adds nothing to
|
|
29
|
+
* it and takes nothing away: `gemma-vlm` behaviour is byte-identical, and
|
|
30
|
+
* a test asserts that against a fake manager rather than trusting it.
|
|
31
|
+
*
|
|
32
|
+
* 2. the SPECIALIST watcher — its own `ModelManager`, its own models dir and
|
|
33
|
+
* therefore its own `models_manifest.json`.
|
|
34
|
+
*
|
|
35
|
+
* TWO managers and not one, for a reason that is not tidiness: the VLM watcher
|
|
36
|
+
* declines to run at all when the operator pointed `localSensing.model` at a
|
|
37
|
+
* custom litert-lm model, and a specialist must still be delivered in that
|
|
38
|
+
* state. A shared manager built inside `startGemmaModelOta` cannot express
|
|
39
|
+
* that. Separate managers also make the byte-identical claim structural — the
|
|
40
|
+
* VLM's manifest, poll loop and event stream are untouched by anything here.
|
|
41
|
+
*
|
|
42
|
+
* ── What may be delivered, and what is refused OUT LOUD ────────────────────
|
|
43
|
+
*
|
|
44
|
+
* A capability is offered only where a producer will write its answer down. The
|
|
45
|
+
* connector delivers a granted model only when it has a DESTINATION for its
|
|
46
|
+
* format — today `onnx`, whose destination is the path `modelpath.py` computes
|
|
47
|
+
* and the detector service reads. Every other format is SKIPPED WITH ITS REASON
|
|
48
|
+
* (once per model, not once per poll): `tflite`/`mlkit`/`mediapipe` are Android
|
|
49
|
+
* loaders with no runtime on this box, and `litert-lm` is the VLM watcher's own
|
|
50
|
+
* business. A format we cannot place is never guessed at — a near-miss path is
|
|
51
|
+
* a silent no-delivery that reports success, which is the failure this file
|
|
52
|
+
* exists to end.
|
|
53
|
+
*
|
|
54
|
+
* ── Non-fatal, always ──────────────────────────────────────────────────────
|
|
55
|
+
*
|
|
56
|
+
* Every failure here means the connector keeps serving what it already has. A
|
|
57
|
+
* specialist that cannot download must not stop the VLM, and must not stop
|
|
58
|
+
* capture. `ModelManager`'s atomic `.tmp` + rename + SHA-256 verify means a
|
|
59
|
+
* failed download leaves NO artifact rather than a truncated one, so the
|
|
60
|
+
* detector service keeps answering 503 *with the reason* instead of silently
|
|
61
|
+
* serving no detections. Down is never empty.
|
|
62
|
+
*
|
|
63
|
+
* Node built-ins only (survives bundling into the self-contained connector;
|
|
64
|
+
* trivially mockable in tests).
|
|
65
|
+
*/
|
|
66
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
67
|
+
if (k2 === undefined) k2 = k;
|
|
68
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
69
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
70
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
71
|
+
}
|
|
72
|
+
Object.defineProperty(o, k2, desc);
|
|
73
|
+
}) : (function(o, m, k, k2) {
|
|
74
|
+
if (k2 === undefined) k2 = k;
|
|
75
|
+
o[k2] = m[k];
|
|
76
|
+
}));
|
|
77
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
78
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
79
|
+
}) : function(o, v) {
|
|
80
|
+
o["default"] = v;
|
|
81
|
+
});
|
|
82
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
83
|
+
var ownKeys = function(o) {
|
|
84
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
85
|
+
var ar = [];
|
|
86
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
87
|
+
return ar;
|
|
88
|
+
};
|
|
89
|
+
return ownKeys(o);
|
|
90
|
+
};
|
|
91
|
+
return function (mod) {
|
|
92
|
+
if (mod && mod.__esModule) return mod;
|
|
93
|
+
var result = {};
|
|
94
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
95
|
+
__setModuleDefault(result, mod);
|
|
96
|
+
return result;
|
|
97
|
+
};
|
|
98
|
+
})();
|
|
99
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
100
|
+
exports.CONNECTOR_LOADABLE_MODEL_FORMATS = void 0;
|
|
101
|
+
exports.resolveOnnxModelPath = resolveOnnxModelPath;
|
|
102
|
+
exports.destinationForGrantedModel = destinationForGrantedModel;
|
|
103
|
+
exports.buildGrantedModelSource = buildGrantedModelSource;
|
|
104
|
+
exports.startModelOta = startModelOta;
|
|
105
|
+
const os = __importStar(require("os"));
|
|
106
|
+
const path = __importStar(require("path"));
|
|
107
|
+
const edge_core_1 = require("@trillboards/edge-core");
|
|
108
|
+
const gemmaModelOta_1 = require("./gemmaModelOta");
|
|
109
|
+
/**
|
|
110
|
+
* Formats this connector has a DESTINATION for — i.e. a place to write the
|
|
111
|
+
* artifact where something on this box will read it.
|
|
112
|
+
*
|
|
113
|
+
* `onnx` only, and that is a statement about what exists rather than a
|
|
114
|
+
* limitation to apologise for: `deploy/detector` reads
|
|
115
|
+
* `${TRILLBOARDS_DATA_DIR:-~/.trillboards}/models/<id>/model.onnx` and nothing
|
|
116
|
+
* else on the connector loads a model file at all. Widening this list is an edit
|
|
117
|
+
* that must arrive WITH its reader, in the same commit — the same discipline
|
|
118
|
+
* `advertisedClasses` applies to the class vocabulary.
|
|
119
|
+
*/
|
|
120
|
+
exports.CONNECTOR_LOADABLE_MODEL_FORMATS = Object.freeze(['onnx']);
|
|
121
|
+
/** Fixed file name inside the per-model directory — `modelpath.py`'s `MODEL_FILE_NAME`. */
|
|
122
|
+
const ONNX_MODEL_FILE_NAME = 'model.onnx';
|
|
123
|
+
// ─── Destination resolution ─────────────────────────────────────────────────
|
|
124
|
+
/**
|
|
125
|
+
* Where an ONNX artifact must land, mirroring
|
|
126
|
+
* `deploy/detector/trillboards_detector/modelpath.py` EXACTLY:
|
|
127
|
+
*
|
|
128
|
+
* ${TRILLBOARDS_DATA_DIR:-${HOME:-${USERPROFILE:-/tmp}}/.trillboards}
|
|
129
|
+
* /models/<model-id>/model.onnx
|
|
130
|
+
*
|
|
131
|
+
* THE ENVIRONMENT, NOT `config.dataDir`. The reader is a separate PROCESS — a
|
|
132
|
+
* LaunchAgent, a systemd unit or a Windows Scheduled Task — that resolves this
|
|
133
|
+
* path from its own environment and has never heard of the connector's config
|
|
134
|
+
* file. Writing to `config.dataDir` would put the artifact somewhere the service
|
|
135
|
+
* does not look, which is a permanently-503 detector on a box whose model is
|
|
136
|
+
* sitting right there. `trillboards-detector.service` pins `HOME=/opt/trillboards`
|
|
137
|
+
* for precisely this reason and says so.
|
|
138
|
+
*
|
|
139
|
+
* `TRILLBOARDS_DETECTOR_MODEL` is NOT honoured here. It is `modelpath.py`'s bench
|
|
140
|
+
* escape hatch that points the SERVICE at an arbitrary file; an OTA that followed
|
|
141
|
+
* it would overwrite whatever a bench run was pointed at.
|
|
142
|
+
*
|
|
143
|
+
* One directory per model NAME, never per version — `ModelManager` rewrites the
|
|
144
|
+
* one file in place, so an update reclaims the old bytes by definition. A
|
|
145
|
+
* version-named directory would add a second leak on a path that already orphans
|
|
146
|
+
* ~752 MiB per Gemma update and has no GC.
|
|
147
|
+
*/
|
|
148
|
+
function resolveOnnxModelPath(opts) {
|
|
149
|
+
const env = opts.env ?? process.env;
|
|
150
|
+
const override = (env.TRILLBOARDS_DATA_DIR || '').trim();
|
|
151
|
+
const dataDir = override
|
|
152
|
+
|| path.join((env.HOME || '').trim() || (env.USERPROFILE || '').trim() || '/tmp', '.trillboards');
|
|
153
|
+
return path.join(dataDir, 'models', opts.modelId, ONNX_MODEL_FILE_NAME);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Decide a granted model's fate. A `skip` ALWAYS carries a reason — the whole
|
|
157
|
+
* point of this surface is that an exclusion is a stated fact rather than a
|
|
158
|
+
* silent drop, so there is no branch that returns nothing.
|
|
159
|
+
*/
|
|
160
|
+
function destinationForGrantedModel(model, opts = {}) {
|
|
161
|
+
const modelId = typeof model?.modelId === 'string' ? model.modelId.trim() : '';
|
|
162
|
+
if (!modelId) {
|
|
163
|
+
return { kind: 'skip', modelId: String(model?.modelId ?? ''), reason: 'no model id' };
|
|
164
|
+
}
|
|
165
|
+
// A path traversal in a server-supplied id would let a compromised or buggy
|
|
166
|
+
// manifest write outside the model store. The ids are `sensing_model_registry`
|
|
167
|
+
// primary keys — snake_case identifiers — so anything else is refused rather
|
|
168
|
+
// than sanitised: silently rewriting an id would put the artifact at a path
|
|
169
|
+
// the reader does not compute, which is the no-delivery case again.
|
|
170
|
+
if (!/^[A-Za-z0-9._-]+$/.test(modelId) || modelId === '.' || modelId === '..') {
|
|
171
|
+
return { kind: 'skip', modelId, reason: `model id is not a bare registry identifier` };
|
|
172
|
+
}
|
|
173
|
+
const format = typeof model.modelFormat === 'string' ? model.modelFormat.trim().toLowerCase() : '';
|
|
174
|
+
if (!format) {
|
|
175
|
+
return { kind: 'skip', modelId, reason: 'the server declared no model_format for it' };
|
|
176
|
+
}
|
|
177
|
+
if (format === 'litert-lm') {
|
|
178
|
+
return {
|
|
179
|
+
kind: 'skip',
|
|
180
|
+
modelId,
|
|
181
|
+
reason: 'litert-lm artifacts are the VLM watcher\'s own business (store path + serve restart)',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (!exports.CONNECTOR_LOADABLE_MODEL_FORMATS.includes(format)) {
|
|
185
|
+
return {
|
|
186
|
+
kind: 'skip',
|
|
187
|
+
modelId,
|
|
188
|
+
reason: `format '${format}' has no reader on this connector `
|
|
189
|
+
+ `(loadable: ${exports.CONNECTOR_LOADABLE_MODEL_FORMATS.join(', ')})`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return { kind: 'download', modelId, filePath: resolveOnnxModelPath({ modelId, env: opts.env }) };
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Union the granted lists of every feed's screen.
|
|
196
|
+
*
|
|
197
|
+
* A connector is ONE box with ONE model store but N feeds on N screens, whose
|
|
198
|
+
* manifests can differ. The union is the right answer: a model granted to ANY of
|
|
199
|
+
* this box's screens has to be on this box's disk for that screen to be served.
|
|
200
|
+
* A screen that fails to answer simply does not contribute, and a total failure
|
|
201
|
+
* returns null so the caller keeps what it already had — the same
|
|
202
|
+
* failure-is-silence-not-a-reset rule `deployedCadenceSource` uses, and for the
|
|
203
|
+
* same reason: snapping back on a blip is worse than being briefly stale.
|
|
204
|
+
*/
|
|
205
|
+
function buildGrantedModelSource(options) {
|
|
206
|
+
const feeds = [
|
|
207
|
+
...(options.feeds || []).filter((f) => f && f.client),
|
|
208
|
+
...(options.profileClients || []).filter(Boolean).map((client) => ({ screenId: null, client })),
|
|
209
|
+
];
|
|
210
|
+
if (feeds.length === 0)
|
|
211
|
+
return undefined;
|
|
212
|
+
return {
|
|
213
|
+
async resolve() {
|
|
214
|
+
const results = await Promise.all(feeds.map(async (f) => ({
|
|
215
|
+
screenId: f.screenId,
|
|
216
|
+
profile: await f.client.fetchProfile().catch(() => null),
|
|
217
|
+
})));
|
|
218
|
+
let anyAnswered = false;
|
|
219
|
+
const byId = new Map();
|
|
220
|
+
for (const { screenId, profile } of results) {
|
|
221
|
+
if (!profile)
|
|
222
|
+
continue;
|
|
223
|
+
anyAnswered = true;
|
|
224
|
+
for (const m of Array.isArray(profile.models) ? profile.models : []) {
|
|
225
|
+
if (!m || typeof m.modelId !== 'string' || !m.modelId)
|
|
226
|
+
continue;
|
|
227
|
+
const existing = byId.get(m.modelId);
|
|
228
|
+
// First non-null format wins; a screen that reports null for a model
|
|
229
|
+
// another screen typed must not erase the type. Same rule for the
|
|
230
|
+
// granting screen: the FIRST screen that could name itself owns the
|
|
231
|
+
// download identity, so a later screen answering with none cannot
|
|
232
|
+
// demote a model back to the host fingerprint.
|
|
233
|
+
if (!existing) {
|
|
234
|
+
byId.set(m.modelId, {
|
|
235
|
+
modelId: m.modelId,
|
|
236
|
+
modelFormat: typeof m.modelFormat === 'string' ? m.modelFormat : null,
|
|
237
|
+
grantedByScreenId: screenId || null,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
if (existing.modelFormat === null && typeof m.modelFormat === 'string') {
|
|
242
|
+
existing.modelFormat = m.modelFormat;
|
|
243
|
+
}
|
|
244
|
+
if (!existing.grantedByScreenId && screenId)
|
|
245
|
+
existing.grantedByScreenId = screenId;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!anyAnswered)
|
|
250
|
+
return null;
|
|
251
|
+
return [...byId.values()].sort((a, b) => a.modelId.localeCompare(b.modelId));
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/** How often to re-ask what this device was granted, when a source is supplied. */
|
|
256
|
+
const DEFAULT_GRANTED_POLL_MS = 15 * 60 * 1000;
|
|
257
|
+
/**
|
|
258
|
+
* Start the connector's model OTA: the VLM watcher plus one download per granted
|
|
259
|
+
* specialist whose format this box can place.
|
|
260
|
+
*
|
|
261
|
+
* NON-FATAL throughout — a thrown error is swallowed and logged, and the VLM
|
|
262
|
+
* watcher is started FIRST so a specialist failure can never take it down.
|
|
263
|
+
*/
|
|
264
|
+
function startModelOta(config, models = [], deps = {}) {
|
|
265
|
+
const log = deps.log ?? ((m) => console.log(m));
|
|
266
|
+
// 1. The VLM, FIRST and unchanged. If everything below throws, this survives.
|
|
267
|
+
let vlm = null;
|
|
268
|
+
try {
|
|
269
|
+
vlm = (deps.startVlmOta ?? gemmaModelOta_1.startGemmaModelOta)(config);
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
log(`[model-ota] VLM watcher failed to start (non-fatal): ${err?.message || err}`);
|
|
273
|
+
}
|
|
274
|
+
// 2. The specialist watcher. Built lazily on the first granted model that has a
|
|
275
|
+
// destination, so a fleet with no specialists creates no manager, no models
|
|
276
|
+
// dir and no poll loop — byte-identical to before this module existed.
|
|
277
|
+
const env = deps.env ?? process.env;
|
|
278
|
+
const otaEnabled = config.localSensing?.modelOtaEnabled !== false;
|
|
279
|
+
/**
|
|
280
|
+
* ONE MANAGER PER GRANTING IDENTITY.
|
|
281
|
+
*
|
|
282
|
+
* `ModelManager` sends one `deviceFingerprint`/`screen_id` for every model it
|
|
283
|
+
* holds, and the server re-decides eligibility — including the rollout hash
|
|
284
|
+
* bucket — from that identity. So a model granted to screen A has to be asked
|
|
285
|
+
* for AS screen A; putting it on a manager that identifies as screen B asks a
|
|
286
|
+
* question nobody answered yes to. In practice a connector's feeds are granted
|
|
287
|
+
* the same set and this is one manager; it is more only when two screens on the
|
|
288
|
+
* box genuinely differ, which is exactly when it matters.
|
|
289
|
+
*
|
|
290
|
+
* Keyed on the identity, and every model is registered on EXACTLY ONE of them
|
|
291
|
+
* (first grant wins) — two managers writing the same `model.onnx` would race on
|
|
292
|
+
* the same `.tmp`.
|
|
293
|
+
*/
|
|
294
|
+
const managers = new Map();
|
|
295
|
+
const startedManagers = new Set();
|
|
296
|
+
const watched = new Set();
|
|
297
|
+
const skipLogged = new Set();
|
|
298
|
+
let pollHandle = null;
|
|
299
|
+
let stopped = false;
|
|
300
|
+
const hostFingerprint = connectorFingerprint(config);
|
|
301
|
+
/** Filesystem-safe segment for a manager's own manifest directory. */
|
|
302
|
+
const identityDirSegment = (identity) => identity.replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 64) || 'host';
|
|
303
|
+
const ensureManager = (identity, screenId) => {
|
|
304
|
+
const existing = managers.get(identity);
|
|
305
|
+
if (existing)
|
|
306
|
+
return existing;
|
|
307
|
+
const apiBaseUrl = config.apiBaseUrl || 'https://api.trillboards.com';
|
|
308
|
+
// The manifest STATE dir, not the artifact dir — it records which version of
|
|
309
|
+
// each specialist is on disk. Nested under the VLM watcher's `model-ota` root
|
|
310
|
+
// so there is one OTA state home, but its OWN directory PER IDENTITY: two
|
|
311
|
+
// managers sharing one `models_manifest.json` would clobber each other's
|
|
312
|
+
// versions, and `saveManifest()` rewrites the whole file from its own map.
|
|
313
|
+
const stateRoot = config.dataDir || path.join(os.homedir(), '.trillboards');
|
|
314
|
+
const modelsDir = path.join(stateRoot, 'model-ota', 'specialists', identityDirSegment(identity));
|
|
315
|
+
const created = (deps.createModelManager ?? defaultCreateSpecialistManager)({
|
|
316
|
+
modelsDir,
|
|
317
|
+
apiBaseUrl,
|
|
318
|
+
deviceFingerprint: identity,
|
|
319
|
+
deviceScreenId: screenId,
|
|
320
|
+
...(config.localSensing?.modelOtaCheckIntervalMs
|
|
321
|
+
? { checkIntervalMs: config.localSensing.modelOtaCheckIntervalMs }
|
|
322
|
+
: {}),
|
|
323
|
+
autoUpdate: true,
|
|
324
|
+
});
|
|
325
|
+
created.on('modelUpdated', (evt) => {
|
|
326
|
+
// No serve restart and no cache clear: the detector service picks an
|
|
327
|
+
// arriving artifact up within one poll of it landing, with no restart. That
|
|
328
|
+
// is its contract, and re-implementing a restart here would be a second
|
|
329
|
+
// authority over a lifecycle it already owns.
|
|
330
|
+
log(`[model-ota] ${evt?.name} updated to v${evt?.newVersion} at ${evt?.path}`);
|
|
331
|
+
// A COMPLETED download frees the one concurrency slot, so ask again rather
|
|
332
|
+
// than waiting for the next tick. `ModelManager` defaults
|
|
333
|
+
// `maxConcurrentDownloads` to 1 and BREAKS out of its update loop the moment
|
|
334
|
+
// the slot is busy, so with N models granted at once only the first is
|
|
335
|
+
// fetched and the rest wait a full check interval EACH — an hour per model
|
|
336
|
+
// by default. Kicked only on a genuine completion, so this terminates: a
|
|
337
|
+
// failing model is retried by the periodic tick, never by a tight loop.
|
|
338
|
+
const m = managers.get(identity);
|
|
339
|
+
if (m?.checkForUpdates) {
|
|
340
|
+
Promise.resolve()
|
|
341
|
+
.then(() => m.checkForUpdates())
|
|
342
|
+
.catch((err) => log(`[model-ota] follow-up check failed (non-fatal): ${err?.message || err}`));
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
created.on('downloadProgress', (evt) => {
|
|
346
|
+
if (typeof evt?.percent === 'number' && evt.percent % 25 === 0) {
|
|
347
|
+
log(`[model-ota] downloading ${evt?.name}… ${evt.percent}%`);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
created.on('downloadError', (evt) => {
|
|
351
|
+
// Includes the disk preflight's refusal, which names the bytes needed, the
|
|
352
|
+
// bytes free and the volume. Non-fatal by construction: nothing was written,
|
|
353
|
+
// so the detector keeps answering 503 with its own reason.
|
|
354
|
+
log(`[model-ota] ${evt?.name} update issue (non-fatal): ${evt?.error}`);
|
|
355
|
+
});
|
|
356
|
+
managers.set(identity, created);
|
|
357
|
+
return created;
|
|
358
|
+
};
|
|
359
|
+
const setGrantedModels = (next) => {
|
|
360
|
+
if (stopped)
|
|
361
|
+
return;
|
|
362
|
+
try {
|
|
363
|
+
if (!otaEnabled)
|
|
364
|
+
return;
|
|
365
|
+
const touched = new Set();
|
|
366
|
+
for (const model of Array.isArray(next) ? next : []) {
|
|
367
|
+
const dest = destinationForGrantedModel(model, { env });
|
|
368
|
+
if (dest.kind === 'skip') {
|
|
369
|
+
if (!skipLogged.has(dest.modelId)) {
|
|
370
|
+
skipLogged.add(dest.modelId);
|
|
371
|
+
log(`[model-ota] not delivering '${dest.modelId}': ${dest.reason}`);
|
|
372
|
+
}
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (watched.has(dest.modelId))
|
|
376
|
+
continue;
|
|
377
|
+
const screenId = typeof model.grantedByScreenId === 'string' && model.grantedByScreenId
|
|
378
|
+
? model.grantedByScreenId
|
|
379
|
+
: null;
|
|
380
|
+
const identity = screenId || hostFingerprint;
|
|
381
|
+
const m = ensureManager(identity, screenId);
|
|
382
|
+
if (!m)
|
|
383
|
+
continue;
|
|
384
|
+
// The registry id IS the OTA modelType — see GrantedModel.modelId.
|
|
385
|
+
m.registerModel(dest.modelId, dest.filePath);
|
|
386
|
+
watched.add(dest.modelId);
|
|
387
|
+
touched.add(identity);
|
|
388
|
+
log(`[model-ota] watching ${dest.modelId} → ${dest.filePath}`
|
|
389
|
+
+ ` (asking as ${screenId ? `screen ${screenId}` : `host ${hostFingerprint}`})`);
|
|
390
|
+
}
|
|
391
|
+
for (const identity of touched) {
|
|
392
|
+
const m = managers.get(identity);
|
|
393
|
+
if (!m)
|
|
394
|
+
continue;
|
|
395
|
+
if (!startedManagers.has(identity)) {
|
|
396
|
+
startedManagers.add(identity);
|
|
397
|
+
// initialize() loads the manifest + hashes what is already on disk;
|
|
398
|
+
// start() arms the periodic check; the immediate check is what stops a
|
|
399
|
+
// fresh install waiting a full interval for an artifact it has none of.
|
|
400
|
+
Promise.resolve()
|
|
401
|
+
.then(() => m.initialize())
|
|
402
|
+
.then(() => { m.start(); })
|
|
403
|
+
.then(() => (m.checkForUpdates ? m.checkForUpdates() : undefined))
|
|
404
|
+
.catch((err) => log(`[model-ota] init failed (non-fatal): ${err?.message || err}`));
|
|
405
|
+
}
|
|
406
|
+
else if (m.checkForUpdates) {
|
|
407
|
+
// Already running and a NEW model just appeared — do not make it wait
|
|
408
|
+
// for the next tick.
|
|
409
|
+
Promise.resolve()
|
|
410
|
+
.then(() => m.checkForUpdates())
|
|
411
|
+
.catch((err) => log(`[model-ota] check failed (non-fatal): ${err?.message || err}`));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
log(`[model-ota] granted-model apply failed (non-fatal): ${err?.message || err}`);
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
if (!otaEnabled) {
|
|
420
|
+
log('[model-ota] localSensing.modelOtaEnabled=false — specialist model delivery disabled');
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
setGrantedModels(models);
|
|
424
|
+
}
|
|
425
|
+
// 3. Keep asking. A model can be granted (or a canary advanced) long after the
|
|
426
|
+
// connector started, and nothing else on this box would ever notice.
|
|
427
|
+
if (otaEnabled && deps.grantedModelSource) {
|
|
428
|
+
const source = deps.grantedModelSource;
|
|
429
|
+
const pollMs = config.localSensing?.modelOtaCheckIntervalMs || DEFAULT_GRANTED_POLL_MS;
|
|
430
|
+
const refresh = () => {
|
|
431
|
+
Promise.resolve()
|
|
432
|
+
.then(() => source.resolve())
|
|
433
|
+
.then((resolved) => { if (resolved)
|
|
434
|
+
setGrantedModels(resolved); })
|
|
435
|
+
.catch((err) => log(`[model-ota] granted-model refresh failed (non-fatal): ${err?.message || err}`));
|
|
436
|
+
};
|
|
437
|
+
refresh();
|
|
438
|
+
const setTimer = deps.setInterval ?? ((cb, ms) => setInterval(cb, ms));
|
|
439
|
+
pollHandle = setTimer(refresh, pollMs);
|
|
440
|
+
// Never hold the process open for a model check.
|
|
441
|
+
if (pollHandle && typeof pollHandle.unref === 'function')
|
|
442
|
+
pollHandle.unref();
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
stop() {
|
|
446
|
+
stopped = true;
|
|
447
|
+
try {
|
|
448
|
+
vlm?.stop();
|
|
449
|
+
}
|
|
450
|
+
catch { /* best-effort */ }
|
|
451
|
+
for (const m of managers.values()) {
|
|
452
|
+
try {
|
|
453
|
+
m.stop();
|
|
454
|
+
}
|
|
455
|
+
catch { /* best-effort */ }
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
if (pollHandle)
|
|
459
|
+
(deps.clearInterval ?? clearInterval)(pollHandle);
|
|
460
|
+
}
|
|
461
|
+
catch { /* best-effort */ }
|
|
462
|
+
pollHandle = null;
|
|
463
|
+
},
|
|
464
|
+
setGrantedModels,
|
|
465
|
+
watchedModelIds() {
|
|
466
|
+
return [...watched].sort();
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
/** Stable, non-PII connector fingerprint for the OTA version-check query param. */
|
|
471
|
+
function connectorFingerprint(config) {
|
|
472
|
+
if (config.screenId)
|
|
473
|
+
return String(config.screenId);
|
|
474
|
+
const raw = `${os.hostname()}|${process.platform}|${os.arch()}`;
|
|
475
|
+
let h = 0;
|
|
476
|
+
for (let i = 0; i < raw.length; i += 1)
|
|
477
|
+
h = (Math.imul(31, h) + raw.charCodeAt(i)) | 0;
|
|
478
|
+
return `conn_${(h >>> 0).toString(16)}`;
|
|
479
|
+
}
|
|
480
|
+
function defaultCreateSpecialistManager(opts) {
|
|
481
|
+
return new edge_core_1.ModelManager({
|
|
482
|
+
modelsDir: opts.modelsDir,
|
|
483
|
+
apiBaseUrl: opts.apiBaseUrl,
|
|
484
|
+
deviceFingerprint: opts.deviceFingerprint,
|
|
485
|
+
// The identity the SERVER re-decides eligibility under. Without it the
|
|
486
|
+
// download asks as a different device than the grant answered for.
|
|
487
|
+
...(opts.deviceScreenId ? { deviceScreenId: opts.deviceScreenId } : {}),
|
|
488
|
+
...(opts.checkIntervalMs ? { checkIntervalMs: opts.checkIntervalMs } : {}),
|
|
489
|
+
autoUpdate: opts.autoUpdate ?? true,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
//# sourceMappingURL=modelOta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelOta.js","sourceRoot":"","sources":["../src/modelOta.ts"],"names":[],"mappings":"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"}
|
package/dist/sensingRunMode.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CloudSensingManager, type ResolvedFeed } from './CloudSensingManager';
|
|
2
2
|
import type { SharedFrameSource } from './MosaicSource';
|
|
3
3
|
import type { EdgeAgentConfig } from './config';
|
|
4
|
+
import { SensingProfileClient } from './SensingProfileClient';
|
|
4
5
|
/**
|
|
5
6
|
* Optional cross-rail mosaic-pull sharing: the combined `sense` entrypoint passes
|
|
6
7
|
* a `sharedSources` registry so the VLM rail attaches to the SAME SharedFrameSource
|
|
@@ -13,6 +14,26 @@ export interface SensingManagerOpts {
|
|
|
13
14
|
/** Reuse a caller-owned recovery coordinator across shared sensing rails. */
|
|
14
15
|
streamUrlResolver?: (feed: ResolvedFeed, currentUrl: string, error: Error) => Promise<string | null>;
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* One `SensingProfileClient` per feed that carries a device token, PAIRED WITH
|
|
19
|
+
* THE SCREEN that token binds to — the ONE construction, so the cadence source
|
|
20
|
+
* and the model OTA ask the same screens the same way. A feed without a token
|
|
21
|
+
* cannot ask (the token IS the screen binding), and contributes nothing rather
|
|
22
|
+
* than erroring.
|
|
23
|
+
*
|
|
24
|
+
* The screen id rides alongside because the model OTA needs it and the response
|
|
25
|
+
* does not carry it back: the server decides which models a device may receive
|
|
26
|
+
* under `screenId`, and the DOWNLOAD has to quote that same identity or it is
|
|
27
|
+
* re-judged as a different device in a different rollout bucket. See
|
|
28
|
+
* `modelOta.GrantedModel.grantedByScreenId`. `config.screenId` is not a
|
|
29
|
+
* substitute — `pairedConfigToFile` stores screen ids only inside `cameras[]`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function profileFeedsForConfig(config: EdgeAgentConfig): Array<{
|
|
32
|
+
screenId: string | null;
|
|
33
|
+
client: SensingProfileClient;
|
|
34
|
+
}>;
|
|
35
|
+
/** The clients only — for consumers that do not need the screen identity. */
|
|
36
|
+
export declare function profileClientsForFeeds(config: EdgeAgentConfig): SensingProfileClient[];
|
|
16
37
|
/**
|
|
17
38
|
* The lifecycle surface the auto-sense supervisor drives. `CloudSensingManager`
|
|
18
39
|
* already satisfies it: `start()` opens cameras + runs the capture loops;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensingRunMode.d.ts","sourceRoot":"","sources":["../src/sensingRunMode.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EAGnB,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"sensingRunMode.d.ts","sourceRoot":"","sources":["../src/sensingRunMode.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EAGnB,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAO9D;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC/C,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,CAClB,IAAI,EAAE,YAAY,EAClB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,KAAK,KACT,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7B;AA2CD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,eAAe,GACtB,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAC,CAUlE;AAED,6EAA6E;AAC7E,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,GAAG,oBAAoB,EAAE,CAEtF;AAKD;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAI;QACnB,eAAe,EAAE,MAAM,CAAC;QACxB;;;;;;WAMG;QACH,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,eAAe,CAAC,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QACvC;;;;;;WAMG;QACH,WAAW,CAAC,EAAE;YACZ,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC;YAC1C,iBAAiB,CAAC,EAAE,OAAO,CAAC;YAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;SACtB,GAAG,IAAI,CAAC;QACT,YAAY,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;KACtE,CAAC;CACH;AAED,iFAAiF;AACjF,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,qEAAqE;AACrE,eAAO,MAAM,SAAS,EAAE,SAIvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAEgD,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QAAgB,CAAC;AAE5D;;;;;;;;;GASG;AACH,eAAO,MAAM,qCAAqC,QAAiB,CAAC;AAmWpE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,CAAC,GAC1D,MAAM,CAyCR;AAED;;;;;;;;;;GAUG;AACH;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,SAAS,EACb,GAAG,GAAE,MAAM,MAAiB,GAC3B;IAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAA;CAAE,CA+BjD;AA8ID;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,eAAe,EACvB,EAAE,GAAE,SAAqB,EACzB,IAAI,CAAC,EAAE,kBAAkB,GACxB,mBAAmB,GAAG,IAAI,CAqJ5B"}
|
package/dist/sensingRunMode.js
CHANGED
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.TRANSPORT_FAILURE_RESTATE_INTERVAL_MS = exports.SENSING_HEALTH_LOG_INTERVAL_MS = exports.NO_FEEDS_MESSAGE = exports.consoleIo = void 0;
|
|
37
|
+
exports.profileFeedsForConfig = profileFeedsForConfig;
|
|
38
|
+
exports.profileClientsForFeeds = profileClientsForFeeds;
|
|
37
39
|
exports.formatSensingHealth = formatSensingHealth;
|
|
38
40
|
exports.createTransportFailureReporter = createTransportFailureReporter;
|
|
39
41
|
exports.buildLocalSensingManager = buildLocalSensingManager;
|
|
@@ -73,19 +75,42 @@ function cadenceSourceFor(config, feeds, mode,
|
|
|
73
75
|
* A feed that has not settled returns null and simply does not vote.
|
|
74
76
|
*/
|
|
75
77
|
observedAnalyzeMs) {
|
|
76
|
-
const profileClients = feeds
|
|
77
|
-
.filter((f) => !!f.deviceToken)
|
|
78
|
-
.map((f) => new SensingProfileClient_1.SensingProfileClient({
|
|
79
|
-
apiBaseUrl: config.apiBaseUrl,
|
|
80
|
-
deviceToken: f.deviceToken,
|
|
81
|
-
}));
|
|
82
78
|
return (0, deployedCadenceSource_1.buildDeployedCadenceSource)({
|
|
83
|
-
profileClients,
|
|
79
|
+
profileClients: profileClientsForFeeds(config),
|
|
84
80
|
mode,
|
|
85
81
|
feedCount: feeds.length,
|
|
86
82
|
observedAnalyzeMs,
|
|
87
83
|
});
|
|
88
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* One `SensingProfileClient` per feed that carries a device token, PAIRED WITH
|
|
87
|
+
* THE SCREEN that token binds to — the ONE construction, so the cadence source
|
|
88
|
+
* and the model OTA ask the same screens the same way. A feed without a token
|
|
89
|
+
* cannot ask (the token IS the screen binding), and contributes nothing rather
|
|
90
|
+
* than erroring.
|
|
91
|
+
*
|
|
92
|
+
* The screen id rides alongside because the model OTA needs it and the response
|
|
93
|
+
* does not carry it back: the server decides which models a device may receive
|
|
94
|
+
* under `screenId`, and the DOWNLOAD has to quote that same identity or it is
|
|
95
|
+
* re-judged as a different device in a different rollout bucket. See
|
|
96
|
+
* `modelOta.GrantedModel.grantedByScreenId`. `config.screenId` is not a
|
|
97
|
+
* substitute — `pairedConfigToFile` stores screen ids only inside `cameras[]`.
|
|
98
|
+
*/
|
|
99
|
+
function profileFeedsForConfig(config) {
|
|
100
|
+
return (0, CloudSensingManager_1.resolveFeeds)(config)
|
|
101
|
+
.filter((f) => !!f.deviceToken)
|
|
102
|
+
.map((f) => ({
|
|
103
|
+
screenId: f.screenId ? String(f.screenId) : null,
|
|
104
|
+
client: new SensingProfileClient_1.SensingProfileClient({
|
|
105
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
106
|
+
deviceToken: f.deviceToken,
|
|
107
|
+
}),
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
/** The clients only — for consumers that do not need the screen identity. */
|
|
111
|
+
function profileClientsForFeeds(config) {
|
|
112
|
+
return profileFeedsForConfig(config).map((f) => f.client);
|
|
113
|
+
}
|
|
89
114
|
const DEFAULT_LITERT_LM_BASE_URL = 'http://localhost:9379';
|
|
90
115
|
const DEFAULT_LITERT_LM_MODEL = 'gemma-4-E2B-it.litertlm';
|
|
91
116
|
/** Default sink: the real console (what the CLI entrypoints use). */
|