@camstack/addon-benchmark 1.2.195 → 1.2.197
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/{MotionZonesSettings-BjoeE0S1.mjs → MotionZonesSettings-BrVV9EXF.mjs} +19 -19
- package/dist/{PrivacyMaskSettings-CbjkLYU-.mjs → PrivacyMaskSettings-D8sjuyhC.mjs} +6 -6
- package/dist/{SceneMonitorEditor-CpBgHc-O.mjs → SceneMonitorEditor-BYSSKL6B.mjs} +304 -304
- package/dist/_stub.js +5658 -7849
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_benchmark_page-Bl4Vins5.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_benchmark_page-lkKeR65E.mjs} +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C0SUUHYE.mjs +26 -0
- package/dist/addon-benchmark.css +1 -1
- package/dist/{hostInit-BsI8UbEL.mjs → hostInit-DObjad6K.mjs} +2 -2
- package/dist/index.js +47 -1797
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -1800
- package/dist/index.mjs.map +1 -1
- package/dist/{player-overlays-C4j465z3.mjs → player-overlays-CMnxOmtI.mjs} +5 -5
- package/dist/remoteEntry.js +1 -1
- package/dist/{responsive-tU-nXS1h.mjs → responsive-DzjUS3a_.mjs} +3 -3
- package/dist/{scene-monitor-copy-BvGoUL8z.mjs → scene-monitor-copy-DPRVdwkx.mjs} +1 -1
- package/dist/{square-R4Tms3sB.mjs → square-B09gv3_J.mjs} +1 -1
- package/dist/{trash-2-CfS9mZ-d.mjs → trash-2-0IB1iAfr.mjs} +1 -1
- package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_benchmark_page__remoteEntry_js-eRgM758x.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_benchmark_page__remoteEntry_js-tSKEr7Zz.mjs} +1 -1
- package/package.json +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DOYp1G-I.mjs +0 -26
package/dist/index.mjs
CHANGED
|
@@ -5,14 +5,10 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/benchmark-addon.ts
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
errMsg,
|
|
13
|
-
nodePin,
|
|
14
|
-
parseJsonUnknown
|
|
15
|
-
} from "@camstack/types";
|
|
8
|
+
import { BaseAddon, addonPagesSourceCapability } from "@camstack/types";
|
|
9
|
+
|
|
10
|
+
// src/benchmark-actions.ts
|
|
11
|
+
import { customAction, defineCustomActions, PipelineRunResultBridge } from "@camstack/types";
|
|
16
12
|
|
|
17
13
|
// ../../node_modules/zod/v4/classic/external.js
|
|
18
14
|
var external_exports = {};
|
|
@@ -11504,42 +11500,42 @@ function extractDefs(ctx, schema) {
|
|
|
11504
11500
|
if (!root)
|
|
11505
11501
|
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
11506
11502
|
const idToSchema = /* @__PURE__ */ new Map();
|
|
11507
|
-
for (const
|
|
11508
|
-
const id = ctx.metadataRegistry.get(
|
|
11503
|
+
for (const entry of ctx.seen.entries()) {
|
|
11504
|
+
const id = ctx.metadataRegistry.get(entry[0])?.id;
|
|
11509
11505
|
if (id) {
|
|
11510
11506
|
const existing = idToSchema.get(id);
|
|
11511
|
-
if (existing && existing !==
|
|
11507
|
+
if (existing && existing !== entry[0]) {
|
|
11512
11508
|
throw new Error(`Duplicate schema id "${id}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);
|
|
11513
11509
|
}
|
|
11514
|
-
idToSchema.set(id,
|
|
11510
|
+
idToSchema.set(id, entry[0]);
|
|
11515
11511
|
}
|
|
11516
11512
|
}
|
|
11517
|
-
const makeURI = (
|
|
11513
|
+
const makeURI = (entry) => {
|
|
11518
11514
|
const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
|
|
11519
11515
|
if (ctx.external) {
|
|
11520
|
-
const externalId = ctx.external.registry.get(
|
|
11516
|
+
const externalId = ctx.external.registry.get(entry[0])?.id;
|
|
11521
11517
|
const uriGenerator = ctx.external.uri ?? ((id2) => id2);
|
|
11522
11518
|
if (externalId) {
|
|
11523
11519
|
return { ref: uriGenerator(externalId) };
|
|
11524
11520
|
}
|
|
11525
|
-
const id =
|
|
11526
|
-
|
|
11521
|
+
const id = entry[1].defId ?? entry[1].schema.id ?? `schema${ctx.counter++}`;
|
|
11522
|
+
entry[1].defId = id;
|
|
11527
11523
|
return { defId: id, ref: `${uriGenerator("__shared")}#/${defsSegment}/${id}` };
|
|
11528
11524
|
}
|
|
11529
|
-
if (
|
|
11525
|
+
if (entry[1] === root) {
|
|
11530
11526
|
return { ref: "#" };
|
|
11531
11527
|
}
|
|
11532
11528
|
const uriPrefix = `#`;
|
|
11533
11529
|
const defUriPrefix = `${uriPrefix}/${defsSegment}/`;
|
|
11534
|
-
const defId =
|
|
11530
|
+
const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;
|
|
11535
11531
|
return { defId, ref: defUriPrefix + defId };
|
|
11536
11532
|
};
|
|
11537
|
-
const extractToDef = (
|
|
11538
|
-
if (
|
|
11533
|
+
const extractToDef = (entry) => {
|
|
11534
|
+
if (entry[1].schema.$ref) {
|
|
11539
11535
|
return;
|
|
11540
11536
|
}
|
|
11541
|
-
const seen =
|
|
11542
|
-
const { ref, defId } = makeURI(
|
|
11537
|
+
const seen = entry[1];
|
|
11538
|
+
const { ref, defId } = makeURI(entry);
|
|
11543
11539
|
seen.def = { ...seen.schema };
|
|
11544
11540
|
if (defId)
|
|
11545
11541
|
seen.defId = defId;
|
|
@@ -11550,8 +11546,8 @@ function extractDefs(ctx, schema) {
|
|
|
11550
11546
|
schema2.$ref = ref;
|
|
11551
11547
|
};
|
|
11552
11548
|
if (ctx.cycles === "throw") {
|
|
11553
|
-
for (const
|
|
11554
|
-
const seen =
|
|
11549
|
+
for (const entry of ctx.seen.entries()) {
|
|
11550
|
+
const seen = entry[1];
|
|
11555
11551
|
if (seen.cycle) {
|
|
11556
11552
|
throw new Error(`Cycle detected: #/${seen.cycle?.join("/")}/<root>
|
|
11557
11553
|
|
|
@@ -11559,31 +11555,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11559
11555
|
}
|
|
11560
11556
|
}
|
|
11561
11557
|
}
|
|
11562
|
-
for (const
|
|
11563
|
-
const seen =
|
|
11564
|
-
if (schema ===
|
|
11565
|
-
extractToDef(
|
|
11558
|
+
for (const entry of ctx.seen.entries()) {
|
|
11559
|
+
const seen = entry[1];
|
|
11560
|
+
if (schema === entry[0]) {
|
|
11561
|
+
extractToDef(entry);
|
|
11566
11562
|
continue;
|
|
11567
11563
|
}
|
|
11568
11564
|
if (ctx.external) {
|
|
11569
|
-
const ext = ctx.external.registry.get(
|
|
11570
|
-
if (schema !==
|
|
11571
|
-
extractToDef(
|
|
11565
|
+
const ext = ctx.external.registry.get(entry[0])?.id;
|
|
11566
|
+
if (schema !== entry[0] && ext) {
|
|
11567
|
+
extractToDef(entry);
|
|
11572
11568
|
continue;
|
|
11573
11569
|
}
|
|
11574
11570
|
}
|
|
11575
|
-
const id = ctx.metadataRegistry.get(
|
|
11571
|
+
const id = ctx.metadataRegistry.get(entry[0])?.id;
|
|
11576
11572
|
if (id) {
|
|
11577
|
-
extractToDef(
|
|
11573
|
+
extractToDef(entry);
|
|
11578
11574
|
continue;
|
|
11579
11575
|
}
|
|
11580
11576
|
if (seen.cycle) {
|
|
11581
|
-
extractToDef(
|
|
11577
|
+
extractToDef(entry);
|
|
11582
11578
|
continue;
|
|
11583
11579
|
}
|
|
11584
11580
|
if (seen.count > 1) {
|
|
11585
11581
|
if (ctx.reused === "ref") {
|
|
11586
|
-
extractToDef(
|
|
11582
|
+
extractToDef(entry);
|
|
11587
11583
|
continue;
|
|
11588
11584
|
}
|
|
11589
11585
|
}
|
|
@@ -11655,8 +11651,8 @@ function finalize(ctx, schema) {
|
|
|
11655
11651
|
path: seen.path ?? []
|
|
11656
11652
|
});
|
|
11657
11653
|
};
|
|
11658
|
-
for (const
|
|
11659
|
-
flattenRef(
|
|
11654
|
+
for (const entry of [...ctx.seen.entries()].reverse()) {
|
|
11655
|
+
flattenRef(entry[0]);
|
|
11660
11656
|
}
|
|
11661
11657
|
const result = {};
|
|
11662
11658
|
if (ctx.target === "draft-2020-12") {
|
|
@@ -11679,8 +11675,8 @@ function finalize(ctx, schema) {
|
|
|
11679
11675
|
if (rootMetaId !== void 0 && result.id === rootMetaId)
|
|
11680
11676
|
delete result.id;
|
|
11681
11677
|
const defs = ctx.external?.defs ?? {};
|
|
11682
|
-
for (const
|
|
11683
|
-
const seen =
|
|
11678
|
+
for (const entry of ctx.seen.entries()) {
|
|
11679
|
+
const seen = entry[1];
|
|
11684
11680
|
if (seen.def && seen.defId) {
|
|
11685
11681
|
if (seen.def.id === seen.defId)
|
|
11686
11682
|
delete seen.def.id;
|
|
@@ -12296,8 +12292,8 @@ function toJSONSchema(input, params) {
|
|
|
12296
12292
|
const registry2 = input;
|
|
12297
12293
|
const ctx2 = initializeContext({ ...params, processors: allProcessors });
|
|
12298
12294
|
const defs = {};
|
|
12299
|
-
for (const
|
|
12300
|
-
const [_, schema] =
|
|
12295
|
+
for (const entry of registry2._idmap.entries()) {
|
|
12296
|
+
const [_, schema] = entry;
|
|
12301
12297
|
process(schema, ctx2);
|
|
12302
12298
|
}
|
|
12303
12299
|
const schemas = {};
|
|
@@ -12307,8 +12303,8 @@ function toJSONSchema(input, params) {
|
|
|
12307
12303
|
defs
|
|
12308
12304
|
};
|
|
12309
12305
|
ctx2.external = external;
|
|
12310
|
-
for (const
|
|
12311
|
-
const [key, schema] =
|
|
12306
|
+
for (const entry of registry2._idmap.entries()) {
|
|
12307
|
+
const [key, schema] = entry;
|
|
12312
12308
|
extractDefs(ctx2, schema);
|
|
12313
12309
|
schemas[key] = finalize(ctx2, schema);
|
|
12314
12310
|
}
|
|
@@ -14528,205 +14524,10 @@ function date4(params) {
|
|
|
14528
14524
|
// ../../node_modules/zod/v4/classic/external.js
|
|
14529
14525
|
config(en_default());
|
|
14530
14526
|
|
|
14531
|
-
// src/benchmark-actions.ts
|
|
14532
|
-
import { customAction, defineCustomActions, PipelineRunResultBridge } from "@camstack/types";
|
|
14533
|
-
|
|
14534
14527
|
// src/benchmark-schemas.ts
|
|
14535
|
-
import { PipelineStepInputSchema as PipelineStepInputSchema2 } from "@camstack/types";
|
|
14536
|
-
|
|
14537
|
-
// src/session-replay/schemas.ts
|
|
14538
14528
|
import { PipelineStepInputSchema } from "@camstack/types";
|
|
14539
|
-
var NormBoxSchema = external_exports.object({
|
|
14540
|
-
x: external_exports.number(),
|
|
14541
|
-
y: external_exports.number(),
|
|
14542
|
-
w: external_exports.number(),
|
|
14543
|
-
h: external_exports.number()
|
|
14544
|
-
});
|
|
14545
|
-
var ReplayBoxSchema = external_exports.object({
|
|
14546
|
-
className: external_exports.string(),
|
|
14547
|
-
score: external_exports.number(),
|
|
14548
|
-
box: NormBoxSchema
|
|
14549
|
-
});
|
|
14550
|
-
var StoredPositionSchema = external_exports.object({
|
|
14551
|
-
trackId: external_exports.string(),
|
|
14552
|
-
className: external_exports.string(),
|
|
14553
|
-
t: external_exports.number(),
|
|
14554
|
-
box: NormBoxSchema,
|
|
14555
|
-
score: external_exports.number().nullable()
|
|
14556
|
-
});
|
|
14557
|
-
var ReferenceBasisSchema = external_exports.enum(["exact", "interpolated", "uncertain"]);
|
|
14558
|
-
var ReferenceEntrySchema = StoredPositionSchema.extend({ basis: ReferenceBasisSchema });
|
|
14559
|
-
var TimelineReplayBoxSchema = ReplayBoxSchema.extend({
|
|
14560
|
-
/** `unreferenced`: no stored track was alive at this instant — nothing to contradict. */
|
|
14561
|
-
outcome: external_exports.enum(["matched", "class-changed", "added", "unreferenced"])
|
|
14562
|
-
});
|
|
14563
|
-
var TimelineStoredBoxSchema = ReferenceEntrySchema.extend({
|
|
14564
|
-
outcome: external_exports.enum(["matched", "class-changed", "missed"])
|
|
14565
|
-
});
|
|
14566
|
-
var TimelineFrameSchema = external_exports.object({
|
|
14567
|
-
t: external_exports.number(),
|
|
14568
|
-
/** Some stored track was alive at `t` — the instant HAS a reference. */
|
|
14569
|
-
referenced: external_exports.boolean(),
|
|
14570
|
-
replay: external_exports.array(TimelineReplayBoxSchema).readonly(),
|
|
14571
|
-
stored: external_exports.array(TimelineStoredBoxSchema).readonly(),
|
|
14572
|
-
inferenceMs: external_exports.number().nullable()
|
|
14573
|
-
});
|
|
14574
|
-
var ClassCountsSchema = external_exports.object({
|
|
14575
|
-
replay: external_exports.number(),
|
|
14576
|
-
stored: external_exports.number(),
|
|
14577
|
-
matched: external_exports.number(),
|
|
14578
|
-
added: external_exports.number(),
|
|
14579
|
-
missed: external_exports.number(),
|
|
14580
|
-
unreferenced: external_exports.number(),
|
|
14581
|
-
/** Comparisons (matched / class-changed / missed) made on a non-exact stored box. */
|
|
14582
|
-
interpolated: external_exports.number()
|
|
14583
|
-
});
|
|
14584
|
-
var ReplaySummarySchema = external_exports.object({
|
|
14585
|
-
totals: external_exports.object({
|
|
14586
|
-
matched: external_exports.number(),
|
|
14587
|
-
classChanged: external_exports.number(),
|
|
14588
|
-
added: external_exports.number(),
|
|
14589
|
-
missed: external_exports.number(),
|
|
14590
|
-
unreferenced: external_exports.number(),
|
|
14591
|
-
interpolated: external_exports.number()
|
|
14592
|
-
}),
|
|
14593
|
-
perClass: external_exports.record(external_exports.string(), ClassCountsSchema),
|
|
14594
|
-
scoreDelta: external_exports.object({ mean: external_exports.number(), p10: external_exports.number(), p50: external_exports.number(), p90: external_exports.number() }).nullable(),
|
|
14595
|
-
inferenceMs: external_exports.object({ p50: external_exports.number(), p95: external_exports.number(), count: external_exports.number() }).nullable()
|
|
14596
|
-
});
|
|
14597
|
-
var CadenceSourceSchema = external_exports.enum(["operator", "stored-positions", "default"]);
|
|
14598
|
-
var ReplayCadenceSchema = external_exports.object({ fps: external_exports.number(), source: CadenceSourceSchema });
|
|
14599
|
-
var SessionReplayReportSchema = external_exports.object({
|
|
14600
|
-
deviceId: external_exports.number(),
|
|
14601
|
-
motionEventId: external_exports.number(),
|
|
14602
|
-
window: external_exports.object({
|
|
14603
|
-
startMs: external_exports.number(),
|
|
14604
|
-
endMs: external_exports.number(),
|
|
14605
|
-
motionStartMs: external_exports.number(),
|
|
14606
|
-
motionEndMs: external_exports.number(),
|
|
14607
|
-
/** Where the pre-roll / tail came from: the camera's own settings, or the fleet default. */
|
|
14608
|
-
prerollSource: external_exports.enum(["camera", "default"]),
|
|
14609
|
-
tailSource: external_exports.enum(["camera", "default"])
|
|
14610
|
-
}),
|
|
14611
|
-
pin: external_exports.object({
|
|
14612
|
-
nodeId: external_exports.string(),
|
|
14613
|
-
deviceKey: external_exports.string().nullable(),
|
|
14614
|
-
modelId: external_exports.string(),
|
|
14615
|
-
cameraModelId: external_exports.string().nullable()
|
|
14616
|
-
}),
|
|
14617
|
-
rootStep: PipelineStepInputSchema,
|
|
14618
|
-
cadence: ReplayCadenceSchema,
|
|
14619
|
-
frameSize: external_exports.object({ width: external_exports.number(), source: external_exports.enum(["stored-tracks", "default"]) }),
|
|
14620
|
-
frames: external_exports.object({
|
|
14621
|
-
planned: external_exports.number(),
|
|
14622
|
-
decoded: external_exports.number(),
|
|
14623
|
-
ran: external_exports.number(),
|
|
14624
|
-
failed: external_exports.number(),
|
|
14625
|
-
withoutFootage: external_exports.number(),
|
|
14626
|
-
notDecoded: external_exports.number(),
|
|
14627
|
-
truncated: external_exports.boolean(),
|
|
14628
|
-
truncatedBySafetyCap: external_exports.boolean(),
|
|
14629
|
-
aborted: external_exports.boolean()
|
|
14630
|
-
}),
|
|
14631
|
-
reference: external_exports.object({
|
|
14632
|
-
tracks: external_exports.number(),
|
|
14633
|
-
positionsCompared: external_exports.number(),
|
|
14634
|
-
recoveryExcluded: external_exports.number(),
|
|
14635
|
-
withoutGeometry: external_exports.number(),
|
|
14636
|
-
models: external_exports.record(external_exports.string(), external_exports.number()),
|
|
14637
|
-
/** The stored-track read: how many came back, and whether its limit cut it short. */
|
|
14638
|
-
read: external_exports.object({ tracks: external_exports.number(), truncated: external_exports.boolean() })
|
|
14639
|
-
}),
|
|
14640
|
-
confirmation: external_exports.object({
|
|
14641
|
-
liveVerdicts: external_exports.record(external_exports.string(), external_exports.number()),
|
|
14642
|
-
/** Added or unreferenced detections near a live SUPPRESSED birth of their class. */
|
|
14643
|
-
nearSuppressedBirth: external_exports.number()
|
|
14644
|
-
}),
|
|
14645
|
-
summary: ReplaySummarySchema,
|
|
14646
|
-
timeline: external_exports.array(TimelineFrameSchema).readonly(),
|
|
14647
|
-
failures: external_exports.array(external_exports.object({ t: external_exports.number(), error: external_exports.string() })).readonly()
|
|
14648
|
-
});
|
|
14649
|
-
var SessionReplayRefusalSchema = external_exports.enum([
|
|
14650
|
-
"session-not-found",
|
|
14651
|
-
"session-open",
|
|
14652
|
-
"window-too-long",
|
|
14653
|
-
"no-root-step",
|
|
14654
|
-
"model-unpinned",
|
|
14655
|
-
"model-not-servable",
|
|
14656
|
-
"device-not-servable",
|
|
14657
|
-
"no-recording",
|
|
14658
|
-
"no-footage",
|
|
14659
|
-
"session-length-unknown",
|
|
14660
|
-
"pool-failing"
|
|
14661
|
-
]);
|
|
14662
|
-
var SessionReplayOutcomeSchema = external_exports.discriminatedUnion("kind", [
|
|
14663
|
-
external_exports.object({
|
|
14664
|
-
kind: external_exports.literal("refused"),
|
|
14665
|
-
reason: SessionReplayRefusalSchema,
|
|
14666
|
-
detail: external_exports.string()
|
|
14667
|
-
}),
|
|
14668
|
-
external_exports.object({ kind: external_exports.literal("ran"), report: SessionReplayReportSchema })
|
|
14669
|
-
]);
|
|
14670
|
-
var SessionReplayProgressSchema = external_exports.object({
|
|
14671
|
-
phase: external_exports.enum(["preparing", "replaying", "done"]),
|
|
14672
|
-
framesPlanned: external_exports.number(),
|
|
14673
|
-
framesDone: external_exports.number()
|
|
14674
|
-
});
|
|
14675
|
-
|
|
14676
|
-
// src/benchmark-schemas.ts
|
|
14677
|
-
var BenchModeSchema = external_exports.enum(["inference", "full-tree", "live-frame"]);
|
|
14678
|
-
var BenchPoolTuningSchema = external_exports.object({
|
|
14679
|
-
runtime: external_exports.string().nullable(),
|
|
14680
|
-
numWorkers: external_exports.number().nullable(),
|
|
14681
|
-
concurrency: external_exports.number().nullable(),
|
|
14682
|
-
batchMode: external_exports.string().nullable(),
|
|
14683
|
-
maxBatchSize: external_exports.number().nullable()
|
|
14684
|
-
});
|
|
14685
|
-
var HistoryMeasurementFields = {
|
|
14686
|
-
stepId: external_exports.string(),
|
|
14687
|
-
modelId: external_exports.string(),
|
|
14688
|
-
iterations: external_exports.number(),
|
|
14689
|
-
warmup: external_exports.number(),
|
|
14690
|
-
fps: external_exports.number(),
|
|
14691
|
-
avgMs: external_exports.number(),
|
|
14692
|
-
medianMs: external_exports.number(),
|
|
14693
|
-
p95Ms: external_exports.number(),
|
|
14694
|
-
p99Ms: external_exports.number(),
|
|
14695
|
-
/**
|
|
14696
|
-
* OPTIONAL, and absent on every entry the page writes: the runner reports
|
|
14697
|
-
* mean / p50 / p95 / p99 and measures no per-call min or max. A fabricated
|
|
14698
|
-
* latency in the history table is worse than a blank one.
|
|
14699
|
-
*/
|
|
14700
|
-
minMs: external_exports.number().optional(),
|
|
14701
|
-
maxMs: external_exports.number().optional(),
|
|
14702
|
-
avgDetections: external_exports.number(),
|
|
14703
|
-
referenceImage: external_exports.string().optional(),
|
|
14704
|
-
/** Concurrent in-flight requests (simulated cameras) — NOT pool workers. */
|
|
14705
|
-
parallelRuns: external_exports.number().min(1).default(1)
|
|
14706
|
-
};
|
|
14707
|
-
var PipelineBenchmarkHistoryEntrySchema = external_exports.object({
|
|
14708
|
-
kind: external_exports.literal("pipeline").default("pipeline"),
|
|
14709
|
-
id: external_exports.string(),
|
|
14710
|
-
date: external_exports.string(),
|
|
14711
|
-
...HistoryMeasurementFields,
|
|
14712
|
-
nodeId: external_exports.string().optional(),
|
|
14713
|
-
deviceKey: external_exports.string().nullable().optional(),
|
|
14714
|
-
plane: external_exports.enum(["full", "frame"]).nullable().optional(),
|
|
14715
|
-
mode: BenchModeSchema.optional(),
|
|
14716
|
-
pool: BenchPoolTuningSchema.optional(),
|
|
14717
|
-
runtime: external_exports.string().optional()
|
|
14718
|
-
});
|
|
14719
|
-
var PipelineBenchmarkHistoryEntryInputSchema = external_exports.object({
|
|
14720
|
-
...HistoryMeasurementFields,
|
|
14721
|
-
nodeId: external_exports.string(),
|
|
14722
|
-
deviceKey: external_exports.string().nullable(),
|
|
14723
|
-
plane: external_exports.enum(["full", "frame"]).nullable(),
|
|
14724
|
-
mode: BenchModeSchema,
|
|
14725
|
-
pool: BenchPoolTuningSchema
|
|
14726
|
-
});
|
|
14727
|
-
var HistoryEntrySchema = PipelineBenchmarkHistoryEntrySchema;
|
|
14728
14529
|
var PipelineRunInputSchema = external_exports.object({
|
|
14729
|
-
steps: external_exports.array(
|
|
14530
|
+
steps: external_exports.array(PipelineStepInputSchema).readonly(),
|
|
14730
14531
|
imageBase64: external_exports.string().optional(),
|
|
14731
14532
|
referenceImage: external_exports.string().optional(),
|
|
14732
14533
|
/** Target node. Forwarded to `pipelineExecutor.runPipeline`. Omit to run on the hub. */
|
|
@@ -14736,1242 +14537,17 @@ var PipelineRunInputSchema = external_exports.object({
|
|
|
14736
14537
|
deviceKey: external_exports.string().optional(),
|
|
14737
14538
|
plane: external_exports.enum(["full", "frame"]).optional()
|
|
14738
14539
|
});
|
|
14739
|
-
var SyntheticBenchPoolSchema = external_exports.object({
|
|
14740
|
-
nodeId: external_exports.string(),
|
|
14741
|
-
deviceKey: external_exports.string().nullable(),
|
|
14742
|
-
runtime: external_exports.string().nullable(),
|
|
14743
|
-
numWorkers: external_exports.number().nullable(),
|
|
14744
|
-
concurrency: external_exports.number().nullable(),
|
|
14745
|
-
batchMode: external_exports.string().nullable(),
|
|
14746
|
-
maxBatchSize: external_exports.number().nullable()
|
|
14747
|
-
});
|
|
14748
|
-
var SyntheticBenchStatsSchema = external_exports.object({
|
|
14749
|
-
runs: external_exports.number(),
|
|
14750
|
-
wallSec: external_exports.number(),
|
|
14751
|
-
fps: external_exports.number(),
|
|
14752
|
-
detectionsPerSec: external_exports.number(),
|
|
14753
|
-
avgDetections: external_exports.number(),
|
|
14754
|
-
callMs: external_exports.object({ mean: external_exports.number(), p50: external_exports.number(), p95: external_exports.number(), p99: external_exports.number() }),
|
|
14755
|
-
inferMs: external_exports.number(),
|
|
14756
|
-
preprocessMs: external_exports.number(),
|
|
14757
|
-
predictMs: external_exports.number(),
|
|
14758
|
-
batchSizeMean: external_exports.number(),
|
|
14759
|
-
batchSizeMax: external_exports.number(),
|
|
14760
|
-
pool: SyntheticBenchPoolSchema,
|
|
14761
|
-
path: external_exports.string()
|
|
14762
|
-
});
|
|
14763
|
-
var SyntheticBenchStatusSchema = external_exports.object({
|
|
14764
|
-
sessionId: external_exports.string(),
|
|
14765
|
-
state: external_exports.enum(["running", "done", "failed", "aborted"]),
|
|
14766
|
-
startedAtMs: external_exports.number(),
|
|
14767
|
-
finishedAtMs: external_exports.number().nullable(),
|
|
14768
|
-
progress: external_exports.object({ runs: external_exports.number(), totalRuns: external_exports.number() }).nullable(),
|
|
14769
|
-
result: SyntheticBenchStatsSchema.nullable(),
|
|
14770
|
-
error: external_exports.string().nullable()
|
|
14771
|
-
}).nullable();
|
|
14772
|
-
var MotionSessionSchema = external_exports.object({
|
|
14773
|
-
motionEventId: external_exports.number(),
|
|
14774
|
-
deviceId: external_exports.number(),
|
|
14775
|
-
startMs: external_exports.number(),
|
|
14776
|
-
/** `null` while the session is still open. */
|
|
14777
|
-
durationMs: external_exports.number().nullable(),
|
|
14778
|
-
open: external_exports.boolean(),
|
|
14779
|
-
/** Why a replay of this session would be refused (`session-open`, `session-length-unknown`, …); `null` = replayable. */
|
|
14780
|
-
refusal: external_exports.string().nullable(),
|
|
14781
|
-
/** Stored tracks born inside the session window (slim read). */
|
|
14782
|
-
storedTracks: external_exports.number()
|
|
14783
|
-
});
|
|
14784
|
-
var ListMotionSessionsInputSchema = external_exports.object({
|
|
14785
|
-
deviceId: external_exports.number(),
|
|
14786
|
-
/** Default: the last 24 h. */
|
|
14787
|
-
sinceMs: external_exports.number().optional(),
|
|
14788
|
-
untilMs: external_exports.number().optional(),
|
|
14789
|
-
limit: external_exports.number().int().min(1).max(200).default(50)
|
|
14790
|
-
});
|
|
14791
|
-
var SESSION_REPLAY_MAX_FRAMES = 1200;
|
|
14792
|
-
var StartSessionReplayInputSchema = external_exports.object({
|
|
14793
|
-
deviceId: external_exports.number(),
|
|
14794
|
-
motionEventId: external_exports.number(),
|
|
14795
|
-
/** The session's start (`getMotionEvents.timestamp`) — the lookup is windowed on it. */
|
|
14796
|
-
motionStartMs: external_exports.number(),
|
|
14797
|
-
nodeId: external_exports.string(),
|
|
14798
|
-
/** Absent ⇒ the node-default pool. */
|
|
14799
|
-
deviceKey: external_exports.string().optional(),
|
|
14800
|
-
/** Absent ⇒ the model the camera's resolved tree names. */
|
|
14801
|
-
modelId: external_exports.string().optional(),
|
|
14802
|
-
/** Absent ⇒ measured from the stored positions (the live cadence). */
|
|
14803
|
-
fps: external_exports.number().min(1).max(30).optional(),
|
|
14804
|
-
maxFrames: external_exports.number().int().min(1).max(SESSION_REPLAY_MAX_FRAMES).default(600)
|
|
14805
|
-
});
|
|
14806
|
-
var StartSessionReplayAcceptedSchema = external_exports.object({
|
|
14807
|
-
accepted: external_exports.literal(true),
|
|
14808
|
-
runId: external_exports.string()
|
|
14809
|
-
});
|
|
14810
|
-
var SessionReplayKeySchema = external_exports.object({ runId: external_exports.string() });
|
|
14811
|
-
var SessionReplayStatusSchema = external_exports.object({
|
|
14812
|
-
runId: external_exports.string(),
|
|
14813
|
-
state: external_exports.enum(["running", "done", "failed", "aborted"]),
|
|
14814
|
-
startedAtMs: external_exports.number(),
|
|
14815
|
-
finishedAtMs: external_exports.number().nullable(),
|
|
14816
|
-
progress: SessionReplayProgressSchema.nullable(),
|
|
14817
|
-
outcome: SessionReplayOutcomeSchema.nullable(),
|
|
14818
|
-
error: external_exports.string().nullable()
|
|
14819
|
-
}).nullable();
|
|
14820
|
-
var StoredSessionReplaySummarySchema = external_exports.object({
|
|
14821
|
-
id: external_exports.string(),
|
|
14822
|
-
deviceId: external_exports.number(),
|
|
14823
|
-
nodeId: external_exports.string(),
|
|
14824
|
-
createdAtMs: external_exports.number(),
|
|
14825
|
-
report: SessionReplayReportSchema.omit({ timeline: true })
|
|
14826
|
-
});
|
|
14827
|
-
var ListSessionReplaysInputSchema = external_exports.object({ deviceId: external_exports.number() });
|
|
14828
|
-
var ListSessionReplaysResultSchema = external_exports.object({
|
|
14829
|
-
runs: external_exports.array(StoredSessionReplaySummarySchema).readonly(),
|
|
14830
|
-
/** Rows that did not read as a session replay (the removed clip replays). */
|
|
14831
|
-
skipped: external_exports.number()
|
|
14832
|
-
});
|
|
14833
|
-
var GetSessionReplayInputSchema = external_exports.object({ id: external_exports.string() });
|
|
14834
|
-
var StoredSessionReplaySchema = external_exports.object({
|
|
14835
|
-
id: external_exports.string(),
|
|
14836
|
-
deviceId: external_exports.number(),
|
|
14837
|
-
nodeId: external_exports.string(),
|
|
14838
|
-
createdAtMs: external_exports.number(),
|
|
14839
|
-
report: SessionReplayReportSchema
|
|
14840
|
-
});
|
|
14841
14540
|
|
|
14842
14541
|
// src/benchmark-actions.ts
|
|
14843
14542
|
var benchmarkActions = defineCustomActions({
|
|
14844
|
-
//
|
|
14543
|
+
// Image — one run of a step tree on a (node, deviceKey).
|
|
14845
14544
|
runPipelineTest: customAction(PipelineRunInputSchema, PipelineRunResultBridge, {
|
|
14846
14545
|
kind: "mutation"
|
|
14847
|
-
})
|
|
14848
|
-
/**
|
|
14849
|
-
* History.
|
|
14850
|
-
*
|
|
14851
|
-
* `z.object({})`, never `z.void()`: the `addons.custom` envelope insists
|
|
14852
|
-
* on an `input` object, and a `z.void()` schema REJECTS `{}`.
|
|
14853
|
-
*/
|
|
14854
|
-
listBenchmarkHistory: customAction(external_exports.object({}), external_exports.array(HistoryEntrySchema).readonly()),
|
|
14855
|
-
saveBenchmarkResult: customAction(
|
|
14856
|
-
PipelineBenchmarkHistoryEntryInputSchema,
|
|
14857
|
-
PipelineBenchmarkHistoryEntrySchema,
|
|
14858
|
-
{ kind: "mutation" }
|
|
14859
|
-
),
|
|
14860
|
-
deleteBenchmarkResult: customAction(
|
|
14861
|
-
external_exports.object({ id: external_exports.string() }),
|
|
14862
|
-
external_exports.object({ success: external_exports.literal(true) }),
|
|
14863
|
-
{ kind: "mutation" }
|
|
14864
|
-
),
|
|
14865
|
-
clearBenchmarkHistory: customAction(external_exports.object({}), external_exports.object({ success: external_exports.literal(true) }), {
|
|
14866
|
-
kind: "mutation"
|
|
14867
|
-
}),
|
|
14868
|
-
// Session replay (D646) — the stored sessions to pick from, then start / poll / abort.
|
|
14869
|
-
listMotionSessions: customAction(
|
|
14870
|
-
ListMotionSessionsInputSchema,
|
|
14871
|
-
external_exports.array(MotionSessionSchema).readonly()
|
|
14872
|
-
),
|
|
14873
|
-
startSessionReplay: customAction(
|
|
14874
|
-
StartSessionReplayInputSchema,
|
|
14875
|
-
StartSessionReplayAcceptedSchema,
|
|
14876
|
-
{ kind: "mutation", auth: "admin" }
|
|
14877
|
-
),
|
|
14878
|
-
getSessionReplayStatus: customAction(SessionReplayKeySchema, SessionReplayStatusSchema),
|
|
14879
|
-
abortSessionReplay: customAction(SessionReplayKeySchema, external_exports.object({ aborted: external_exports.boolean() }), {
|
|
14880
|
-
kind: "mutation",
|
|
14881
|
-
auth: "admin"
|
|
14882
|
-
}),
|
|
14883
|
-
listSessionReplays: customAction(ListSessionReplaysInputSchema, ListSessionReplaysResultSchema),
|
|
14884
|
-
getSessionReplay: customAction(GetSessionReplayInputSchema, StoredSessionReplaySchema.nullable())
|
|
14885
|
-
});
|
|
14886
|
-
|
|
14887
|
-
// src/replay/mjpeg-split.ts
|
|
14888
|
-
var SOI = Buffer.from([255, 216]);
|
|
14889
|
-
var EOI = Buffer.from([255, 217]);
|
|
14890
|
-
function splitMjpegStream(bytes) {
|
|
14891
|
-
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
14892
|
-
const out = [];
|
|
14893
|
-
let pos = 0;
|
|
14894
|
-
for (; ; ) {
|
|
14895
|
-
const soi = buf.indexOf(SOI, pos);
|
|
14896
|
-
if (soi === -1) break;
|
|
14897
|
-
const eoi = buf.indexOf(EOI, soi + SOI.length);
|
|
14898
|
-
if (eoi === -1) break;
|
|
14899
|
-
out.push(buf.subarray(soi, eoi + EOI.length));
|
|
14900
|
-
pos = eoi + EOI.length;
|
|
14901
|
-
}
|
|
14902
|
-
return out;
|
|
14903
|
-
}
|
|
14904
|
-
|
|
14905
|
-
// src/replay/replay-decode.ts
|
|
14906
|
-
import { spawn } from "child_process";
|
|
14907
|
-
function buildReplaySequenceArgs(opts) {
|
|
14908
|
-
const { startSec, durationSec, fps, maxWidth } = opts;
|
|
14909
|
-
return [
|
|
14910
|
-
"-hide_banner",
|
|
14911
|
-
"-loglevel",
|
|
14912
|
-
"error",
|
|
14913
|
-
"-nostdin",
|
|
14914
|
-
"-ss",
|
|
14915
|
-
String(Math.max(0, startSec)),
|
|
14916
|
-
"-i",
|
|
14917
|
-
"pipe:0",
|
|
14918
|
-
"-t",
|
|
14919
|
-
String(Math.max(0, durationSec)),
|
|
14920
|
-
"-vf",
|
|
14921
|
-
`fps=${fps},scale='min(${maxWidth},iw)':-2`,
|
|
14922
|
-
"-f",
|
|
14923
|
-
"mjpeg",
|
|
14924
|
-
"pipe:1"
|
|
14925
|
-
];
|
|
14926
|
-
}
|
|
14927
|
-
var REPLAY_DECODE_MAX_CONCURRENT = 2;
|
|
14928
|
-
var REPLAY_DECODE_TIMEOUT_MS = 15e3;
|
|
14929
|
-
var KILL_GRACE_MS = 500;
|
|
14930
|
-
var ReplayDecoder = class {
|
|
14931
|
-
resolveFfmpegPath;
|
|
14932
|
-
spawnFn;
|
|
14933
|
-
maxConcurrent;
|
|
14934
|
-
timeoutMs;
|
|
14935
|
-
inFlight = 0;
|
|
14936
|
-
constructor(deps) {
|
|
14937
|
-
this.resolveFfmpegPath = deps.resolveFfmpegPath;
|
|
14938
|
-
this.spawnFn = deps.spawnFn ?? ((bin, args) => spawn(bin, [...args]));
|
|
14939
|
-
this.maxConcurrent = deps.maxConcurrent ?? REPLAY_DECODE_MAX_CONCURRENT;
|
|
14940
|
-
this.timeoutMs = deps.timeoutMs ?? REPLAY_DECODE_TIMEOUT_MS;
|
|
14941
|
-
}
|
|
14942
|
-
/** Decodes currently running (tests / metrics). */
|
|
14943
|
-
get running() {
|
|
14944
|
-
return this.inFlight;
|
|
14945
|
-
}
|
|
14946
|
-
/**
|
|
14947
|
-
* Run the sequence once, feeding `input` on stdin (`-i pipe:0` in
|
|
14948
|
-
* {@link buildReplaySequenceArgs}) — the same shape as
|
|
14949
|
-
* `RecordedStillService`, which pipes a GOP rather than pointing ffmpeg at
|
|
14950
|
-
* a file path, because the source bytes (a recorder GOP window, or an
|
|
14951
|
-
* uploaded clip) are fetched through a capability call, not a local path.
|
|
14952
|
-
* Resolves with the whole MJPEG stdout stream.
|
|
14953
|
-
*/
|
|
14954
|
-
async decodeSequence(args, input, opts = {}) {
|
|
14955
|
-
if (this.inFlight >= this.maxConcurrent) {
|
|
14956
|
-
throw new Error(
|
|
14957
|
-
`replay decode busy: node already running ${this.inFlight}/${this.maxConcurrent} decodes`
|
|
14958
|
-
);
|
|
14959
|
-
}
|
|
14960
|
-
this.inFlight += 1;
|
|
14961
|
-
try {
|
|
14962
|
-
const ffmpegPath = await this.resolveFfmpegPath();
|
|
14963
|
-
return await this.runOnce(ffmpegPath, args, input, opts.timeoutMs ?? this.timeoutMs);
|
|
14964
|
-
} finally {
|
|
14965
|
-
this.inFlight -= 1;
|
|
14966
|
-
}
|
|
14967
|
-
}
|
|
14968
|
-
runOnce(ffmpegPath, args, input, timeoutMs) {
|
|
14969
|
-
return new Promise((resolve, reject) => {
|
|
14970
|
-
const chunks = [];
|
|
14971
|
-
const stderrTail = [];
|
|
14972
|
-
let settled = false;
|
|
14973
|
-
const child = (() => {
|
|
14974
|
-
try {
|
|
14975
|
-
return this.spawnFn(ffmpegPath, args);
|
|
14976
|
-
} catch (err) {
|
|
14977
|
-
reject(new Error(`replay decode: ffmpeg spawn failed: ${String(err)}`));
|
|
14978
|
-
return null;
|
|
14979
|
-
}
|
|
14980
|
-
})();
|
|
14981
|
-
if (child === null) return;
|
|
14982
|
-
const settle = (fn) => {
|
|
14983
|
-
if (settled) return;
|
|
14984
|
-
settled = true;
|
|
14985
|
-
clearTimeout(timer);
|
|
14986
|
-
fn();
|
|
14987
|
-
};
|
|
14988
|
-
const kill = () => {
|
|
14989
|
-
try {
|
|
14990
|
-
child.kill("SIGTERM");
|
|
14991
|
-
} catch {
|
|
14992
|
-
}
|
|
14993
|
-
const t = setTimeout(() => {
|
|
14994
|
-
if (child.exitCode === null && child.signalCode === null) {
|
|
14995
|
-
try {
|
|
14996
|
-
child.kill("SIGKILL");
|
|
14997
|
-
} catch {
|
|
14998
|
-
}
|
|
14999
|
-
}
|
|
15000
|
-
}, KILL_GRACE_MS);
|
|
15001
|
-
t.unref?.();
|
|
15002
|
-
};
|
|
15003
|
-
const timer = setTimeout(() => {
|
|
15004
|
-
settle(() => reject(new Error("replay decode: timed out")));
|
|
15005
|
-
kill();
|
|
15006
|
-
}, timeoutMs);
|
|
15007
|
-
timer.unref?.();
|
|
15008
|
-
child.stdout?.on("data", (chunk) => chunks.push(chunk));
|
|
15009
|
-
child.stderr?.on("data", (data) => {
|
|
15010
|
-
const line = data.toString().trim();
|
|
15011
|
-
if (line) {
|
|
15012
|
-
stderrTail.push(line);
|
|
15013
|
-
if (stderrTail.length > 8) stderrTail.shift();
|
|
15014
|
-
}
|
|
15015
|
-
});
|
|
15016
|
-
child.on("error", (err) => {
|
|
15017
|
-
settle(() => reject(new Error(`replay decode: ffmpeg process error: ${err.message}`)));
|
|
15018
|
-
});
|
|
15019
|
-
child.on("exit", (code, signal) => {
|
|
15020
|
-
const stdout = Buffer.concat(chunks);
|
|
15021
|
-
if (code === 0) {
|
|
15022
|
-
settle(() => resolve(stdout));
|
|
15023
|
-
return;
|
|
15024
|
-
}
|
|
15025
|
-
settle(
|
|
15026
|
-
() => reject(
|
|
15027
|
-
new Error(
|
|
15028
|
-
`replay decode: ffmpeg exited code=${String(code)} signal=${String(signal)}: ${stderrTail.join(" | ") || "(no stderr)"}`
|
|
15029
|
-
)
|
|
15030
|
-
)
|
|
15031
|
-
);
|
|
15032
|
-
});
|
|
15033
|
-
try {
|
|
15034
|
-
child.stdin?.write(Buffer.from(input.buffer, input.byteOffset, input.byteLength));
|
|
15035
|
-
child.stdin?.end();
|
|
15036
|
-
} catch {
|
|
15037
|
-
}
|
|
15038
|
-
});
|
|
15039
|
-
}
|
|
15040
|
-
};
|
|
15041
|
-
|
|
15042
|
-
// src/replay/replay-store.ts
|
|
15043
|
-
var REPLAY_RUNS_COLLECTION = "benchmark:replay-runs";
|
|
15044
|
-
var REPLAY_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
15045
|
-
var REPLAY_RUNS_COLUMNS = [
|
|
15046
|
-
{ name: "id", type: "TEXT", primaryKey: true, notNull: true },
|
|
15047
|
-
{ name: "deviceId", type: "INTEGER", notNull: true },
|
|
15048
|
-
{ name: "nodeId", type: "TEXT", notNull: true },
|
|
15049
|
-
{ name: "clipId", type: "TEXT", notNull: true },
|
|
15050
|
-
{ name: "createdAtMs", type: "INTEGER", notNull: true },
|
|
15051
|
-
{ name: "source", type: "JSON", notNull: true },
|
|
15052
|
-
{ name: "plan", type: "JSON", notNull: true },
|
|
15053
|
-
{ name: "verdict", type: "JSON", notNull: true }
|
|
15054
|
-
];
|
|
15055
|
-
var REPLAY_RUNS_INDEXES = [
|
|
15056
|
-
{ name: "idx_replay_runs_device_created", columns: ["deviceId", "createdAtMs"] },
|
|
15057
|
-
{ name: "idx_replay_runs_created", columns: ["createdAtMs"] }
|
|
15058
|
-
];
|
|
15059
|
-
var StoredRowSchema = external_exports.object({
|
|
15060
|
-
deviceId: external_exports.number(),
|
|
15061
|
-
nodeId: external_exports.string(),
|
|
15062
|
-
createdAtMs: external_exports.number(),
|
|
15063
|
-
source: external_exports.object({ kind: external_exports.literal("motion-session") }).loose(),
|
|
15064
|
-
verdict: SessionReplayReportSchema
|
|
15065
|
-
});
|
|
15066
|
-
var ReplayStore = class _ReplayStore {
|
|
15067
|
-
constructor(store) {
|
|
15068
|
-
this.store = store;
|
|
15069
|
-
}
|
|
15070
|
-
store;
|
|
15071
|
-
/** Declare the collection. Call in `onInitialize`, before the first
|
|
15072
|
-
* `save` — an undeclared collection crash-loops the runner. */
|
|
15073
|
-
static async declare(store) {
|
|
15074
|
-
await store.declareCollection.mutate({
|
|
15075
|
-
collection: REPLAY_RUNS_COLLECTION,
|
|
15076
|
-
columns: [...REPLAY_RUNS_COLUMNS],
|
|
15077
|
-
indexes: [...REPLAY_RUNS_INDEXES]
|
|
15078
|
-
});
|
|
15079
|
-
}
|
|
15080
|
-
async save(run) {
|
|
15081
|
-
const { report } = run;
|
|
15082
|
-
await this.store.insert.mutate({
|
|
15083
|
-
collection: REPLAY_RUNS_COLLECTION,
|
|
15084
|
-
record: {
|
|
15085
|
-
id: run.id,
|
|
15086
|
-
data: {
|
|
15087
|
-
deviceId: run.deviceId,
|
|
15088
|
-
nodeId: run.nodeId,
|
|
15089
|
-
clipId: `session:${report.deviceId}:${report.motionEventId}`,
|
|
15090
|
-
createdAtMs: run.createdAtMs,
|
|
15091
|
-
source: { kind: "motion-session", motionEventId: report.motionEventId, ...report.window },
|
|
15092
|
-
plan: {
|
|
15093
|
-
pin: report.pin,
|
|
15094
|
-
rootStep: report.rootStep,
|
|
15095
|
-
cadence: report.cadence,
|
|
15096
|
-
frameSize: report.frameSize
|
|
15097
|
-
},
|
|
15098
|
-
verdict: report
|
|
15099
|
-
}
|
|
15100
|
-
}
|
|
15101
|
-
});
|
|
15102
|
-
}
|
|
15103
|
-
/** Session replays newest-first, optionally scoped to one camera. */
|
|
15104
|
-
async list(query) {
|
|
15105
|
-
const rows = await this.store.query.query({
|
|
15106
|
-
collection: REPLAY_RUNS_COLLECTION,
|
|
15107
|
-
filter: {
|
|
15108
|
-
orderBy: { field: "createdAtMs", direction: "desc" },
|
|
15109
|
-
...query.deviceId !== void 0 ? { where: { deviceId: query.deviceId } } : {},
|
|
15110
|
-
...query.limit !== void 0 ? { limit: query.limit } : {}
|
|
15111
|
-
}
|
|
15112
|
-
});
|
|
15113
|
-
const runs = [];
|
|
15114
|
-
let skipped = 0;
|
|
15115
|
-
for (const row of rows) {
|
|
15116
|
-
const parsed = _ReplayStore.readRow(row.id, row.data);
|
|
15117
|
-
if (parsed === null) skipped += 1;
|
|
15118
|
-
else runs.push(parsed);
|
|
15119
|
-
}
|
|
15120
|
-
return { runs, skipped };
|
|
15121
|
-
}
|
|
15122
|
-
async get(id) {
|
|
15123
|
-
const rows = await this.store.query.query({
|
|
15124
|
-
collection: REPLAY_RUNS_COLLECTION,
|
|
15125
|
-
filter: { where: { id }, limit: 1 }
|
|
15126
|
-
});
|
|
15127
|
-
const row = rows[0];
|
|
15128
|
-
return row === void 0 ? null : _ReplayStore.readRow(row.id, row.data);
|
|
15129
|
-
}
|
|
15130
|
-
/** Delete every run created before `cutoffMs`. This store's OWN retention
|
|
15131
|
-
* clock — see the file header for why that matters. */
|
|
15132
|
-
async pruneBefore(cutoffMs) {
|
|
15133
|
-
const { deleted } = await this.store.deleteWhere.mutate({
|
|
15134
|
-
collection: REPLAY_RUNS_COLLECTION,
|
|
15135
|
-
filter: { whereBetween: { createdAtMs: [0, cutoffMs] } }
|
|
15136
|
-
});
|
|
15137
|
-
return deleted;
|
|
15138
|
-
}
|
|
15139
|
-
static readRow(id, data) {
|
|
15140
|
-
const parsed = StoredRowSchema.safeParse(data);
|
|
15141
|
-
if (!parsed.success) return null;
|
|
15142
|
-
return {
|
|
15143
|
-
id,
|
|
15144
|
-
deviceId: parsed.data.deviceId,
|
|
15145
|
-
nodeId: parsed.data.nodeId,
|
|
15146
|
-
createdAtMs: parsed.data.createdAtMs,
|
|
15147
|
-
report: parsed.data.verdict
|
|
15148
|
-
};
|
|
15149
|
-
}
|
|
15150
|
-
};
|
|
15151
|
-
|
|
15152
|
-
// src/session-replay/recording-reader.ts
|
|
15153
|
-
async function readRecordingForInstants(deps, instants, stepMs) {
|
|
15154
|
-
const parts = [];
|
|
15155
|
-
let withoutFootage = 0;
|
|
15156
|
-
let truncated = false;
|
|
15157
|
-
let i = 0;
|
|
15158
|
-
while (i < instants.length) {
|
|
15159
|
-
const t = instants[i];
|
|
15160
|
-
if (t === void 0) break;
|
|
15161
|
-
const located = await deps.locateSegment(t);
|
|
15162
|
-
if (located.kind !== "segment") {
|
|
15163
|
-
const edge = located.nearestEdgeMs;
|
|
15164
|
-
let j2 = i + 1;
|
|
15165
|
-
if (edge === null) j2 = instants.length;
|
|
15166
|
-
else if (edge > t) while (j2 < instants.length && (instants[j2] ?? edge) < edge) j2++;
|
|
15167
|
-
withoutFootage += j2 - i;
|
|
15168
|
-
i = j2;
|
|
15169
|
-
continue;
|
|
15170
|
-
}
|
|
15171
|
-
const segEnd = located.startMs + located.durMs;
|
|
15172
|
-
let j = i;
|
|
15173
|
-
while (j < instants.length && (instants[j] ?? segEnd) < segEnd) j++;
|
|
15174
|
-
const covered = instants.slice(i, j);
|
|
15175
|
-
const lastCovered = covered[covered.length - 1] ?? t;
|
|
15176
|
-
const window = await deps.readWindowBytes({
|
|
15177
|
-
startMs: located.startMs,
|
|
15178
|
-
fromMs: t,
|
|
15179
|
-
toMs: Math.min(segEnd, lastCovered + Math.round(stepMs))
|
|
15180
|
-
});
|
|
15181
|
-
if (window.kind === "ok") {
|
|
15182
|
-
if (!window.reachesRequestedEnd) truncated = true;
|
|
15183
|
-
parts.push({ bytes: window.data, gopStartMs: window.gopStartMs, instants: covered });
|
|
15184
|
-
} else {
|
|
15185
|
-
withoutFootage += covered.length;
|
|
15186
|
-
}
|
|
15187
|
-
i = j === i ? i + 1 : j;
|
|
15188
|
-
}
|
|
15189
|
-
if (parts.length === 0) {
|
|
15190
|
-
return {
|
|
15191
|
-
kind: "refused",
|
|
15192
|
-
reason: "no-footage",
|
|
15193
|
-
detail: `no recording covers any of the ${instants.length} planned instants`
|
|
15194
|
-
};
|
|
15195
|
-
}
|
|
15196
|
-
return {
|
|
15197
|
-
kind: "ok",
|
|
15198
|
-
parts,
|
|
15199
|
-
instantsWithoutFootage: withoutFootage,
|
|
15200
|
-
truncatedBySafetyCap: truncated
|
|
15201
|
-
};
|
|
15202
|
-
}
|
|
15203
|
-
|
|
15204
|
-
// src/session-replay/cadence.ts
|
|
15205
|
-
var DEFAULT_DETECTION_FPS = 10;
|
|
15206
|
-
var MIN_DETECTION_FPS = 1;
|
|
15207
|
-
var MAX_DETECTION_FPS = 30;
|
|
15208
|
-
var MIN_GAPS_TO_MEASURE = 3;
|
|
15209
|
-
function clampFps(fps) {
|
|
15210
|
-
return Math.min(MAX_DETECTION_FPS, Math.max(MIN_DETECTION_FPS, fps));
|
|
15211
|
-
}
|
|
15212
|
-
function resolveReplayCadence(requestedFps, positionTimesByTrack) {
|
|
15213
|
-
if (requestedFps !== void 0) return { fps: clampFps(requestedFps), source: "operator" };
|
|
15214
|
-
const gaps = [];
|
|
15215
|
-
for (const times of positionTimesByTrack) {
|
|
15216
|
-
for (let i = 1; i < times.length; i++) {
|
|
15217
|
-
const prev = times[i - 1];
|
|
15218
|
-
const cur = times[i];
|
|
15219
|
-
if (prev !== void 0 && cur !== void 0 && cur > prev) gaps.push(cur - prev);
|
|
15220
|
-
}
|
|
15221
|
-
}
|
|
15222
|
-
if (gaps.length < MIN_GAPS_TO_MEASURE) return { fps: DEFAULT_DETECTION_FPS, source: "default" };
|
|
15223
|
-
const sorted = gaps.toSorted((a, b) => a - b);
|
|
15224
|
-
const quartile = sorted[Math.floor((sorted.length - 1) / 4)] ?? 0;
|
|
15225
|
-
if (!(quartile > 0)) return { fps: DEFAULT_DETECTION_FPS, source: "default" };
|
|
15226
|
-
return { fps: clampFps(Math.round(1e3 / quartile)), source: "stored-positions" };
|
|
15227
|
-
}
|
|
15228
|
-
|
|
15229
|
-
// src/session-replay/compare.ts
|
|
15230
|
-
var MATCH_IOU_MIN = 0.5;
|
|
15231
|
-
var RELAXED_IOU_MIN = 0.3;
|
|
15232
|
-
var MAX_INTERPOLATION_GAP_MS = 5e3;
|
|
15233
|
-
function iou(a, b) {
|
|
15234
|
-
const x1 = Math.max(a.x, b.x);
|
|
15235
|
-
const y1 = Math.max(a.y, b.y);
|
|
15236
|
-
const x2 = Math.min(a.x + a.w, b.x + b.w);
|
|
15237
|
-
const y2 = Math.min(a.y + a.h, b.y + b.h);
|
|
15238
|
-
const inter = Math.max(0, x2 - x1) * Math.max(0, y2 - y1);
|
|
15239
|
-
if (inter === 0) return 0;
|
|
15240
|
-
const union2 = a.w * a.h + b.w * b.h - inter;
|
|
15241
|
-
return union2 > 0 ? inter / union2 : 0;
|
|
15242
|
-
}
|
|
15243
|
-
function lerp(a, b, f) {
|
|
15244
|
-
return a + (b - a) * f;
|
|
15245
|
-
}
|
|
15246
|
-
function entry(p, t, basis) {
|
|
15247
|
-
return { ...p, t, basis };
|
|
15248
|
-
}
|
|
15249
|
-
function trackAt(track, t, halfStepMs) {
|
|
15250
|
-
let prev;
|
|
15251
|
-
let next;
|
|
15252
|
-
let nearest;
|
|
15253
|
-
for (const p of track.positions) {
|
|
15254
|
-
if (p.t <= t) prev = p;
|
|
15255
|
-
if (p.t >= t && next === void 0) next = p;
|
|
15256
|
-
if (nearest === void 0 || Math.abs(p.t - t) < Math.abs(nearest.t - t)) nearest = p;
|
|
15257
|
-
}
|
|
15258
|
-
if (nearest !== void 0 && Math.abs(nearest.t - t) <= halfStepMs) {
|
|
15259
|
-
return entry(nearest, nearest.t, "exact");
|
|
15260
|
-
}
|
|
15261
|
-
if (t < track.firstSeen || t > track.lastSeen || nearest === void 0) return null;
|
|
15262
|
-
if (prev !== void 0 && next !== void 0 && next.t - prev.t <= MAX_INTERPOLATION_GAP_MS) {
|
|
15263
|
-
const f = next.t === prev.t ? 0 : (t - prev.t) / (next.t - prev.t);
|
|
15264
|
-
return {
|
|
15265
|
-
trackId: track.trackId,
|
|
15266
|
-
className: track.className,
|
|
15267
|
-
t,
|
|
15268
|
-
box: {
|
|
15269
|
-
x: lerp(prev.box.x, next.box.x, f),
|
|
15270
|
-
y: lerp(prev.box.y, next.box.y, f),
|
|
15271
|
-
w: lerp(prev.box.w, next.box.w, f),
|
|
15272
|
-
h: lerp(prev.box.h, next.box.h, f)
|
|
15273
|
-
},
|
|
15274
|
-
score: prev.score !== null && next.score !== null ? lerp(prev.score, next.score, f) : null,
|
|
15275
|
-
basis: "interpolated"
|
|
15276
|
-
};
|
|
15277
|
-
}
|
|
15278
|
-
return entry(nearest, t, "uncertain");
|
|
15279
|
-
}
|
|
15280
|
-
function referenceAt(tracks, t, halfStepMs) {
|
|
15281
|
-
const entries = [];
|
|
15282
|
-
let live = false;
|
|
15283
|
-
for (const track of tracks) {
|
|
15284
|
-
if (t >= track.firstSeen && t <= track.lastSeen) live = true;
|
|
15285
|
-
const e = trackAt(track, t, halfStepMs);
|
|
15286
|
-
if (e !== null) {
|
|
15287
|
-
entries.push(e);
|
|
15288
|
-
live = true;
|
|
15289
|
-
}
|
|
15290
|
-
}
|
|
15291
|
-
return { live, entries };
|
|
15292
|
-
}
|
|
15293
|
-
function sameClass(a, b) {
|
|
15294
|
-
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
15295
|
-
}
|
|
15296
|
-
function barFor(basis, iouMin) {
|
|
15297
|
-
if (basis === "exact") return iouMin;
|
|
15298
|
-
if (basis === "interpolated") return Math.min(iouMin, RELAXED_IOU_MIN);
|
|
15299
|
-
return 0;
|
|
15300
|
-
}
|
|
15301
|
-
function pairUp(replay, stored, accept, bar) {
|
|
15302
|
-
const candidates = [];
|
|
15303
|
-
replay.forEach((r, ri) => {
|
|
15304
|
-
stored.forEach((s, si) => {
|
|
15305
|
-
if (!accept(r, s)) return;
|
|
15306
|
-
const v = iou(r.box, s.box);
|
|
15307
|
-
if (v >= bar(s)) candidates.push({ r: ri, s: si, iou: v });
|
|
15308
|
-
});
|
|
15309
|
-
});
|
|
15310
|
-
const ranked = candidates.toSorted((a, b) => b.iou - a.iou);
|
|
15311
|
-
const usedR = /* @__PURE__ */ new Set();
|
|
15312
|
-
const usedS = /* @__PURE__ */ new Set();
|
|
15313
|
-
const pairs = [];
|
|
15314
|
-
for (const c of ranked) {
|
|
15315
|
-
if (usedR.has(c.r) || usedS.has(c.s)) continue;
|
|
15316
|
-
const r = replay[c.r];
|
|
15317
|
-
const s = stored[c.s];
|
|
15318
|
-
if (r === void 0 || s === void 0) continue;
|
|
15319
|
-
usedR.add(c.r);
|
|
15320
|
-
usedS.add(c.s);
|
|
15321
|
-
pairs.push({
|
|
15322
|
-
replay: r,
|
|
15323
|
-
stored: s,
|
|
15324
|
-
iou: c.iou,
|
|
15325
|
-
scoreDelta: s.score === null ? null : r.score - s.score
|
|
15326
|
-
});
|
|
15327
|
-
}
|
|
15328
|
-
return {
|
|
15329
|
-
pairs,
|
|
15330
|
-
restReplay: replay.filter((_r, i) => !usedR.has(i)),
|
|
15331
|
-
restStored: stored.filter((_s, i) => !usedS.has(i))
|
|
15332
|
-
};
|
|
15333
|
-
}
|
|
15334
|
-
function compareFrame(t, replay, reference, iouMin = MATCH_IOU_MIN) {
|
|
15335
|
-
const same = pairUp(
|
|
15336
|
-
replay,
|
|
15337
|
-
reference.entries,
|
|
15338
|
-
(r, s) => sameClass(r.className, s.className),
|
|
15339
|
-
(s) => barFor(s.basis, iouMin)
|
|
15340
|
-
);
|
|
15341
|
-
const changed = pairUp(
|
|
15342
|
-
same.restReplay,
|
|
15343
|
-
same.restStored,
|
|
15344
|
-
(_r, s) => s.basis !== "uncertain",
|
|
15345
|
-
(s) => barFor(s.basis, iouMin)
|
|
15346
|
-
);
|
|
15347
|
-
return {
|
|
15348
|
-
t,
|
|
15349
|
-
referenced: reference.live,
|
|
15350
|
-
matched: same.pairs,
|
|
15351
|
-
classChanged: changed.pairs,
|
|
15352
|
-
added: reference.live ? changed.restReplay : [],
|
|
15353
|
-
unreferenced: reference.live ? [] : changed.restReplay,
|
|
15354
|
-
missed: changed.restStored
|
|
15355
|
-
};
|
|
15356
|
-
}
|
|
15357
|
-
function percentile(sorted, q) {
|
|
15358
|
-
const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil(q * sorted.length) - 1));
|
|
15359
|
-
return sorted[idx] ?? 0;
|
|
15360
|
-
}
|
|
15361
|
-
function summarizeReplay(frames, inferenceMs) {
|
|
15362
|
-
const perClass = /* @__PURE__ */ new Map();
|
|
15363
|
-
const bump = (cls, field) => {
|
|
15364
|
-
const key = cls.trim().toLowerCase();
|
|
15365
|
-
const e = perClass.get(key) ?? {
|
|
15366
|
-
replay: 0,
|
|
15367
|
-
stored: 0,
|
|
15368
|
-
matched: 0,
|
|
15369
|
-
added: 0,
|
|
15370
|
-
missed: 0,
|
|
15371
|
-
unreferenced: 0,
|
|
15372
|
-
interpolated: 0
|
|
15373
|
-
};
|
|
15374
|
-
e[field] += 1;
|
|
15375
|
-
perClass.set(key, e);
|
|
15376
|
-
};
|
|
15377
|
-
const deltas = [];
|
|
15378
|
-
let matched = 0;
|
|
15379
|
-
let classChanged = 0;
|
|
15380
|
-
let added = 0;
|
|
15381
|
-
let missed = 0;
|
|
15382
|
-
let unreferenced = 0;
|
|
15383
|
-
let interpolated = 0;
|
|
15384
|
-
const noteBasis = (s) => {
|
|
15385
|
-
if (s.basis === "exact") return;
|
|
15386
|
-
interpolated += 1;
|
|
15387
|
-
bump(s.className, "interpolated");
|
|
15388
|
-
};
|
|
15389
|
-
for (const f of frames) {
|
|
15390
|
-
for (const p of f.matched) {
|
|
15391
|
-
matched += 1;
|
|
15392
|
-
bump(p.replay.className, "replay");
|
|
15393
|
-
bump(p.stored.className, "stored");
|
|
15394
|
-
bump(p.replay.className, "matched");
|
|
15395
|
-
noteBasis(p.stored);
|
|
15396
|
-
if (p.scoreDelta !== null) deltas.push(p.scoreDelta);
|
|
15397
|
-
}
|
|
15398
|
-
for (const p of f.classChanged) {
|
|
15399
|
-
classChanged += 1;
|
|
15400
|
-
bump(p.replay.className, "replay");
|
|
15401
|
-
bump(p.stored.className, "stored");
|
|
15402
|
-
noteBasis(p.stored);
|
|
15403
|
-
}
|
|
15404
|
-
for (const r of f.added) {
|
|
15405
|
-
added += 1;
|
|
15406
|
-
bump(r.className, "replay");
|
|
15407
|
-
bump(r.className, "added");
|
|
15408
|
-
}
|
|
15409
|
-
for (const r of f.unreferenced) {
|
|
15410
|
-
unreferenced += 1;
|
|
15411
|
-
bump(r.className, "replay");
|
|
15412
|
-
bump(r.className, "unreferenced");
|
|
15413
|
-
}
|
|
15414
|
-
for (const s of f.missed) {
|
|
15415
|
-
missed += 1;
|
|
15416
|
-
bump(s.className, "stored");
|
|
15417
|
-
bump(s.className, "missed");
|
|
15418
|
-
noteBasis(s);
|
|
15419
|
-
}
|
|
15420
|
-
}
|
|
15421
|
-
const sortedDeltas = deltas.toSorted((a, b) => a - b);
|
|
15422
|
-
const sortedInfer = [...inferenceMs].toSorted((a, b) => a - b);
|
|
15423
|
-
return {
|
|
15424
|
-
totals: { matched, classChanged, added, missed, unreferenced, interpolated },
|
|
15425
|
-
perClass: Object.fromEntries(
|
|
15426
|
-
[...perClass.entries()].toSorted(([a], [b]) => a.localeCompare(b))
|
|
15427
|
-
),
|
|
15428
|
-
scoreDelta: sortedDeltas.length === 0 ? null : {
|
|
15429
|
-
mean: sortedDeltas.reduce((s, v) => s + v, 0) / sortedDeltas.length,
|
|
15430
|
-
p10: percentile(sortedDeltas, 0.1),
|
|
15431
|
-
p50: percentile(sortedDeltas, 0.5),
|
|
15432
|
-
p90: percentile(sortedDeltas, 0.9)
|
|
15433
|
-
},
|
|
15434
|
-
inferenceMs: sortedInfer.length === 0 ? null : {
|
|
15435
|
-
p50: percentile(sortedInfer, 0.5),
|
|
15436
|
-
p95: percentile(sortedInfer, 0.95),
|
|
15437
|
-
count: sortedInfer.length
|
|
15438
|
-
}
|
|
15439
|
-
};
|
|
15440
|
-
}
|
|
15441
|
-
|
|
15442
|
-
// src/session-replay/frame-plan.ts
|
|
15443
|
-
var REPLAY_CHUNK_MS = 1e4;
|
|
15444
|
-
function planReplayFrames(window, fps, maxFrames, chunkMs = REPLAY_CHUNK_MS) {
|
|
15445
|
-
const stepMs = 1e3 / fps;
|
|
15446
|
-
const instants = [];
|
|
15447
|
-
let truncated = false;
|
|
15448
|
-
for (let k = 0; ; k++) {
|
|
15449
|
-
const t = window.startMs + Math.round(k * stepMs);
|
|
15450
|
-
if (t >= window.endMs) break;
|
|
15451
|
-
if (instants.length >= maxFrames) {
|
|
15452
|
-
truncated = true;
|
|
15453
|
-
break;
|
|
15454
|
-
}
|
|
15455
|
-
instants.push(t);
|
|
15456
|
-
}
|
|
15457
|
-
const chunks = [];
|
|
15458
|
-
let current = [];
|
|
15459
|
-
let chunkStart = window.startMs;
|
|
15460
|
-
const flush = (toMs) => {
|
|
15461
|
-
if (current.length > 0) chunks.push({ fromMs: chunkStart, toMs, instants: current });
|
|
15462
|
-
current = [];
|
|
15463
|
-
};
|
|
15464
|
-
for (const t of instants) {
|
|
15465
|
-
while (t >= chunkStart + chunkMs) {
|
|
15466
|
-
flush(chunkStart + chunkMs);
|
|
15467
|
-
chunkStart += chunkMs;
|
|
15468
|
-
}
|
|
15469
|
-
current.push(t);
|
|
15470
|
-
}
|
|
15471
|
-
const last = instants[instants.length - 1];
|
|
15472
|
-
flush(
|
|
15473
|
-
truncated && last !== void 0 ? last + Math.round(stepMs) : Math.min(window.endMs, chunkStart + chunkMs)
|
|
15474
|
-
);
|
|
15475
|
-
return { chunks, planned: instants.length, truncated };
|
|
15476
|
-
}
|
|
15477
|
-
|
|
15478
|
-
// src/session-replay/replay-steps.ts
|
|
15479
|
-
var REPLAY_ROOT_STEP = "object-detection";
|
|
15480
|
-
function selectRootReplayStep(tree, requestedModelId) {
|
|
15481
|
-
const root = tree.steps.find((s) => s.addonId === REPLAY_ROOT_STEP);
|
|
15482
|
-
if (root === void 0 || !root.enabled) {
|
|
15483
|
-
return {
|
|
15484
|
-
kind: "refused",
|
|
15485
|
-
reason: "no-root-step",
|
|
15486
|
-
detail: `the camera does not run ${REPLAY_ROOT_STEP} as an enabled root step`
|
|
15487
|
-
};
|
|
15488
|
-
}
|
|
15489
|
-
const cameraModelId = root.modelId ?? null;
|
|
15490
|
-
const modelId = requestedModelId ?? cameraModelId;
|
|
15491
|
-
if (modelId === null || modelId.length === 0) {
|
|
15492
|
-
return {
|
|
15493
|
-
kind: "refused",
|
|
15494
|
-
reason: "model-unpinned",
|
|
15495
|
-
detail: `the camera's ${REPLAY_ROOT_STEP} names no model (it runs the node's format default); pick the model to replay explicitly`
|
|
15496
|
-
};
|
|
15497
|
-
}
|
|
15498
|
-
return {
|
|
15499
|
-
kind: "ok",
|
|
15500
|
-
cameraModelId,
|
|
15501
|
-
step: {
|
|
15502
|
-
addonId: root.addonId,
|
|
15503
|
-
modelId,
|
|
15504
|
-
enabled: true,
|
|
15505
|
-
...root.settings !== void 0 ? { settings: root.settings } : {},
|
|
15506
|
-
children: []
|
|
15507
|
-
}
|
|
15508
|
-
};
|
|
15509
|
-
}
|
|
15510
|
-
|
|
15511
|
-
// src/session-replay/session-window.ts
|
|
15512
|
-
var SESSION_PREROLL_MS = 5e3;
|
|
15513
|
-
var SESSION_TAIL_MS = 3e4;
|
|
15514
|
-
var SESSION_MAX_WINDOW_MS = 10 * 6e4;
|
|
15515
|
-
function motionSessionWindow(session, camera = {}, opts = {}) {
|
|
15516
|
-
if (session.durationMs === null) {
|
|
15517
|
-
return {
|
|
15518
|
-
kind: "refused",
|
|
15519
|
-
reason: "session-open",
|
|
15520
|
-
detail: `motion session ${session.id} is still open \u2014 its end is not known yet`
|
|
15521
|
-
};
|
|
15522
|
-
}
|
|
15523
|
-
if (session.durationMs === void 0) {
|
|
15524
|
-
return {
|
|
15525
|
-
kind: "refused",
|
|
15526
|
-
reason: "session-length-unknown",
|
|
15527
|
-
detail: `motion session ${session.id} predates the episode duration column \u2014 its length is unknown`
|
|
15528
|
-
};
|
|
15529
|
-
}
|
|
15530
|
-
const motionEndMs = session.timestamp + session.durationMs;
|
|
15531
|
-
const prerollSource = camera.preRollInferenceEnabled === false ? "camera" : "default";
|
|
15532
|
-
const prerollMs = camera.preRollInferenceEnabled === false ? 0 : SESSION_PREROLL_MS;
|
|
15533
|
-
const tailSource = camera.motionCooldownMs !== void 0 ? "camera" : "default";
|
|
15534
|
-
const tailMs = camera.motionCooldownMs ?? SESSION_TAIL_MS;
|
|
15535
|
-
const startMs = session.timestamp - prerollMs;
|
|
15536
|
-
const endMs = motionEndMs + tailMs;
|
|
15537
|
-
const maxWindowMs = opts.maxWindowMs ?? SESSION_MAX_WINDOW_MS;
|
|
15538
|
-
if (endMs - startMs > maxWindowMs) {
|
|
15539
|
-
return {
|
|
15540
|
-
kind: "refused",
|
|
15541
|
-
reason: "window-too-long",
|
|
15542
|
-
detail: `motion session ${session.id} spans ${Math.round((endMs - startMs) / 1e3)} s \u2014 longer than the ${Math.round(maxWindowMs / 1e3)} s a replay reads`
|
|
15543
|
-
};
|
|
15544
|
-
}
|
|
15545
|
-
return {
|
|
15546
|
-
kind: "window",
|
|
15547
|
-
motionEventId: session.id,
|
|
15548
|
-
motionStartMs: session.timestamp,
|
|
15549
|
-
motionEndMs,
|
|
15550
|
-
startMs,
|
|
15551
|
-
endMs,
|
|
15552
|
-
prerollSource,
|
|
15553
|
-
tailSource
|
|
15554
|
-
};
|
|
15555
|
-
}
|
|
15556
|
-
|
|
15557
|
-
// src/session-replay/stored-reference.ts
|
|
15558
|
-
var UNKNOWN_MODEL = "unknown";
|
|
15559
|
-
function buildStoredReference(tracks, window) {
|
|
15560
|
-
const refs = [];
|
|
15561
|
-
const models = /* @__PURE__ */ new Map();
|
|
15562
|
-
const sizes = /* @__PURE__ */ new Map();
|
|
15563
|
-
const timesByTrack = [];
|
|
15564
|
-
let recoveryExcluded = 0;
|
|
15565
|
-
let withoutGeometry = 0;
|
|
15566
|
-
let trackCount = 0;
|
|
15567
|
-
for (const track of tracks) {
|
|
15568
|
-
const inWindow = track.positions.filter(
|
|
15569
|
-
(p) => p.timestamp >= window.startMs && p.timestamp < window.endMs
|
|
15570
|
-
);
|
|
15571
|
-
const aliveInWindow = track.firstSeen < window.endMs && track.lastSeen >= window.startMs;
|
|
15572
|
-
if (inWindow.length === 0 && !aliveInWindow) continue;
|
|
15573
|
-
trackCount += 1;
|
|
15574
|
-
const model = track.modelId ?? UNKNOWN_MODEL;
|
|
15575
|
-
models.set(model, (models.get(model) ?? 0) + 1);
|
|
15576
|
-
const fw = track.frameWidth;
|
|
15577
|
-
const fh = track.frameHeight;
|
|
15578
|
-
const hasGeometry = fw !== void 0 && fh !== void 0 && fw > 0 && fh > 0;
|
|
15579
|
-
if (hasGeometry) {
|
|
15580
|
-
const key = `${fw}x${fh}`;
|
|
15581
|
-
const entry2 = sizes.get(key) ?? { width: fw, height: fh, count: 0 };
|
|
15582
|
-
entry2.count += 1;
|
|
15583
|
-
sizes.set(key, entry2);
|
|
15584
|
-
}
|
|
15585
|
-
const times = [];
|
|
15586
|
-
const positions = [];
|
|
15587
|
-
for (const p of inWindow) {
|
|
15588
|
-
if (p.source === "recovery") {
|
|
15589
|
-
recoveryExcluded += 1;
|
|
15590
|
-
continue;
|
|
15591
|
-
}
|
|
15592
|
-
times.push(p.timestamp);
|
|
15593
|
-
if (!hasGeometry) {
|
|
15594
|
-
withoutGeometry += 1;
|
|
15595
|
-
continue;
|
|
15596
|
-
}
|
|
15597
|
-
positions.push({
|
|
15598
|
-
trackId: track.trackId,
|
|
15599
|
-
className: track.className,
|
|
15600
|
-
t: p.timestamp,
|
|
15601
|
-
box: { x: p.bbox.x / fw, y: p.bbox.y / fh, w: p.bbox.w / fw, h: p.bbox.h / fh },
|
|
15602
|
-
score: p.score ?? null
|
|
15603
|
-
});
|
|
15604
|
-
}
|
|
15605
|
-
timesByTrack.push(times.toSorted((a, b) => a - b));
|
|
15606
|
-
if (hasGeometry) {
|
|
15607
|
-
const sorted = positions.toSorted((a, b) => a.t - b.t);
|
|
15608
|
-
refs.push({
|
|
15609
|
-
trackId: track.trackId,
|
|
15610
|
-
className: track.className,
|
|
15611
|
-
firstSeen: Math.min(track.firstSeen, sorted[0]?.t ?? track.firstSeen),
|
|
15612
|
-
lastSeen: Math.max(track.lastSeen, sorted[sorted.length - 1]?.t ?? track.lastSeen),
|
|
15613
|
-
positions: sorted
|
|
15614
|
-
});
|
|
15615
|
-
}
|
|
15616
|
-
}
|
|
15617
|
-
const commonest = [...sizes.values()].toSorted((a, b) => b.count - a.count)[0];
|
|
15618
|
-
return {
|
|
15619
|
-
tracks: refs,
|
|
15620
|
-
trackCount,
|
|
15621
|
-
recoveryExcluded,
|
|
15622
|
-
withoutGeometry,
|
|
15623
|
-
models: Object.fromEntries(models),
|
|
15624
|
-
frameSize: commonest ? { width: commonest.width, height: commonest.height } : null,
|
|
15625
|
-
positionTimesByTrack: timesByTrack
|
|
15626
|
-
};
|
|
15627
|
-
}
|
|
15628
|
-
|
|
15629
|
-
// src/session-replay/run-session-replay.ts
|
|
15630
|
-
var TRACK_LOOKBACK_MS = 12e4;
|
|
15631
|
-
var DEFAULT_REPLAY_WIDTH = 1280;
|
|
15632
|
-
var SUPPRESSED_BIRTH_PROXIMITY_MS = 2e3;
|
|
15633
|
-
var MAX_REPORTED_FAILURES = 20;
|
|
15634
|
-
var MAX_CONSECUTIVE_FAILURES = 10;
|
|
15635
|
-
var STORED_TRACKS_READ_LIMIT = 1e3;
|
|
15636
|
-
var REAL_PACE = {
|
|
15637
|
-
now: () => Date.now(),
|
|
15638
|
-
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
15639
|
-
};
|
|
15640
|
-
var PIN_REFUSALS = ["model-not-servable", "device-not-servable"];
|
|
15641
|
-
function pinRefusalOf(err) {
|
|
15642
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
15643
|
-
for (const reason of PIN_REFUSALS) {
|
|
15644
|
-
if (message.includes(`${reason}:`)) return { reason, detail: message };
|
|
15645
|
-
}
|
|
15646
|
-
return null;
|
|
15647
|
-
}
|
|
15648
|
-
function toReplayBoxes(frame) {
|
|
15649
|
-
const w = frame.width;
|
|
15650
|
-
const h = frame.height;
|
|
15651
|
-
if (!(w > 0 && h > 0)) return [];
|
|
15652
|
-
return frame.detections.filter((d) => d.kind === "first-level").map((d) => ({
|
|
15653
|
-
className: d.macroClass,
|
|
15654
|
-
score: d.score,
|
|
15655
|
-
box: { x: d.bbox.x / w, y: d.bbox.y / h, w: d.bbox.width / w, h: d.bbox.height / h }
|
|
15656
|
-
}));
|
|
15657
|
-
}
|
|
15658
|
-
function sameClass2(a, b) {
|
|
15659
|
-
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
15660
|
-
}
|
|
15661
|
-
async function runSessionReplay(deps, request, run) {
|
|
15662
|
-
const tags = { deviceId: request.deviceId };
|
|
15663
|
-
const refuse = (reason, detail) => {
|
|
15664
|
-
deps.logger.warn("session replay refused", {
|
|
15665
|
-
tags,
|
|
15666
|
-
meta: { reason, detail, motionEventId: request.motionEventId, nodeId: request.nodeId }
|
|
15667
|
-
});
|
|
15668
|
-
return { kind: "refused", reason, detail };
|
|
15669
|
-
};
|
|
15670
|
-
run.report({ phase: "preparing", framesPlanned: 0, framesDone: 0 });
|
|
15671
|
-
const session = await deps.findMotionSession(
|
|
15672
|
-
request.deviceId,
|
|
15673
|
-
request.motionEventId,
|
|
15674
|
-
request.motionStartMs
|
|
15675
|
-
);
|
|
15676
|
-
if (session === null) {
|
|
15677
|
-
return refuse(
|
|
15678
|
-
"session-not-found",
|
|
15679
|
-
`no stored motion session ${request.motionEventId} for camera ${request.deviceId}`
|
|
15680
|
-
);
|
|
15681
|
-
}
|
|
15682
|
-
const window = motionSessionWindow(session, await deps.cameraSessionSettings(request.deviceId));
|
|
15683
|
-
if (window.kind === "refused") return refuse(window.reason, window.detail);
|
|
15684
|
-
const tree = await deps.resolveCameraTree(request.deviceId, request.nodeId);
|
|
15685
|
-
const selection = selectRootReplayStep(tree, request.modelId);
|
|
15686
|
-
if (selection.kind === "refused") return refuse(selection.reason, selection.detail);
|
|
15687
|
-
const step = selection.step;
|
|
15688
|
-
const modelId = step.modelId ?? "";
|
|
15689
|
-
const validation = await deps.validate([step], request.nodeId, request.deviceKey);
|
|
15690
|
-
const pool = `${request.nodeId}/${request.deviceKey ?? "node default"}`;
|
|
15691
|
-
if (!validation.ok) {
|
|
15692
|
-
return refuse(
|
|
15693
|
-
"device-not-servable",
|
|
15694
|
-
`${pool} cannot run ${step.addonId}: ${validation.issues.map((i) => i.detail).join("; ")}`
|
|
15695
|
-
);
|
|
15696
|
-
}
|
|
15697
|
-
const substitution = validation.substitutions[0];
|
|
15698
|
-
if (substitution !== void 0) {
|
|
15699
|
-
return refuse(
|
|
15700
|
-
"model-not-servable",
|
|
15701
|
-
`${pool} would run ${substitution.running} instead of ${substitution.chosen} (${substitution.format})`
|
|
15702
|
-
);
|
|
15703
|
-
}
|
|
15704
|
-
if (!await deps.isRecording(request.deviceId)) {
|
|
15705
|
-
return refuse("no-recording", `camera ${request.deviceId} does not have recording enabled`);
|
|
15706
|
-
}
|
|
15707
|
-
const [tracks, births] = await Promise.all([
|
|
15708
|
-
deps.listStoredTracks(request.deviceId, window.startMs - TRACK_LOOKBACK_MS, window.endMs),
|
|
15709
|
-
deps.listBirthDecisions(request.deviceId, window.startMs)
|
|
15710
|
-
]);
|
|
15711
|
-
const reference = buildStoredReference(tracks, window);
|
|
15712
|
-
const readTruncated = tracks.length >= STORED_TRACKS_READ_LIMIT;
|
|
15713
|
-
if (readTruncated) {
|
|
15714
|
-
deps.logger.warn(
|
|
15715
|
-
"session replay: stored-track read hit its limit \u2014 the reference may be incomplete",
|
|
15716
|
-
{
|
|
15717
|
-
tags,
|
|
15718
|
-
meta: { limit: STORED_TRACKS_READ_LIMIT, motionEventId: request.motionEventId }
|
|
15719
|
-
}
|
|
15720
|
-
);
|
|
15721
|
-
}
|
|
15722
|
-
const cadence = resolveReplayCadence(request.fps, reference.positionTimesByTrack);
|
|
15723
|
-
const frameSize = reference.frameSize !== null ? { width: reference.frameSize.width, source: "stored-tracks" } : { width: DEFAULT_REPLAY_WIDTH, source: "default" };
|
|
15724
|
-
const windowBirths = births.filter((b) => b.at >= window.startMs && b.at < window.endMs);
|
|
15725
|
-
const suppressedBirths = windowBirths.filter((b) => b.verdict === "suppressed");
|
|
15726
|
-
const liveVerdicts = {};
|
|
15727
|
-
for (const b of windowBirths) liveVerdicts[b.verdict] = (liveVerdicts[b.verdict] ?? 0) + 1;
|
|
15728
|
-
const plan = planReplayFrames(window, cadence.fps, request.maxFrames);
|
|
15729
|
-
if (plan.truncated) {
|
|
15730
|
-
deps.logger.warn("session replay: frame plan truncated by maxFrames", {
|
|
15731
|
-
tags,
|
|
15732
|
-
meta: { maxFrames: request.maxFrames, motionEventId: request.motionEventId }
|
|
15733
|
-
});
|
|
15734
|
-
}
|
|
15735
|
-
const stepMs = 1e3 / cadence.fps;
|
|
15736
|
-
const halfStepMs = stepMs / 2;
|
|
15737
|
-
const timeline = [];
|
|
15738
|
-
const comparisons = [];
|
|
15739
|
-
const inferenceMs = [];
|
|
15740
|
-
const failures = [];
|
|
15741
|
-
let decoded = 0;
|
|
15742
|
-
let ran = 0;
|
|
15743
|
-
let failed = 0;
|
|
15744
|
-
let consecutiveFailures = 0;
|
|
15745
|
-
let lastCallAt = null;
|
|
15746
|
-
const pace = deps.pace ?? REAL_PACE;
|
|
15747
|
-
let withoutFootage = 0;
|
|
15748
|
-
let notDecoded = 0;
|
|
15749
|
-
let truncatedBySafetyCap = false;
|
|
15750
|
-
let nearSuppressedBirth = 0;
|
|
15751
|
-
let positionsCompared = 0;
|
|
15752
|
-
let partsRead = 0;
|
|
15753
|
-
const progress = () => run.report({ phase: "replaying", framesPlanned: plan.planned, framesDone: ran + failed });
|
|
15754
|
-
progress();
|
|
15755
|
-
for (const chunk of plan.chunks) {
|
|
15756
|
-
if (run.signal.aborted) break;
|
|
15757
|
-
const recording = await deps.readRecording(request.deviceId, chunk.instants, stepMs);
|
|
15758
|
-
if (recording.kind === "refused") {
|
|
15759
|
-
withoutFootage += chunk.instants.length;
|
|
15760
|
-
deps.logger.warn("session replay: no footage for a stretch of the session", {
|
|
15761
|
-
tags,
|
|
15762
|
-
meta: { fromMs: chunk.fromMs, toMs: chunk.toMs, detail: recording.detail }
|
|
15763
|
-
});
|
|
15764
|
-
continue;
|
|
15765
|
-
}
|
|
15766
|
-
withoutFootage += recording.instantsWithoutFootage;
|
|
15767
|
-
partsRead += recording.parts.length;
|
|
15768
|
-
if (recording.truncatedBySafetyCap) truncatedBySafetyCap = true;
|
|
15769
|
-
for (const part of recording.parts) {
|
|
15770
|
-
if (run.signal.aborted) break;
|
|
15771
|
-
const first = part.instants[0];
|
|
15772
|
-
if (first === void 0) continue;
|
|
15773
|
-
const frames = await deps.decode(part.bytes, {
|
|
15774
|
-
startSec: (first - part.gopStartMs) / 1e3,
|
|
15775
|
-
durationSec: part.instants.length / cadence.fps,
|
|
15776
|
-
fps: cadence.fps,
|
|
15777
|
-
maxWidth: frameSize.width
|
|
15778
|
-
});
|
|
15779
|
-
decoded += Math.min(frames.length, part.instants.length);
|
|
15780
|
-
if (frames.length < part.instants.length) {
|
|
15781
|
-
notDecoded += part.instants.length - frames.length;
|
|
15782
|
-
deps.logger.warn("session replay: decode produced fewer frames than planned", {
|
|
15783
|
-
tags,
|
|
15784
|
-
meta: { planned: part.instants.length, decoded: frames.length, fromMs: first }
|
|
15785
|
-
});
|
|
15786
|
-
}
|
|
15787
|
-
for (let k = 0; k < part.instants.length; k++) {
|
|
15788
|
-
if (run.signal.aborted) break;
|
|
15789
|
-
const t = part.instants[k];
|
|
15790
|
-
const image = frames[k];
|
|
15791
|
-
if (t === void 0 || image === void 0) break;
|
|
15792
|
-
if (lastCallAt !== null) {
|
|
15793
|
-
const wait = lastCallAt + stepMs - pace.now();
|
|
15794
|
-
if (wait > 0) await pace.sleep(wait);
|
|
15795
|
-
}
|
|
15796
|
-
lastCallAt = pace.now();
|
|
15797
|
-
let result;
|
|
15798
|
-
try {
|
|
15799
|
-
result = await deps.runRootDetector({
|
|
15800
|
-
nodeId: request.nodeId,
|
|
15801
|
-
...request.deviceKey !== void 0 ? { deviceKey: request.deviceKey } : {},
|
|
15802
|
-
steps: [step],
|
|
15803
|
-
image,
|
|
15804
|
-
deviceId: request.deviceId,
|
|
15805
|
-
t
|
|
15806
|
-
});
|
|
15807
|
-
} catch (err) {
|
|
15808
|
-
const pinRefusal = pinRefusalOf(err);
|
|
15809
|
-
if (pinRefusal !== null) return refuse(pinRefusal.reason, pinRefusal.detail);
|
|
15810
|
-
failed += 1;
|
|
15811
|
-
consecutiveFailures += 1;
|
|
15812
|
-
const error51 = err instanceof Error ? err.message : String(err);
|
|
15813
|
-
if (failures.length < MAX_REPORTED_FAILURES) failures.push({ t, error: error51 });
|
|
15814
|
-
deps.logger.warn("session replay: frame failed \u2014 counted, the run continues", {
|
|
15815
|
-
tags,
|
|
15816
|
-
meta: { t, error: error51, consecutiveFailures }
|
|
15817
|
-
});
|
|
15818
|
-
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
15819
|
-
return refuse(
|
|
15820
|
-
"pool-failing",
|
|
15821
|
-
`${pool} failed ${consecutiveFailures} frames in a row (last: ${error51}) \u2014 stopped rather than keep loading a live pool`
|
|
15822
|
-
);
|
|
15823
|
-
}
|
|
15824
|
-
progress();
|
|
15825
|
-
continue;
|
|
15826
|
-
}
|
|
15827
|
-
consecutiveFailures = 0;
|
|
15828
|
-
if (result.rootModelId !== modelId) {
|
|
15829
|
-
return refuse(
|
|
15830
|
-
"model-not-servable",
|
|
15831
|
-
result.rootModelId === void 0 ? `${pool} does not report the model it ran (the executor does not report it) \u2014 the pin cannot be verified` : `${pool} reported ${result.rootModelId} for a frame pinned to ${modelId}`
|
|
15832
|
-
);
|
|
15833
|
-
}
|
|
15834
|
-
ran += 1;
|
|
15835
|
-
const frameInferMs = result.debug?.totalInferenceMs;
|
|
15836
|
-
if (frameInferMs !== void 0) inferenceMs.push(frameInferMs);
|
|
15837
|
-
const replayBoxes = toReplayBoxes(result);
|
|
15838
|
-
const instantRef = referenceAt(reference.tracks, t, halfStepMs);
|
|
15839
|
-
positionsCompared += instantRef.entries.length;
|
|
15840
|
-
const cmp = compareFrame(t, replayBoxes, instantRef);
|
|
15841
|
-
comparisons.push(cmp);
|
|
15842
|
-
for (const unmatched of [...cmp.added, ...cmp.unreferenced]) {
|
|
15843
|
-
if (suppressedBirths.some(
|
|
15844
|
-
(b) => sameClass2(b.className, unmatched.className) && Math.abs(b.at - t) <= SUPPRESSED_BIRTH_PROXIMITY_MS
|
|
15845
|
-
)) {
|
|
15846
|
-
nearSuppressedBirth += 1;
|
|
15847
|
-
}
|
|
15848
|
-
}
|
|
15849
|
-
timeline.push({
|
|
15850
|
-
t,
|
|
15851
|
-
referenced: cmp.referenced,
|
|
15852
|
-
inferenceMs: frameInferMs ?? null,
|
|
15853
|
-
replay: [
|
|
15854
|
-
...cmp.matched.map((p) => ({ ...p.replay, outcome: "matched" })),
|
|
15855
|
-
...cmp.classChanged.map((p) => ({ ...p.replay, outcome: "class-changed" })),
|
|
15856
|
-
...cmp.added.map((r) => ({ ...r, outcome: "added" })),
|
|
15857
|
-
...cmp.unreferenced.map((r) => ({ ...r, outcome: "unreferenced" }))
|
|
15858
|
-
],
|
|
15859
|
-
stored: [
|
|
15860
|
-
...cmp.matched.map((p) => ({ ...p.stored, outcome: "matched" })),
|
|
15861
|
-
...cmp.classChanged.map((p) => ({ ...p.stored, outcome: "class-changed" })),
|
|
15862
|
-
...cmp.missed.map((s) => ({ ...s, outcome: "missed" }))
|
|
15863
|
-
]
|
|
15864
|
-
});
|
|
15865
|
-
progress();
|
|
15866
|
-
}
|
|
15867
|
-
}
|
|
15868
|
-
}
|
|
15869
|
-
const aborted2 = run.signal.aborted;
|
|
15870
|
-
if (partsRead === 0 && !aborted2) {
|
|
15871
|
-
return refuse(
|
|
15872
|
-
"no-footage",
|
|
15873
|
-
`no recording covers any of the ${plan.planned} planned instants of motion session ${request.motionEventId}`
|
|
15874
|
-
);
|
|
15875
|
-
}
|
|
15876
|
-
if (aborted2) {
|
|
15877
|
-
deps.logger.info("session replay aborted \u2014 the report covers the frames that ran", {
|
|
15878
|
-
tags,
|
|
15879
|
-
meta: { ran, planned: plan.planned }
|
|
15880
|
-
});
|
|
15881
|
-
}
|
|
15882
|
-
run.report({ phase: "done", framesPlanned: plan.planned, framesDone: ran + failed });
|
|
15883
|
-
return {
|
|
15884
|
-
kind: "ran",
|
|
15885
|
-
report: {
|
|
15886
|
-
deviceId: request.deviceId,
|
|
15887
|
-
motionEventId: request.motionEventId,
|
|
15888
|
-
window: {
|
|
15889
|
-
startMs: window.startMs,
|
|
15890
|
-
endMs: window.endMs,
|
|
15891
|
-
motionStartMs: window.motionStartMs,
|
|
15892
|
-
motionEndMs: window.motionEndMs,
|
|
15893
|
-
prerollSource: window.prerollSource,
|
|
15894
|
-
tailSource: window.tailSource
|
|
15895
|
-
},
|
|
15896
|
-
pin: {
|
|
15897
|
-
nodeId: request.nodeId,
|
|
15898
|
-
deviceKey: request.deviceKey ?? null,
|
|
15899
|
-
modelId,
|
|
15900
|
-
cameraModelId: selection.cameraModelId
|
|
15901
|
-
},
|
|
15902
|
-
rootStep: step,
|
|
15903
|
-
cadence,
|
|
15904
|
-
frameSize,
|
|
15905
|
-
frames: {
|
|
15906
|
-
planned: plan.planned,
|
|
15907
|
-
decoded,
|
|
15908
|
-
ran,
|
|
15909
|
-
failed,
|
|
15910
|
-
withoutFootage,
|
|
15911
|
-
notDecoded,
|
|
15912
|
-
truncated: plan.truncated,
|
|
15913
|
-
truncatedBySafetyCap,
|
|
15914
|
-
aborted: aborted2
|
|
15915
|
-
},
|
|
15916
|
-
reference: {
|
|
15917
|
-
tracks: reference.trackCount,
|
|
15918
|
-
positionsCompared,
|
|
15919
|
-
recoveryExcluded: reference.recoveryExcluded,
|
|
15920
|
-
withoutGeometry: reference.withoutGeometry,
|
|
15921
|
-
models: reference.models,
|
|
15922
|
-
read: { tracks: tracks.length, truncated: readTruncated }
|
|
15923
|
-
},
|
|
15924
|
-
confirmation: { liveVerdicts, nearSuppressedBirth },
|
|
15925
|
-
summary: summarizeReplay(comparisons, inferenceMs),
|
|
15926
|
-
timeline,
|
|
15927
|
-
failures
|
|
15928
|
-
}
|
|
15929
|
-
};
|
|
15930
|
-
}
|
|
15931
|
-
|
|
15932
|
-
// src/session-replay/camera-settings.ts
|
|
15933
|
-
var FieldSchema = external_exports.object({
|
|
15934
|
-
key: external_exports.string().optional(),
|
|
15935
|
-
value: external_exports.unknown().optional(),
|
|
15936
|
-
fields: external_exports.array(external_exports.unknown()).optional(),
|
|
15937
|
-
tabs: external_exports.array(external_exports.object({ fields: external_exports.array(external_exports.unknown()).optional() }).loose()).optional()
|
|
15938
|
-
}).loose();
|
|
15939
|
-
var FormSchema = external_exports.object({
|
|
15940
|
-
sections: external_exports.array(external_exports.object({ fields: external_exports.array(external_exports.unknown()) }).loose())
|
|
14546
|
+
})
|
|
15941
14547
|
});
|
|
15942
|
-
function collect(fields, out) {
|
|
15943
|
-
for (const raw of fields) {
|
|
15944
|
-
const field = FieldSchema.safeParse(raw);
|
|
15945
|
-
if (!field.success) continue;
|
|
15946
|
-
if (field.data.key !== void 0 && "value" in field.data)
|
|
15947
|
-
out.set(field.data.key, field.data.value);
|
|
15948
|
-
if (field.data.fields !== void 0) collect(field.data.fields, out);
|
|
15949
|
-
for (const tab of field.data.tabs ?? []) collect(tab.fields ?? [], out);
|
|
15950
|
-
}
|
|
15951
|
-
}
|
|
15952
|
-
function readCameraSessionSettings(raw) {
|
|
15953
|
-
const form = FormSchema.safeParse(raw);
|
|
15954
|
-
if (!form.success) return {};
|
|
15955
|
-
const values = /* @__PURE__ */ new Map();
|
|
15956
|
-
for (const section of form.data.sections) collect(section.fields, values);
|
|
15957
|
-
const cooldown = values.get("motionCooldownMs");
|
|
15958
|
-
const preRoll = values.get("preRollInferenceEnabled");
|
|
15959
|
-
return {
|
|
15960
|
-
...typeof cooldown === "number" && Number.isFinite(cooldown) && cooldown >= 0 ? { motionCooldownMs: cooldown } : {},
|
|
15961
|
-
...typeof preRoll === "boolean" ? { preRollInferenceEnabled: preRoll } : {}
|
|
15962
|
-
};
|
|
15963
|
-
}
|
|
15964
14548
|
|
|
15965
14549
|
// src/benchmark-addon.ts
|
|
15966
|
-
var KEY_BENCHMARK_HISTORY = "benchmarkHistory";
|
|
15967
|
-
var MAX_HISTORY_ENTRIES = 100;
|
|
15968
|
-
var EMPTY_HISTORY_JSON = "[]";
|
|
15969
14550
|
var BENCH_RUN_TIMEOUT_MS = 55e3;
|
|
15970
|
-
var REPLAY_RECORDING_PROFILE = "high";
|
|
15971
|
-
var REPLAY_BIRTHS_LIMIT = 1e3;
|
|
15972
|
-
var REPLAY_DECODE_TIMEOUT_FLOOR_MS = 15e3;
|
|
15973
|
-
var REPLAY_DECODE_TIMEOUT_PER_SEC_MS = 3e3;
|
|
15974
|
-
var MOTION_SESSIONS_DEFAULT_SPAN_MS = 24 * 60 * 60 * 1e3;
|
|
15975
14551
|
async function withHardTimeout(promise2, ms, label) {
|
|
15976
14552
|
let timer;
|
|
15977
14553
|
const timeout = new Promise((_resolve, reject) => {
|
|
@@ -15987,36 +14563,8 @@ async function withHardTimeout(promise2, ms, label) {
|
|
|
15987
14563
|
if (timer) clearTimeout(timer);
|
|
15988
14564
|
}
|
|
15989
14565
|
}
|
|
15990
|
-
var HistoryArraySchema = external_exports.array(HistoryEntrySchema);
|
|
15991
14566
|
var BenchmarkAddon = class extends BaseAddon {
|
|
15992
14567
|
id = "benchmark";
|
|
15993
|
-
replayStore = null;
|
|
15994
|
-
/** The replay's ffmpeg spawns — the PINNED binary (`ctx.deps.ensureFfmpeg`, D540). */
|
|
15995
|
-
replayDecoder = new ReplayDecoder({
|
|
15996
|
-
resolveFfmpegPath: () => this.ctx.deps.ensureFfmpeg()
|
|
15997
|
-
});
|
|
15998
|
-
/**
|
|
15999
|
-
* Session replays, start-then-poll (D646). One at a time — a replay drives
|
|
16000
|
-
* one inference call after another on a node that also serves live
|
|
16001
|
-
* cameras — and the last few finished runs kept for a late poll.
|
|
16002
|
-
*/
|
|
16003
|
-
replays = new BackgroundRunRegistry(
|
|
16004
|
-
{
|
|
16005
|
-
maxConcurrent: 1,
|
|
16006
|
-
keepFinished: 10,
|
|
16007
|
-
onSettled: (status) => {
|
|
16008
|
-
if (status.state === "failed") {
|
|
16009
|
-
this.ctx.logger.warn("session replay failed", {
|
|
16010
|
-
meta: { runId: status.runId, error: status.error }
|
|
16011
|
-
});
|
|
16012
|
-
return;
|
|
16013
|
-
}
|
|
16014
|
-
const outcome = status.result;
|
|
16015
|
-
if (outcome === null || outcome.kind !== "ran") return;
|
|
16016
|
-
void this.persistReplay(status.runId, outcome.report.pin.nodeId, outcome);
|
|
16017
|
-
}
|
|
16018
|
-
}
|
|
16019
|
-
);
|
|
16020
14568
|
pages = [
|
|
16021
14569
|
{
|
|
16022
14570
|
id: "benchmark",
|
|
@@ -16037,29 +14585,15 @@ var BenchmarkAddon = class extends BaseAddon {
|
|
|
16037
14585
|
id: this.id,
|
|
16038
14586
|
listPages: () => this.pages
|
|
16039
14587
|
};
|
|
16040
|
-
await ReplayStore.declare(this.api.settingsStore);
|
|
16041
|
-
this.replayStore = new ReplayStore(this.api.settingsStore);
|
|
16042
|
-
await this.pruneReplays();
|
|
16043
14588
|
return {
|
|
16044
14589
|
providers: [{ capability: addonPagesSourceCapability, provider }],
|
|
16045
14590
|
customActions: benchmarkActions,
|
|
16046
14591
|
actionHandlers: {
|
|
16047
|
-
runPipelineTest: async (input) => this.runPipelineTest(input)
|
|
16048
|
-
listBenchmarkHistory: async () => this.loadHistory(),
|
|
16049
|
-
saveBenchmarkResult: async (input) => this.saveBenchmarkResult(input),
|
|
16050
|
-
deleteBenchmarkResult: async (input) => this.deleteBenchmarkResult(input),
|
|
16051
|
-
clearBenchmarkHistory: async () => this.clearBenchmarkHistory(),
|
|
16052
|
-
listMotionSessions: async (input) => this.listMotionSessions(input),
|
|
16053
|
-
startSessionReplay: async (input) => this.startSessionReplay(input),
|
|
16054
|
-
getSessionReplayStatus: async (input) => this.getSessionReplayStatus(input),
|
|
16055
|
-
abortSessionReplay: async (input) => ({ aborted: this.replays.abort(input.runId) }),
|
|
16056
|
-
listSessionReplays: async (input) => this.listSessionReplays(input),
|
|
16057
|
-
getSessionReplay: async (input) => this.replayStore === null ? null : this.replayStore.get(input.id)
|
|
14592
|
+
runPipelineTest: async (input) => this.runPipelineTest(input)
|
|
16058
14593
|
}
|
|
16059
14594
|
};
|
|
16060
14595
|
}
|
|
16061
14596
|
async onShutdown() {
|
|
16062
|
-
this.replays.abortAll();
|
|
16063
14597
|
}
|
|
16064
14598
|
get api() {
|
|
16065
14599
|
const api = this.ctx.api;
|
|
@@ -16113,297 +14647,6 @@ var BenchmarkAddon = class extends BaseAddon {
|
|
|
16113
14647
|
"runPipelineTest"
|
|
16114
14648
|
);
|
|
16115
14649
|
}
|
|
16116
|
-
// ── Session replay (D646) ─────────────────────────────────────────────────
|
|
16117
|
-
async listMotionSessions(input) {
|
|
16118
|
-
const untilMs = input.untilMs ?? Date.now();
|
|
16119
|
-
const sinceMs = input.sinceMs ?? untilMs - MOTION_SESSIONS_DEFAULT_SPAN_MS;
|
|
16120
|
-
const events = await this.api.pipelineAnalytics.getMotionEvents.query({
|
|
16121
|
-
deviceId: input.deviceId,
|
|
16122
|
-
since: sinceMs,
|
|
16123
|
-
until: untilMs,
|
|
16124
|
-
limit: input.limit,
|
|
16125
|
-
projection: "full"
|
|
16126
|
-
});
|
|
16127
|
-
const camera = await this.cameraSessionSettings(input.deviceId);
|
|
16128
|
-
const windows = events.map((ev) => ({ ev, window: motionSessionWindow(ev, camera) }));
|
|
16129
|
-
const starts = windows.map(
|
|
16130
|
-
({ ev, window }) => window.kind === "window" ? window.startMs : ev.timestamp
|
|
16131
|
-
);
|
|
16132
|
-
const ends = windows.map(
|
|
16133
|
-
({ ev, window }) => window.kind === "window" ? window.endMs : ev.timestamp
|
|
16134
|
-
);
|
|
16135
|
-
const tracks = events.length === 0 ? [] : await this.api.pipelineAnalytics.listTracks.query({
|
|
16136
|
-
deviceId: input.deviceId,
|
|
16137
|
-
since: Math.min(...starts),
|
|
16138
|
-
until: Math.max(...ends),
|
|
16139
|
-
projection: "slim",
|
|
16140
|
-
excludeSources: ["sensor", "audio"],
|
|
16141
|
-
limit: STORED_TRACKS_READ_LIMIT
|
|
16142
|
-
});
|
|
16143
|
-
return windows.map(({ ev, window }) => ({
|
|
16144
|
-
motionEventId: ev.id,
|
|
16145
|
-
deviceId: ev.deviceId,
|
|
16146
|
-
startMs: ev.timestamp,
|
|
16147
|
-
durationMs: ev.durationMs ?? null,
|
|
16148
|
-
open: ev.durationMs === null,
|
|
16149
|
-
refusal: window.kind === "refused" ? window.reason : null,
|
|
16150
|
-
storedTracks: window.kind === "window" ? tracks.filter((t) => t.firstSeen >= window.startMs && t.firstSeen < window.endMs).length : 0
|
|
16151
|
-
}));
|
|
16152
|
-
}
|
|
16153
|
-
startSessionReplay(input) {
|
|
16154
|
-
const runId = `replay-${input.deviceId}-${input.motionEventId}-${Date.now().toString(36)}`;
|
|
16155
|
-
this.ctx.logger.info("session replay started", {
|
|
16156
|
-
tags: { deviceId: input.deviceId },
|
|
16157
|
-
meta: {
|
|
16158
|
-
runId,
|
|
16159
|
-
motionEventId: input.motionEventId,
|
|
16160
|
-
nodeId: input.nodeId,
|
|
16161
|
-
deviceKey: input.deviceKey ?? null,
|
|
16162
|
-
modelId: input.modelId ?? null,
|
|
16163
|
-
fps: input.fps ?? null,
|
|
16164
|
-
maxFrames: input.maxFrames
|
|
16165
|
-
}
|
|
16166
|
-
});
|
|
16167
|
-
this.replays.start(
|
|
16168
|
-
runId,
|
|
16169
|
-
(run) => runSessionReplay(
|
|
16170
|
-
this.sessionReplayDeps(),
|
|
16171
|
-
{
|
|
16172
|
-
deviceId: input.deviceId,
|
|
16173
|
-
motionEventId: input.motionEventId,
|
|
16174
|
-
motionStartMs: input.motionStartMs,
|
|
16175
|
-
nodeId: input.nodeId,
|
|
16176
|
-
maxFrames: input.maxFrames,
|
|
16177
|
-
...input.deviceKey !== void 0 ? { deviceKey: input.deviceKey } : {},
|
|
16178
|
-
...input.modelId !== void 0 ? { modelId: input.modelId } : {},
|
|
16179
|
-
...input.fps !== void 0 ? { fps: input.fps } : {}
|
|
16180
|
-
},
|
|
16181
|
-
run
|
|
16182
|
-
)
|
|
16183
|
-
);
|
|
16184
|
-
return { accepted: true, runId };
|
|
16185
|
-
}
|
|
16186
|
-
getSessionReplayStatus(input) {
|
|
16187
|
-
const status = this.replays.status(input.runId);
|
|
16188
|
-
if (status === null) return null;
|
|
16189
|
-
return {
|
|
16190
|
-
runId: status.runId,
|
|
16191
|
-
state: status.state,
|
|
16192
|
-
startedAtMs: status.startedAtMs,
|
|
16193
|
-
finishedAtMs: status.finishedAtMs,
|
|
16194
|
-
progress: status.progress,
|
|
16195
|
-
outcome: status.result,
|
|
16196
|
-
error: status.error
|
|
16197
|
-
};
|
|
16198
|
-
}
|
|
16199
|
-
async listSessionReplays(input) {
|
|
16200
|
-
if (this.replayStore === null) return { runs: [], skipped: 0 };
|
|
16201
|
-
const { runs, skipped } = await this.replayStore.list({ deviceId: input.deviceId, limit: 50 });
|
|
16202
|
-
if (skipped > 0) {
|
|
16203
|
-
this.ctx.logger.info("session replays: rows skipped that are not session replays", {
|
|
16204
|
-
tags: { deviceId: input.deviceId },
|
|
16205
|
-
meta: { skipped }
|
|
16206
|
-
});
|
|
16207
|
-
}
|
|
16208
|
-
return {
|
|
16209
|
-
runs: runs.map((r) => {
|
|
16210
|
-
const { timeline: _timeline, ...report } = r.report;
|
|
16211
|
-
return {
|
|
16212
|
-
id: r.id,
|
|
16213
|
-
deviceId: r.deviceId,
|
|
16214
|
-
nodeId: r.nodeId,
|
|
16215
|
-
createdAtMs: r.createdAtMs,
|
|
16216
|
-
report
|
|
16217
|
-
};
|
|
16218
|
-
}),
|
|
16219
|
-
skipped
|
|
16220
|
-
};
|
|
16221
|
-
}
|
|
16222
|
-
async persistReplay(runId, nodeId, outcome) {
|
|
16223
|
-
const store = this.replayStore;
|
|
16224
|
-
if (store === null) return;
|
|
16225
|
-
const tags = { deviceId: outcome.report.deviceId };
|
|
16226
|
-
try {
|
|
16227
|
-
await store.save({
|
|
16228
|
-
id: runId,
|
|
16229
|
-
deviceId: outcome.report.deviceId,
|
|
16230
|
-
nodeId,
|
|
16231
|
-
createdAtMs: Date.now(),
|
|
16232
|
-
report: outcome.report
|
|
16233
|
-
});
|
|
16234
|
-
await this.pruneReplays();
|
|
16235
|
-
} catch (err) {
|
|
16236
|
-
this.ctx.logger.warn("session replay: report NOT saved", {
|
|
16237
|
-
tags,
|
|
16238
|
-
meta: { runId, error: errMsg(err) }
|
|
16239
|
-
});
|
|
16240
|
-
}
|
|
16241
|
-
}
|
|
16242
|
-
async pruneReplays() {
|
|
16243
|
-
const store = this.replayStore;
|
|
16244
|
-
if (store === null) return;
|
|
16245
|
-
try {
|
|
16246
|
-
const deleted = await store.pruneBefore(Date.now() - REPLAY_RETENTION_MS);
|
|
16247
|
-
if (deleted > 0) this.ctx.logger.info("session replays pruned", { meta: { deleted } });
|
|
16248
|
-
} catch (err) {
|
|
16249
|
-
this.ctx.logger.warn("session replays: prune failed", { meta: { error: errMsg(err) } });
|
|
16250
|
-
}
|
|
16251
|
-
}
|
|
16252
|
-
/**
|
|
16253
|
-
* The camera's own cooldown + pre-roll switch, read from the orchestrator's
|
|
16254
|
-
* hydrated device settings (the authority, not a copy). A failed read is
|
|
16255
|
-
* `{}` — the fleet defaults apply and the report says `default`.
|
|
16256
|
-
*/
|
|
16257
|
-
async cameraSessionSettings(deviceId) {
|
|
16258
|
-
try {
|
|
16259
|
-
const raw = await this.api.addonSettings.getDeviceSettings.query({
|
|
16260
|
-
addonId: "pipeline-orchestrator",
|
|
16261
|
-
deviceId
|
|
16262
|
-
});
|
|
16263
|
-
return readCameraSessionSettings(raw);
|
|
16264
|
-
} catch (err) {
|
|
16265
|
-
this.ctx.logger.warn("session replay: camera session settings unreadable \u2014 fleet defaults", {
|
|
16266
|
-
tags: { deviceId },
|
|
16267
|
-
meta: { error: errMsg(err) }
|
|
16268
|
-
});
|
|
16269
|
-
return {};
|
|
16270
|
-
}
|
|
16271
|
-
}
|
|
16272
|
-
/** The replay's outside world — every call through `ctx.api`, node-pinned where it matters. */
|
|
16273
|
-
sessionReplayDeps() {
|
|
16274
|
-
const api = this.api;
|
|
16275
|
-
const profile = REPLAY_RECORDING_PROFILE;
|
|
16276
|
-
return {
|
|
16277
|
-
logger: this.ctx.logger,
|
|
16278
|
-
cameraSessionSettings: (deviceId) => this.cameraSessionSettings(deviceId),
|
|
16279
|
-
findMotionSession: async (deviceId, motionEventId, motionStartMs) => {
|
|
16280
|
-
const events = await api.pipelineAnalytics.getMotionEvents.query({
|
|
16281
|
-
deviceId,
|
|
16282
|
-
since: motionStartMs - 1,
|
|
16283
|
-
until: motionStartMs + 1,
|
|
16284
|
-
limit: 50,
|
|
16285
|
-
projection: "full"
|
|
16286
|
-
});
|
|
16287
|
-
return events.find((e) => e.id === motionEventId) ?? null;
|
|
16288
|
-
},
|
|
16289
|
-
resolveCameraTree: (deviceId, nodeId) => api.pipelineOrchestrator.resolvePipeline.query({ deviceId, agentNodeId: nodeId }),
|
|
16290
|
-
validate: (steps, nodeId, deviceKey) => api.pipelineExecutor.validatePipeline.query(
|
|
16291
|
-
{ steps: [...steps], ...deviceKey !== void 0 ? { deviceKey } : {} },
|
|
16292
|
-
nodePin(nodeId)
|
|
16293
|
-
),
|
|
16294
|
-
isRecording: async (deviceId) => {
|
|
16295
|
-
const config2 = await api.recordingArchive.getDeviceConfig.query({ deviceId });
|
|
16296
|
-
return config2.enabled !== false;
|
|
16297
|
-
},
|
|
16298
|
-
listStoredTracks: async (deviceId, sinceMs, untilMs) => api.pipelineAnalytics.listTracks.query({
|
|
16299
|
-
deviceId,
|
|
16300
|
-
since: sinceMs,
|
|
16301
|
-
until: untilMs,
|
|
16302
|
-
projection: "full",
|
|
16303
|
-
excludeSources: ["sensor", "audio"],
|
|
16304
|
-
limit: STORED_TRACKS_READ_LIMIT
|
|
16305
|
-
}),
|
|
16306
|
-
listBirthDecisions: (deviceId, sinceMs) => api.pipelineAnalytics.listBirthDecisions.query({
|
|
16307
|
-
deviceId,
|
|
16308
|
-
since: sinceMs,
|
|
16309
|
-
limit: REPLAY_BIRTHS_LIMIT
|
|
16310
|
-
}),
|
|
16311
|
-
readRecording: (deviceId, instants, stepMs) => readRecordingForInstants(
|
|
16312
|
-
{
|
|
16313
|
-
locateSegment: (epochMs) => api.recordingArchive.locateSegment.query({ deviceId, profile, epochMs }),
|
|
16314
|
-
readWindowBytes: (q) => api.recordingArchive.readWindowBytes.query({ deviceId, profile, ...q })
|
|
16315
|
-
},
|
|
16316
|
-
instants,
|
|
16317
|
-
stepMs
|
|
16318
|
-
),
|
|
16319
|
-
decode: async (bytes, args) => {
|
|
16320
|
-
const mjpeg = await this.replayDecoder.decodeSequence(
|
|
16321
|
-
buildReplaySequenceArgs(args),
|
|
16322
|
-
bytes,
|
|
16323
|
-
{
|
|
16324
|
-
timeoutMs: Math.max(
|
|
16325
|
-
REPLAY_DECODE_TIMEOUT_FLOOR_MS,
|
|
16326
|
-
Math.round(args.durationSec * REPLAY_DECODE_TIMEOUT_PER_SEC_MS)
|
|
16327
|
-
)
|
|
16328
|
-
}
|
|
16329
|
-
);
|
|
16330
|
-
return splitMjpegStream(mjpeg);
|
|
16331
|
-
},
|
|
16332
|
-
runRootDetector: (call) => api.pipelineExecutor.runPipeline.mutate({
|
|
16333
|
-
nodeId: call.nodeId,
|
|
16334
|
-
steps: [...call.steps],
|
|
16335
|
-
// A fresh `Uint8Array<ArrayBuffer>`: `mjpeg-split` hands back a
|
|
16336
|
-
// `Buffer` view, and the generated cap input is typed against the
|
|
16337
|
-
// narrower `ArrayBuffer` backing.
|
|
16338
|
-
image: new Uint8Array(call.image),
|
|
16339
|
-
deviceId: call.deviceId,
|
|
16340
|
-
plane: "frame",
|
|
16341
|
-
replay: true,
|
|
16342
|
-
...call.deviceKey !== void 0 ? { deviceKey: call.deviceKey } : {}
|
|
16343
|
-
})
|
|
16344
|
-
};
|
|
16345
|
-
}
|
|
16346
|
-
// ── History persistence (per-addon store on the hub) ──────────────────
|
|
16347
|
-
/**
|
|
16348
|
-
* Durable handle over the persisted history blob. LEGACY SHAPE, PRESERVED
|
|
16349
|
-
* VERBATIM: the value under `benchmarkHistory` is a JSON **string** (not
|
|
16350
|
-
* the array itself), so the handle is typed over the string — the
|
|
16351
|
-
* encode/decode + entry-level recovery below stays the codec, the handle
|
|
16352
|
-
* owns store access (retry budget + validated fallback).
|
|
16353
|
-
*/
|
|
16354
|
-
historyState() {
|
|
16355
|
-
return this.state(KEY_BENCHMARK_HISTORY, external_exports.string(), EMPTY_HISTORY_JSON);
|
|
16356
|
-
}
|
|
16357
|
-
async loadHistory() {
|
|
16358
|
-
const raw = await this.historyState().get();
|
|
16359
|
-
try {
|
|
16360
|
-
const parsed = parseJsonUnknown(raw);
|
|
16361
|
-
const result = HistoryArraySchema.safeParse(parsed);
|
|
16362
|
-
if (result.success) return [...result.data];
|
|
16363
|
-
const arr = Array.isArray(parsed) ? parsed : [];
|
|
16364
|
-
const accepted = [];
|
|
16365
|
-
let rejected = 0;
|
|
16366
|
-
for (const entry2 of arr) {
|
|
16367
|
-
const parsedEntry = HistoryEntrySchema.safeParse(entry2);
|
|
16368
|
-
if (parsedEntry.success) accepted.push(parsedEntry.data);
|
|
16369
|
-
else rejected += 1;
|
|
16370
|
-
}
|
|
16371
|
-
if (rejected > 0) {
|
|
16372
|
-
this.ctx.logger.warn("benchmark history: dropped unparseable entries", {
|
|
16373
|
-
meta: { rejected, accepted: accepted.length, error: result.error.message }
|
|
16374
|
-
});
|
|
16375
|
-
}
|
|
16376
|
-
return accepted;
|
|
16377
|
-
} catch (err) {
|
|
16378
|
-
this.ctx.logger.warn("benchmark history parse failed", { meta: { error: errMsg(err) } });
|
|
16379
|
-
return [];
|
|
16380
|
-
}
|
|
16381
|
-
}
|
|
16382
|
-
async writeHistory(history) {
|
|
16383
|
-
await this.historyState().set(JSON.stringify(history));
|
|
16384
|
-
}
|
|
16385
|
-
async saveBenchmarkResult(entry2) {
|
|
16386
|
-
const history = await this.loadHistory();
|
|
16387
|
-
const saved = {
|
|
16388
|
-
...entry2,
|
|
16389
|
-
kind: "pipeline",
|
|
16390
|
-
id: `bench-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
16391
|
-
date: (/* @__PURE__ */ new Date()).toISOString()
|
|
16392
|
-
};
|
|
16393
|
-
const next = [saved, ...history];
|
|
16394
|
-
if (next.length > MAX_HISTORY_ENTRIES) next.length = MAX_HISTORY_ENTRIES;
|
|
16395
|
-
await this.writeHistory(next);
|
|
16396
|
-
return saved;
|
|
16397
|
-
}
|
|
16398
|
-
async deleteBenchmarkResult(input) {
|
|
16399
|
-
const history = (await this.loadHistory()).filter((e) => e.id !== input.id);
|
|
16400
|
-
await this.writeHistory(history);
|
|
16401
|
-
return { success: true };
|
|
16402
|
-
}
|
|
16403
|
-
async clearBenchmarkHistory() {
|
|
16404
|
-
await this.writeHistory([]);
|
|
16405
|
-
return { success: true };
|
|
16406
|
-
}
|
|
16407
14650
|
};
|
|
16408
14651
|
export {
|
|
16409
14652
|
BenchmarkAddon,
|