@camstack/addon-pipeline 1.2.296 → 1.2.298
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/dist/audio-analyzer/index.js +571 -305
- package/dist/audio-analyzer/index.mjs +571 -305
- package/dist/{default-detection-model-D1daTtqT.js → default-detection-model-BTJZvSr_.js} +368 -5
- package/dist/{default-detection-model-Co578D8C.mjs → default-detection-model-Bim_g_Ui.mjs} +339 -6
- package/dist/detection-pipeline/index.js +521 -217
- package/dist/detection-pipeline/index.mjs +520 -216
- package/dist/{dist-CCd0Q3nr.mjs → dist-DVz_88D_.mjs} +80 -123
- package/dist/{dist-8up-f2TX.js → dist-L_HlB0At.js} +91 -140
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-lpQgHes9.js → node-CmzZ_-zB.js} +1 -1
- package/dist/{node-DgMSXSWP.mjs → node-Cr8Ch1ZN.mjs} +1 -1
- package/dist/pipeline-runner/index.js +213 -203
- package/dist/pipeline-runner/index.mjs +213 -203
- package/dist/{process-memory-DFC_O5zE.mjs → process-memory-DO7La1yB.mjs} +1 -1
- package/dist/{process-memory-CX_92V_r.js → process-memory-FfRDGu11.js} +1 -1
- package/dist/recorder/index.js +3 -3
- package/dist/recorder/index.mjs +3 -3
- package/dist/{segment-demux-js-FZbBuk3F.mjs → segment-demux-js-BuioTwCh.mjs} +1 -1
- package/dist/{segment-demux-js-DzBx6NN2.js → segment-demux-js-Cd7btWna.js} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_i7oFBl.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CsbrrsyY.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DisouA2F.mjs +26 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dti8Ex88.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-BPtppL3W.mjs → hostInit-NCsHzij7.mjs} +2 -2
- package/dist/stream-broker/index.js +3 -3
- package/dist/stream-broker/index.mjs +3 -3
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DUGQKsKL.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CkbplMHA.mjs +0 -26
|
@@ -3,11 +3,11 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_node = require("../node-
|
|
8
|
-
const require_default_detection_model = require("../default-detection-model-
|
|
6
|
+
const require_dist = require("../dist-L_HlB0At.js");
|
|
7
|
+
const require_node = require("../node-CmzZ_-zB.js");
|
|
8
|
+
const require_default_detection_model = require("../default-detection-model-BTJZvSr_.js");
|
|
9
9
|
const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
|
|
10
|
-
const require_process_memory = require("../process-memory-
|
|
10
|
+
const require_process_memory = require("../process-memory-FfRDGu11.js");
|
|
11
11
|
let node_os = require("node:os");
|
|
12
12
|
node_os = require_chunk.__toESM(node_os);
|
|
13
13
|
let node_fs = require("node:fs");
|
|
@@ -367,12 +367,14 @@ function normalizeEngineNodeId(rawNodeId) {
|
|
|
367
367
|
* builders eventually reaches ten of them, and the cameras attached by the
|
|
368
368
|
* forgotten one quietly write a foreign feature space into the shared index.
|
|
369
369
|
*
|
|
370
|
-
* ## Why the failure mode keeps the last value
|
|
370
|
+
* ## Why the failure mode keeps the last value — and refuses before the first
|
|
371
371
|
*
|
|
372
372
|
* A failed read must never revert to the registry default MID-PASS: that would
|
|
373
373
|
* silently change what every remaining vector of that dispatch means, which is
|
|
374
374
|
* the exact class of harm the scope exists to prevent. So a failure keeps the
|
|
375
|
-
* last known row and says so,
|
|
375
|
+
* last known row and says so, once per change. Before the FIRST answer there is
|
|
376
|
+
* no row to keep: the step is refused by name (D658) — a default run then made
|
|
377
|
+
* the default model the pool's active variant for the process lifetime.
|
|
376
378
|
*/
|
|
377
379
|
/**
|
|
378
380
|
* The addon whose GLOBAL settings own the cluster row. Same owner as the crop
|
|
@@ -381,35 +383,58 @@ function normalizeEngineNodeId(rawNodeId) {
|
|
|
381
383
|
* enforces it live — neither should own a value the other depends on).
|
|
382
384
|
*/
|
|
383
385
|
var CLUSTER_MODEL_OWNER_ADDON_ID = "pipeline-orchestrator";
|
|
384
|
-
/**
|
|
386
|
+
/**
|
|
387
|
+
* TTL-cached read of the cluster model row.
|
|
388
|
+
*
|
|
389
|
+
* A step's model is KNOWN only once the owner has answered for it (`stored` or
|
|
390
|
+
* `absent`). Until then it is UNKNOWN, and a cluster-scoped step is refused by
|
|
391
|
+
* name rather than run with the registry default (D49, D658): a default run
|
|
392
|
+
* during the owner's boot window made that model the pool's active variant for
|
|
393
|
+
* the whole process, and the face default was ArcFace until D651. Once known,
|
|
394
|
+
* an unanswered read keeps the LAST KNOWN model — never reverts to a default.
|
|
395
|
+
*/
|
|
385
396
|
var ClusterModelSource = class {
|
|
386
|
-
models =
|
|
397
|
+
models = {};
|
|
398
|
+
unknown = new Map(require_dist.CLUSTER_MODEL_SCOPED_STEPS.map((s) => [s.stepId, "not-yet-asked"]));
|
|
387
399
|
settings = require_dist.DEFAULT_CLUSTER_STEP_SETTINGS;
|
|
388
400
|
lastReadAtMs = Number.NEGATIVE_INFINITY;
|
|
389
401
|
inFlight = null;
|
|
390
402
|
logger;
|
|
391
403
|
now;
|
|
392
404
|
ttlMs;
|
|
405
|
+
unknownRetryMs;
|
|
406
|
+
/** The last "not known" state logged, so it is said once per change. */
|
|
407
|
+
lastUnknownLogKey = "";
|
|
393
408
|
constructor(options) {
|
|
394
409
|
this.logger = options.logger;
|
|
395
410
|
this.now = options.now ?? (() => Date.now());
|
|
396
411
|
this.ttlMs = options.ttlMs ?? 6e4;
|
|
412
|
+
this.unknownRetryMs = options.unknownRetryMs ?? 5e3;
|
|
397
413
|
}
|
|
398
|
-
/**
|
|
414
|
+
/**
|
|
415
|
+
* The KNOWN models, `stepId → modelId`. A step absent here is unknown (see
|
|
416
|
+
* {@link unknownSteps}) and must be refused, never defaulted.
|
|
417
|
+
*/
|
|
399
418
|
current() {
|
|
400
419
|
return this.models;
|
|
401
420
|
}
|
|
421
|
+
/** Cluster-scoped steps whose model the owner has not answered yet, with why. */
|
|
422
|
+
unknownSteps() {
|
|
423
|
+
return this.unknown;
|
|
424
|
+
}
|
|
402
425
|
/** Cluster-scoped step knobs in force right now (admission floors, …). */
|
|
403
426
|
currentSettings() {
|
|
404
427
|
return this.settings;
|
|
405
428
|
}
|
|
406
429
|
/**
|
|
407
|
-
* Re-read if the cached row has expired
|
|
408
|
-
* in-flight read; a failure keeps the LAST
|
|
430
|
+
* Re-read if the cached row has expired (sooner while a step is unknown).
|
|
431
|
+
* Concurrent callers share one in-flight read; a failure keeps the LAST
|
|
432
|
+
* KNOWN row.
|
|
409
433
|
*/
|
|
410
434
|
async refresh(api) {
|
|
411
435
|
if (api === void 0) return this.models;
|
|
412
|
-
|
|
436
|
+
const ttl = this.unknown.size > 0 ? Math.min(this.ttlMs, this.unknownRetryMs) : this.ttlMs;
|
|
437
|
+
if (this.now() - this.lastReadAtMs < ttl) return this.models;
|
|
413
438
|
this.inFlight ??= this.read(api).finally(() => {
|
|
414
439
|
this.inFlight = null;
|
|
415
440
|
});
|
|
@@ -417,44 +442,186 @@ var ClusterModelSource = class {
|
|
|
417
442
|
return this.models;
|
|
418
443
|
}
|
|
419
444
|
async read(api) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return [...new Set([...Object.keys(from), ...Object.keys(to)])].some((key) => from[key] !== to[key]);
|
|
436
|
-
});
|
|
437
|
-
if (changed.length > 0) {
|
|
438
|
-
this.logger.info("cluster step model changed — vectors already stored by the previous model need a re-embed pass", { meta: { changed: changed.map((stepId) => ({
|
|
439
|
-
stepId,
|
|
440
|
-
from: this.models[stepId],
|
|
441
|
-
to: next[stepId]
|
|
442
|
-
})) } });
|
|
443
|
-
this.models = next;
|
|
444
|
-
}
|
|
445
|
-
if (settingsChanged) {
|
|
446
|
-
this.logger.info("cluster step settings changed", { meta: {
|
|
447
|
-
from: { ...this.settings },
|
|
448
|
-
to: { ...nextSettings }
|
|
449
|
-
} });
|
|
450
|
-
this.settings = nextSettings;
|
|
445
|
+
this.lastReadAtMs = this.now();
|
|
446
|
+
await Promise.all([this.readModels(api), this.readSettings(api)]);
|
|
447
|
+
}
|
|
448
|
+
async readModels(api) {
|
|
449
|
+
const answers = await Promise.all(require_dist.CLUSTER_MODEL_SCOPED_STEPS.map(async (step) => ({
|
|
450
|
+
stepId: step.stepId,
|
|
451
|
+
answer: await askOwner(api, step.stepId)
|
|
452
|
+
})));
|
|
453
|
+
const nextModels = { ...this.models };
|
|
454
|
+
const nextUnknown = /* @__PURE__ */ new Map();
|
|
455
|
+
const unanswered = [];
|
|
456
|
+
for (const { stepId, answer } of answers) {
|
|
457
|
+
if (answer.kind === "model") {
|
|
458
|
+
nextModels[stepId] = answer.modelId;
|
|
459
|
+
continue;
|
|
451
460
|
}
|
|
461
|
+
const keeping = this.models[stepId] ?? null;
|
|
462
|
+
unanswered.push({
|
|
463
|
+
stepId,
|
|
464
|
+
reason: answer.reason,
|
|
465
|
+
keeping
|
|
466
|
+
});
|
|
467
|
+
if (keeping === null) nextUnknown.set(stepId, answer.reason);
|
|
468
|
+
}
|
|
469
|
+
const changed = Object.keys(nextModels).filter((stepId) => this.models[stepId] !== void 0 && nextModels[stepId] !== this.models[stepId]);
|
|
470
|
+
if (changed.length > 0) this.logger.info("cluster step model changed — vectors already stored by the previous model need a re-embed pass", { meta: { changed: changed.map((stepId) => ({
|
|
471
|
+
stepId,
|
|
472
|
+
from: this.models[stepId],
|
|
473
|
+
to: nextModels[stepId]
|
|
474
|
+
})) } });
|
|
475
|
+
const learned = Object.keys(nextModels).filter((stepId) => this.models[stepId] === void 0);
|
|
476
|
+
if (learned.length > 0) this.logger.info("cluster step model known — cluster-scoped steps run it", { meta: { known: Object.fromEntries(learned.map((id) => [id, nextModels[id]])) } });
|
|
477
|
+
this.models = nextModels;
|
|
478
|
+
this.unknown = nextUnknown;
|
|
479
|
+
this.reportUnanswered(unanswered);
|
|
480
|
+
}
|
|
481
|
+
/** Once per change of the unanswered set — never once per retry. */
|
|
482
|
+
reportUnanswered(unanswered) {
|
|
483
|
+
const key = JSON.stringify(unanswered);
|
|
484
|
+
if (key === this.lastUnknownLogKey) return;
|
|
485
|
+
this.lastUnknownLogKey = key;
|
|
486
|
+
if (unanswered.length === 0) return;
|
|
487
|
+
this.logger.warn("cluster step model not answered by its owner — a step never known is REFUSED, a known one keeps its last model", { meta: {
|
|
488
|
+
owner: CLUSTER_MODEL_OWNER_ADDON_ID,
|
|
489
|
+
unanswered: unanswered.map((u) => ({
|
|
490
|
+
stepId: u.stepId,
|
|
491
|
+
reason: u.reason,
|
|
492
|
+
...u.keeping !== null ? { keeping: u.keeping } : { refused: true }
|
|
493
|
+
}))
|
|
494
|
+
} });
|
|
495
|
+
}
|
|
496
|
+
async readSettings(api) {
|
|
497
|
+
let view;
|
|
498
|
+
try {
|
|
499
|
+
view = await api.addonSettings.getGlobalSettings.query({ addonId: CLUSTER_MODEL_OWNER_ADDON_ID });
|
|
452
500
|
} catch (err) {
|
|
453
|
-
this.logger.warn("cluster step
|
|
501
|
+
this.logger.warn("cluster step settings read failed — keeping the last known knobs", { meta: {
|
|
454
502
|
owner: CLUSTER_MODEL_OWNER_ADDON_ID,
|
|
455
|
-
inUse: { ...this.models },
|
|
456
503
|
error: err instanceof Error ? err.message : String(err)
|
|
457
504
|
} });
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (view === null) return;
|
|
508
|
+
const nextSettings = require_dist.pickClusterStepSettings(view);
|
|
509
|
+
if (Object.keys(nextSettings).some((stepId) => {
|
|
510
|
+
const from = this.settings[stepId] ?? {};
|
|
511
|
+
const to = nextSettings[stepId] ?? {};
|
|
512
|
+
return [...new Set([...Object.keys(from), ...Object.keys(to)])].some((key) => from[key] !== to[key]);
|
|
513
|
+
})) {
|
|
514
|
+
this.logger.info("cluster step settings changed", { meta: {
|
|
515
|
+
from: { ...this.settings },
|
|
516
|
+
to: { ...nextSettings }
|
|
517
|
+
} });
|
|
518
|
+
this.settings = nextSettings;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
/** One step's model from the owner's three-way answer; a throw is no answer. */
|
|
523
|
+
async function askOwner(api, stepId) {
|
|
524
|
+
try {
|
|
525
|
+
const choice = await api.pipelineOrchestrator.getClusterModelChoice.query({ stepId });
|
|
526
|
+
switch (choice.kind) {
|
|
527
|
+
case "stored": return {
|
|
528
|
+
kind: "model",
|
|
529
|
+
modelId: choice.modelId
|
|
530
|
+
};
|
|
531
|
+
case "absent": return {
|
|
532
|
+
kind: "model",
|
|
533
|
+
modelId: choice.defaultModelId
|
|
534
|
+
};
|
|
535
|
+
case "unreadable": return {
|
|
536
|
+
kind: "none",
|
|
537
|
+
reason: choice.reason
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
} catch (err) {
|
|
541
|
+
return {
|
|
542
|
+
kind: "none",
|
|
543
|
+
reason: `read-failed: ${err instanceof Error ? err.message : String(err)}`
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/detection-pipeline/cluster-model-switch-log.ts
|
|
549
|
+
function* clusterScopedSteps(steps) {
|
|
550
|
+
for (const step of steps) {
|
|
551
|
+
if (step.definition.modelScope === "cluster") yield step;
|
|
552
|
+
yield* clusterScopedSteps(step.children);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
var ClusterModelSwitchLog = class {
|
|
556
|
+
logger;
|
|
557
|
+
/** `deviceId|stepId` → the model that step last ran for that camera. */
|
|
558
|
+
lastRun = /* @__PURE__ */ new Map();
|
|
559
|
+
constructor(logger) {
|
|
560
|
+
this.logger = logger;
|
|
561
|
+
}
|
|
562
|
+
/** Record what `tree` runs for `deviceId`; say so when a cluster step changed model. */
|
|
563
|
+
note(deviceId, tree) {
|
|
564
|
+
if (deviceId <= 0) return;
|
|
565
|
+
for (const step of clusterScopedSteps(tree.roots)) {
|
|
566
|
+
const key = `${String(deviceId)}|${step.stepId}`;
|
|
567
|
+
const previous = this.lastRun.get(key);
|
|
568
|
+
if (previous === step.modelId) continue;
|
|
569
|
+
this.lastRun.set(key, step.modelId);
|
|
570
|
+
if (previous === void 0) continue;
|
|
571
|
+
this.logger.info("cluster-scoped step re-provisioned for this camera — now runs the new model", {
|
|
572
|
+
tags: { deviceId },
|
|
573
|
+
meta: {
|
|
574
|
+
stepId: step.stepId,
|
|
575
|
+
from: previous,
|
|
576
|
+
to: step.modelId
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
function* enabledStepIds(steps) {
|
|
583
|
+
for (const step of steps) {
|
|
584
|
+
if (!step.enabled) continue;
|
|
585
|
+
yield step.addonId;
|
|
586
|
+
if (step.children) yield* enabledStepIds(step.children);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/** The enabled steps of `steps` (any depth) whose cluster model is unknown. */
|
|
590
|
+
function unknownStepsNamed(steps, unknown) {
|
|
591
|
+
if (unknown.size === 0) return [];
|
|
592
|
+
return [...enabledStepIds(steps)].filter((id) => unknown.has(id));
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* One line per (camera, step) while a cluster-scoped step is REFUSED because
|
|
596
|
+
* the owner has not answered which model the cluster runs (D658). Said once per
|
|
597
|
+
* episode: when every step becomes known the memory is cleared, so the next
|
|
598
|
+
* episode is said again.
|
|
599
|
+
*/
|
|
600
|
+
var ClusterUnknownRefusalLog = class {
|
|
601
|
+
logger;
|
|
602
|
+
said = /* @__PURE__ */ new Set();
|
|
603
|
+
constructor(logger) {
|
|
604
|
+
this.logger = logger;
|
|
605
|
+
}
|
|
606
|
+
note(deviceId, steps, unknown) {
|
|
607
|
+
if (unknown.size === 0) {
|
|
608
|
+
if (this.said.size > 0) this.said = /* @__PURE__ */ new Set();
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (deviceId <= 0) return;
|
|
612
|
+
for (const stepId of enabledStepIds(steps)) {
|
|
613
|
+
const reason = unknown.get(stepId);
|
|
614
|
+
if (reason === void 0) continue;
|
|
615
|
+
const key = `${String(deviceId)}|${stepId}`;
|
|
616
|
+
if (this.said.has(key)) continue;
|
|
617
|
+
this.said = new Set(this.said).add(key);
|
|
618
|
+
this.logger.warn("cluster-scoped step refused for this camera — the owner has not answered which model the cluster runs", {
|
|
619
|
+
tags: { deviceId },
|
|
620
|
+
meta: {
|
|
621
|
+
stepId,
|
|
622
|
+
reason
|
|
623
|
+
}
|
|
624
|
+
});
|
|
458
625
|
}
|
|
459
626
|
}
|
|
460
627
|
};
|
|
@@ -2154,7 +2321,8 @@ function collectFramePlaneSteps(steps, isDetailStep) {
|
|
|
2154
2321
|
}
|
|
2155
2322
|
/**
|
|
2156
2323
|
* Immutably remove from a frame-plane dispatch tree every enabled DETAIL
|
|
2157
|
-
* subtree (at any depth below the top level)
|
|
2324
|
+
* subtree (at any depth below the top level) that `isUnrunnable` — judged
|
|
2325
|
+
* WHOLE at its top, because its children ride its call (D657).
|
|
2158
2326
|
* Those steps can never load into the dispatch device's pool — leaving them
|
|
2159
2327
|
* in the tree makes `ensureModelsForSteps` fail the whole call on a format
|
|
2160
2328
|
* build that does not exist. They are NOT dropped work: each runs later as
|
|
@@ -2166,6 +2334,11 @@ function collectFramePlaneSteps(steps, isDetailStep) {
|
|
|
2166
2334
|
*/
|
|
2167
2335
|
function pruneUnrunnableDetailSteps(steps, isDetailStep, isUnrunnable) {
|
|
2168
2336
|
const prunedAddonIds = [];
|
|
2337
|
+
const removedAddonIds = [];
|
|
2338
|
+
const collect = (step) => {
|
|
2339
|
+
removedAddonIds.push(step.addonId);
|
|
2340
|
+
for (const child of step.children ?? []) collect(child);
|
|
2341
|
+
};
|
|
2169
2342
|
const walk = (nodes, topLevel) => {
|
|
2170
2343
|
const kept = [];
|
|
2171
2344
|
for (const step of nodes) {
|
|
@@ -2173,8 +2346,9 @@ function pruneUnrunnableDetailSteps(steps, isDetailStep, isUnrunnable) {
|
|
|
2173
2346
|
kept.push(step);
|
|
2174
2347
|
continue;
|
|
2175
2348
|
}
|
|
2176
|
-
if (!topLevel && isDetailStep(step.addonId) && isUnrunnable(step
|
|
2349
|
+
if (!topLevel && isDetailStep(step.addonId) && isUnrunnable(step)) {
|
|
2177
2350
|
prunedAddonIds.push(step.addonId);
|
|
2351
|
+
collect(step);
|
|
2178
2352
|
continue;
|
|
2179
2353
|
}
|
|
2180
2354
|
kept.push(step.children?.length ? {
|
|
@@ -2186,7 +2360,8 @@ function pruneUnrunnableDetailSteps(steps, isDetailStep, isUnrunnable) {
|
|
|
2186
2360
|
};
|
|
2187
2361
|
return {
|
|
2188
2362
|
steps: walk(steps, true),
|
|
2189
|
-
prunedAddonIds
|
|
2363
|
+
prunedAddonIds,
|
|
2364
|
+
removedAddonIds
|
|
2190
2365
|
};
|
|
2191
2366
|
}
|
|
2192
2367
|
//#endregion
|
|
@@ -2459,16 +2634,26 @@ var PipelineModelManager = class {
|
|
|
2459
2634
|
for (const { entry, step } of diff.unchanged) await this.reconcileDecode(entry, step.settings);
|
|
2460
2635
|
}
|
|
2461
2636
|
/**
|
|
2462
|
-
*
|
|
2463
|
-
*
|
|
2464
|
-
*
|
|
2465
|
-
*
|
|
2466
|
-
*
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2637
|
+
* The warm variant that runs `(stepId, modelId)` — its handle AND the model
|
|
2638
|
+
* loaded at that pool index, which is the only honest answer to "which model
|
|
2639
|
+
* produced this output" (D658).
|
|
2640
|
+
*
|
|
2641
|
+
* No active-variant fallback, deliberately. The step-only lookup this
|
|
2642
|
+
* replaces (`getHandle(stepId)`, deleted) answered the step's ACTIVE
|
|
2643
|
+
* variant — the first one loaded after boot — and a tree that paired that
|
|
2644
|
+
* handle with the step's REQUESTED id stamped SigLIP2 on MobileCLIP vectors
|
|
2645
|
+
* for twenty minutes on 2026-09-27. A pair that is not resident throws,
|
|
2646
|
+
* naming both.
|
|
2647
|
+
*/
|
|
2648
|
+
getVariant(stepId, modelId) {
|
|
2649
|
+
const entry = this.loaded.get(stepId)?.get(modelId);
|
|
2650
|
+
if (entry === void 0) throw new Error(`Step "${stepId}" has no warm variant for model "${modelId}" in the inference pool — refusing rather than running another variant`);
|
|
2470
2651
|
this.touch(entry);
|
|
2471
|
-
return
|
|
2652
|
+
return {
|
|
2653
|
+
engine: this.pool.getHandle(entry.poolIndex),
|
|
2654
|
+
modelId: entry.modelId,
|
|
2655
|
+
poolIndex: entry.poolIndex
|
|
2656
|
+
};
|
|
2472
2657
|
}
|
|
2473
2658
|
/** True iff the step has any model loaded. */
|
|
2474
2659
|
isLoaded(stepId) {
|
|
@@ -2499,13 +2684,14 @@ var PipelineModelManager = class {
|
|
|
2499
2684
|
return this.activeByStep.get(stepId);
|
|
2500
2685
|
}
|
|
2501
2686
|
/**
|
|
2502
|
-
* Pool index for
|
|
2503
|
-
*
|
|
2504
|
-
*
|
|
2687
|
+
* Pool index for exactly `(stepId, modelId)`, or `null` when that pair is not
|
|
2688
|
+
* warm. Used by the inference fast paths that bypass `getVariant` and call
|
|
2689
|
+
* `pool.inferBatch` directly. The model is REQUIRED: the active-variant
|
|
2690
|
+
* fallback this had timed a benchmark on whichever variant was active (D658).
|
|
2505
2691
|
*/
|
|
2506
2692
|
getPoolIndex(stepId, modelId) {
|
|
2507
|
-
const entry = this.
|
|
2508
|
-
if (
|
|
2693
|
+
const entry = this.loaded.get(stepId)?.get(modelId);
|
|
2694
|
+
if (entry === void 0) return null;
|
|
2509
2695
|
this.touch(entry);
|
|
2510
2696
|
return entry.poolIndex;
|
|
2511
2697
|
}
|
|
@@ -2725,18 +2911,6 @@ var PipelineModelManager = class {
|
|
|
2725
2911
|
await this.unloadEntry(evicted);
|
|
2726
2912
|
}
|
|
2727
2913
|
}
|
|
2728
|
-
resolve(stepId, modelId) {
|
|
2729
|
-
const perStep = this.loaded.get(stepId);
|
|
2730
|
-
if (!perStep) return null;
|
|
2731
|
-
const targetModelId = modelId ?? this.activeByStep.get(stepId);
|
|
2732
|
-
if (!targetModelId) return null;
|
|
2733
|
-
return perStep.get(targetModelId) ?? null;
|
|
2734
|
-
}
|
|
2735
|
-
resolveOrThrow(stepId, modelId) {
|
|
2736
|
-
const entry = this.resolve(stepId, modelId);
|
|
2737
|
-
if (!entry) throw new Error(`Step "${stepId}"${modelId ? ` (model "${modelId}")` : ""} is not loaded in the inference pool`);
|
|
2738
|
-
return entry;
|
|
2739
|
-
}
|
|
2740
2914
|
touch(entry) {
|
|
2741
2915
|
entry.lruTick = ++this.lruClock;
|
|
2742
2916
|
}
|
|
@@ -2932,16 +3106,15 @@ var EngineFactory = class {
|
|
|
2932
3106
|
await this.poolManager.applyConfig(steps);
|
|
2933
3107
|
}
|
|
2934
3108
|
/**
|
|
2935
|
-
*
|
|
2936
|
-
*
|
|
2937
|
-
*
|
|
2938
|
-
* paths picking a non-runtime model).
|
|
3109
|
+
* The resident variant for exactly `(stepId, modelId)`, with the model it
|
|
3110
|
+
* runs — never the step's active variant in its place (D658). The executable
|
|
3111
|
+
* tree is built from this, and a result's model stamp comes from it.
|
|
2939
3112
|
*
|
|
2940
|
-
* @throws if
|
|
3113
|
+
* @throws if that exact pair is not warm.
|
|
2941
3114
|
*/
|
|
2942
|
-
|
|
3115
|
+
getStepVariant(stepId, modelId) {
|
|
2943
3116
|
if (!this.poolManager) throw new Error("EngineFactory not initialized");
|
|
2944
|
-
return this.poolManager.
|
|
3117
|
+
return this.poolManager.getVariant(stepId, modelId);
|
|
2945
3118
|
}
|
|
2946
3119
|
/** Check if a step is loaded (any variant). */
|
|
2947
3120
|
isLoaded(stepId) {
|
|
@@ -3045,7 +3218,7 @@ var EngineFactory = class {
|
|
|
3045
3218
|
async batchInferRaw(stepId, items, modelId, frameId = 0) {
|
|
3046
3219
|
if (!this.poolManager) throw new Error("EngineFactory.batchInferRaw: pool not initialized");
|
|
3047
3220
|
const idx = this.poolManager.getPoolIndex(stepId, modelId);
|
|
3048
|
-
if (idx === null) throw new Error(`EngineFactory.batchInferRaw: step "${stepId}"
|
|
3221
|
+
if (idx === null) throw new Error(`EngineFactory.batchInferRaw: step "${stepId}" (model "${modelId}") is not loaded`);
|
|
3049
3222
|
return this.poolManager.getPool().inferBatch(idx, items, frameId);
|
|
3050
3223
|
}
|
|
3051
3224
|
async cacheFrameInPool(raw, width, height, format) {
|
|
@@ -3055,7 +3228,7 @@ var EngineFactory = class {
|
|
|
3055
3228
|
async inferCached(stepId, frameId, modelId) {
|
|
3056
3229
|
if (!this.poolManager) throw new Error("inferCached: pool not available");
|
|
3057
3230
|
const idx = this.poolManager.getPoolIndex(stepId, modelId);
|
|
3058
|
-
if (idx === null) throw new Error(`inferCached: step "${stepId}"
|
|
3231
|
+
if (idx === null) throw new Error(`inferCached: step "${stepId}" (model "${modelId}") not loaded`);
|
|
3059
3232
|
return this.poolManager.getPool().inferCached(idx, frameId);
|
|
3060
3233
|
}
|
|
3061
3234
|
async uncacheFrame(frameId) {
|
|
@@ -3950,13 +4123,16 @@ function checkReplayPin(input) {
|
|
|
3950
4123
|
}
|
|
3951
4124
|
var PRIMARY_ROOT_STEP = "object-detection";
|
|
3952
4125
|
/**
|
|
3953
|
-
* The
|
|
3954
|
-
*
|
|
3955
|
-
*
|
|
4126
|
+
* The same rule over an EXECUTABLE tree's roots (D658): their model ids are the
|
|
4127
|
+
* pool variants that will run — after resolution, per-step loads and drops —
|
|
4128
|
+
* so this is what a live `runPipeline` stamps. The tree holds only enabled
|
|
4129
|
+
* video roots already.
|
|
3956
4130
|
*/
|
|
3957
|
-
function
|
|
3958
|
-
|
|
3959
|
-
|
|
4131
|
+
function resolveTreeRootModelId(roots) {
|
|
4132
|
+
return primaryRootModelId(roots);
|
|
4133
|
+
}
|
|
4134
|
+
function primaryRootModelId(roots) {
|
|
4135
|
+
return (roots.find((s) => s.stepId === PRIMARY_ROOT_STEP) ?? roots[0])?.modelId;
|
|
3960
4136
|
}
|
|
3961
4137
|
//#endregion
|
|
3962
4138
|
//#region src/detection-pipeline/engine-provisioner.ts
|
|
@@ -6749,26 +6925,47 @@ function resolveEffectiveClassMap(definition, modelId, resolveModel) {
|
|
|
6749
6925
|
* Build an executable tree from user config.
|
|
6750
6926
|
*
|
|
6751
6927
|
* @param steps - User-configured pipeline steps (from PipelineDefaultStep[])
|
|
6752
|
-
* @param getEngine -
|
|
6753
|
-
* Throws if
|
|
6928
|
+
* @param getEngine - Returns the engine that runs exactly `(stepId, modelId)`
|
|
6929
|
+
* and the model it runs. Throws if that pair is not loaded.
|
|
6754
6930
|
*/
|
|
6755
|
-
function buildExecutableTree(steps, getEngine, resolveModel) {
|
|
6756
|
-
return { roots: steps.filter((s) => s.enabled).filter((s) => s.slot !== "audio-classifier").map((s) => buildNode(s, getEngine, resolveModel)) };
|
|
6931
|
+
function buildExecutableTree(steps, getEngine, resolveModel, onMissingVariant) {
|
|
6932
|
+
return { roots: steps.filter((s) => s.enabled).filter((s) => s.slot !== "audio-classifier").map((s) => buildNode(s, getEngine, resolveModel, onMissingVariant)) };
|
|
6933
|
+
}
|
|
6934
|
+
function subtreeIds(step) {
|
|
6935
|
+
return [step.addonId, ...(step.children ?? []).filter((c) => c.enabled).flatMap((c) => subtreeIds(c))];
|
|
6936
|
+
}
|
|
6937
|
+
function buildChild(child, parentStepId, getEngine, resolveModel, onMissingVariant) {
|
|
6938
|
+
if (onMissingVariant === void 0) return buildNode(child, getEngine, resolveModel, onMissingVariant);
|
|
6939
|
+
let variant;
|
|
6940
|
+
try {
|
|
6941
|
+
variant = getEngine(child.addonId, child.modelId);
|
|
6942
|
+
} catch (err) {
|
|
6943
|
+
onMissingVariant({
|
|
6944
|
+
step: child,
|
|
6945
|
+
stepId: child.addonId,
|
|
6946
|
+
modelId: child.modelId,
|
|
6947
|
+
parentStepId,
|
|
6948
|
+
droppedSubtree: subtreeIds(child),
|
|
6949
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6950
|
+
});
|
|
6951
|
+
return null;
|
|
6952
|
+
}
|
|
6953
|
+
return buildNode(child, getEngine, resolveModel, onMissingVariant, variant);
|
|
6757
6954
|
}
|
|
6758
|
-
function buildNode(step, getEngine, resolveModel) {
|
|
6955
|
+
function buildNode(step, getEngine, resolveModel, onMissingVariant, resolved) {
|
|
6759
6956
|
const definition = require_default_detection_model.getStepDefinition(step.addonId);
|
|
6760
|
-
const
|
|
6761
|
-
const children = (step.children ?? []).filter((c) => c.enabled).map((c) =>
|
|
6957
|
+
const variant = resolved ?? getEngine(step.addonId, step.modelId);
|
|
6958
|
+
const children = (step.children ?? []).filter((c) => c.enabled).map((c) => buildChild(c, step.addonId, getEngine, resolveModel, onMissingVariant)).filter((c) => c !== null);
|
|
6762
6959
|
const mergedSettings = {
|
|
6763
6960
|
...collectSchemaDefaults(step.addonId),
|
|
6764
6961
|
...step.settings
|
|
6765
6962
|
};
|
|
6766
|
-
const effectiveClassMap = resolveEffectiveClassMap(definition,
|
|
6963
|
+
const effectiveClassMap = resolveEffectiveClassMap(definition, variant.modelId, resolveModel);
|
|
6767
6964
|
return {
|
|
6768
6965
|
stepId: step.addonId,
|
|
6769
6966
|
definition,
|
|
6770
|
-
engine,
|
|
6771
|
-
modelId:
|
|
6967
|
+
engine: variant.engine,
|
|
6968
|
+
modelId: variant.modelId,
|
|
6772
6969
|
inputClasses: definition.inputClasses ?? [],
|
|
6773
6970
|
enabled: step.enabled,
|
|
6774
6971
|
children,
|
|
@@ -6798,43 +6995,6 @@ function walkFieldsForDefaults(fields, out) {
|
|
|
6798
6995
|
}
|
|
6799
6996
|
}
|
|
6800
6997
|
//#endregion
|
|
6801
|
-
//#region src/detection-pipeline/registry/custom-models.ts
|
|
6802
|
-
/**
|
|
6803
|
-
* Group a flat list of custom-model descriptors (as returned by the
|
|
6804
|
-
* `custom-model-registry` collection cap) into a `stepId → entries` map for
|
|
6805
|
-
* the picker / resolution union. Pure; order within a step preserved.
|
|
6806
|
-
*/
|
|
6807
|
-
function groupCustomModelsByStep(descriptors) {
|
|
6808
|
-
const byStep = /* @__PURE__ */ new Map();
|
|
6809
|
-
for (const d of descriptors) {
|
|
6810
|
-
const arr = byStep.get(d.stepId) ?? [];
|
|
6811
|
-
arr.push(d.entry);
|
|
6812
|
-
byStep.set(d.stepId, arr);
|
|
6813
|
-
}
|
|
6814
|
-
return byStep;
|
|
6815
|
-
}
|
|
6816
|
-
/**
|
|
6817
|
-
* Union a step's static catalog models with operator-registered custom
|
|
6818
|
-
* models. On an `id` collision the static catalog entry wins — a custom
|
|
6819
|
-
* model can never shadow a built-in one.
|
|
6820
|
-
*
|
|
6821
|
-
* Pure + side-effect-free so it can be unit-tested in isolation and called
|
|
6822
|
-
* from the (free) `buildSchemaSlots` builder without any addon context.
|
|
6823
|
-
*/
|
|
6824
|
-
function mergeCustomModels(staticModels, customModels) {
|
|
6825
|
-
const seen = new Set(staticModels.map((m) => m.id));
|
|
6826
|
-
const merged = [...staticModels];
|
|
6827
|
-
for (const m of customModels) {
|
|
6828
|
-
if (seen.has(m.id)) continue;
|
|
6829
|
-
seen.add(m.id);
|
|
6830
|
-
merged.push({
|
|
6831
|
-
...m,
|
|
6832
|
-
provider: require_dist.inferModelProvider(m)
|
|
6833
|
-
});
|
|
6834
|
-
}
|
|
6835
|
-
return merged;
|
|
6836
|
-
}
|
|
6837
|
-
//#endregion
|
|
6838
6998
|
//#region src/detection-pipeline/cluster-model-resolution.ts
|
|
6839
6999
|
/**
|
|
6840
7000
|
* The cluster model rule, enforced on the node that has to obey it.
|
|
@@ -6939,8 +7099,10 @@ function collectSubstitutions(steps, format) {
|
|
|
6939
7099
|
return result;
|
|
6940
7100
|
}
|
|
6941
7101
|
/**
|
|
6942
|
-
* Return one {@link ZeroBuildIssue} per step in `steps`
|
|
6943
|
-
*
|
|
7102
|
+
* Return one {@link ZeroBuildIssue} per step in `steps` that a pool of `format`
|
|
7103
|
+
* cannot run: neither the model it names nor any NON-legacy model of its
|
|
7104
|
+
* catalog (via `getStepDef`) ships a build for `format`
|
|
7105
|
+
* (`stepModelRunsOnFormat`, D657). `steps` is
|
|
6944
7106
|
* expected to already be flattened + filtered to the set the caller cares
|
|
6945
7107
|
* about (e.g. enabled video steps) — this does not recurse into children or
|
|
6946
7108
|
* filter by `enabled`, unlike {@link collectSubstitutions}.
|
|
@@ -6961,7 +7123,7 @@ function collectZeroBuildIssues(steps, format, getStepDef = require_default_dete
|
|
|
6961
7123
|
} catch {
|
|
6962
7124
|
continue;
|
|
6963
7125
|
}
|
|
6964
|
-
if (!def.models.
|
|
7126
|
+
if (!require_dist.stepModelRunsOnFormat(def.models, step.modelId, format)) result.push({
|
|
6965
7127
|
addonId: step.addonId,
|
|
6966
7128
|
format
|
|
6967
7129
|
});
|
|
@@ -7011,11 +7173,20 @@ function collectUnknownAddonIssues(steps, getStepDef = require_default_detection
|
|
|
7011
7173
|
* `diagnostics.substitutions` instead of happening silently. The caller
|
|
7012
7174
|
* (the provider) owns logging + dedup for both.
|
|
7013
7175
|
*/
|
|
7014
|
-
function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings) {
|
|
7176
|
+
function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings, unknownClusterSteps) {
|
|
7015
7177
|
const resolvedSteps = [];
|
|
7016
7178
|
const unknownAddonIds = [];
|
|
7017
7179
|
const substitutions = [];
|
|
7018
7180
|
const clusterRefusals = [];
|
|
7181
|
+
const clusterUnknown = [];
|
|
7182
|
+
const recurse = (children) => {
|
|
7183
|
+
const r = resolveInputSteps(children, format, engine, clusterModels, clusterSettings, unknownClusterSteps);
|
|
7184
|
+
unknownAddonIds.push(...r.diagnostics.unknownAddonIds);
|
|
7185
|
+
substitutions.push(...r.diagnostics.substitutions);
|
|
7186
|
+
clusterRefusals.push(...r.diagnostics.clusterRefusals);
|
|
7187
|
+
clusterUnknown.push(...r.diagnostics.clusterUnknown);
|
|
7188
|
+
return r;
|
|
7189
|
+
};
|
|
7019
7190
|
for (const s of steps) {
|
|
7020
7191
|
let def;
|
|
7021
7192
|
try {
|
|
@@ -7024,6 +7195,10 @@ function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings
|
|
|
7024
7195
|
unknownAddonIds.push(s.addonId);
|
|
7025
7196
|
continue;
|
|
7026
7197
|
}
|
|
7198
|
+
if (def.modelScope === "cluster" && unknownClusterSteps?.has(s.addonId) === true) {
|
|
7199
|
+
clusterUnknown.push(s.addonId);
|
|
7200
|
+
continue;
|
|
7201
|
+
}
|
|
7027
7202
|
if (clusterModels !== void 0) {
|
|
7028
7203
|
const verdict = resolveClusterStepModel(s.addonId, clusterModels, format, () => def);
|
|
7029
7204
|
if (verdict.kind !== "node-scoped") {
|
|
@@ -7033,12 +7208,7 @@ function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings
|
|
|
7033
7208
|
format: verdict.format,
|
|
7034
7209
|
formatsShipped: verdict.formatsShipped
|
|
7035
7210
|
});
|
|
7036
|
-
const childResult = s.children ?
|
|
7037
|
-
if (childResult) {
|
|
7038
|
-
unknownAddonIds.push(...childResult.diagnostics.unknownAddonIds);
|
|
7039
|
-
substitutions.push(...childResult.diagnostics.substitutions);
|
|
7040
|
-
clusterRefusals.push(...childResult.diagnostics.clusterRefusals);
|
|
7041
|
-
}
|
|
7211
|
+
const childResult = s.children ? recurse(s.children) : null;
|
|
7042
7212
|
const settings = require_dist.overlayClusterStepSettings(s.addonId, s.settings, clusterSettings ?? {});
|
|
7043
7213
|
resolvedSteps.push({
|
|
7044
7214
|
addonId: s.addonId,
|
|
@@ -7062,12 +7232,7 @@ function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings
|
|
|
7062
7232
|
running: runningModelId,
|
|
7063
7233
|
format
|
|
7064
7234
|
});
|
|
7065
|
-
const childResult = s.children ?
|
|
7066
|
-
if (childResult) {
|
|
7067
|
-
unknownAddonIds.push(...childResult.diagnostics.unknownAddonIds);
|
|
7068
|
-
substitutions.push(...childResult.diagnostics.substitutions);
|
|
7069
|
-
clusterRefusals.push(...childResult.diagnostics.clusterRefusals);
|
|
7070
|
-
}
|
|
7235
|
+
const childResult = s.children ? recurse(s.children) : null;
|
|
7071
7236
|
const settings = require_dist.overlayClusterStepSettings(s.addonId, s.settings, clusterSettings ?? {});
|
|
7072
7237
|
resolvedSteps.push({
|
|
7073
7238
|
addonId: s.addonId,
|
|
@@ -7086,7 +7251,8 @@ function resolveInputSteps(steps, format, engine, clusterModels, clusterSettings
|
|
|
7086
7251
|
diagnostics: {
|
|
7087
7252
|
unknownAddonIds,
|
|
7088
7253
|
substitutions,
|
|
7089
|
-
clusterRefusals
|
|
7254
|
+
clusterRefusals,
|
|
7255
|
+
clusterUnknown
|
|
7090
7256
|
}
|
|
7091
7257
|
};
|
|
7092
7258
|
}
|
|
@@ -7329,6 +7495,62 @@ function stepModelKey(step) {
|
|
|
7329
7495
|
function stateOf(verdict) {
|
|
7330
7496
|
return verdict.kind === "refused" ? "refused" : `failed:${verdict.generation}`;
|
|
7331
7497
|
}
|
|
7498
|
+
/** The load policy of a `runPipeline` call — see {@link DispatchLoadPolicy}. */
|
|
7499
|
+
function dispatchLoadPolicyOf(input) {
|
|
7500
|
+
return input.deviceId !== void 0 && input.replay !== true ? "partial" : "all-or-nothing";
|
|
7501
|
+
}
|
|
7502
|
+
/**
|
|
7503
|
+
* An all-or-nothing call lost steps to failed loads (D657). Names every one,
|
|
7504
|
+
* so a benchmark never reports timings for a tree it did not run.
|
|
7505
|
+
*/
|
|
7506
|
+
var DispatchStepsNotLoadedError = class extends Error {
|
|
7507
|
+
failedSteps;
|
|
7508
|
+
constructor(failures) {
|
|
7509
|
+
const named = failures.map((f) => `${f.step.addonId}/${f.step.modelId} (${f.error instanceof Error ? f.error.message : String(f.error)})`);
|
|
7510
|
+
super(`pipeline steps failed to load — the call runs all of them or none: ${named.join("; ")}`, { cause: failures[0]?.error });
|
|
7511
|
+
this.name = "DispatchStepsNotLoadedError";
|
|
7512
|
+
this.failedSteps = failures.map((f) => `${f.step.addonId}/${f.step.modelId}`);
|
|
7513
|
+
}
|
|
7514
|
+
};
|
|
7515
|
+
/**
|
|
7516
|
+
* A step's model artifact cannot be made present for the pool's format — no
|
|
7517
|
+
* build for that format, a custom model missing from this node, a download
|
|
7518
|
+
* that failed. Named by step and model so the failure is charged to THAT
|
|
7519
|
+
* model only (D657): a plain `Error` was charged to every model of the set,
|
|
7520
|
+
* and one face model with no tflite build backed the Coral's root detector
|
|
7521
|
+
* off for minutes.
|
|
7522
|
+
*/
|
|
7523
|
+
var StepModelArtifactError = class extends Error {
|
|
7524
|
+
stepId;
|
|
7525
|
+
modelId;
|
|
7526
|
+
constructor(stepId, modelId, cause) {
|
|
7527
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
7528
|
+
this.name = "StepModelArtifactError";
|
|
7529
|
+
this.stepId = stepId;
|
|
7530
|
+
this.modelId = modelId;
|
|
7531
|
+
}
|
|
7532
|
+
};
|
|
7533
|
+
/** The one model a failed load is charged to, when the error names it. */
|
|
7534
|
+
function failedModelOf(err) {
|
|
7535
|
+
if (err instanceof StepVariantLoadError || err instanceof StepModelArtifactError) return `${err.stepId}/${err.modelId}`;
|
|
7536
|
+
return null;
|
|
7537
|
+
}
|
|
7538
|
+
/**
|
|
7539
|
+
* `steps` without the subtrees of the steps whose model did not load (D657).
|
|
7540
|
+
* A step's children consume its output, so they leave with it. Matched by
|
|
7541
|
+
* INSTANCE: two steps of one addon in a tree are two steps.
|
|
7542
|
+
*/
|
|
7543
|
+
function withoutFailedSteps(steps, failed) {
|
|
7544
|
+
const kept = [];
|
|
7545
|
+
for (const step of steps) {
|
|
7546
|
+
if (failed.has(step)) continue;
|
|
7547
|
+
kept.push(step.children && step.children.length > 0 ? {
|
|
7548
|
+
...step,
|
|
7549
|
+
children: withoutFailedSteps(step.children, failed)
|
|
7550
|
+
} : step);
|
|
7551
|
+
}
|
|
7552
|
+
return kept;
|
|
7553
|
+
}
|
|
7332
7554
|
/**
|
|
7333
7555
|
* The reason a condemned pool is charged against its device's restart budget.
|
|
7334
7556
|
* A pool recycled because it HUNG (D653) names the hang — `inference device
|
|
@@ -7436,18 +7658,18 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7436
7658
|
* paths don't issue a cap round-trip on every call. Empty map = no provider
|
|
7437
7659
|
* (or a query failure) → behaviour identical to the static-catalog-only path.
|
|
7438
7660
|
*/
|
|
7439
|
-
|
|
7661
|
+
customModels = new require_default_detection_model.CustomModelCatalog({ warn: (message, extras) => this.log.warn(message, extras) });
|
|
7440
7662
|
/**
|
|
7441
7663
|
* SYNC custom-model lookup handed to every live {@link EngineFactory} so
|
|
7442
7664
|
* `buildPoolModelConfigForStep` can resolve an operator-registered model at
|
|
7443
|
-
* pool-load time. Reads the {@link
|
|
7665
|
+
* pool-load time. Reads the {@link customModels} snapshot — every load
|
|
7444
7666
|
* path (`ensureModelsForSteps`, `ensureModelsForCurrentSteps`) refreshes the
|
|
7445
7667
|
* cache via `resolveModelEntry` BEFORE `loadAdditional`/`applyConfig`, so
|
|
7446
7668
|
* the snapshot is fresh when the pool asks. Without this seam a selected
|
|
7447
7669
|
* custom model failed the WHOLE pool applyConfig with
|
|
7448
7670
|
* `Model "X" not found in step "Y" catalog`.
|
|
7449
7671
|
*/
|
|
7450
|
-
customModelResolver = (stepId, modelId) => this.
|
|
7672
|
+
customModelResolver = (stepId, modelId) => this.customModels.current().get(stepId)?.find((m) => m.id === modelId);
|
|
7451
7673
|
/**
|
|
7452
7674
|
* Per-device {@link DeviceProxy} cache used for zone gating at the
|
|
7453
7675
|
* runtime path. Reads `state.zones.value` + `state.zoneRules.value`
|
|
@@ -7470,6 +7692,10 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7470
7692
|
info: (message, extras) => this.log.info(message, extras),
|
|
7471
7693
|
warn: (message, extras) => this.log.warn(message, extras)
|
|
7472
7694
|
} });
|
|
7695
|
+
/** One line per camera when a cluster-scoped step starts running another model (D658). */
|
|
7696
|
+
clusterModelSwitchLog = new ClusterModelSwitchLog({ info: (message, extras) => this.log.info(message, extras) });
|
|
7697
|
+
/** One line per (camera, step) while a cluster step is refused as unknown (D658). */
|
|
7698
|
+
clusterUnknownLog = new ClusterUnknownRefusalLog({ warn: (message, extras) => this.log.warn(message, extras) });
|
|
7473
7699
|
/**
|
|
7474
7700
|
* Last logged "step configured" signature — used to dedupe the
|
|
7475
7701
|
* per-step debug trail so it only fires on actual config CHANGE
|
|
@@ -7936,7 +8162,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
7936
8162
|
if (!steps) return;
|
|
7937
8163
|
const format = this.currentEngine.format;
|
|
7938
8164
|
const substitutionIssues = this.getActiveModelSubstitutions().map((s) => `${s.addonId}: chose ${s.chosen}, running ${s.running} (${s.format})`);
|
|
7939
|
-
const zeroBuildIssues = collectZeroBuildIssues(flattenSteps(steps), format).map((i) => `${i.addonId}: no model has a ${i.format} build`);
|
|
8165
|
+
const zeroBuildIssues = collectZeroBuildIssues(flattenSteps(steps), format, this.mergedStepDefOrThrow).map((i) => `${i.addonId}: no model has a ${i.format} build`);
|
|
7940
8166
|
this.configIssues = [...substitutionIssues, ...zeroBuildIssues];
|
|
7941
8167
|
} catch (err) {
|
|
7942
8168
|
this.configIssues = [];
|
|
@@ -8070,24 +8296,21 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8070
8296
|
* a warning — callers then behave exactly like the static-catalog path.
|
|
8071
8297
|
*/
|
|
8072
8298
|
async getCustomModels() {
|
|
8073
|
-
|
|
8074
|
-
if (this.customModelsCache && now - this.customModelsCache.at < 5e3) return this.customModelsCache.byStep;
|
|
8075
|
-
let byStep = /* @__PURE__ */ new Map();
|
|
8076
|
-
try {
|
|
8077
|
-
const api = this.addonCtx?.api;
|
|
8078
|
-
if (api) {
|
|
8079
|
-
if ((await api.addons.listCapabilityProviders.query({ capName: "custom-model-registry" })).some((p) => p.isActive)) byStep = groupCustomModelsByStep(await api.customModelRegistry.listModels.query());
|
|
8080
|
-
}
|
|
8081
|
-
} catch (err) {
|
|
8082
|
-
this.log.warn("custom-model-registry query failed — using static catalog only", { meta: { error: require_dist.errMsg(err) } });
|
|
8083
|
-
}
|
|
8084
|
-
this.customModelsCache = {
|
|
8085
|
-
at: now,
|
|
8086
|
-
byStep
|
|
8087
|
-
};
|
|
8088
|
-
return byStep;
|
|
8299
|
+
return this.customModels.refresh(this.addonCtx?.api);
|
|
8089
8300
|
}
|
|
8090
8301
|
/**
|
|
8302
|
+
* A step's definition MERGED with its custom models, synchronously, from
|
|
8303
|
+
* the last custom-model read (D657 round 2). Every placement gate here
|
|
8304
|
+
* judges this catalog — the one the orchestrator's schema carries.
|
|
8305
|
+
*/
|
|
8306
|
+
mergedStepDef = (addonId) => this.customModels.stepDefinition(addonId);
|
|
8307
|
+
/** {@link mergedStepDef} as the throwing lookup `collectZeroBuildIssues` takes. */
|
|
8308
|
+
mergedStepDefOrThrow = (addonId) => {
|
|
8309
|
+
const def = this.mergedStepDef(addonId);
|
|
8310
|
+
if (def === null) throw new Error(`Unknown pipeline step: "${addonId}"`);
|
|
8311
|
+
return def;
|
|
8312
|
+
};
|
|
8313
|
+
/**
|
|
8091
8314
|
* Resolve a model id within a step to a catalog entry — static catalog
|
|
8092
8315
|
* first, then the custom registry. Returns undefined if neither has it.
|
|
8093
8316
|
*/
|
|
@@ -8278,7 +8501,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8278
8501
|
const enabledSteps = this.pruneToEnabledSteps(steps);
|
|
8279
8502
|
const substitutions = collectSubstitutions(enabledSteps, format);
|
|
8280
8503
|
const unknownAddonIssues = collectUnknownAddonIssues(enabledSteps);
|
|
8281
|
-
const zeroBuildIssues = collectZeroBuildIssues(this.flattenEnabledVideoStepInputs(steps), format);
|
|
8504
|
+
const zeroBuildIssues = collectZeroBuildIssues(this.flattenEnabledVideoStepInputs(steps), format, this.mergedStepDefOrThrow);
|
|
8282
8505
|
const issues = [...unknownAddonIssues.map((u) => ({
|
|
8283
8506
|
addonId: u.addonId,
|
|
8284
8507
|
kind: "unknown-addon",
|
|
@@ -8595,6 +8818,12 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8595
8818
|
}
|
|
8596
8819
|
async runPipeline(input, onProgress) {
|
|
8597
8820
|
await this.clusterModels.refresh(this.addonCtx?.api);
|
|
8821
|
+
this.clusterUnknownLog.note(input.deviceId ?? 0, input.steps, this.clusterModels.unknownSteps());
|
|
8822
|
+
if (dispatchLoadPolicyOf(input) === "all-or-nothing") {
|
|
8823
|
+
const unknown = unknownStepsNamed(input.steps, this.clusterModels.unknownSteps());
|
|
8824
|
+
if (unknown.length > 0) throw new Error(`cluster-model-unknown: the owner has not answered which model the cluster runs for ${unknown.join(", ")} — the call runs all of its steps or none`);
|
|
8825
|
+
}
|
|
8826
|
+
await this.getCustomModels();
|
|
8598
8827
|
const nodeId = this.addonCtx?.kernel?.localNodeId ?? "hub";
|
|
8599
8828
|
const sessionId = input.sessionId ?? `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
8600
8829
|
const isRuntime = Boolean(input.frame || input.frameRef);
|
|
@@ -8759,11 +8988,6 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8759
8988
|
}
|
|
8760
8989
|
const enabledSteps = flattenSteps(benchmarkSteps);
|
|
8761
8990
|
emit(`Pipeline: ${enabledSteps.length} step(s) — ${enabledSteps.map((s) => s.addonId).join(" → ")}`);
|
|
8762
|
-
const rootModelId = resolveRootModelId(benchmarkSteps);
|
|
8763
|
-
const stampRoot = (r) => rootModelId === void 0 ? r : {
|
|
8764
|
-
...r,
|
|
8765
|
-
rootModelId
|
|
8766
|
-
};
|
|
8767
8991
|
if (input.replay === true) {
|
|
8768
8992
|
const verdict = checkReplayPin({
|
|
8769
8993
|
requested: input.steps,
|
|
@@ -8805,24 +9029,51 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8805
9029
|
const dispatchEngine = dispatchDeviceKey ? resolveDeviceEngine(dispatchDeviceKey) : runEngine;
|
|
8806
9030
|
const dispatchFactory = dispatchDeviceKey ? await this.resolveDeviceFactory(dispatchDeviceKey) : this.engineFactory;
|
|
8807
9031
|
const needed = enabledSteps.filter((s) => dispatchFactory.needsPoolUpdate(s));
|
|
9032
|
+
let runSteps = benchmarkSteps;
|
|
9033
|
+
const loadPolicy = dispatchLoadPolicyOf(input);
|
|
8808
9034
|
if (needed.length > 0) {
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
9035
|
+
const dispatchDeviceKeyLabel = deviceKeyOf(dispatchEngine);
|
|
9036
|
+
const toIssue = needed.filter((s) => this.loadGovernor.check(dispatchFactory, dispatchDeviceKeyLabel, [stepModelKey(s)]).kind === "go");
|
|
9037
|
+
if (toIssue.length > 0) this.log.info("Benchmark: models to load", { meta: {
|
|
9038
|
+
count: toIssue.length,
|
|
9039
|
+
models: toIssue.map((s) => `${s.addonId}/${s.modelId}`)
|
|
8812
9040
|
} });
|
|
8813
|
-
for (const s of
|
|
9041
|
+
for (const s of toIssue) emit(`Loading model: ${s.addonName} (${s.modelId})...`, {
|
|
8814
9042
|
step: s.addonId,
|
|
8815
9043
|
addonId: s.addonId,
|
|
8816
9044
|
modelId: s.modelId
|
|
8817
9045
|
});
|
|
8818
|
-
await this.
|
|
9046
|
+
const failures = await this.loadDispatchModels(benchmarkSteps, dispatchFactory, dispatchEngine.format, {
|
|
8819
9047
|
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
8820
|
-
deviceKey:
|
|
8821
|
-
});
|
|
8822
|
-
|
|
9048
|
+
deviceKey: dispatchDeviceKeyLabel
|
|
9049
|
+
}, loadPolicy);
|
|
9050
|
+
runSteps = stepsSurvivingLoad(benchmarkSteps, failures, loadPolicy);
|
|
9051
|
+
emit(failures.length === 0 ? `All models loaded` : `Models loaded (${failures.length} failed)`);
|
|
8823
9052
|
}
|
|
8824
9053
|
emit("Running inference...");
|
|
8825
|
-
const
|
|
9054
|
+
const notWarm = [];
|
|
9055
|
+
const tree = buildExecutableTree(runSteps, (stepId, modelId) => dispatchFactory.getStepVariant(stepId, modelId), this.customModelResolver, (drop) => notWarm.push({
|
|
9056
|
+
step: drop.step,
|
|
9057
|
+
error: new Error(drop.error)
|
|
9058
|
+
}));
|
|
9059
|
+
if (notWarm.length > 0) {
|
|
9060
|
+
if (loadPolicy === "all-or-nothing") throw new DispatchStepsNotLoadedError(notWarm);
|
|
9061
|
+
const dispatchDeviceKeyLabel = deviceKeyOf(dispatchEngine);
|
|
9062
|
+
for (const failure of notWarm) {
|
|
9063
|
+
const model = stepModelKey(failure.step);
|
|
9064
|
+
this.reportDispatchLoadLoss({
|
|
9065
|
+
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
9066
|
+
deviceKey: dispatchDeviceKeyLabel,
|
|
9067
|
+
outcome: "step-dropped"
|
|
9068
|
+
}, dispatchDeviceKeyLabel, [model], model, "not-warm", require_dist.errMsg(failure.error));
|
|
9069
|
+
}
|
|
9070
|
+
}
|
|
9071
|
+
const rootModelId = resolveTreeRootModelId(tree.roots);
|
|
9072
|
+
const stampRoot = (r) => rootModelId === void 0 ? r : {
|
|
9073
|
+
...r,
|
|
9074
|
+
rootModelId
|
|
9075
|
+
};
|
|
9076
|
+
if (input.replay !== true) this.clusterModelSwitchLog.note(input.deviceId ?? 0, tree);
|
|
8826
9077
|
setupMs = performance.now() - wallT0 - decodeMs;
|
|
8827
9078
|
const effectiveDeviceId = input.deviceId ?? 0;
|
|
8828
9079
|
const deviceOverrides = effectiveDeviceId > 0 ? await this.deviceOverrides.resolve(effectiveDeviceId) : {};
|
|
@@ -8928,7 +9179,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
8928
9179
|
backend: this.currentEngine.backend,
|
|
8929
9180
|
device: this.currentEngine.device ?? null
|
|
8930
9181
|
}, clusterRefusalsOut) {
|
|
8931
|
-
const { steps: resolvedSteps, diagnostics } = resolveInputSteps(steps, format, engine, this.clusterModels.current(), this.clusterModels.currentSettings());
|
|
9182
|
+
const { steps: resolvedSteps, diagnostics } = resolveInputSteps(steps, format, engine, this.clusterModels.current(), this.clusterModels.currentSettings(), this.clusterModels.unknownSteps());
|
|
8932
9183
|
if (clusterRefusalsOut) clusterRefusalsOut.push(...diagnostics.clusterRefusals);
|
|
8933
9184
|
for (const addonId of diagnostics.unknownAddonIds) this.logLiveDispatchIssueOnce(`unknown:${addonId}`, () => this.log.warn("Live pipeline step references an unknown addon — dropping step", { meta: { addonId } }));
|
|
8934
9185
|
for (const sub of diagnostics.substitutions) this.logLiveDispatchIssueOnce(`${sub.addonId}|${sub.chosen}|${sub.running}|${sub.format}`, () => this.log.info("Live pipeline step model substituted for engine format (node-local)", { meta: {
|
|
@@ -9041,7 +9292,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9041
9292
|
const deviceRefusals = [];
|
|
9042
9293
|
const steps = this.inputStepsToPipelineSteps(args.steps, deviceFormat, deviceStepEngine, deviceRefusals);
|
|
9043
9294
|
const framePlane = args.plane === "frame";
|
|
9044
|
-
const zeroBuild = collectZeroBuildIssues(framePlane ? collectFramePlaneSteps(steps, isDetailPlaneStep) : flattenSteps(steps), deviceFormat);
|
|
9295
|
+
const zeroBuild = collectZeroBuildIssues(framePlane ? collectFramePlaneSteps(steps, isDetailPlaneStep) : flattenSteps(steps), deviceFormat, this.mergedStepDefOrThrow);
|
|
9045
9296
|
if (zeroBuild.length === 0) {
|
|
9046
9297
|
if (!framePlane) {
|
|
9047
9298
|
this.logClusterRefusals(deviceRefusals, deviceId, `${deviceKey} (${deviceFormat})`);
|
|
@@ -9050,7 +9301,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9050
9301
|
steps
|
|
9051
9302
|
};
|
|
9052
9303
|
}
|
|
9053
|
-
const pruneResult = pruneUnrunnableDetailSteps(steps, isDetailPlaneStep, (
|
|
9304
|
+
const pruneResult = pruneUnrunnableDetailSteps(steps, isDetailPlaneStep, (subtree) => !require_default_detection_model.buildSubtreeCanRun(subtree, this.mergedStepDef)(deviceFormat));
|
|
9054
9305
|
if (pruneResult.prunedAddonIds.length > 0) {
|
|
9055
9306
|
const prunedList = pruneResult.prunedAddonIds.join(",");
|
|
9056
9307
|
this.logLiveDispatchIssueOnce(`detail-defer:${deviceKey}:${deviceFormat}:${prunedList}`, () => this.log.info("Detail steps have no model build for the dispatch device format — deferred to the detail-plane device jump", {
|
|
@@ -9062,7 +9313,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9062
9313
|
}
|
|
9063
9314
|
}));
|
|
9064
9315
|
}
|
|
9065
|
-
this.logClusterRefusals(deviceRefusals.filter((r) => !pruneResult.
|
|
9316
|
+
this.logClusterRefusals(deviceRefusals.filter((r) => !pruneResult.removedAddonIds.includes(r.stepId)), deviceId, `${deviceKey} (${deviceFormat})`);
|
|
9066
9317
|
return {
|
|
9067
9318
|
deviceKey,
|
|
9068
9319
|
steps: pruneResult.steps
|
|
@@ -9096,7 +9347,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9096
9347
|
* microseconds instead of a 3-retry download backoff).
|
|
9097
9348
|
*/
|
|
9098
9349
|
assertStepsRunnable(steps, format, deviceId, enginesTried) {
|
|
9099
|
-
const zeroBuild = collectZeroBuildIssues(flattenSteps(steps), format);
|
|
9350
|
+
const zeroBuild = collectZeroBuildIssues(flattenSteps(steps), format, this.mergedStepDefOrThrow);
|
|
9100
9351
|
if (zeroBuild.length === 0) return;
|
|
9101
9352
|
const detail = zeroBuild.map((issue) => {
|
|
9102
9353
|
const step = flattenSteps(steps).find((s) => s.addonId === issue.addonId);
|
|
@@ -9130,6 +9381,40 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9130
9381
|
nullPoolKey = {};
|
|
9131
9382
|
/** Negative cache, per-model compile-timeout refusals, per-camera reporting (D653). */
|
|
9132
9383
|
loadGovernor = new ModelLoadGovernor();
|
|
9384
|
+
/**
|
|
9385
|
+
* Load a dispatch's models ONE STEP AT A TIME (D657), and answer which steps
|
|
9386
|
+
* did not load. Never throws: each step is its own load, joined, backed off
|
|
9387
|
+
* and charged on its own model, so a step whose model cannot load on this
|
|
9388
|
+
* pool — no build for its format, a compile that failed — costs that step,
|
|
9389
|
+
* never the root or a sibling. The caller decides what the dispatch runs
|
|
9390
|
+
* without it ({@link stepsSurvivingLoad}).
|
|
9391
|
+
*/
|
|
9392
|
+
async loadDispatchModels(steps, factory, format, context, policy) {
|
|
9393
|
+
const failures = [];
|
|
9394
|
+
const walk = async (nodes, root) => {
|
|
9395
|
+
for (const step of nodes) {
|
|
9396
|
+
if (!step.enabled || step.slot === "audio-classifier") continue;
|
|
9397
|
+
if (factory === null || factory.needsPoolUpdate(step)) try {
|
|
9398
|
+
await this.ensureModelsForSteps([{
|
|
9399
|
+
...step,
|
|
9400
|
+
children: []
|
|
9401
|
+
}], factory, format, {
|
|
9402
|
+
...context,
|
|
9403
|
+
outcome: policy === "all-or-nothing" ? "call-failed" : root ? "root-lost" : "step-dropped"
|
|
9404
|
+
});
|
|
9405
|
+
} catch (err) {
|
|
9406
|
+
failures.push({
|
|
9407
|
+
step,
|
|
9408
|
+
error: err
|
|
9409
|
+
});
|
|
9410
|
+
continue;
|
|
9411
|
+
}
|
|
9412
|
+
await walk(step.children ?? [], false);
|
|
9413
|
+
}
|
|
9414
|
+
};
|
|
9415
|
+
await walk(steps, true);
|
|
9416
|
+
return failures;
|
|
9417
|
+
}
|
|
9133
9418
|
/** Ensure all models needed by steps are downloaded and loaded in the engine pool.
|
|
9134
9419
|
* `factory`/`format` default to the node's engine; a per-device dispatch passes
|
|
9135
9420
|
* that device's factory + format (Phase 2 multi-device). `context` names the
|
|
@@ -9159,7 +9444,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9159
9444
|
this.reportDispatchLoadLoss(context, deviceKey, models, err.model, err.state, err.message);
|
|
9160
9445
|
throw err;
|
|
9161
9446
|
}
|
|
9162
|
-
const failed = err
|
|
9447
|
+
const failed = failedModelOf(err) ?? models[0] ?? "";
|
|
9163
9448
|
this.reportDispatchLoadLoss(context, deviceKey, models, failed, `failed:${flight.generation ?? "unknown"}`, require_dist.errMsg(err));
|
|
9164
9449
|
throw err;
|
|
9165
9450
|
}
|
|
@@ -9251,7 +9536,8 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9251
9536
|
}
|
|
9252
9537
|
/** Charge a failed load to the model that failed; say so once if it is now refused. */
|
|
9253
9538
|
recordModelLoadFailure(pool, deviceKey, models, err) {
|
|
9254
|
-
const
|
|
9539
|
+
const named = failedModelOf(err);
|
|
9540
|
+
const failedModels = named !== null ? [named] : models;
|
|
9255
9541
|
const compileTimeout = err instanceof StepVariantLoadError && err.reason === "compile-timeout";
|
|
9256
9542
|
const poolModel = err instanceof StepVariantLoadError ? err.poolModel : null;
|
|
9257
9543
|
let generation = 0;
|
|
@@ -9284,24 +9570,31 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9284
9570
|
models,
|
|
9285
9571
|
model,
|
|
9286
9572
|
state,
|
|
9287
|
-
error
|
|
9573
|
+
error,
|
|
9574
|
+
...context.outcome !== void 0 ? { outcome: context.outcome } : {}
|
|
9288
9575
|
}
|
|
9289
9576
|
});
|
|
9290
9577
|
}
|
|
9291
9578
|
/** Download any model of `needed` missing in `format` (the device's). */
|
|
9292
9579
|
async downloadNeededModels(needed, format) {
|
|
9293
|
-
for (const step of needed) {
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
}
|
|
9580
|
+
for (const step of needed) try {
|
|
9581
|
+
await this.ensureStepModelArtifact(step, format);
|
|
9582
|
+
} catch (err) {
|
|
9583
|
+
throw new StepModelArtifactError(step.addonId, step.modelId, err);
|
|
9584
|
+
}
|
|
9585
|
+
}
|
|
9586
|
+
/** Make one step's model artifact present on disk for `format`, or say why it cannot be. */
|
|
9587
|
+
async ensureStepModelArtifact(step, format) {
|
|
9588
|
+
const modelEntry = await this.resolveModelEntry(step.addonId, step.modelId);
|
|
9589
|
+
if (modelEntry && !(0, _camstack_system_addon_utils.isModelDownloaded)(this.modelsDir, modelEntry, format)) {
|
|
9590
|
+
if (modelEntry.formats[format] === void 0) throw new Error(`Model "${modelEntry.id}" has no ${format} format build (available: ${Object.keys(modelEntry.formats).join(", ") || "none"}) — not retrying a permanent format mismatch`);
|
|
9591
|
+
if (modelEntry.formats[format]?.url.startsWith("camstack-local://") === true) throw new Error(`Custom model "${modelEntry.id}" (${format}) is not present in this node's models directory — distribute it to this node from Model Studio before selecting it here`);
|
|
9592
|
+
this.log.info("Downloading model for step", { meta: {
|
|
9593
|
+
modelId: step.modelId,
|
|
9594
|
+
format,
|
|
9595
|
+
step: step.addonId
|
|
9596
|
+
} });
|
|
9597
|
+
await this.downloadWithRetry(modelEntry, format, 3);
|
|
9305
9598
|
}
|
|
9306
9599
|
}
|
|
9307
9600
|
/** Download a model with retry + exponential backoff */
|
|
@@ -9399,6 +9692,7 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9399
9692
|
}
|
|
9400
9693
|
async runPipelineBatchImpl(input) {
|
|
9401
9694
|
if (input.frames.length === 0) return { results: [] };
|
|
9695
|
+
await this.getCustomModels();
|
|
9402
9696
|
const dispatchResolution = this.resolveStepsForDispatch({
|
|
9403
9697
|
steps: input.steps,
|
|
9404
9698
|
deviceKey: input.deviceKey,
|
|
@@ -9641,9 +9935,10 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
9641
9935
|
return this.engineFactory.cacheFrameInPool(buf, input.width, input.height, input.format);
|
|
9642
9936
|
}
|
|
9643
9937
|
async inferCached(input) {
|
|
9938
|
+
if (typeof input.modelId !== "string" || input.modelId === "") throw new Error(`inferCached: no modelId for step "${input.stepId}" — refusing rather than running the step's active variant`);
|
|
9644
9939
|
await this.ensureEngineFactory();
|
|
9645
9940
|
if (!this.engineFactory) throw new Error("inferCached: factory not initialized");
|
|
9646
|
-
return this.engineFactory.inferCached(input.stepId, input.frameId);
|
|
9941
|
+
return this.engineFactory.inferCached(input.stepId, input.frameId, input.modelId);
|
|
9647
9942
|
}
|
|
9648
9943
|
async uncacheFrame(input) {
|
|
9649
9944
|
if (!this.engineFactory) return;
|
|
@@ -10283,7 +10578,7 @@ function buildSchemaSlots(formats, modelsDir, customByStep) {
|
|
|
10283
10578
|
const formatList = [...formats];
|
|
10284
10579
|
for (const pipelineStep of require_default_detection_model.ALL_PIPELINE_STEPS) {
|
|
10285
10580
|
const step = pipelineStep.definition;
|
|
10286
|
-
const availableModels = mergeCustomModels(step.models, customByStep?.get(step.id) ?? []).filter((m) => m.legacy !== true && formatList.some((f) => m.formats[f]));
|
|
10581
|
+
const availableModels = require_default_detection_model.mergeCustomModels(step.models, customByStep?.get(step.id) ?? []).filter((m) => m.legacy !== true && formatList.some((f) => m.formats[f]));
|
|
10287
10582
|
if (availableModels.length === 0) continue;
|
|
10288
10583
|
const slot = step.slot;
|
|
10289
10584
|
if (!slotMap.has(slot)) slotMap.set(slot, []);
|
|
@@ -10447,13 +10742,22 @@ function isDetailPlaneStep(addonId) {
|
|
|
10447
10742
|
}
|
|
10448
10743
|
}
|
|
10449
10744
|
/**
|
|
10450
|
-
*
|
|
10451
|
-
*
|
|
10452
|
-
*
|
|
10453
|
-
*
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10745
|
+
* What a dispatch runs after its per-step loads (D657).
|
|
10746
|
+
*
|
|
10747
|
+
* An ALL-OR-NOTHING call (a benchmark, a replay — D56 — a capacity ramp) fails
|
|
10748
|
+
* on any failure, naming every step it lost ({@link DispatchStepsNotLoadedError}):
|
|
10749
|
+
* it measures or reproduces exactly what it names. A camera's LIVE dispatch
|
|
10750
|
+
* runs the tree without the steps whose model did not load, and fails — with
|
|
10751
|
+
* the first root's own named error — only when no root survives: a dispatch
|
|
10752
|
+
* with no root has nothing to produce.
|
|
10753
|
+
*/
|
|
10754
|
+
function stepsSurvivingLoad(steps, failures, policy) {
|
|
10755
|
+
if (failures.length === 0) return steps;
|
|
10756
|
+
if (policy === "all-or-nothing") throw new DispatchStepsNotLoadedError(failures);
|
|
10757
|
+
const kept = withoutFailedSteps(steps, new Set(failures.map((f) => f.step)));
|
|
10758
|
+
if (kept.some((s) => s.enabled)) return kept;
|
|
10759
|
+
const roots = new Set(steps);
|
|
10760
|
+
throw (failures.find((f) => roots.has(f.step)) ?? failures[0]).error;
|
|
10457
10761
|
}
|
|
10458
10762
|
/**
|
|
10459
10763
|
* The union of model formats a step's catalog ships ANY build for — the
|