@camstack/addon-benchmark 1.2.194 → 1.2.196

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