@camstack/addon-post-analysis 1.2.92 → 1.2.94
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/{dist-BfrtqGyh.js → dist-BdojoP7i.js} +346 -2
- package/dist/{dist-VnaTfwAL.mjs → dist-C-_TL-I1.mjs} +329 -3
- package/dist/embedding-encoder/index.js +171 -4
- package/dist/embedding-encoder/index.mjs +171 -4
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BIDGLXRs.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CeFPqm3h.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-CpWvyhen.mjs → hostInit-BExsGl4h.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +51 -2
- package/dist/pipeline-analytics/index.mjs +51 -2
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +3 -2
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { O as PoolMemoryWatchdog, Q as embeddingEncoderCapability, dt as resolvePoolMemoryPolicy, ot as parseProcStatus, tt as hfModelUrl, yt as BaseAddon } from "../dist-C-_TL-I1.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import "node:fs";
|
|
5
5
|
import * as path$1 from "node:path";
|
|
6
6
|
import "node:path";
|
|
7
7
|
import "node:crypto";
|
|
8
|
+
import { execFile, spawn } from "node:child_process";
|
|
8
9
|
import { promisify } from "node:util";
|
|
9
10
|
import { brotliCompress, gzip } from "node:zlib";
|
|
10
|
-
import { spawn } from "node:child_process";
|
|
11
11
|
//#region \0rolldown/runtime.js
|
|
12
12
|
var __defProp = Object.defineProperty;
|
|
13
13
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -21,6 +21,53 @@ var __exportAll = (all, no_symbols) => {
|
|
|
21
21
|
};
|
|
22
22
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
23
23
|
//#endregion
|
|
24
|
+
//#region src/embedding-encoder/shared/process-memory.ts
|
|
25
|
+
/**
|
|
26
|
+
* Read a child process's memory footprint for the pool memory watchdog.
|
|
27
|
+
*
|
|
28
|
+
* Deliberate twin of `packages/addon-pipeline/src/shared/process-memory.ts`:
|
|
29
|
+
* addons must not import each other, and `@camstack/system` stays EXTERNAL in
|
|
30
|
+
* addon bundles (a symbol added there would be missing at runtime until the
|
|
31
|
+
* next framework train). The pure parse (`parseProcStatus`) and the whole
|
|
32
|
+
* watchdog policy live once in `@camstack/types` (inlined at build time);
|
|
33
|
+
* only this thin `fs`/`ps` read is duplicated.
|
|
34
|
+
*
|
|
35
|
+
* Linux (the fleet): `/proc/<pid>/status` — RSS, VmSize, VmHWM (peak RSS ≈
|
|
36
|
+
* getrusage maxrss), VmSwap, Threads in one read. macOS (dev): `ps -o rss=`
|
|
37
|
+
* gives RSS alone. Returns null when the pid is gone — the CALLER logs the
|
|
38
|
+
* dropped sample.
|
|
39
|
+
*/
|
|
40
|
+
var IS_LINUX = process.platform === "linux";
|
|
41
|
+
async function readProcessMemory(pid) {
|
|
42
|
+
if (IS_LINUX) try {
|
|
43
|
+
return parseProcStatus(await fs.promises.readFile(`/proc/${pid}/status`, "utf8"));
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return readViaPs(pid);
|
|
48
|
+
}
|
|
49
|
+
function readViaPs(pid) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
execFile("ps", [
|
|
52
|
+
"-o",
|
|
53
|
+
"rss=",
|
|
54
|
+
"-p",
|
|
55
|
+
String(pid)
|
|
56
|
+
], { timeout: 2e3 }, (err, stdout) => {
|
|
57
|
+
if (err) return resolve(null);
|
|
58
|
+
const kb = Number(stdout.trim());
|
|
59
|
+
if (!Number.isFinite(kb) || kb <= 0) return resolve(null);
|
|
60
|
+
resolve({
|
|
61
|
+
rssBytes: kb * 1024,
|
|
62
|
+
vmBytes: 0,
|
|
63
|
+
hwmBytes: 0,
|
|
64
|
+
swapBytes: 0,
|
|
65
|
+
threads: 0
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
24
71
|
//#region ../system/dist/file-data-plane-CuE_hBli.mjs
|
|
25
72
|
function isNonEmptyFile(filePath) {
|
|
26
73
|
return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
|
|
@@ -507,8 +554,20 @@ var PythonRawTensorEngine = class {
|
|
|
507
554
|
if (ready.length !== 1 || ready[0] !== 1) throw new Error("PythonRawTensorEngine: unexpected ready frame");
|
|
508
555
|
this.log.info("ONNX raw-tensor engine ready (embedded Python)", { meta: { modelPath: this.modelPath } });
|
|
509
556
|
}
|
|
557
|
+
/** Native pid of the Python subprocess — sampled by the pool memory
|
|
558
|
+
* watchdog (`/proc/<pid>/status`). */
|
|
559
|
+
getPid() {
|
|
560
|
+
return this.process?.pid ?? null;
|
|
561
|
+
}
|
|
562
|
+
/** Inference requests served since spawn — the denominator that separates
|
|
563
|
+
* "RSS grew per unit of work" (leak) from "RSS grew because it worked". */
|
|
564
|
+
getRequestCount() {
|
|
565
|
+
return this.requestCount;
|
|
566
|
+
}
|
|
567
|
+
requestCount = 0;
|
|
510
568
|
async run(input, inputShape) {
|
|
511
569
|
if (!this.process?.stdin) throw new Error("PythonRawTensorEngine: not initialized — call initialize() first");
|
|
570
|
+
this.requestCount++;
|
|
512
571
|
const ndims = inputShape.length;
|
|
513
572
|
const meta = Buffer.allocUnsafe(1 + ndims * 4);
|
|
514
573
|
meta.writeUInt8(ndims, 0);
|
|
@@ -633,9 +692,21 @@ var PythonTextEncoderEngine = class {
|
|
|
633
692
|
tokenizerPath: this.tokenizerPath
|
|
634
693
|
} });
|
|
635
694
|
}
|
|
695
|
+
/** Native pid of the Python subprocess — sampled by the pool memory
|
|
696
|
+
* watchdog (`/proc/<pid>/status`). */
|
|
697
|
+
getPid() {
|
|
698
|
+
return this.process?.pid ?? null;
|
|
699
|
+
}
|
|
700
|
+
/** Encode requests served since spawn — the denominator that separates
|
|
701
|
+
* "RSS grew per unit of work" (leak) from "RSS grew because it worked". */
|
|
702
|
+
getRequestCount() {
|
|
703
|
+
return this.requestCount;
|
|
704
|
+
}
|
|
705
|
+
requestCount = 0;
|
|
636
706
|
/** Tokenize + encode `text` into the model embedding (float32). */
|
|
637
707
|
async encode(text) {
|
|
638
708
|
if (!this.process?.stdin) throw new Error("PythonTextEncoderEngine: not initialized — call initialize() first");
|
|
709
|
+
this.requestCount++;
|
|
639
710
|
const payload = Buffer.from(text, "utf-8");
|
|
640
711
|
const lenBuf = Buffer.allocUnsafe(4);
|
|
641
712
|
lenBuf.writeUInt32LE(payload.length, 0);
|
|
@@ -761,6 +832,22 @@ var EmbeddingEncoderAddon = class extends BaseAddon {
|
|
|
761
832
|
imageRawEngine = null;
|
|
762
833
|
textEngine = null;
|
|
763
834
|
models = null;
|
|
835
|
+
/**
|
|
836
|
+
* RSS bound + periodic memory telemetry for the two Python engines
|
|
837
|
+
* (`raw_tensor_inference.py` — one of the four leaking pools in the
|
|
838
|
+
* 2026-08-18 OOM audit — and `text_encoder_inference.py`). Shared
|
|
839
|
+
* watchdog from `@camstack/types`; a restart here is dispose + null, and
|
|
840
|
+
* the next encode lazily re-resolves the engine (the exact recovery an
|
|
841
|
+
* operator model change already uses). Started in `onInitialize`,
|
|
842
|
+
* stopped in `onShutdown`.
|
|
843
|
+
*/
|
|
844
|
+
memoryGuard = null;
|
|
845
|
+
/** Single-flight guards for the lazy engine builds — a watchdog restart
|
|
846
|
+
* followed by two concurrent encodes must not spawn the engine twice
|
|
847
|
+
* (the same bug detection-pipeline fixed three times; see its
|
|
848
|
+
* `engineFactoryInflight`). */
|
|
849
|
+
imageEngineInflight = null;
|
|
850
|
+
textEngineInflight = null;
|
|
764
851
|
constructor() {
|
|
765
852
|
super({
|
|
766
853
|
modelId: DEFAULT_CLIP_MODEL,
|
|
@@ -771,11 +858,75 @@ var EmbeddingEncoderAddon = class extends BaseAddon {
|
|
|
771
858
|
async onInitialize() {
|
|
772
859
|
const modelsDir = await this.resolveModelsDir();
|
|
773
860
|
this.models = new ModelDownloadService(modelsDir, [...CLIP_IMAGE_MODELS, ...CLIP_TEXT_MODELS]);
|
|
861
|
+
this.memoryGuard = new PoolMemoryWatchdog({
|
|
862
|
+
policy: resolvePoolMemoryPolicy(process.env),
|
|
863
|
+
log: this.ctx.logger.child("pool-memory"),
|
|
864
|
+
sample: () => this.sampleEngineMemory(),
|
|
865
|
+
restart: (key) => this.restartEngineForMemory(key)
|
|
866
|
+
});
|
|
867
|
+
this.memoryGuard.start();
|
|
774
868
|
return [{
|
|
775
869
|
capability: embeddingEncoderCapability,
|
|
776
870
|
provider: this
|
|
777
871
|
}];
|
|
778
872
|
}
|
|
873
|
+
/** Watchdog sample: one telemetry row per live Python engine. A pid the
|
|
874
|
+
* read cannot see is logged, never silently skipped. */
|
|
875
|
+
async sampleEngineMemory() {
|
|
876
|
+
const engines = [{
|
|
877
|
+
key: "clip-image",
|
|
878
|
+
pid: this.imageRawEngine?.getPid() ?? null,
|
|
879
|
+
requests: this.imageRawEngine?.getRequestCount() ?? 0
|
|
880
|
+
}, {
|
|
881
|
+
key: "clip-text",
|
|
882
|
+
pid: this.textEngine?.getPid() ?? null,
|
|
883
|
+
requests: this.textEngine?.getRequestCount() ?? 0
|
|
884
|
+
}];
|
|
885
|
+
const out = [];
|
|
886
|
+
for (const engine of engines) {
|
|
887
|
+
if (engine.pid === null) continue;
|
|
888
|
+
const mem = await readProcessMemory(engine.pid);
|
|
889
|
+
if (!mem) {
|
|
890
|
+
this.ctx.logger.warn("embedding engine memory sample dropped", { meta: {
|
|
891
|
+
poolKey: engine.key,
|
|
892
|
+
pid: engine.pid
|
|
893
|
+
} });
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
const mb = (bytes) => Math.round(bytes / (1024 * 1024));
|
|
897
|
+
out.push({
|
|
898
|
+
key: engine.key,
|
|
899
|
+
pids: [engine.pid],
|
|
900
|
+
rssBytes: mem.rssBytes,
|
|
901
|
+
meta: {
|
|
902
|
+
modelId: this.config.modelId,
|
|
903
|
+
vmMb: mb(mem.vmBytes),
|
|
904
|
+
peakRssMb: mb(mem.hwmBytes),
|
|
905
|
+
swapMb: mb(mem.swapBytes),
|
|
906
|
+
threads: mem.threads,
|
|
907
|
+
requestsServed: engine.requests
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
return out;
|
|
912
|
+
}
|
|
913
|
+
/** Watchdog restart: dispose + null; the next encode re-resolves lazily
|
|
914
|
+
* through the single-flight ensure. An in-flight encode fails once — its
|
|
915
|
+
* callers are the retry-until-landed bulk passes (D58) and RPCs that
|
|
916
|
+
* surface errors, never silent loss. */
|
|
917
|
+
async restartEngineForMemory(key) {
|
|
918
|
+
if (key === "clip-image") {
|
|
919
|
+
const engine = this.imageRawEngine;
|
|
920
|
+
if (!engine) return;
|
|
921
|
+
this.imageRawEngine = null;
|
|
922
|
+
await engine.dispose();
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const engine = this.textEngine;
|
|
926
|
+
if (!engine) return;
|
|
927
|
+
this.textEngine = null;
|
|
928
|
+
await engine.dispose();
|
|
929
|
+
}
|
|
779
930
|
async encode(input) {
|
|
780
931
|
const { crop, width, height } = input;
|
|
781
932
|
await this.ensureImageEngine();
|
|
@@ -819,17 +970,31 @@ var EmbeddingEncoderAddon = class extends BaseAddon {
|
|
|
819
970
|
}
|
|
820
971
|
async ensureImageEngine() {
|
|
821
972
|
if (this.imageRawEngine) return;
|
|
973
|
+
if (this.imageEngineInflight) return this.imageEngineInflight;
|
|
822
974
|
const meta = getModelMeta(this.config.modelId);
|
|
823
975
|
const imageEntry = CLIP_IMAGE_MODELS.find((m) => m.id === meta.imageModelId);
|
|
824
976
|
if (!imageEntry) throw new Error(`EmbeddingEncoderAddon: unknown image model "${meta.imageModelId}"`);
|
|
825
|
-
|
|
977
|
+
const inflight = this.resolveForEntry(imageEntry, "image");
|
|
978
|
+
this.imageEngineInflight = inflight;
|
|
979
|
+
try {
|
|
980
|
+
await inflight;
|
|
981
|
+
} finally {
|
|
982
|
+
this.imageEngineInflight = null;
|
|
983
|
+
}
|
|
826
984
|
}
|
|
827
985
|
async ensureTextEngine() {
|
|
828
986
|
if (this.textEngine) return;
|
|
987
|
+
if (this.textEngineInflight) return this.textEngineInflight;
|
|
829
988
|
const meta = getModelMeta(this.config.modelId);
|
|
830
989
|
const textEntry = CLIP_TEXT_MODELS.find((m) => m.id === meta.textModelId);
|
|
831
990
|
if (!textEntry) throw new Error(`EmbeddingEncoderAddon: unknown text model "${meta.textModelId}"`);
|
|
832
|
-
|
|
991
|
+
const inflight = this.resolveForEntry(textEntry, "text");
|
|
992
|
+
this.textEngineInflight = inflight;
|
|
993
|
+
try {
|
|
994
|
+
await inflight;
|
|
995
|
+
} finally {
|
|
996
|
+
this.textEngineInflight = null;
|
|
997
|
+
}
|
|
833
998
|
}
|
|
834
999
|
async resolveForEntry(entry, target) {
|
|
835
1000
|
const engineLogger = this.ctx.logger.withTags({
|
|
@@ -855,6 +1020,8 @@ var EmbeddingEncoderAddon = class extends BaseAddon {
|
|
|
855
1020
|
this.textEngine = textEngine;
|
|
856
1021
|
}
|
|
857
1022
|
async onShutdown() {
|
|
1023
|
+
this.memoryGuard?.stop();
|
|
1024
|
+
this.memoryGuard = null;
|
|
858
1025
|
await this.imageRawEngine?.dispose();
|
|
859
1026
|
await this.textEngine?.dispose();
|
|
860
1027
|
}
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.24",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.88",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.60",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.2.
|
|
39
|
+
version: "1.2.88",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -45,7 +45,7 @@ async function r() {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"@camstack/sdk": {
|
|
48
|
-
version: "1.2.
|
|
48
|
+
version: "1.2.24",
|
|
49
49
|
scope: "default",
|
|
50
50
|
shareConfig: {
|
|
51
51
|
singleton: !0,
|
|
@@ -81,7 +81,7 @@ async function r() {
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"@camstack/ui-library": {
|
|
84
|
-
version: "1.2.
|
|
84
|
+
version: "1.2.60",
|
|
85
85
|
scope: "default",
|
|
86
86
|
shareConfig: {
|
|
87
87
|
singleton: !0,
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-BdojoP7i.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -8289,6 +8289,8 @@ var en_default = {
|
|
|
8289
8289
|
"system.node-offline.body.worker": "Worker {{nodeId}} is offline",
|
|
8290
8290
|
"system.node-inference-unavailable.title": "Node has no usable inference device",
|
|
8291
8291
|
"system.node-inference-unavailable.body": "Node {{nodeId}} can no longer run inference — every enabled device is unavailable ({{devices}}). It will not receive cameras until one returns.",
|
|
8292
|
+
"system.addon-crash-loop.title": "Addon stopped after repeated crashes",
|
|
8293
|
+
"system.addon-crash-loop.body": "The {{addons}} addon on node {{nodeId}} crashed {{count}} times in a row and will NOT be restarted. Everything it provides is offline until it is repaired.",
|
|
8292
8294
|
"system.detection-blind.title": "Camera detecting nothing",
|
|
8293
8295
|
"system.detection-blind.body": {
|
|
8294
8296
|
"one": "Camera {{deviceName}} has been analysed for {{minutes}} minute with no detection at all",
|
|
@@ -8501,6 +8503,8 @@ var it_default = {
|
|
|
8501
8503
|
"system.node-offline.body.worker": "Il worker {{nodeId}} è offline",
|
|
8502
8504
|
"system.node-inference-unavailable.title": "Nodo senza dispositivi di inferenza utilizzabili",
|
|
8503
8505
|
"system.node-inference-unavailable.body": "Il nodo {{nodeId}} non può più eseguire inferenza — tutti i dispositivi abilitati non sono disponibili ({{devices}}). Non riceverà camere finché uno non torna disponibile.",
|
|
8506
|
+
"system.addon-crash-loop.title": "Addon fermato dopo ripetuti crash",
|
|
8507
|
+
"system.addon-crash-loop.body": "L'addon {{addons}} sul nodo {{nodeId}} è andato in crash {{count}} volte di seguito e NON verrà riavviato. Tutto ciò che fornisce resta offline finché non viene riparato.",
|
|
8504
8508
|
"system.detection-blind.title": "Camera che non rileva nulla",
|
|
8505
8509
|
"system.detection-blind.body": {
|
|
8506
8510
|
"one": "La camera {{deviceName}} è analizzata da {{minutes}} minuto senza alcun rilevamento",
|
|
@@ -10273,6 +10277,27 @@ function incomingFromSystemEvent(event, lookup) {
|
|
|
10273
10277
|
nodeId
|
|
10274
10278
|
});
|
|
10275
10279
|
}
|
|
10280
|
+
case "addon.runner-failed": {
|
|
10281
|
+
const nodeId = stringField(data, "nodeId");
|
|
10282
|
+
const runnerId = stringField(data, "runnerId");
|
|
10283
|
+
const crashes = numberField(data, "crashesInWindow");
|
|
10284
|
+
if (nodeId === void 0 || runnerId === void 0 || crashes === void 0) return null;
|
|
10285
|
+
const ids = data["addonIds"];
|
|
10286
|
+
const addons = Array.isArray(ids) ? ids.filter((id) => typeof id === "string") : [];
|
|
10287
|
+
return systemSubject({
|
|
10288
|
+
facts,
|
|
10289
|
+
kind: "addon-crash-loop",
|
|
10290
|
+
subject: `addon-crash:${nodeId}:${runnerId}`,
|
|
10291
|
+
titleKey: "system.addon-crash-loop.title",
|
|
10292
|
+
bodyKey: "system.addon-crash-loop.body",
|
|
10293
|
+
textParams: {
|
|
10294
|
+
nodeId,
|
|
10295
|
+
addons: addons.length > 0 ? addons.join(", ") : runnerId,
|
|
10296
|
+
count: String(crashes)
|
|
10297
|
+
},
|
|
10298
|
+
nodeId
|
|
10299
|
+
});
|
|
10300
|
+
}
|
|
10276
10301
|
case "pipeline.detection-blind": {
|
|
10277
10302
|
const deviceId = numberField(data, "deviceId");
|
|
10278
10303
|
const blindForMs = numberField(data, "blindForMs");
|
|
@@ -21940,6 +21965,9 @@ function systemEventKeys(kind) {
|
|
|
21940
21965
|
}
|
|
21941
21966
|
/** The arm mode a synthetic alarm event stands in for — see {@link systemEventKeys}. */
|
|
21942
21967
|
var TEST_ALARM_MODE = "away";
|
|
21968
|
+
/** The runner a synthetic `addon-crash-loop` stands in for. A test fire has no
|
|
21969
|
+
* real crashed runner, and a blank one would render "The addon on node hub". */
|
|
21970
|
+
var TEST_CRASHED_RUNNER = "recorder";
|
|
21943
21971
|
/** The dedup subject the live intake stamps for this kind — same families. */
|
|
21944
21972
|
function systemEventSubject(kind, scope) {
|
|
21945
21973
|
if (kind === "device-disabled" || kind === "device-enabled") return `device-switch:${scope.deviceId ?? 0}`;
|
|
@@ -21948,6 +21976,7 @@ function systemEventSubject(kind, scope) {
|
|
|
21948
21976
|
if (kind === "node-online" || kind === "node-offline") return `node:${scope.nodeId ?? "hub"}`;
|
|
21949
21977
|
if (kind === "node-inference-unavailable") return `node-inference:${scope.nodeId ?? "hub"}`;
|
|
21950
21978
|
if (kind === "detection-blind") return `detection-blind:${scope.deviceId ?? 0}`;
|
|
21979
|
+
if (kind === "addon-crash-loop") return `addon-crash:${scope.nodeId ?? "hub"}:${TEST_CRASHED_RUNNER}`;
|
|
21951
21980
|
if (kind === "addon-update-available" || kind === "server-update-available") return `package:${scope.packageName ?? "@camstack/server"}`;
|
|
21952
21981
|
if (kind === "addon-updated") return `packages:addon-updated:${scope.packageName ?? "pkg"}`;
|
|
21953
21982
|
if (kind === "server-updated") return `server-updated:${scope.nodeId ?? "hub"}`;
|
|
@@ -22064,6 +22093,7 @@ function planRuleTest(input) {
|
|
|
22064
22093
|
seconds: "30",
|
|
22065
22094
|
devices: "cuda:0",
|
|
22066
22095
|
minutes: "15",
|
|
22096
|
+
addons: TEST_CRASHED_RUNNER,
|
|
22067
22097
|
list: "@camstack/addon-pipeline 1.0.0 (current 0.0.0)",
|
|
22068
22098
|
nodes: "hub",
|
|
22069
22099
|
fromVersion: "0.0.0",
|
|
@@ -30017,6 +30047,22 @@ function stillInstantMs(ranges) {
|
|
|
30017
30047
|
if (best === void 0) return void 0;
|
|
30018
30048
|
return Math.floor((best.startMs + best.endMs) / 2);
|
|
30019
30049
|
}
|
|
30050
|
+
/**
|
|
30051
|
+
* The distinct object classes attached to a visit, most frequent first,
|
|
30052
|
+
* capped at {@link MAX_CLIP_LABELS}. Empty when nothing classified overlapped
|
|
30053
|
+
* — the caller then omits the field so a motion/audio-only visit keeps its
|
|
30054
|
+
* kind label instead of carrying an empty array every consumer length-checks.
|
|
30055
|
+
*/
|
|
30056
|
+
function dominantLabels(attached) {
|
|
30057
|
+
const counts = /* @__PURE__ */ new Map();
|
|
30058
|
+
for (const a of attached) {
|
|
30059
|
+
if (a.kind !== "object") continue;
|
|
30060
|
+
const name = a.event.className;
|
|
30061
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
30062
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
30063
|
+
}
|
|
30064
|
+
return [...counts.entries()].toSorted((x, y) => y[1] - x[1]).slice(0, 3).map(([name]) => name);
|
|
30065
|
+
}
|
|
30020
30066
|
function visitsToClips(visits, events, pad, vouched) {
|
|
30021
30067
|
const tagged = [
|
|
30022
30068
|
...events.object.map((event) => ({
|
|
@@ -30037,6 +30083,7 @@ function visitsToClips(visits, events, pad, vouched) {
|
|
|
30037
30083
|
const kind = dominantKind(attached);
|
|
30038
30084
|
const withThumb = attached.find((a) => a.kind === kind && a.event.mediaUrl && vouched.has(a.event.id)) ?? attached.find((a) => a.event.mediaUrl && vouched.has(a.event.id));
|
|
30039
30085
|
const stillAtMs = stillInstantMs(visit.ranges);
|
|
30086
|
+
const labels = dominantLabels(attached);
|
|
30040
30087
|
return {
|
|
30041
30088
|
id: `analytics:${visit.startMs}:${visit.endMs}:${VISIT_EVENT_ID}`,
|
|
30042
30089
|
source: "analytics",
|
|
@@ -30049,7 +30096,8 @@ function visitsToClips(visits, events, pad, vouched) {
|
|
|
30049
30096
|
...stillAtMs !== void 0 ? { stillAtMs } : {},
|
|
30050
30097
|
eventIds: attached.slice(0, 24).map((a) => a.event.id),
|
|
30051
30098
|
eventCount: attached.length,
|
|
30052
|
-
holes: [...visit.holes]
|
|
30099
|
+
holes: [...visit.holes],
|
|
30100
|
+
...labels.length > 0 ? { labels } : {}
|
|
30053
30101
|
};
|
|
30054
30102
|
}).toSorted((a, b) => b.timeRange.startMs - a.timeRange.startMs);
|
|
30055
30103
|
}
|
|
@@ -46109,6 +46157,7 @@ var NC_SYSTEM_EVENT_CATEGORIES = [
|
|
|
46109
46157
|
require_dist.EventCategory.WorkerOffline,
|
|
46110
46158
|
require_dist.EventCategory.PipelineNodeInferenceUnavailable,
|
|
46111
46159
|
require_dist.EventCategory.PipelineDetectionBlind,
|
|
46160
|
+
require_dist.EventCategory.AddonRunnerFailed,
|
|
46112
46161
|
require_dist.EventCategory.UpdateAvailable,
|
|
46113
46162
|
require_dist.EventCategory.AddonUpdated,
|
|
46114
46163
|
require_dist.EventCategory.SystemRestartCompleted,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as encodeVectorBase64, A as RetrainStatusSchema, At as literal, B as TrackSourceSchema, C as NcSnoozeSchema, Ct as isDeviceScopedCap, D as OpsLogEntrySchema, Dt as array, E as NcTaxonomySchema, Et as _enum, F as SceneMonitorSchema, Ft as string, G as audioMetricsCapability, H as addonWidgetsSourceCapability, I as TIMELAPSE_DENSE_FLOOR_SEC, It as unknown, J as cosineSimilarity$1, K as audioModeOf, L as TimelapseRuleInputSchema, Lt as EventCategory, M as SCENE_DEFAULT_ANCHOR_THRESHOLD, Mt as object, N as SCENE_DEFAULT_UNCOVERED_POLICY, Nt as partialRecord, Ot as boolean, P as SCENE_DIVERGED, Pt as record, R as TimelapseRulePatchSchema, S as NcSnoozeInputSchema, St as hydrateSchema, T as NcSystemEventKindSchema, Tt as sleep, U as alarmPanelCapability, V as VISIT_MERGE_GAP_MS, W as assertTimelapseCadences, X as defineCustomActions, Y as customAction, Z as deriveRecordingMode, _ as NcRuleInputSchema, _t as zoneAnalyticsCapability, at as notificationRulesCapability, b as NcRuleTargetSchema, bt as DeviceType, c as EVENT_PAD_MS, ct as plateGalleryCapability, d as NC_ALARM_SYSTEM_EVENT_KINDS, et as faceGalleryCapability, f as NC_CONDITION_CATALOG, ft as sceneMonitorCapability, g as NcConditionDescriptorSchema, gt as videoclipsCapability, h as NC_TAXONOMY, ht as vectorDimFromBase64, i as DETECTION_MACRO_CLASSES, it as kebabToCamel, jt as number, k as RECORDING_EXPORT_MAX_READ_BYTES, kt as discriminatedUnion, l as LabelAttributionSchema, lt as readDeviceStateFrom, mt as systemEventFilterApplies, n as DEFAULT_EVENT_COLOR, nt as isDetectionMacroClass, o as DeclaredDevices, p as NC_DEFAULT_SNOOZE_MINUTES, pt as subKindsOf, q as buildEventKindDescriptor, rt as isScheduleActive, s as EVENT_KIND_BY_CAP, st as pipelineAnalyticsCapability, t as BaseDevice, u as MACRO_LABELS, ut as readTimelapseGeneratedAt, v as NcRulePatchSchema, vt as errMsg, w as NcSnoozeSuppressedSchema, wt as nodePin, x as NcScheduleSchema, xt as createEvent, y as NcRuleSchema, yt as BaseAddon, z as TimelapseRuleSchema } from "../dist-C-_TL-I1.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -8282,6 +8282,8 @@ var en_default = {
|
|
|
8282
8282
|
"system.node-offline.body.worker": "Worker {{nodeId}} is offline",
|
|
8283
8283
|
"system.node-inference-unavailable.title": "Node has no usable inference device",
|
|
8284
8284
|
"system.node-inference-unavailable.body": "Node {{nodeId}} can no longer run inference — every enabled device is unavailable ({{devices}}). It will not receive cameras until one returns.",
|
|
8285
|
+
"system.addon-crash-loop.title": "Addon stopped after repeated crashes",
|
|
8286
|
+
"system.addon-crash-loop.body": "The {{addons}} addon on node {{nodeId}} crashed {{count}} times in a row and will NOT be restarted. Everything it provides is offline until it is repaired.",
|
|
8285
8287
|
"system.detection-blind.title": "Camera detecting nothing",
|
|
8286
8288
|
"system.detection-blind.body": {
|
|
8287
8289
|
"one": "Camera {{deviceName}} has been analysed for {{minutes}} minute with no detection at all",
|
|
@@ -8494,6 +8496,8 @@ var it_default = {
|
|
|
8494
8496
|
"system.node-offline.body.worker": "Il worker {{nodeId}} è offline",
|
|
8495
8497
|
"system.node-inference-unavailable.title": "Nodo senza dispositivi di inferenza utilizzabili",
|
|
8496
8498
|
"system.node-inference-unavailable.body": "Il nodo {{nodeId}} non può più eseguire inferenza — tutti i dispositivi abilitati non sono disponibili ({{devices}}). Non riceverà camere finché uno non torna disponibile.",
|
|
8499
|
+
"system.addon-crash-loop.title": "Addon fermato dopo ripetuti crash",
|
|
8500
|
+
"system.addon-crash-loop.body": "L'addon {{addons}} sul nodo {{nodeId}} è andato in crash {{count}} volte di seguito e NON verrà riavviato. Tutto ciò che fornisce resta offline finché non viene riparato.",
|
|
8497
8501
|
"system.detection-blind.title": "Camera che non rileva nulla",
|
|
8498
8502
|
"system.detection-blind.body": {
|
|
8499
8503
|
"one": "La camera {{deviceName}} è analizzata da {{minutes}} minuto senza alcun rilevamento",
|
|
@@ -10266,6 +10270,27 @@ function incomingFromSystemEvent(event, lookup) {
|
|
|
10266
10270
|
nodeId
|
|
10267
10271
|
});
|
|
10268
10272
|
}
|
|
10273
|
+
case "addon.runner-failed": {
|
|
10274
|
+
const nodeId = stringField(data, "nodeId");
|
|
10275
|
+
const runnerId = stringField(data, "runnerId");
|
|
10276
|
+
const crashes = numberField(data, "crashesInWindow");
|
|
10277
|
+
if (nodeId === void 0 || runnerId === void 0 || crashes === void 0) return null;
|
|
10278
|
+
const ids = data["addonIds"];
|
|
10279
|
+
const addons = Array.isArray(ids) ? ids.filter((id) => typeof id === "string") : [];
|
|
10280
|
+
return systemSubject({
|
|
10281
|
+
facts,
|
|
10282
|
+
kind: "addon-crash-loop",
|
|
10283
|
+
subject: `addon-crash:${nodeId}:${runnerId}`,
|
|
10284
|
+
titleKey: "system.addon-crash-loop.title",
|
|
10285
|
+
bodyKey: "system.addon-crash-loop.body",
|
|
10286
|
+
textParams: {
|
|
10287
|
+
nodeId,
|
|
10288
|
+
addons: addons.length > 0 ? addons.join(", ") : runnerId,
|
|
10289
|
+
count: String(crashes)
|
|
10290
|
+
},
|
|
10291
|
+
nodeId
|
|
10292
|
+
});
|
|
10293
|
+
}
|
|
10269
10294
|
case "pipeline.detection-blind": {
|
|
10270
10295
|
const deviceId = numberField(data, "deviceId");
|
|
10271
10296
|
const blindForMs = numberField(data, "blindForMs");
|
|
@@ -21918,6 +21943,9 @@ function systemEventKeys(kind) {
|
|
|
21918
21943
|
}
|
|
21919
21944
|
/** The arm mode a synthetic alarm event stands in for — see {@link systemEventKeys}. */
|
|
21920
21945
|
var TEST_ALARM_MODE = "away";
|
|
21946
|
+
/** The runner a synthetic `addon-crash-loop` stands in for. A test fire has no
|
|
21947
|
+
* real crashed runner, and a blank one would render "The addon on node hub". */
|
|
21948
|
+
var TEST_CRASHED_RUNNER = "recorder";
|
|
21921
21949
|
/** The dedup subject the live intake stamps for this kind — same families. */
|
|
21922
21950
|
function systemEventSubject(kind, scope) {
|
|
21923
21951
|
if (kind === "device-disabled" || kind === "device-enabled") return `device-switch:${scope.deviceId ?? 0}`;
|
|
@@ -21926,6 +21954,7 @@ function systemEventSubject(kind, scope) {
|
|
|
21926
21954
|
if (kind === "node-online" || kind === "node-offline") return `node:${scope.nodeId ?? "hub"}`;
|
|
21927
21955
|
if (kind === "node-inference-unavailable") return `node-inference:${scope.nodeId ?? "hub"}`;
|
|
21928
21956
|
if (kind === "detection-blind") return `detection-blind:${scope.deviceId ?? 0}`;
|
|
21957
|
+
if (kind === "addon-crash-loop") return `addon-crash:${scope.nodeId ?? "hub"}:${TEST_CRASHED_RUNNER}`;
|
|
21929
21958
|
if (kind === "addon-update-available" || kind === "server-update-available") return `package:${scope.packageName ?? "@camstack/server"}`;
|
|
21930
21959
|
if (kind === "addon-updated") return `packages:addon-updated:${scope.packageName ?? "pkg"}`;
|
|
21931
21960
|
if (kind === "server-updated") return `server-updated:${scope.nodeId ?? "hub"}`;
|
|
@@ -22042,6 +22071,7 @@ function planRuleTest(input) {
|
|
|
22042
22071
|
seconds: "30",
|
|
22043
22072
|
devices: "cuda:0",
|
|
22044
22073
|
minutes: "15",
|
|
22074
|
+
addons: TEST_CRASHED_RUNNER,
|
|
22045
22075
|
list: "@camstack/addon-pipeline 1.0.0 (current 0.0.0)",
|
|
22046
22076
|
nodes: "hub",
|
|
22047
22077
|
fromVersion: "0.0.0",
|
|
@@ -29958,6 +29988,22 @@ function stillInstantMs(ranges) {
|
|
|
29958
29988
|
if (best === void 0) return void 0;
|
|
29959
29989
|
return Math.floor((best.startMs + best.endMs) / 2);
|
|
29960
29990
|
}
|
|
29991
|
+
/**
|
|
29992
|
+
* The distinct object classes attached to a visit, most frequent first,
|
|
29993
|
+
* capped at {@link MAX_CLIP_LABELS}. Empty when nothing classified overlapped
|
|
29994
|
+
* — the caller then omits the field so a motion/audio-only visit keeps its
|
|
29995
|
+
* kind label instead of carrying an empty array every consumer length-checks.
|
|
29996
|
+
*/
|
|
29997
|
+
function dominantLabels(attached) {
|
|
29998
|
+
const counts = /* @__PURE__ */ new Map();
|
|
29999
|
+
for (const a of attached) {
|
|
30000
|
+
if (a.kind !== "object") continue;
|
|
30001
|
+
const name = a.event.className;
|
|
30002
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
30003
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
30004
|
+
}
|
|
30005
|
+
return [...counts.entries()].toSorted((x, y) => y[1] - x[1]).slice(0, 3).map(([name]) => name);
|
|
30006
|
+
}
|
|
29961
30007
|
function visitsToClips(visits, events, pad, vouched) {
|
|
29962
30008
|
const tagged = [
|
|
29963
30009
|
...events.object.map((event) => ({
|
|
@@ -29978,6 +30024,7 @@ function visitsToClips(visits, events, pad, vouched) {
|
|
|
29978
30024
|
const kind = dominantKind(attached);
|
|
29979
30025
|
const withThumb = attached.find((a) => a.kind === kind && a.event.mediaUrl && vouched.has(a.event.id)) ?? attached.find((a) => a.event.mediaUrl && vouched.has(a.event.id));
|
|
29980
30026
|
const stillAtMs = stillInstantMs(visit.ranges);
|
|
30027
|
+
const labels = dominantLabels(attached);
|
|
29981
30028
|
return {
|
|
29982
30029
|
id: `analytics:${visit.startMs}:${visit.endMs}:${VISIT_EVENT_ID}`,
|
|
29983
30030
|
source: "analytics",
|
|
@@ -29990,7 +30037,8 @@ function visitsToClips(visits, events, pad, vouched) {
|
|
|
29990
30037
|
...stillAtMs !== void 0 ? { stillAtMs } : {},
|
|
29991
30038
|
eventIds: attached.slice(0, 24).map((a) => a.event.id),
|
|
29992
30039
|
eventCount: attached.length,
|
|
29993
|
-
holes: [...visit.holes]
|
|
30040
|
+
holes: [...visit.holes],
|
|
30041
|
+
...labels.length > 0 ? { labels } : {}
|
|
29994
30042
|
};
|
|
29995
30043
|
}).toSorted((a, b) => b.timeRange.startMs - a.timeRange.startMs);
|
|
29996
30044
|
}
|
|
@@ -46049,6 +46097,7 @@ var NC_SYSTEM_EVENT_CATEGORIES = [
|
|
|
46049
46097
|
EventCategory.WorkerOffline,
|
|
46050
46098
|
EventCategory.PipelineNodeInferenceUnavailable,
|
|
46051
46099
|
EventCategory.PipelineDetectionBlind,
|
|
46100
|
+
EventCategory.AddonRunnerFailed,
|
|
46052
46101
|
EventCategory.UpdateAvailable,
|
|
46053
46102
|
EventCategory.AddonUpdated,
|
|
46054
46103
|
EventCategory.SystemRestartCompleted,
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CeFPqm3h.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-post-analysis",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.94",
|
|
4
4
|
"description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -66,7 +66,8 @@
|
|
|
66
66
|
"description": "Zone analytics + audio metrics aggregator. Consumes pipeline events to surface per-camera zone counters and audio-level histograms.",
|
|
67
67
|
"entry": "./dist/pipeline-analytics/index.js",
|
|
68
68
|
"execution": {
|
|
69
|
-
"placement": "any-node"
|
|
69
|
+
"placement": "any-node",
|
|
70
|
+
"heapProfile": "heavy"
|
|
70
71
|
},
|
|
71
72
|
"protected": true,
|
|
72
73
|
"capabilities": [
|