@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.
Files changed (21) hide show
  1. package/dist/{MotionZonesSettings-BjoeE0S1.mjs → MotionZonesSettings-BrVV9EXF.mjs} +19 -19
  2. package/dist/{PrivacyMaskSettings-CbjkLYU-.mjs → PrivacyMaskSettings-D8sjuyhC.mjs} +6 -6
  3. package/dist/{SceneMonitorEditor-CpBgHc-O.mjs → SceneMonitorEditor-BYSSKL6B.mjs} +304 -304
  4. package/dist/_stub.js +5658 -7849
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_benchmark_page-Bl4Vins5.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_benchmark_page-lkKeR65E.mjs} +3 -3
  6. package/dist/_virtual_mf___mfe_internal__addon_benchmark_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C0SUUHYE.mjs +26 -0
  7. package/dist/addon-benchmark.css +1 -1
  8. package/dist/{hostInit-BsI8UbEL.mjs → hostInit-DObjad6K.mjs} +2 -2
  9. package/dist/index.js +47 -1797
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +43 -1800
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/{player-overlays-C4j465z3.mjs → player-overlays-CMnxOmtI.mjs} +5 -5
  14. package/dist/remoteEntry.js +1 -1
  15. package/dist/{responsive-tU-nXS1h.mjs → responsive-DzjUS3a_.mjs} +3 -3
  16. package/dist/{scene-monitor-copy-BvGoUL8z.mjs → scene-monitor-copy-DPRVdwkx.mjs} +1 -1
  17. package/dist/{square-R4Tms3sB.mjs → square-B09gv3_J.mjs} +1 -1
  18. package/dist/{trash-2-CfS9mZ-d.mjs → trash-2-0IB1iAfr.mjs} +1 -1
  19. 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
  20. package/package.json +3 -3
  21. 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
- BackgroundRunRegistry,
10
- BaseAddon,
11
- addonPagesSourceCapability,
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 entry2 of ctx.seen.entries()) {
11508
- const id = ctx.metadataRegistry.get(entry2[0])?.id;
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 !== entry2[0]) {
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, entry2[0]);
11510
+ idToSchema.set(id, entry[0]);
11515
11511
  }
11516
11512
  }
11517
- const makeURI = (entry2) => {
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(entry2[0])?.id;
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 = entry2[1].defId ?? entry2[1].schema.id ?? `schema${ctx.counter++}`;
11526
- entry2[1].defId = id;
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 (entry2[1] === root) {
11525
+ if (entry[1] === root) {
11530
11526
  return { ref: "#" };
11531
11527
  }
11532
11528
  const uriPrefix = `#`;
11533
11529
  const defUriPrefix = `${uriPrefix}/${defsSegment}/`;
11534
- const defId = entry2[1].schema.id ?? `__schema${ctx.counter++}`;
11530
+ const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;
11535
11531
  return { defId, ref: defUriPrefix + defId };
11536
11532
  };
11537
- const extractToDef = (entry2) => {
11538
- if (entry2[1].schema.$ref) {
11533
+ const extractToDef = (entry) => {
11534
+ if (entry[1].schema.$ref) {
11539
11535
  return;
11540
11536
  }
11541
- const seen = entry2[1];
11542
- const { ref, defId } = makeURI(entry2);
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 entry2 of ctx.seen.entries()) {
11554
- const seen = entry2[1];
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 entry2 of ctx.seen.entries()) {
11563
- const seen = entry2[1];
11564
- if (schema === entry2[0]) {
11565
- extractToDef(entry2);
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(entry2[0])?.id;
11570
- if (schema !== entry2[0] && ext) {
11571
- extractToDef(entry2);
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(entry2[0])?.id;
11571
+ const id = ctx.metadataRegistry.get(entry[0])?.id;
11576
11572
  if (id) {
11577
- extractToDef(entry2);
11573
+ extractToDef(entry);
11578
11574
  continue;
11579
11575
  }
11580
11576
  if (seen.cycle) {
11581
- extractToDef(entry2);
11577
+ extractToDef(entry);
11582
11578
  continue;
11583
11579
  }
11584
11580
  if (seen.count > 1) {
11585
11581
  if (ctx.reused === "ref") {
11586
- extractToDef(entry2);
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 entry2 of [...ctx.seen.entries()].reverse()) {
11659
- flattenRef(entry2[0]);
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 entry2 of ctx.seen.entries()) {
11683
- const seen = entry2[1];
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 entry2 of registry2._idmap.entries()) {
12300
- const [_, schema] = entry2;
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 entry2 of registry2._idmap.entries()) {
12311
- const [key, schema] = entry2;
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(PipelineStepInputSchema2).readonly(),
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
- // Detection Pipeline — one run.
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,