@trillboards/edge-sdk 0.2.106 → 0.2.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deploy/detector/bench/litert-twin-parity.json +27 -1
- package/deploy/detector/bench/regrade_control_selection.py +371 -0
- package/deploy/detector/bench/venue_adjudication.py +959 -0
- package/deploy/detector/bench/verify_litert_twin_parity.py +34 -10
- package/deploy/detector/tests/test_regrade_control_selection.py +120 -0
- package/deploy/detector/tests/test_venue_adjudication_grades.py +83 -0
- package/deploy/detector/trillboards_detector/labels.py +54 -1
- package/dist/EdgeAgent.js +1 -1
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.js +1 -1
- package/package.json +1 -1
|
@@ -143,22 +143,44 @@ def main() -> int:
|
|
|
143
143
|
out_details = interp.get_output_details()
|
|
144
144
|
shape = tuple(int(v) for v in in_detail["shape"])
|
|
145
145
|
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
# READ THE INPUT LAYOUT OFF THE GRAPH, the same rule this file already applies
|
|
147
|
+
# to outputs. It used to assert NCHW and refuse anything else, on the reasoning
|
|
148
|
+
# that "the LiteRT export of an ultralytics detect model is NOT NHWC". That is
|
|
149
|
+
# true of the toolchain the 21 committed twins were exported with and it is NOT
|
|
150
|
+
# a property of LiteRT: `onnx2tf` transposes to channels-last by design, and an
|
|
151
|
+
# export made with ultralytics 8.3.145 declares [1,640,640,3]. The assertion
|
|
152
|
+
# fired on a real, correct twin (gh_fall_tonlong, 2026-08-26).
|
|
153
|
+
#
|
|
154
|
+
# The warning it carried is still right and is why this branches on the DECLARED
|
|
155
|
+
# shape rather than on a flag: transposing "because LiteRT is channels-last"
|
|
156
|
+
# feeds a rotated image to a model that wanted NCHW, and every residual then
|
|
157
|
+
# looks like export noise. Both runtimes must receive the SAME IMAGE, each in
|
|
158
|
+
# the layout its own graph declares — never the same buffer.
|
|
159
|
+
if len(shape) != 4:
|
|
160
|
+
raise ParityError(f"expected a 4-D [1,3,H,W] or [1,H,W,3] input, got {shape}")
|
|
161
|
+
if shape[1] == 3 and shape[3] != 3:
|
|
162
|
+
litert_layout, imgsz = "nchw", shape[2]
|
|
163
|
+
elif shape[3] == 3 and shape[1] != 3:
|
|
164
|
+
litert_layout, imgsz = "nhwc", shape[1]
|
|
165
|
+
elif shape[1] == 3 and shape[3] == 3:
|
|
166
|
+
# [1,3,H,3] is ambiguous and no real detector input is 3 px wide.
|
|
167
|
+
raise ParityError(f"input {shape} is ambiguous between NCHW and NHWC")
|
|
168
|
+
else:
|
|
169
|
+
raise ParityError(f"no channel axis of size 3 in {shape}")
|
|
152
170
|
|
|
153
171
|
if args.image:
|
|
154
172
|
from PIL import Image
|
|
155
173
|
|
|
156
174
|
pixels = np.asarray(Image.open(args.image).convert("RGB").resize((imgsz, imgsz)))
|
|
157
|
-
|
|
175
|
+
nchw = (pixels.astype(np.float32) / 255.0).transpose(2, 0, 1)[None]
|
|
158
176
|
else:
|
|
159
|
-
|
|
177
|
+
nchw = np.random.default_rng(args.seed).random((1, 3, imgsz, imgsz)).astype(np.float32)
|
|
178
|
+
|
|
179
|
+
# ONNX always gets NCHW; LiteRT gets whatever it declared, from the same pixels.
|
|
180
|
+
x = nchw
|
|
181
|
+
x_litert = nchw if litert_layout == "nchw" else nchw.transpose(0, 2, 3, 1).copy()
|
|
160
182
|
|
|
161
|
-
interp.set_tensor(in_detail["index"],
|
|
183
|
+
interp.set_tensor(in_detail["index"], x_litert)
|
|
162
184
|
interp.invoke()
|
|
163
185
|
litert_out = interp.get_tensor(out_details[0]["index"])
|
|
164
186
|
|
|
@@ -173,11 +195,13 @@ def main() -> int:
|
|
|
173
195
|
result["reason"] = why
|
|
174
196
|
result["onnx"] = Path(args.onnx).name
|
|
175
197
|
result["litert"] = Path(args.litert).name
|
|
198
|
+
result["litert_input_layout"] = litert_layout
|
|
176
199
|
|
|
177
200
|
if args.json:
|
|
178
201
|
print(json.dumps(result, indent=2))
|
|
179
202
|
else:
|
|
180
|
-
print(f"{'PASS' if ok else 'FAIL'} {result['onnx']} layout={layout}
|
|
203
|
+
print(f"{'PASS' if ok else 'FAIL'} {result['onnx']} layout={layout} "
|
|
204
|
+
f"litert_in={litert_layout} {why}")
|
|
181
205
|
print(f" box x{result['box_scale_applied']:g}: {result['box_residual_px']:.4g} px"
|
|
182
206
|
f" (unscaled {result['box_residual_unscaled']:.4g})")
|
|
183
207
|
print(f" cls : {result['cls_residual']:.4g}")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""A regrade that writes `UNRESOLVED` must reproduce it, not harden it.
|
|
2
|
+
|
|
3
|
+
`regrade_control_selection.py --apply` writes its verdict back into the
|
|
4
|
+
adjudication it read. That makes the tool its own input, and the branch that
|
|
5
|
+
emits `UNRESOLVED PENDING REMEASURE` was originally gated on
|
|
6
|
+
`prior == "PUBLISH"` — so a second run saw `prior == "UNRESOLVED …"`, took the
|
|
7
|
+
gate as false, and replaced a careful "we do not know" with a definitive
|
|
8
|
+
`NO MODEL CLEARS BAR`, on exactly the same evidence the first run had refused to
|
|
9
|
+
conclude from. Nothing downstream would have caught it: the `positive-control-grade`
|
|
10
|
+
guard audits PUBLISH rows only, and both states are non-PUBLISH.
|
|
11
|
+
|
|
12
|
+
The other half is the mirror error. A prior that was ALREADY a measured
|
|
13
|
+
`NO MODEL CLEARS BAR` came out of a run that scored more controls than this tool
|
|
14
|
+
holds for an elided row, so softening it to `UNRESOLVED` would be the tool
|
|
15
|
+
asserting its own blindness over a measurement.
|
|
16
|
+
|
|
17
|
+
STDLIB ONLY — the detector CI lane runs `python3 -m unittest discover` with no
|
|
18
|
+
pip install, and `regrade_control_selection.py` imports nothing but the stdlib
|
|
19
|
+
for that reason.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import pathlib
|
|
25
|
+
import sys
|
|
26
|
+
import unittest
|
|
27
|
+
|
|
28
|
+
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / "bench"))
|
|
29
|
+
|
|
30
|
+
from regrade_control_selection import ( # noqa: E402
|
|
31
|
+
PRESERVE_ON_BLOCKING,
|
|
32
|
+
UNRESOLVED,
|
|
33
|
+
rateable_controls,
|
|
34
|
+
regrade_capability,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
CTLQ = {
|
|
38
|
+
"big": {"grade": "good"},
|
|
39
|
+
"tiny": {"grade": "good_small_n"},
|
|
40
|
+
"junk": {"grade": "DISCARDED"},
|
|
41
|
+
}
|
|
42
|
+
BAR = {"recall_retention": 0.5, "off_target_mean": 0.1}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cap(prior, *, rateable_rate=0.4, with_elided=True):
|
|
46
|
+
"""One capability whose rateable control FAILS and whose 2nd row is elided."""
|
|
47
|
+
cands = [{
|
|
48
|
+
"model": "m1", "class": "fire", "quiet_t": 0.25,
|
|
49
|
+
"off_target_mean": 0.0, "n_valid_negatives": 100,
|
|
50
|
+
"by_bucket": {}, "best_control": "big", "control_n": 9,
|
|
51
|
+
"control_rate_025": rateable_rate, "control_rate_at_quiet_t": rateable_rate,
|
|
52
|
+
"per_control": {
|
|
53
|
+
"big": {"n": 9, "rate_025": rateable_rate, "rate_at_quiet_t": rateable_rate},
|
|
54
|
+
"tiny": {"n": 5, "rate_025": 0.9, "rate_at_quiet_t": 0.9},
|
|
55
|
+
},
|
|
56
|
+
}]
|
|
57
|
+
if with_elided:
|
|
58
|
+
cands.append({
|
|
59
|
+
"model": "m2", "class": "fire", "quiet_t": 0.25,
|
|
60
|
+
"off_target_mean": 0.0, "n_valid_negatives": 100,
|
|
61
|
+
"by_bucket": {"_elided": "summary row"},
|
|
62
|
+
"best_control": "tiny", "control_n": 5,
|
|
63
|
+
"control_rate_025": 0.9, "control_rate_at_quiet_t": 0.9,
|
|
64
|
+
"per_control": {},
|
|
65
|
+
})
|
|
66
|
+
return {"capability": "fire",
|
|
67
|
+
"decision": {"decision": prior, "control": "tiny", "control_n": 5},
|
|
68
|
+
"candidates": cands}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TestRegradeControlSelection(unittest.TestCase):
|
|
72
|
+
|
|
73
|
+
def test_liveness_only_and_unusable_grades_cannot_rate_a_class(self):
|
|
74
|
+
per = {"big": {"n": 9}, "tiny": {"n": 5}, "junk": {"n": 26}}
|
|
75
|
+
self.assertEqual(sorted(rateable_controls(per, CTLQ)), ["big"])
|
|
76
|
+
|
|
77
|
+
def test_a_publish_on_a_liveness_only_set_is_withdrawn_not_failed(self):
|
|
78
|
+
out = regrade_capability(_cap("PUBLISH"), CTLQ, BAR)
|
|
79
|
+
self.assertEqual(out["regraded_decision"], UNRESOLVED)
|
|
80
|
+
self.assertTrue(out["candidates_unknown"], "the elided row must be named")
|
|
81
|
+
self.assertTrue(out["changed"])
|
|
82
|
+
|
|
83
|
+
def test_rerunning_over_its_own_output_reproduces_unresolved(self):
|
|
84
|
+
first = regrade_capability(_cap("PUBLISH"), CTLQ, BAR)
|
|
85
|
+
second = regrade_capability(_cap(first["regraded_decision"]), CTLQ, BAR)
|
|
86
|
+
self.assertEqual(
|
|
87
|
+
second["regraded_decision"], UNRESOLVED,
|
|
88
|
+
"a second --apply hardened an unknown into a definitive verdict")
|
|
89
|
+
self.assertFalse(second["changed"], "a settled UNRESOLVED must not read as changed")
|
|
90
|
+
|
|
91
|
+
def test_a_measured_failure_is_left_alone(self):
|
|
92
|
+
out = regrade_capability(_cap("NO MODEL CLEARS BAR"), CTLQ, BAR)
|
|
93
|
+
self.assertEqual(out["regraded_decision"], "NO MODEL CLEARS BAR")
|
|
94
|
+
self.assertFalse(out["changed"])
|
|
95
|
+
|
|
96
|
+
def test_with_no_elided_row_the_verdict_is_definitive(self):
|
|
97
|
+
out = regrade_capability(_cap("PUBLISH", with_elided=False), CTLQ, BAR)
|
|
98
|
+
self.assertEqual(out["regraded_decision"], "NO MODEL CLEARS BAR")
|
|
99
|
+
self.assertEqual(out["status"], "proven")
|
|
100
|
+
|
|
101
|
+
def test_a_candidate_that_clears_on_the_rateable_set_still_publishes(self):
|
|
102
|
+
out = regrade_capability(_cap("PUBLISH", rateable_rate=0.8), CTLQ, BAR)
|
|
103
|
+
self.assertEqual(out["regraded_decision"], "PUBLISH")
|
|
104
|
+
self.assertEqual(out["winner"]["control"], "big")
|
|
105
|
+
self.assertEqual(out["winner"]["control_n"], 9)
|
|
106
|
+
|
|
107
|
+
def test_the_preserve_set_names_both_states_and_nothing_else(self):
|
|
108
|
+
self.assertEqual(sorted(PRESERVE_ON_BLOCKING), sorted(["PUBLISH", UNRESOLVED]))
|
|
109
|
+
|
|
110
|
+
def test_branches_resolved_before_a_control_is_read_are_untouched(self):
|
|
111
|
+
for prior in ("NO CANDIDATE", "NOT SCREENABLE HERE"):
|
|
112
|
+
out = regrade_capability(
|
|
113
|
+
{"capability": "x", "decision": {"decision": prior}, "candidates": []},
|
|
114
|
+
CTLQ, BAR)
|
|
115
|
+
self.assertEqual(out["regraded_decision"], prior)
|
|
116
|
+
self.assertEqual(out["status"], "not_affected")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
unittest.main()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""An ungraded control must stop the run, not quietly become eligible to decide.
|
|
2
|
+
|
|
3
|
+
`liveness_only` and `unusable` are both built by MEMBERSHIP of a grade set, so a
|
|
4
|
+
control with no grade at all falls through both and is treated as able to rate a
|
|
5
|
+
class — the flattering direction. The committed-artifact guard catches it
|
|
6
|
+
downstream, but by then a wrong number exists and may have been quoted.
|
|
7
|
+
|
|
8
|
+
The same asymmetry is why `--control-quality` defaults to the COMPLETE sorted set
|
|
9
|
+
rather than to one file: the grades live in dated files (the 2026-08-25 record is
|
|
10
|
+
pinned by the score cards that cite it), and a run reading a subset would give the
|
|
11
|
+
sets graded in the omitted file exactly that benefit of the doubt.
|
|
12
|
+
|
|
13
|
+
STDLIB ONLY — this lane runs `python3 -m unittest discover` with no pip install,
|
|
14
|
+
and `venue_adjudication.py` imports numpy at module scope, so it is loaded here
|
|
15
|
+
with `importlib` under a guard and the whole module is SKIPPED if numpy is absent
|
|
16
|
+
rather than erroring the lane during discovery.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import importlib.util
|
|
22
|
+
import os
|
|
23
|
+
import pathlib
|
|
24
|
+
import unittest
|
|
25
|
+
|
|
26
|
+
BENCH = pathlib.Path(__file__).resolve().parent.parent / "bench"
|
|
27
|
+
MODULE_PATH = BENCH / "venue_adjudication.py"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _load():
|
|
31
|
+
spec = importlib.util.spec_from_file_location("venue_adjudication_under_test", MODULE_PATH)
|
|
32
|
+
module = importlib.util.module_from_spec(spec)
|
|
33
|
+
spec.loader.exec_module(module)
|
|
34
|
+
return module
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
VA = _load()
|
|
39
|
+
except ImportError: # numpy absent on this runner
|
|
40
|
+
VA = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@unittest.skipIf(VA is None, "numpy is not installed on this runner")
|
|
44
|
+
class TestControlGradeCompleteness(unittest.TestCase):
|
|
45
|
+
|
|
46
|
+
def test_an_ungraded_control_that_is_used_refuses_the_run(self):
|
|
47
|
+
with self.assertRaises(SystemExit) as caught:
|
|
48
|
+
VA.assert_every_control_is_graded(
|
|
49
|
+
["fire_cctv", "fire_firesense"], {"fire_firesense": {"grade": "good"}})
|
|
50
|
+
self.assertIn("fire_cctv", str(caught.exception))
|
|
51
|
+
|
|
52
|
+
def test_a_fully_graded_set_passes(self):
|
|
53
|
+
VA.assert_every_control_is_graded(
|
|
54
|
+
["fire_cctv", "fire_firesense"],
|
|
55
|
+
{"fire_cctv": {"grade": "good"}, "fire_firesense": {"grade": "good"}})
|
|
56
|
+
|
|
57
|
+
def test_a_set_no_class_references_is_not_required_to_be_graded(self):
|
|
58
|
+
# `pos_sets` carries every scored set, including off-target ones that no
|
|
59
|
+
# CLASS_CONTROL entry names. Those cannot become `best_control`, so
|
|
60
|
+
# demanding a grade for them would refuse runs for no reason.
|
|
61
|
+
VA.assert_every_control_is_graded(["some_off_target_set"], {})
|
|
62
|
+
|
|
63
|
+
def test_discovery_finds_every_dated_grade_file(self):
|
|
64
|
+
found = [os.path.basename(p) for p in VA.discover_control_quality()]
|
|
65
|
+
self.assertTrue(found, "no positive-control-quality-*.json discovered")
|
|
66
|
+
self.assertEqual(found, sorted(found), "discovery must be sorted so later files win")
|
|
67
|
+
for name in found:
|
|
68
|
+
self.assertTrue(name.startswith("positive-control-quality-"))
|
|
69
|
+
self.assertTrue(name.endswith(".json"))
|
|
70
|
+
# The two the repo carries today. A third is fine; fewer is not.
|
|
71
|
+
self.assertIn("positive-control-quality-2026-08-25.json", found)
|
|
72
|
+
self.assertIn("positive-control-quality-2026-08-27.json", found)
|
|
73
|
+
|
|
74
|
+
def test_discovery_of_a_missing_directory_is_empty_not_an_exception(self):
|
|
75
|
+
self.assertEqual(VA.discover_control_quality("/nonexistent/grade/dir"), [])
|
|
76
|
+
|
|
77
|
+
def test_the_grade_lists_stay_disjoint(self):
|
|
78
|
+
# A grade in both lists would make the refusal order-dependent.
|
|
79
|
+
self.assertFalse(set(VA.LIVENESS_ONLY_GRADES) & set(VA.UNUSABLE_GRADES))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
unittest.main()
|
|
@@ -523,7 +523,58 @@ PPE_KILLUMINATI_19CLS_V8S = ModelVocabulary(
|
|
|
523
523
|
#: this artifact as the fixture that exercises `_verify_class_axis_width`, where
|
|
524
524
|
#: `4 + 7 = 11` matches its real `[1, 11, 8400]` head and `4 + 6 = 10` would
|
|
525
525
|
#: reject it.
|
|
526
|
-
|
|
526
|
+
#: Person Down Detector — published 2026-08-27 by
|
|
527
|
+
#: scripts/sensing/publish-sensing-model.js. The class list is the artifact's own
|
|
528
|
+
#: (graph metadata key 'names'); the footprint is MEASURED, 817.3594 MiB after a
|
|
529
|
+
#: probe inference via rusage-maxrss, not an estimate from the file size.
|
|
530
|
+
#: Coordinate space MEASURED, not assumed: max|box channel| = 1.5467 over 94 real frames; / imgsz 640 = 0.0024. pixels at >= 0.2, normalized at <= 0.02, refused in between.
|
|
531
|
+
FALL_1CLS_11X_CLASS_NAMES: "MappingProxyType[int, str]" = MappingProxyType({
|
|
532
|
+
0: "fall detected",
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
FALL_1CLS_11X = ModelVocabulary(
|
|
536
|
+
model_name="fall_1cls_11x",
|
|
537
|
+
slot_names=FALL_1CLS_11X_CLASS_NAMES,
|
|
538
|
+
logit_slots=1,
|
|
539
|
+
approx_ram_bytes=857063451,
|
|
540
|
+
onnx_variant=VariantContract(
|
|
541
|
+
output_layout="anchor_4_plus_nc",
|
|
542
|
+
box_units="normalized",
|
|
543
|
+
box_scale_to_pixels=640.0,
|
|
544
|
+
),
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
#: Heavy Machinery Detector (venue-adjudicated) — published 2026-08-27 by
|
|
548
|
+
#: scripts/sensing/publish-sensing-model.js. The class list is the artifact's own
|
|
549
|
+
#: (graph metadata key 'names'); the footprint is MEASURED, 593.3125 MiB after a
|
|
550
|
+
#: probe inference via rusage-maxrss, not an estimate from the file size.
|
|
551
|
+
#: Coordinate space MEASURED, not assumed: max|box channel| = 1825.6333 over 120 real frames; / imgsz 640 = 2.8526. pixels at >= 0.2, normalized at <= 0.02, refused in between.
|
|
552
|
+
GH_PPE_VINAYAK_L_CLASS_NAMES: "MappingProxyType[int, str]" = MappingProxyType({
|
|
553
|
+
0: "hardhat",
|
|
554
|
+
1: "mask",
|
|
555
|
+
2: "no hardhat",
|
|
556
|
+
3: "no mask",
|
|
557
|
+
4: "no safety vest",
|
|
558
|
+
5: "person",
|
|
559
|
+
6: "safety cone",
|
|
560
|
+
7: "safety vest",
|
|
561
|
+
8: "machinery",
|
|
562
|
+
9: "vehicle",
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
GH_PPE_VINAYAK_L = ModelVocabulary(
|
|
566
|
+
model_name="gh_ppe_vinayak_l",
|
|
567
|
+
slot_names=GH_PPE_VINAYAK_L_CLASS_NAMES,
|
|
568
|
+
logit_slots=10,
|
|
569
|
+
approx_ram_bytes=622133248,
|
|
570
|
+
onnx_variant=VariantContract(
|
|
571
|
+
output_layout="anchor_4_plus_nc",
|
|
572
|
+
box_units="pixels",
|
|
573
|
+
box_scale_to_pixels=1.0,
|
|
574
|
+
),
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
BUILTIN_VOCABULARIES: "tuple[ModelVocabulary, ...]" = (RFDETR_MEDIUM, GH_FORKLIFT_COMBINE, PPE_VYRA_14CLS_V8M, PPE_CONSTRUCTION_17CLS_V8N, PPE_KILLUMINATI_19CLS_V8S, FALL_1CLS_11X, GH_PPE_VINAYAK_L,)
|
|
527
578
|
|
|
528
579
|
|
|
529
580
|
class ClassIndex:
|
|
@@ -617,6 +668,8 @@ class ClassIndex:
|
|
|
617
668
|
|
|
618
669
|
|
|
619
670
|
__all__ = [
|
|
671
|
+
"GH_PPE_VINAYAK_L",
|
|
672
|
+
"FALL_1CLS_11X",
|
|
620
673
|
"PPE_KILLUMINATI_19CLS_V8S",
|
|
621
674
|
"PPE_CONSTRUCTION_17CLS_V8N",
|
|
622
675
|
"PPE_VYRA_14CLS_V8M",
|
package/dist/EdgeAgent.js
CHANGED
|
@@ -44,7 +44,7 @@ const edge_ads_1 = require("@trillboards/edge-ads");
|
|
|
44
44
|
const CommandRouter_1 = require("./CommandRouter");
|
|
45
45
|
const config_1 = require("./config");
|
|
46
46
|
const dataDir_1 = require("./dataDir");
|
|
47
|
-
const SDK_VERSION = '0.2.
|
|
47
|
+
const SDK_VERSION = '0.2.109';
|
|
48
48
|
class EdgeAgent extends events_1.EventEmitter {
|
|
49
49
|
constructor(config, deps) {
|
|
50
50
|
super();
|
package/dist/diagnose.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface DiagnosticReport {
|
|
|
31
31
|
skipped: number;
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
declare const SDK_VERSION = "0.2.
|
|
34
|
+
declare const SDK_VERSION = "0.2.109";
|
|
35
35
|
declare function checkNodeVersion(): CheckResult;
|
|
36
36
|
declare function checkPlatform(): CheckResult;
|
|
37
37
|
declare function checkMemory(): CheckResult;
|
package/dist/diagnose.js
CHANGED
|
@@ -63,7 +63,7 @@ const localNetworkAccess_1 = require("./localNetworkAccess");
|
|
|
63
63
|
// ---------------------------------------------------------------------------
|
|
64
64
|
// SDK version — mirrors package.json; kept in sync by CI
|
|
65
65
|
// ---------------------------------------------------------------------------
|
|
66
|
-
const SDK_VERSION = '0.2.
|
|
66
|
+
const SDK_VERSION = '0.2.109';
|
|
67
67
|
exports._SDK_VERSION = SDK_VERSION;
|
|
68
68
|
// ---------------------------------------------------------------------------
|
|
69
69
|
// ANSI helpers (no external deps)
|
package/package.json
CHANGED