@camstack/addon-pipeline 1.2.301 → 1.2.303

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 (30) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/{default-detection-model-JDKeiJPf.mjs → default-detection-model-BroB5Jp1.mjs} +1 -1
  4. package/dist/{default-detection-model-P7IIrP0a.js → default-detection-model-vN686W1U.js} +1 -1
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-DvzAabca.mjs → dist-CLZmZfHT.mjs} +35 -4
  8. package/dist/{dist-BUs7H-fe.js → dist-CyWAhtbJ.js} +35 -4
  9. package/dist/motion-wasm/index.js +1 -1
  10. package/dist/motion-wasm/index.mjs +1 -1
  11. package/dist/{node-DxVMyys-.js → node-BDrkzYiH.js} +1 -1
  12. package/dist/{node-DnP_6GBZ.mjs → node-C5LXX8Qo.mjs} +1 -1
  13. package/dist/pipeline-runner/index.js +274 -17
  14. package/dist/pipeline-runner/index.mjs +274 -17
  15. package/dist/{process-memory-BMr61qT1.mjs → process-memory-CCx6i9av.mjs} +1 -1
  16. package/dist/{process-memory-DmYzYip-.js → process-memory-DMS-0oRT.js} +1 -1
  17. package/dist/recorder/index.js +1351 -72
  18. package/dist/recorder/index.mjs +1424 -146
  19. package/dist/{segment-demux-js-DBqdboBI.mjs → segment-demux-js-BUxZFqkb.mjs} +1 -1
  20. package/dist/{segment-demux-js-DANvUJgH.js → segment-demux-js-CwQuMR0h.js} +1 -1
  21. package/dist/session-decode/decode-worker-main.js +250 -57
  22. package/dist/session-decode/decode-worker-main.mjs +250 -57
  23. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D0etdxl0.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CF7C68rV.mjs} +2 -2
  24. package/dist/stream-broker/demux-worker-child.js +1 -1
  25. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  26. package/dist/stream-broker/{hostInit-TY9BRVDy.mjs → hostInit-dac4-ofq.mjs} +2 -2
  27. package/dist/stream-broker/index.js +3 -3
  28. package/dist/stream-broker/index.mjs +3 -3
  29. package/dist/stream-broker/remoteEntry.js +1 -1
  30. package/package.json +1 -1
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-BUs7H-fe.js");
7
- const require_process_memory = require("../process-memory-DmYzYip-.js");
6
+ const require_dist = require("../dist-CyWAhtbJ.js");
7
+ const require_process_memory = require("../process-memory-DMS-0oRT.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
10
10
  let node_path = require("node:path");
@@ -1,6 +1,6 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { E as HF_BASE_URL, G as audioAnalyzerCapability, Rn as EventCategory, Ut as resolvePoolMemoryPolicy, W as audioAnalysisCapability, _n as hydrateSchema, bt as mapAudioLabelToMacro, en as errMsg, fn as audioChunkBytesPerSample, gn as expandAudioChunkToF32le, j as PoolMemoryWatchdog, n as AUDIO_BACKEND_CHOICES, p as DEFAULT_AUDIO_ANALYZER_CONFIG, sn as BaseAddon, xn as nodePin } from "../dist-DvzAabca.mjs";
3
- import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BMr61qT1.mjs";
2
+ import { E as HF_BASE_URL, G as audioAnalyzerCapability, Rn as EventCategory, Ut as resolvePoolMemoryPolicy, W as audioAnalysisCapability, _n as hydrateSchema, bt as mapAudioLabelToMacro, en as errMsg, fn as audioChunkBytesPerSample, gn as expandAudioChunkToF32le, j as PoolMemoryWatchdog, n as AUDIO_BACKEND_CHOICES, p as DEFAULT_AUDIO_ANALYZER_CONFIG, sn as BaseAddon, xn as nodePin } from "../dist-CLZmZfHT.mjs";
3
+ import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-CCx6i9av.mjs";
4
4
  import * as fs from "node:fs";
5
5
  import * as path$1 from "node:path";
6
6
  import { downloadFile } from "@camstack/system/addon-utils";
@@ -1,4 +1,4 @@
1
- import { K as audioKindId, Kt as stepModelRunsOnFormat, d as COCO_80_LABELS, f as COCO_TO_MACRO, gt as inferModelProvider, ht as hfModelUrl, r as AUDIO_MACRO_LABELS, y as DEVICE_BACKEND_TO_FORMAT } from "./dist-DvzAabca.mjs";
1
+ import { K as audioKindId, Kt as stepModelRunsOnFormat, d as COCO_80_LABELS, f as COCO_TO_MACRO, gt as inferModelProvider, ht as hfModelUrl, r as AUDIO_MACRO_LABELS, y as DEVICE_BACKEND_TO_FORMAT } from "./dist-CLZmZfHT.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  //#region src/detection-pipeline/registry/effective-confidence.ts
4
4
  /** The default in force: the model's own floor, else the step's. */
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-BUs7H-fe.js");
1
+ const require_dist = require("./dist-CyWAhtbJ.js");
2
2
  let node_crypto = require("node:crypto");
3
3
  //#region src/detection-pipeline/registry/effective-confidence.ts
4
4
  /** The default in force: the model's own floor, else the step's. */
@@ -3,11 +3,11 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-BUs7H-fe.js");
7
- const require_node = require("../node-DxVMyys-.js");
8
- const require_default_detection_model = require("../default-detection-model-P7IIrP0a.js");
6
+ const require_dist = require("../dist-CyWAhtbJ.js");
7
+ const require_node = require("../node-BDrkzYiH.js");
8
+ const require_default_detection_model = require("../default-detection-model-vN686W1U.js");
9
9
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
10
- const require_process_memory = require("../process-memory-DmYzYip-.js");
10
+ const require_process_memory = require("../process-memory-DMS-0oRT.js");
11
11
  let node_os = require("node:os");
12
12
  node_os = require_chunk.__toESM(node_os);
13
13
  let node_fs = require("node:fs");
@@ -1,9 +1,9 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
- import { Dn as array, Dt as overlayClusterStepSettings, Gt as runtimeDevices$1, H as YAMNET_TO_MACRO, In as string, Kt as stepModelRunsOnFormat, Ln as union, Mt as pipelineExecutorCapability, Nn as object, Rn as EventCategory, Sn as parseJsonUnknown, Tn as sleep, Ut as resolvePoolMemoryPolicy, Zt as supportedRuntimes$1, _n as hydrateSchema, en as errMsg, et as defaultDeviceFor$1, it as detectionPipelineCapability, j as PoolMemoryWatchdog, kt as pickClusterStepSettings, lt as evaluateZoneRules, m as DEFAULT_CLUSTER_STEP_SETTINGS, pn as createEvent, sn as BaseAddon, st as enumerateInferenceDevices, t as APPLE_SA_TO_MACRO, u as CLUSTER_MODEL_SCOPED_STEPS, vt as loadContributionCapability, xn as nodePin, y as DEVICE_BACKEND_TO_FORMAT, zt as resolveClusterStepModelId } from "../dist-DvzAabca.mjs";
3
- import { s as readProcessCost } from "../node-DnP_6GBZ.mjs";
4
- import { _ as resolveModelForFormat, a as buildSubtreeCanRun, b as resolveModelDefaultConfidence, c as macroGateVerdict, f as ALL_PIPELINE_STEPS, g as getStepDefinition, h as getStep, i as mergeCustomModels, m as getDefaultModelForFormat, n as resolveDefaultDetectionModel, p as ALL_STEPS, r as CustomModelCatalog, u as localFrameRegistry, y as landmarkPrecisionVerdict } from "../default-detection-model-JDKeiJPf.mjs";
2
+ import { Dn as array, Dt as overlayClusterStepSettings, Gt as runtimeDevices$1, H as YAMNET_TO_MACRO, In as string, Kt as stepModelRunsOnFormat, Ln as union, Mt as pipelineExecutorCapability, Nn as object, Rn as EventCategory, Sn as parseJsonUnknown, Tn as sleep, Ut as resolvePoolMemoryPolicy, Zt as supportedRuntimes$1, _n as hydrateSchema, en as errMsg, et as defaultDeviceFor$1, it as detectionPipelineCapability, j as PoolMemoryWatchdog, kt as pickClusterStepSettings, lt as evaluateZoneRules, m as DEFAULT_CLUSTER_STEP_SETTINGS, pn as createEvent, sn as BaseAddon, st as enumerateInferenceDevices, t as APPLE_SA_TO_MACRO, u as CLUSTER_MODEL_SCOPED_STEPS, vt as loadContributionCapability, xn as nodePin, y as DEVICE_BACKEND_TO_FORMAT, zt as resolveClusterStepModelId } from "../dist-CLZmZfHT.mjs";
3
+ import { s as readProcessCost } from "../node-C5LXX8Qo.mjs";
4
+ import { _ as resolveModelForFormat, a as buildSubtreeCanRun, b as resolveModelDefaultConfidence, c as macroGateVerdict, f as ALL_PIPELINE_STEPS, g as getStepDefinition, h as getStep, i as mergeCustomModels, m as getDefaultModelForFormat, n as resolveDefaultDetectionModel, p as ALL_STEPS, r as CustomModelCatalog, u as localFrameRegistry, y as landmarkPrecisionVerdict } from "../default-detection-model-BroB5Jp1.mjs";
5
5
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
6
- import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-BMr61qT1.mjs";
6
+ import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-CCx6i9av.mjs";
7
7
  import * as os from "node:os";
8
8
  import * as fs from "node:fs";
9
9
  import * as path$1 from "node:path";
@@ -24228,7 +24228,15 @@ var RebuildObjectEmbeddingsInput = object({
24228
24228
  * force is logged at start and finish so a deliberately slow pass reads
24229
24229
  * differently from a stalled one.
24230
24230
  */
24231
- pacingMs: number().int().nonnegative().optional()
24231
+ pacingMs: number().int().nonnegative().optional(),
24232
+ /**
24233
+ * Re-embed ONLY the tracks whose vector is ALREADY in the pinned model's
24234
+ * space (D664) — the in-space skip inverted. It exists to redo vectors
24235
+ * written from the wrong box, which a normal pass would skip as done
24236
+ * forever. Requires `maxTracks`: a forced pass without a bound is refused
24237
+ * (`force-requires-max-tracks`).
24238
+ */
24239
+ force: boolean().optional()
24232
24240
  });
24233
24241
  /**
24234
24242
  * Result of emptying the CLIP index.
@@ -24264,6 +24272,7 @@ var WipeObjectEmbeddingsResultSchema = object({
24264
24272
  * - `reads-disagree` — the two reads named different choices.
24265
24273
  * - `not-a-clip-model` — the CONFIRMED id has no CLIP metadata, so there is no
24266
24274
  * index to fill and no dimension to check against (D649).
24275
+ * - `force-requires-max-tracks` — `force: true` with no `maxTracks` (D664).
24267
24276
  */
24268
24277
  var RebuildObjectEmbeddingsRefusalSchema = _enum([
24269
24278
  "stores-unavailable",
@@ -24271,7 +24280,8 @@ var RebuildObjectEmbeddingsRefusalSchema = _enum([
24271
24280
  "method-unavailable",
24272
24281
  "read-failed",
24273
24282
  "reads-disagree",
24274
- "not-a-clip-model"
24283
+ "not-a-clip-model",
24284
+ "force-requires-max-tracks"
24275
24285
  ]);
24276
24286
  /**
24277
24287
  * Acknowledgement that a rebuild STARTED.
@@ -24296,11 +24306,32 @@ var RebuildStatusSchema = object({
24296
24306
  running: boolean(),
24297
24307
  scanned: number(),
24298
24308
  rebuilt: number(),
24299
- /** Tracks whose key frame is gone — nothing to re-embed from. */
24309
+ /** Tracks whose key frame is gone — nothing to re-embed from. Pixels ONLY:
24310
+ * `missingEvent` is its own field since D664, never summed in. */
24300
24311
  missingKeyFrame: number(),
24301
- /** Tracks with no usable detection box. */
24312
+ /** Tracks with no object event at all (D664). */
24313
+ missingEvent: number(),
24314
+ /** Tracks with no box measured on the pixels to cut — see `boxSource.refused`. */
24302
24315
  missingBbox: number(),
24303
24316
  /**
24317
+ * Which rule placed each judged track's box (D664): the key frame row's own
24318
+ * `subjectBox`, `track.positions` at exactly its instant (older key frames),
24319
+ * the event box (a key frame recovered from the recording, at the event's
24320
+ * instant), or `refused` by name — the reasons are in the tagged log lines.
24321
+ */
24322
+ boxSource: object({
24323
+ subjectBox: number(),
24324
+ positions: number(),
24325
+ event: number(),
24326
+ refused: number()
24327
+ }),
24328
+ /** Tracks skipped because their vector is already in the pinned space. */
24329
+ alreadyInSpace: number(),
24330
+ /** Whether the current / last pass was a `force` pass; `null` before any (D664). */
24331
+ force: boolean().nullable(),
24332
+ /** In-space tracks a `force` pass selected for re-embedding. */
24333
+ forcedInSpace: number(),
24334
+ /**
24304
24335
  * Tracks an executing node REFUSED rather than broke on — an unreadable key
24305
24336
  * frame, a step that threw. Separate from `failed` because the remedy is
24306
24337
  * different, and because a whole camera silently contributing zero vectors
@@ -24228,7 +24228,15 @@ var RebuildObjectEmbeddingsInput = object({
24228
24228
  * force is logged at start and finish so a deliberately slow pass reads
24229
24229
  * differently from a stalled one.
24230
24230
  */
24231
- pacingMs: number().int().nonnegative().optional()
24231
+ pacingMs: number().int().nonnegative().optional(),
24232
+ /**
24233
+ * Re-embed ONLY the tracks whose vector is ALREADY in the pinned model's
24234
+ * space (D664) — the in-space skip inverted. It exists to redo vectors
24235
+ * written from the wrong box, which a normal pass would skip as done
24236
+ * forever. Requires `maxTracks`: a forced pass without a bound is refused
24237
+ * (`force-requires-max-tracks`).
24238
+ */
24239
+ force: boolean().optional()
24232
24240
  });
24233
24241
  /**
24234
24242
  * Result of emptying the CLIP index.
@@ -24264,6 +24272,7 @@ var WipeObjectEmbeddingsResultSchema = object({
24264
24272
  * - `reads-disagree` — the two reads named different choices.
24265
24273
  * - `not-a-clip-model` — the CONFIRMED id has no CLIP metadata, so there is no
24266
24274
  * index to fill and no dimension to check against (D649).
24275
+ * - `force-requires-max-tracks` — `force: true` with no `maxTracks` (D664).
24267
24276
  */
24268
24277
  var RebuildObjectEmbeddingsRefusalSchema = _enum([
24269
24278
  "stores-unavailable",
@@ -24271,7 +24280,8 @@ var RebuildObjectEmbeddingsRefusalSchema = _enum([
24271
24280
  "method-unavailable",
24272
24281
  "read-failed",
24273
24282
  "reads-disagree",
24274
- "not-a-clip-model"
24283
+ "not-a-clip-model",
24284
+ "force-requires-max-tracks"
24275
24285
  ]);
24276
24286
  /**
24277
24287
  * Acknowledgement that a rebuild STARTED.
@@ -24296,11 +24306,32 @@ var RebuildStatusSchema = object({
24296
24306
  running: boolean(),
24297
24307
  scanned: number(),
24298
24308
  rebuilt: number(),
24299
- /** Tracks whose key frame is gone — nothing to re-embed from. */
24309
+ /** Tracks whose key frame is gone — nothing to re-embed from. Pixels ONLY:
24310
+ * `missingEvent` is its own field since D664, never summed in. */
24300
24311
  missingKeyFrame: number(),
24301
- /** Tracks with no usable detection box. */
24312
+ /** Tracks with no object event at all (D664). */
24313
+ missingEvent: number(),
24314
+ /** Tracks with no box measured on the pixels to cut — see `boxSource.refused`. */
24302
24315
  missingBbox: number(),
24303
24316
  /**
24317
+ * Which rule placed each judged track's box (D664): the key frame row's own
24318
+ * `subjectBox`, `track.positions` at exactly its instant (older key frames),
24319
+ * the event box (a key frame recovered from the recording, at the event's
24320
+ * instant), or `refused` by name — the reasons are in the tagged log lines.
24321
+ */
24322
+ boxSource: object({
24323
+ subjectBox: number(),
24324
+ positions: number(),
24325
+ event: number(),
24326
+ refused: number()
24327
+ }),
24328
+ /** Tracks skipped because their vector is already in the pinned space. */
24329
+ alreadyInSpace: number(),
24330
+ /** Whether the current / last pass was a `force` pass; `null` before any (D664). */
24331
+ force: boolean().nullable(),
24332
+ /** In-space tracks a `force` pass selected for re-embedding. */
24333
+ forcedInSpace: number(),
24334
+ /**
24304
24335
  * Tracks an executing node REFUSED rather than broke on — an unreadable key
24305
24336
  * frame, a step that threw. Separate from `failed` because the remedy is
24306
24337
  * different, and because a whole camera silently contributing zero vectors
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-BUs7H-fe.js");
5
+ const require_dist = require("../dist-CyWAhtbJ.js");
6
6
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Et as motionDetectionCapability, _n as hydrateSchema, dn as DeviceType, lt as evaluateZoneRules, sn as BaseAddon } from "../dist-DvzAabca.mjs";
1
+ import { Et as motionDetectionCapability, _n as hydrateSchema, dn as DeviceType, lt as evaluateZoneRules, sn as BaseAddon } from "../dist-CLZmZfHT.mjs";
2
2
  import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -1,5 +1,5 @@
1
1
  const require_chunk = require("./chunk-emK7D4bc.js");
2
- const require_dist = require("./dist-BUs7H-fe.js");
2
+ const require_dist = require("./dist-CyWAhtbJ.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs, 1);
@@ -1,4 +1,4 @@
1
- import { an as isSoftwareDecode, en as errMsg, nn as Fmp4BoxSplitter, rn as buildFfmpegArgs } from "./dist-DvzAabca.mjs";
1
+ import { an as isSoftwareDecode, en as errMsg, nn as Fmp4BoxSplitter, rn as buildFfmpegArgs } from "./dist-CLZmZfHT.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { realpathSync } from "node:fs";
4
4
  import path from "node:path";
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-emK7D4bc.js");
6
- const require_dist = require("../dist-BUs7H-fe.js");
7
- const require_node = require("../node-DxVMyys-.js");
8
- const require_default_detection_model = require("../default-detection-model-P7IIrP0a.js");
6
+ const require_dist = require("../dist-CyWAhtbJ.js");
7
+ const require_node = require("../node-BDrkzYiH.js");
8
+ const require_default_detection_model = require("../default-detection-model-vN686W1U.js");
9
9
  const require_worker_protocol = require("../worker-protocol-C-G8qmye.js");
10
10
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
11
11
  const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
@@ -41,14 +41,18 @@ var KILL_SIGNALS = {
41
41
  * parameter rather than a second `child.on('exit')` in the caller because
42
42
  * every listener added out here is one somebody can forget to remove.
43
43
  */
44
- function wrapChild(child, onExit) {
44
+ function wrapChild(child, onExit, killedByRunner = () => false) {
45
45
  let spawned = false;
46
46
  let exitDelivered = false;
47
47
  const exitListeners = [];
48
- const deliverExit = (info) => {
48
+ const deliverExit = (exit) => {
49
49
  if (exitDelivered) return;
50
50
  exitDelivered = true;
51
51
  onExit();
52
+ const info = killedByRunner() ? {
53
+ ...exit,
54
+ killedByRunner: true
55
+ } : exit;
52
56
  for (const listener of exitListeners) listener(info);
53
57
  };
54
58
  child.on("spawn", () => {
@@ -102,19 +106,179 @@ function wrapChild(child, onExit) {
102
106
  * node-av through `CAMSTACK_FRAMEWORK_DIR` / `CAMSTACK_BUNDLED_ADDONS_DIR` on
103
107
  * agents, so a replaced env would silently break decoding off the hub.
104
108
  */
105
- function forkDecodeWorker(source, costRegistry) {
106
- const child = (0, node_child_process.fork)(CHILD_ENTRY_PATH, [], {
109
+ function forkDecodeWorker(source, deps) {
110
+ return forkDecodeWorkerAt(CHILD_ENTRY_PATH, source, deps ?? {});
111
+ }
112
+ /**
113
+ * {@link forkDecodeWorker} with the entry named — the one seam a process-level
114
+ * test needs to fork a stub through the REAL fork, claim and live-worker path.
115
+ * Production forks only {@link CHILD_ENTRY_PATH}.
116
+ */
117
+ function forkDecodeWorkerAt(entryPath, source, deps) {
118
+ const child = (0, node_child_process.fork)(entryPath, [], {
107
119
  serialization: "advanced",
108
120
  env: { ...process.env }
109
121
  });
110
- const costClaim = costRegistry?.claim({
122
+ const costClaim = deps.costRegistry?.claim({
111
123
  role: "decode",
112
124
  deviceId: source.deviceId,
113
125
  unit: `${source.deviceId}/decode`,
114
126
  pid: child.pid
115
127
  }) ?? require_node.NO_COST_CLAIM;
116
- return wrapChild(child, () => costClaim.release());
128
+ let killedByRunner = false;
129
+ const untrack = deps.liveWorkers?.add({
130
+ deviceId: source.deviceId,
131
+ pid: child.pid,
132
+ killNow: () => {
133
+ killedByRunner = true;
134
+ child.kill("SIGKILL");
135
+ }
136
+ }) ?? (() => void 0);
137
+ return wrapChild(child, () => {
138
+ costClaim.release();
139
+ untrack();
140
+ }, () => killedByRunner);
141
+ }
142
+ //#endregion
143
+ //#region src/session-decode/live-decode-workers.ts
144
+ var KILL_MESSAGE = {
145
+ shutdown: "session-decode: worker killed at shutdown",
146
+ "forked-after-shutdown": "session-decode: worker forked after shutdown began — killed at once",
147
+ "process-exit": "session-decode: worker killed at process exit"
148
+ };
149
+ /** Send the signal. Never throws: a failure is the outcome. */
150
+ function signal(worker) {
151
+ try {
152
+ worker.killNow();
153
+ return {
154
+ worker,
155
+ error: null
156
+ };
157
+ } catch (err) {
158
+ return {
159
+ worker,
160
+ error: require_dist.errMsg(err)
161
+ };
162
+ }
163
+ }
164
+ /**
165
+ * One line per outcome, each in its own swallowing try. The logger may throw:
166
+ * the `'exit'` hook runs while the transport goes down, and the runner's
167
+ * write path has no guard. A line that fails must not cost another line.
168
+ */
169
+ function logOutcomes(outcomes, reason, logger) {
170
+ for (const { worker, error } of outcomes) try {
171
+ if (error === null) logger.info(KILL_MESSAGE[reason], {
172
+ tags: { deviceId: worker.deviceId },
173
+ meta: {
174
+ pid: worker.pid ?? null,
175
+ signal: "SIGKILL",
176
+ reason
177
+ }
178
+ });
179
+ else logger.warn("session-decode: worker kill failed — it may outlive the runner", {
180
+ tags: { deviceId: worker.deviceId },
181
+ meta: {
182
+ pid: worker.pid ?? null,
183
+ reason,
184
+ error
185
+ }
186
+ });
187
+ } catch {}
117
188
  }
189
+ var LiveDecodeWorkers = class {
190
+ live = /* @__PURE__ */ new Set();
191
+ /** Set by {@link close}: from then on a worker is killed as it is added. */
192
+ closedLogger = null;
193
+ /** Track a forked worker; call the returned function from its real exit. */
194
+ add(worker) {
195
+ if (this.closedLogger !== null) {
196
+ const outcome = signal(worker);
197
+ logOutcomes([outcome], "forked-after-shutdown", this.closedLogger);
198
+ if (outcome.error === null) return () => void 0;
199
+ }
200
+ this.live.add(worker);
201
+ return () => {
202
+ this.live.delete(worker);
203
+ };
204
+ }
205
+ get size() {
206
+ return this.live.size;
207
+ }
208
+ /**
209
+ * SIGKILL every tracked worker now, then one log line per worker. SIGKILL
210
+ * rather than SIGTERM: the runner is about to exit and cannot wait for a
211
+ * fallback, and a worker blocked in a native call never runs a SIGTERM
212
+ * handler (D652).
213
+ *
214
+ * EVERY worker is signalled before ANY line is written: a logger that throws
215
+ * must not stop the kills. A signalled worker is forgotten (a second pass
216
+ * logs nothing twice); one whose kill threw stays tracked, so the exit hook
217
+ * or a later pass tries it again. Returns how many it signalled.
218
+ */
219
+ killAll(reason, logger) {
220
+ const outcomes = [...this.live].map(signal);
221
+ for (const outcome of outcomes) if (outcome.error === null) this.live.delete(outcome.worker);
222
+ logOutcomes(outcomes, reason, logger);
223
+ return outcomes.filter((outcome) => outcome.error === null).length;
224
+ }
225
+ /** Kill everything tracked, and kill every later `add` at once. */
226
+ close(reason, logger) {
227
+ this.closedLogger = logger;
228
+ return this.killAll(reason, logger);
229
+ }
230
+ };
231
+ /**
232
+ * The runner's decode workers: every fork is registered in the set, and
233
+ * {@link shutdown} and any process exit kill whatever is still alive.
234
+ */
235
+ var RunnerDecodeWorkers = class {
236
+ deps;
237
+ live = new LiveDecodeWorkers();
238
+ exitHookArmed = false;
239
+ shutDown = false;
240
+ constructor(deps) {
241
+ this.deps = deps;
242
+ }
243
+ /** Fork one worker, registered and cost-claimed. */
244
+ fork = (source) => {
245
+ this.armExitHook();
246
+ return this.deps.fork(source, {
247
+ costRegistry: this.deps.costRegistry,
248
+ liveWorkers: this.live
249
+ });
250
+ };
251
+ /**
252
+ * The addon is shutting down: kill every worker, and every later fork at
253
+ * once. Synchronous, so it is done before the runner's `process.exit`.
254
+ */
255
+ shutdown() {
256
+ this.shutDown = true;
257
+ const killed = this.live.close("shutdown", this.deps.logger);
258
+ if (this.live.size === 0) this.disarmExitHook();
259
+ return killed;
260
+ }
261
+ /**
262
+ * The process is exiting without having shut this addon down (an uncaught
263
+ * error, or an `onShutdown` that never reached {@link shutdown}). The log
264
+ * line is attempted but will probably not land: the transport goes down
265
+ * with the process.
266
+ */
267
+ onProcessExit = () => {
268
+ this.exitHookArmed = false;
269
+ this.live.close("process-exit", this.deps.logger);
270
+ };
271
+ armExitHook() {
272
+ if (this.exitHookArmed || this.shutDown) return;
273
+ this.exitHookArmed = true;
274
+ this.deps.process.once("exit", this.onProcessExit);
275
+ }
276
+ disarmExitHook() {
277
+ if (!this.exitHookArmed) return;
278
+ this.exitHookArmed = false;
279
+ this.deps.process.removeListener("exit", this.onProcessExit);
280
+ }
281
+ };
118
282
  //#endregion
119
283
  //#region src/session-decode/native-lease-config.ts
120
284
  /**
@@ -1722,6 +1886,11 @@ var SessionDecodeSession = class {
1722
1886
  /** Set when `kill()` is sent, so handleExit can tell an intentional stop from a death. */
1723
1887
  killSent = false;
1724
1888
  /**
1889
+ * A SIGKILL already went out — an `'immediate'` teardown from the pump
1890
+ * (D666) and the generator's later `finally` both escalate; one kill is enough.
1891
+ */
1892
+ sigkillSent = false;
1893
+ /**
1725
1894
  * Images yielded and not yet closed that a CONSUMER still holds — queued,
1726
1895
  * in flight, or being read. Each dies with the worker and somebody was going
1727
1896
  * to read it first. This is what a drain waits on.
@@ -1933,7 +2102,7 @@ var SessionDecodeSession = class {
1933
2102
  }
1934
2103
  /** The drain (or a SIGTERM still waiting on its fallback) gives way to SIGKILL now. */
1935
2104
  escalateToImmediate() {
1936
- if (this.exited) return;
2105
+ if (this.exited || this.sigkillSent) return;
1937
2106
  if (this.drainTimer !== null) {
1938
2107
  clearTimeout(this.drainTimer);
1939
2108
  this.drainTimer = null;
@@ -2120,7 +2289,14 @@ var SessionDecodeSession = class {
2120
2289
  }
2121
2290
  handleExit(info) {
2122
2291
  if (!this.exited) this.hooks.onWorkerExit?.();
2123
- if (!this.killSent && !this.exited) this.logger.warn(this.stopRequested ? "session-decode worker exited unexpectedly while draining" : "session-decode worker exited unexpectedly", {
2292
+ if (!this.killSent && !this.exited && info?.killedByRunner === true) this.logger.debug("session-decode worker ended by the runner shutdown kill", {
2293
+ tags: { deviceId: this.deviceId },
2294
+ meta: {
2295
+ code: info.code,
2296
+ signal: info.signal
2297
+ }
2298
+ });
2299
+ else if (!this.killSent && !this.exited) this.logger.warn(this.stopRequested ? "session-decode worker exited unexpectedly while draining" : "session-decode worker exited unexpectedly", {
2124
2300
  tags: { deviceId: this.deviceId },
2125
2301
  meta: {
2126
2302
  code: info?.code ?? null,
@@ -2312,11 +2488,15 @@ var SessionDecodeSession = class {
2312
2488
  tryKill(signal) {
2313
2489
  try {
2314
2490
  this.child.kill(signal);
2491
+ if (signal === "SIGKILL") this.sigkillSent = true;
2315
2492
  } catch (err) {
2316
- this.logger.warn("session-decode-coordinator: kill() on child failed", { meta: {
2317
- signal,
2318
- err: toError(err).message
2319
- } });
2493
+ this.logger.warn("session-decode-coordinator: kill() on child failed", {
2494
+ tags: { deviceId: this.deviceId },
2495
+ meta: {
2496
+ signal,
2497
+ err: toError(err).message
2498
+ }
2499
+ });
2320
2500
  }
2321
2501
  }
2322
2502
  };
@@ -2586,6 +2766,7 @@ function startSessionDecodePump(deps) {
2586
2766
  stopped = true;
2587
2767
  teardownMode = mode;
2588
2768
  releaseSpawnSlotOnce();
2769
+ if (mode === "immediate") escalateSession?.("immediate");
2589
2770
  iterator.return().catch(() => {});
2590
2771
  };
2591
2772
  }
@@ -5960,6 +6141,60 @@ var Semaphore = class {
5960
6141
  if (next) next();
5961
6142
  }
5962
6143
  };
6144
+ //#endregion
6145
+ //#region src/pipeline-runner/below-floor-tally.ts
6146
+ var BELOW_FLOOR_BANDS = [
6147
+ "<0.35",
6148
+ "0.35-0.50",
6149
+ "0.50-0.65",
6150
+ ">=0.65"
6151
+ ];
6152
+ function belowFloorBand(score) {
6153
+ if (score < .35) return "<0.35";
6154
+ if (score < .5) return "0.35-0.50";
6155
+ if (score < .65) return "0.50-0.65";
6156
+ return ">=0.65";
6157
+ }
6158
+ function addInto(into, macroClass, band, n) {
6159
+ const row = into[macroClass] ?? {};
6160
+ row[band] = (row[band] ?? 0) + n;
6161
+ into[macroClass] = row;
6162
+ }
6163
+ /** One frame's `below-threshold` discards, tallied. Other reasons are not the
6164
+ * floor's doing and are not counted here. */
6165
+ function tallyBelowFloor(discarded) {
6166
+ const out = {};
6167
+ if (discarded === void 0) return out;
6168
+ for (const d of discarded) {
6169
+ if (d.reason !== "below-threshold") continue;
6170
+ addInto(out, d.macroClass, belowFloorBand(d.score), 1);
6171
+ }
6172
+ return out;
6173
+ }
6174
+ /** Per-device running sum between two stats reports. */
6175
+ var BelowFloorAccumulator = class {
6176
+ byDevice = /* @__PURE__ */ new Map();
6177
+ add(deviceId, tally) {
6178
+ const classes = Object.keys(tally);
6179
+ if (classes.length === 0) return;
6180
+ const into = this.byDevice.get(deviceId) ?? {};
6181
+ for (const macroClass of classes) {
6182
+ const row = tally[macroClass];
6183
+ if (row === void 0) continue;
6184
+ for (const band of BELOW_FLOOR_BANDS) {
6185
+ const n = row[band];
6186
+ if (n !== void 0 && n > 0) addInto(into, macroClass, band, n);
6187
+ }
6188
+ }
6189
+ this.byDevice.set(deviceId, into);
6190
+ }
6191
+ /** The sum since the last drain, and reset. `{}` is a measured zero. */
6192
+ drain(deviceId) {
6193
+ const out = this.byDevice.get(deviceId) ?? {};
6194
+ this.byDevice.delete(deviceId);
6195
+ return out;
6196
+ }
6197
+ };
5963
6198
  var MotionWithoutInferenceDetector = class {
5964
6199
  streak = /* @__PURE__ */ new Map();
5965
6200
  reported = /* @__PURE__ */ new Set();
@@ -6024,6 +6259,8 @@ var PipelineTimingSampler = class {
6024
6259
  droppedSources = /* @__PURE__ */ new Map();
6025
6260
  /** Cumulative value seen at the previous report — the delta base. */
6026
6261
  lastDroppedCumulative = /* @__PURE__ */ new Map();
6262
+ /** Boxes the operator's class floor dropped since the last report (D665). */
6263
+ belowFloor = new BelowFloorAccumulator();
6027
6264
  reportTimer = null;
6028
6265
  log = null;
6029
6266
  runtimeInfo = {};
@@ -6043,6 +6280,7 @@ var PipelineTimingSampler = class {
6043
6280
  addSample(deviceId, s) {
6044
6281
  if (!this.detSamples.has(deviceId)) this.detSamples.set(deviceId, []);
6045
6282
  this.detSamples.get(deviceId).push(s);
6283
+ if (s.belowFloor !== void 0) this.belowFloor.add(deviceId, s.belowFloor);
6046
6284
  }
6047
6285
  /**
6048
6286
  * Moving average inference time (ms) per camera over the current
@@ -6144,6 +6382,7 @@ var PipelineTimingSampler = class {
6144
6382
  p95: p95(inf)
6145
6383
  },
6146
6384
  detections: totalDet,
6385
+ belowFloor: this.belowFloor.drain(deviceId),
6147
6386
  dropped,
6148
6387
  pipelineRuntime: rt.pipelineRuntime ?? null,
6149
6388
  pipelineModels: rt.pipelineModels ?? null
@@ -6967,7 +7206,8 @@ var PipelineRunner = class {
6967
7206
  resultToEmit: emittedAt - inferDoneAt,
6968
7207
  frameAge: typeof capturedAt === "number" && capturedAt > 0 ? pickedAt - capturedAt : -1,
6969
7208
  endToEnd: emittedAt - enqueuedAt,
6970
- detections: result.detections?.length ?? 0
7209
+ detections: result.detections?.length ?? 0,
7210
+ belowFloor: tallyBelowFloor(result.discarded)
6971
7211
  });
6972
7212
  } else frameReleaseReason = "drop";
6973
7213
  } catch {
@@ -7950,6 +8190,22 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
7950
8190
  */
7951
8191
  costRegistry = new require_node.ChildCostRegistry();
7952
8192
  /**
8193
+ * Every decode worker this process forked, from ANY spawn site (live
8194
+ * sessions, occupancy bursts), until its exit. The first thing `onShutdown`
8195
+ * does is kill them, a fork after that is killed at once, and any process
8196
+ * exit kills what is left (D666). The logger is only unready before
8197
+ * `initialize()`, when no worker can have been forked.
8198
+ */
8199
+ decodeWorkers = new RunnerDecodeWorkers({
8200
+ fork: forkDecodeWorker,
8201
+ costRegistry: this.costRegistry,
8202
+ logger: {
8203
+ info: (message, extras) => this.ctxIfReady?.logger.info(message, extras),
8204
+ warn: (message, extras) => this.ctxIfReady?.logger.warn(message, extras)
8205
+ },
8206
+ process
8207
+ });
8208
+ /**
7953
8209
  * Per-camera inference-deadline counters reported through
7954
8210
  * `failure-contribution`. Bounded, cumulative, and read (never drained) by
7955
8211
  * the provider. Constructed eagerly: created on the first timeout it would
@@ -8399,6 +8655,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8399
8655
  };
8400
8656
  }
8401
8657
  async onShutdown() {
8658
+ this.decodeWorkers.shutdown();
8402
8659
  if (this.runtimeInfoTimer !== null) {
8403
8660
  clearInterval(this.runtimeInfoTimer);
8404
8661
  this.runtimeInfoTimer = null;
@@ -9949,7 +10206,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
9949
10206
  * environment. A worker is therefore forkable before anyone knows which
9950
10207
  * camera it will serve.
9951
10208
  */
9952
- forkDecodeWorkerWithLease = (source) => forkDecodeWorker(source, this.costRegistry);
10209
+ forkDecodeWorkerWithLease = (source) => this.decodeWorkers.fork(source);
9953
10210
  /**
9954
10211
  * The lease knobs for the session that is about to start — `setting > env >
9955
10212
  * default`, resolved HERE because this process is the one that carries the