@camstack/addon-pipeline 1.2.125 → 1.2.127

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.
@@ -4,7 +4,7 @@ Object.defineProperties(exports, {
4
4
  });
5
5
  const require_dist = require("../dist-BZUXb5S5.js");
6
6
  const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-BdArVUlx.js");
7
- const require_lazy_sharp = require("../lazy-sharp-BxMwCkCA.js");
7
+ const require_lazy_sharp = require("../lazy-sharp-DTDXrhMG.js");
8
8
  const require_process_memory = require("../process-memory-Dh1SyzdJ.js");
9
9
  const require_addon_utils = require("../addon-utils-BnprivSs.js");
10
10
  let sharp = require("sharp");
@@ -1,7 +1,7 @@
1
1
  import { n as __require } from "../chunk-DnnnRqeS.mjs";
2
2
  import { $ as pickClusterStepModels, D as YAMNET_TO_MACRO, Dt as hydrateSchema, Et as createEvent, Ft as sleep, G as evaluateZoneRules, Gt as string, I as defaultDeviceFor$1, Kt as union, Lt as array, Mt as parseJsonUnknown, St as BaseAddon, Ut as object, V as detectionPipelineCapability, W as enumerateInferenceDevices, Z as overlayClusterStepSettings, c as DEFAULT_CLUSTER_STEP_MODELS, dt as runtimeDevices$1, et as pickClusterStepSettings, gt as errMsg, jt as nodePin, l as DEFAULT_CLUSTER_STEP_SETTINGS, lt as resolvePoolMemoryPolicy, mt as supportedRuntimes$1, p as DEVICE_BACKEND_TO_FORMAT, q as inferModelProvider, qt as EventCategory, rt as pipelineExecutorCapability, st as resolveClusterStepModelId, t as APPLE_SA_TO_MACRO, x as PoolMemoryWatchdog } from "../dist-HLAOw6Pe.mjs";
3
3
  import { a as ALL_PIPELINE_STEPS, c as getDefaultModelForFormatFromDef, d as resolveModelForFormat, f as landmarkPrecisionVerdict, l as getStep, n as startEventLoopStallMonitor, o as ALL_STEPS, r as localFrameRegistry, s as getDefaultModelForFormat, u as getStepDefinition } from "../event-loop-stall-monitor-BH-cL_hg.mjs";
4
- import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
4
+ import { t as getSharp } from "../lazy-sharp-BtT5x-qP.mjs";
5
5
  import { n as pickNodePlatformArch, t as readProcessMemory } from "../process-memory-CDPRLalN.mjs";
6
6
  import { a as ensureModel, o as isModelDownloaded, r as deleteModelFromDisk } from "../addon-utils-CgBCF-mE.mjs";
7
7
  import sharp from "sharp";
@@ -47,9 +47,10 @@ import * as path$1 from "node:path";
47
47
  * `CAMSTACK_FRAMEWORK_DIR` (`<frameworkDir>/node_modules/<pkg>`). The
48
48
  * launcher points this at the ACTIVE closure in data-root mode and forked
49
49
  * children inherit it (`server/backend/src/launcher.ts`).
50
- * - **ELECTRON agent** — the app bundle beside `CAMSTACK_BUNDLED_ADDONS_DIR`
51
- * (`<resources>/addons`): `<resources>/node_modules/<pkg>` and the
52
- * `app.asar.unpacked` mirror.
50
+ * - **ELECTRON agent** — the app bundle around `CAMSTACK_BUNDLED_ADDONS_DIR`
51
+ * (`<resources>/node_modules/@camstack` since 2026-08-25, `<resources>/addons`
52
+ * before it): `<resources>/node_modules/<pkg>` and the `app.asar.unpacked`
53
+ * mirror. See {@link electronResourcesRoot} — the depth is NOT assumed.
53
54
  * - **the process cwd** — always tried last, and it is NOT redundant:
54
55
  * `CAMSTACK_FRAMEWORK_DIR` is only exported on some boots (the Docker
55
56
  * entrypoint sets it in its fallback branch; the launcher only in data-root
@@ -108,6 +109,38 @@ function isModuleResolutionError(err) {
108
109
  return err.message.includes("Cannot find package") || err.message.includes("Cannot find module");
109
110
  }
110
111
  /**
112
+ * Path segments that are containers on the way to a package dir, never the
113
+ * Electron `Resources` dir itself. Walking past them is what makes the anchor
114
+ * derivation independent of WHICH dir the shell chose to seed addons from.
115
+ */
116
+ var SEED_CONTAINER_SEGMENTS = [
117
+ "@camstack",
118
+ "node_modules",
119
+ "addons"
120
+ ];
121
+ /**
122
+ * The Electron `Contents/Resources` dir, derived from `CAMSTACK_BUNDLED_ADDONS_DIR`.
123
+ *
124
+ * That variable names the dir the launcher SEEDS addons from, and its shape has
125
+ * moved: it was `<resources>/addons` (a second, hand-copied tree) and is now
126
+ * `<resources>/node_modules/@camstack`, the closure's own scope dir — one copy
127
+ * (D45). What this function needs is neither: it needs the sibling
128
+ * `<resources>/node_modules`, where the hoisted `sharp` / `node-av` actually
129
+ * live. A plain `path.dirname` answered the first shape and would have silently
130
+ * answered `<resources>/node_modules/node_modules` for the second, losing both
131
+ * packages in every forked decode worker on a desktop agent. So: climb past the
132
+ * container segments instead of assuming a depth.
133
+ */
134
+ function electronResourcesRoot(bundledAddonsDir) {
135
+ let dir = path$1.resolve(bundledAddonsDir);
136
+ while (SEED_CONTAINER_SEGMENTS.includes(path$1.basename(dir))) {
137
+ const parent = path$1.dirname(dir);
138
+ if (parent === dir) break;
139
+ dir = parent;
140
+ }
141
+ return dir;
142
+ }
143
+ /**
111
144
  * The files a resolution is anchored at, in the order they are tried. Each is a
112
145
  * path INSIDE a candidate `node_modules` (so `<dir>/node_modules/<pkg>` is on
113
146
  * the walk), except the cwd anchor, which walks up from the cwd itself.
@@ -120,7 +153,7 @@ function hostExternalAnchors(env, cwd) {
120
153
  if (frameworkDir !== void 0 && frameworkDir.length > 0) anchors.push(path$1.join(frameworkDir, "node_modules", ANCHOR_FILE));
121
154
  const bundledAddons = env["CAMSTACK_BUNDLED_ADDONS_DIR"];
122
155
  if (bundledAddons !== void 0 && bundledAddons.length > 0) {
123
- const resourcesRoot = path$1.dirname(bundledAddons);
156
+ const resourcesRoot = electronResourcesRoot(bundledAddons);
124
157
  anchors.push(path$1.join(resourcesRoot, "node_modules", ANCHOR_FILE));
125
158
  anchors.push(path$1.join(resourcesRoot, "app.asar.unpacked", "node_modules", ANCHOR_FILE));
126
159
  }
@@ -49,9 +49,10 @@ node_path = require_dist.__toESM(node_path);
49
49
  * `CAMSTACK_FRAMEWORK_DIR` (`<frameworkDir>/node_modules/<pkg>`). The
50
50
  * launcher points this at the ACTIVE closure in data-root mode and forked
51
51
  * children inherit it (`server/backend/src/launcher.ts`).
52
- * - **ELECTRON agent** — the app bundle beside `CAMSTACK_BUNDLED_ADDONS_DIR`
53
- * (`<resources>/addons`): `<resources>/node_modules/<pkg>` and the
54
- * `app.asar.unpacked` mirror.
52
+ * - **ELECTRON agent** — the app bundle around `CAMSTACK_BUNDLED_ADDONS_DIR`
53
+ * (`<resources>/node_modules/@camstack` since 2026-08-25, `<resources>/addons`
54
+ * before it): `<resources>/node_modules/<pkg>` and the `app.asar.unpacked`
55
+ * mirror. See {@link electronResourcesRoot} — the depth is NOT assumed.
55
56
  * - **the process cwd** — always tried last, and it is NOT redundant:
56
57
  * `CAMSTACK_FRAMEWORK_DIR` is only exported on some boots (the Docker
57
58
  * entrypoint sets it in its fallback branch; the launcher only in data-root
@@ -110,6 +111,38 @@ function isModuleResolutionError(err) {
110
111
  return err.message.includes("Cannot find package") || err.message.includes("Cannot find module");
111
112
  }
112
113
  /**
114
+ * Path segments that are containers on the way to a package dir, never the
115
+ * Electron `Resources` dir itself. Walking past them is what makes the anchor
116
+ * derivation independent of WHICH dir the shell chose to seed addons from.
117
+ */
118
+ var SEED_CONTAINER_SEGMENTS = [
119
+ "@camstack",
120
+ "node_modules",
121
+ "addons"
122
+ ];
123
+ /**
124
+ * The Electron `Contents/Resources` dir, derived from `CAMSTACK_BUNDLED_ADDONS_DIR`.
125
+ *
126
+ * That variable names the dir the launcher SEEDS addons from, and its shape has
127
+ * moved: it was `<resources>/addons` (a second, hand-copied tree) and is now
128
+ * `<resources>/node_modules/@camstack`, the closure's own scope dir — one copy
129
+ * (D45). What this function needs is neither: it needs the sibling
130
+ * `<resources>/node_modules`, where the hoisted `sharp` / `node-av` actually
131
+ * live. A plain `path.dirname` answered the first shape and would have silently
132
+ * answered `<resources>/node_modules/node_modules` for the second, losing both
133
+ * packages in every forked decode worker on a desktop agent. So: climb past the
134
+ * container segments instead of assuming a depth.
135
+ */
136
+ function electronResourcesRoot(bundledAddonsDir) {
137
+ let dir = node_path.resolve(bundledAddonsDir);
138
+ while (SEED_CONTAINER_SEGMENTS.includes(node_path.basename(dir))) {
139
+ const parent = node_path.dirname(dir);
140
+ if (parent === dir) break;
141
+ dir = parent;
142
+ }
143
+ return dir;
144
+ }
145
+ /**
113
146
  * The files a resolution is anchored at, in the order they are tried. Each is a
114
147
  * path INSIDE a candidate `node_modules` (so `<dir>/node_modules/<pkg>` is on
115
148
  * the walk), except the cwd anchor, which walks up from the cwd itself.
@@ -122,7 +155,7 @@ function hostExternalAnchors(env, cwd) {
122
155
  if (frameworkDir !== void 0 && frameworkDir.length > 0) anchors.push(node_path.join(frameworkDir, "node_modules", ANCHOR_FILE));
123
156
  const bundledAddons = env["CAMSTACK_BUNDLED_ADDONS_DIR"];
124
157
  if (bundledAddons !== void 0 && bundledAddons.length > 0) {
125
- const resourcesRoot = node_path.dirname(bundledAddons);
158
+ const resourcesRoot = electronResourcesRoot(bundledAddons);
126
159
  anchors.push(node_path.join(resourcesRoot, "node_modules", ANCHOR_FILE));
127
160
  anchors.push(node_path.join(resourcesRoot, "app.asar.unpacked", "node_modules", ANCHOR_FILE));
128
161
  }
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_dist = require("../dist-BZUXb5S5.js");
6
- const require_lazy_sharp = require("../lazy-sharp-BxMwCkCA.js");
6
+ const require_lazy_sharp = require("../lazy-sharp-DTDXrhMG.js");
7
7
  let node_path = require("node:path");
8
8
  let node_fs = require("node:fs");
9
9
  //#region src/motion-wasm/wasm-motion-detector.ts
@@ -1,5 +1,5 @@
1
1
  import { Dt as hydrateSchema, G as evaluateZoneRules, St as BaseAddon, Tt as DeviceType, X as motionDetectionCapability } from "../dist-HLAOw6Pe.mjs";
2
- import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
2
+ import { t as getSharp } from "../lazy-sharp-BtT5x-qP.mjs";
3
3
  import { join } from "node:path";
4
4
  import { readFileSync } from "node:fs";
5
5
  //#region src/motion-wasm/wasm-motion-detector.ts
@@ -6,7 +6,7 @@ const require_dist = require("../dist-BZUXb5S5.js");
6
6
  const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-BdArVUlx.js");
7
7
  const require_worker_protocol = require("../worker-protocol-BxkCoVjw.js");
8
8
  const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
9
- const require_lazy_sharp = require("../lazy-sharp-BxMwCkCA.js");
9
+ const require_lazy_sharp = require("../lazy-sharp-DTDXrhMG.js");
10
10
  const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
11
11
  const require_remote_restream = require("../remote-restream-CO36Sr30.js");
12
12
  let sharp = require("sharp");
@@ -2,7 +2,7 @@ import { Bt as lazy, Et as createEvent, F as customAction, Gt as string, Ht as n
2
2
  import { i as resolveFrameViewGeometry, r as localFrameRegistry, t as attributeStall, u as getStepDefinition } from "../event-loop-stall-monitor-BH-cL_hg.mjs";
3
3
  import { a as nativeLeaseSettingEnv, t as isWorkerReply } from "../worker-protocol-h7Smb_0i.mjs";
4
4
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
5
- import { t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
5
+ import { t as getSharp } from "../lazy-sharp-BtT5x-qP.mjs";
6
6
  import { n as withDetectionIntent } from "../restream-intent-B4BXZra7.mjs";
7
7
  import { n as profileForStreamId, t as isRemoteRestream } from "../remote-restream-BeHi78PZ.mjs";
8
8
  import sharp from "sharp";
@@ -3151,65 +3151,6 @@ function buildExportArgs(input) {
3151
3151
  ];
3152
3152
  }
3153
3153
  //#endregion
3154
- //#region src/recorder/addon/export-timelapse-chunks.ts
3155
- /** One hour of concatenated footage — not one hour of wall clock. */
3156
- var TIMELAPSE_CHUNK_MS = 36e5;
3157
- /**
3158
- * Split a concat list into hour-sized chunks. A single file longer than the
3159
- * chunk stays whole — it is already one open, and cutting it would need a
3160
- * second demux we do not have.
3161
- */
3162
- function splitTimelapseChunks(entries, chunkMs = TIMELAPSE_CHUNK_MS) {
3163
- if (entries.length === 0) return [];
3164
- const chunks = [];
3165
- let current = [];
3166
- let currentDur = 0;
3167
- let streamStartMs = 0;
3168
- for (const entry of entries) {
3169
- if (current.length > 0 && currentDur + entry.durMs > chunkMs) {
3170
- chunks.push({
3171
- entries: current,
3172
- streamStartMs
3173
- });
3174
- streamStartMs += currentDur;
3175
- current = [];
3176
- currentDur = 0;
3177
- }
3178
- current.push(entry);
3179
- currentDur += entry.durMs;
3180
- }
3181
- if (current.length > 0) chunks.push({
3182
- entries: current,
3183
- streamStartMs
3184
- });
3185
- return chunks;
3186
- }
3187
- /**
3188
- * Move stream-time dense ranges onto a chunk's own `t=0`. Ranges that miss
3189
- * the chunk disappear; a range that straddles the edge is clipped. Empty →
3190
- * `undefined` so the select stays the single-rate form.
3191
- */
3192
- function denseRangesForChunk(dense, chunkStreamStartMs, chunkDurMs) {
3193
- if (dense === void 0 || dense.ranges.length === 0) return void 0;
3194
- const startSec = chunkStreamStartMs / 1e3;
3195
- const endSec = (chunkStreamStartMs + chunkDurMs) / 1e3;
3196
- const ranges = [];
3197
- for (const r of dense.ranges) {
3198
- const from = Math.max(r.fromSec, startSec);
3199
- const to = Math.min(r.toSec, endSec);
3200
- if (to <= from) continue;
3201
- ranges.push({
3202
- fromSec: from - startSec,
3203
- toSec: to - startSec
3204
- });
3205
- }
3206
- if (ranges.length === 0) return void 0;
3207
- return {
3208
- everyMs: dense.everyMs,
3209
- ranges
3210
- };
3211
- }
3212
- //#endregion
3213
3154
  //#region src/recorder/addon/export-store.ts
3214
3155
  /**
3215
3156
  * Recording-export persistence for the recorder addon.
@@ -3371,6 +3312,65 @@ async function listExportsFor(state, deviceId) {
3371
3312
  })).map((entry) => entry.value);
3372
3313
  }
3373
3314
  //#endregion
3315
+ //#region src/recorder/addon/export-timelapse-chunks.ts
3316
+ /** One hour of concatenated footage — not one hour of wall clock. */
3317
+ var TIMELAPSE_CHUNK_MS = 36e5;
3318
+ /**
3319
+ * Split a concat list into hour-sized chunks. A single file longer than the
3320
+ * chunk stays whole — it is already one open, and cutting it would need a
3321
+ * second demux we do not have.
3322
+ */
3323
+ function splitTimelapseChunks(entries, chunkMs = TIMELAPSE_CHUNK_MS) {
3324
+ if (entries.length === 0) return [];
3325
+ const chunks = [];
3326
+ let current = [];
3327
+ let currentDur = 0;
3328
+ let streamStartMs = 0;
3329
+ for (const entry of entries) {
3330
+ if (current.length > 0 && currentDur + entry.durMs > chunkMs) {
3331
+ chunks.push({
3332
+ entries: current,
3333
+ streamStartMs
3334
+ });
3335
+ streamStartMs += currentDur;
3336
+ current = [];
3337
+ currentDur = 0;
3338
+ }
3339
+ current.push(entry);
3340
+ currentDur += entry.durMs;
3341
+ }
3342
+ if (current.length > 0) chunks.push({
3343
+ entries: current,
3344
+ streamStartMs
3345
+ });
3346
+ return chunks;
3347
+ }
3348
+ /**
3349
+ * Move stream-time dense ranges onto a chunk's own `t=0`. Ranges that miss
3350
+ * the chunk disappear; a range that straddles the edge is clipped. Empty →
3351
+ * `undefined` so the select stays the single-rate form.
3352
+ */
3353
+ function denseRangesForChunk(dense, chunkStreamStartMs, chunkDurMs) {
3354
+ if (dense === void 0 || dense.ranges.length === 0) return void 0;
3355
+ const startSec = chunkStreamStartMs / 1e3;
3356
+ const endSec = (chunkStreamStartMs + chunkDurMs) / 1e3;
3357
+ const ranges = [];
3358
+ for (const r of dense.ranges) {
3359
+ const from = Math.max(r.fromSec, startSec);
3360
+ const to = Math.min(r.toSec, endSec);
3361
+ if (to <= from) continue;
3362
+ ranges.push({
3363
+ fromSec: from - startSec,
3364
+ toSec: to - startSec
3365
+ });
3366
+ }
3367
+ if (ranges.length === 0) return void 0;
3368
+ return {
3369
+ everyMs: dense.everyMs,
3370
+ ranges
3371
+ };
3372
+ }
3373
+ //#endregion
3374
3374
  //#region src/recorder/addon/spawn-failure.ts
3375
3375
  /**
3376
3376
  * How the recorder reads a `child_process` `'error'` — ONE owner of the
@@ -3649,12 +3649,11 @@ var ExportEngine = class {
3649
3649
  const survivors = [];
3650
3650
  const sidecars = [];
3651
3651
  try {
3652
- for (let i = 0; i < chunks.length; i++) {
3652
+ for (const [i, chunk] of chunks.entries()) {
3653
3653
  if (this.cancelled.has(id)) {
3654
3654
  await this.fail(id, "cancelled");
3655
3655
  return;
3656
3656
  }
3657
- const chunk = chunks[i];
3658
3657
  const chunkDur = chunk.entries.reduce((s, e) => s + e.durMs, 0);
3659
3658
  const listPath = `${outPath}.chunk${i}.ffconcat`;
3660
3659
  const concatPath = `${outPath}.chunk${i}.concat.ts`;
@@ -3687,8 +3686,8 @@ var ExportEngine = class {
3687
3686
  await this.fail(id, "every timelapse hour failed");
3688
3687
  return;
3689
3688
  }
3690
- if (survivors.length === 1) {
3691
- const only = survivors[0];
3689
+ const only = survivors[0];
3690
+ if (survivors.length === 1 && only !== void 0) {
3692
3691
  const copyArgs = buildExportArgs({
3693
3692
  inputPlaylist: only,
3694
3693
  outPath,
@@ -12044,6 +12043,13 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
12044
12043
  /**
12045
12044
  * Opt-in fleet walk — NOT armed at boot (2026-08-22: starved scrub/play).
12046
12045
  * Kept so an operator path can still request a full reconcile later.
12046
+ *
12047
+ * ⚠ That operator path DOES NOT EXIST YET. This is the only caller of
12048
+ * `runFullArchiveWalk`, so today the whole walk is unreachable — `tsc` was
12049
+ * flagging it as unused and it was one of the errors keeping this package
12050
+ * red. Made public rather than deleted, because deleting it would have taken
12051
+ * the 100-line walk with it; but a comment promising a path nobody built is
12052
+ * exactly the leftover that reads as verification. Wire it or drop it.
12047
12053
  */
12048
12054
  scheduleDeferredFullWalk(deviceIds) {
12049
12055
  if (this.fullWalkTimer !== null || this.fullWalkInFlight) return;
@@ -3150,65 +3150,6 @@ function buildExportArgs(input) {
3150
3150
  ];
3151
3151
  }
3152
3152
  //#endregion
3153
- //#region src/recorder/addon/export-timelapse-chunks.ts
3154
- /** One hour of concatenated footage — not one hour of wall clock. */
3155
- var TIMELAPSE_CHUNK_MS = 36e5;
3156
- /**
3157
- * Split a concat list into hour-sized chunks. A single file longer than the
3158
- * chunk stays whole — it is already one open, and cutting it would need a
3159
- * second demux we do not have.
3160
- */
3161
- function splitTimelapseChunks(entries, chunkMs = TIMELAPSE_CHUNK_MS) {
3162
- if (entries.length === 0) return [];
3163
- const chunks = [];
3164
- let current = [];
3165
- let currentDur = 0;
3166
- let streamStartMs = 0;
3167
- for (const entry of entries) {
3168
- if (current.length > 0 && currentDur + entry.durMs > chunkMs) {
3169
- chunks.push({
3170
- entries: current,
3171
- streamStartMs
3172
- });
3173
- streamStartMs += currentDur;
3174
- current = [];
3175
- currentDur = 0;
3176
- }
3177
- current.push(entry);
3178
- currentDur += entry.durMs;
3179
- }
3180
- if (current.length > 0) chunks.push({
3181
- entries: current,
3182
- streamStartMs
3183
- });
3184
- return chunks;
3185
- }
3186
- /**
3187
- * Move stream-time dense ranges onto a chunk's own `t=0`. Ranges that miss
3188
- * the chunk disappear; a range that straddles the edge is clipped. Empty →
3189
- * `undefined` so the select stays the single-rate form.
3190
- */
3191
- function denseRangesForChunk(dense, chunkStreamStartMs, chunkDurMs) {
3192
- if (dense === void 0 || dense.ranges.length === 0) return void 0;
3193
- const startSec = chunkStreamStartMs / 1e3;
3194
- const endSec = (chunkStreamStartMs + chunkDurMs) / 1e3;
3195
- const ranges = [];
3196
- for (const r of dense.ranges) {
3197
- const from = Math.max(r.fromSec, startSec);
3198
- const to = Math.min(r.toSec, endSec);
3199
- if (to <= from) continue;
3200
- ranges.push({
3201
- fromSec: from - startSec,
3202
- toSec: to - startSec
3203
- });
3204
- }
3205
- if (ranges.length === 0) return void 0;
3206
- return {
3207
- everyMs: dense.everyMs,
3208
- ranges
3209
- };
3210
- }
3211
- //#endregion
3212
3153
  //#region src/recorder/addon/export-store.ts
3213
3154
  /**
3214
3155
  * Recording-export persistence for the recorder addon.
@@ -3370,6 +3311,65 @@ async function listExportsFor(state, deviceId) {
3370
3311
  })).map((entry) => entry.value);
3371
3312
  }
3372
3313
  //#endregion
3314
+ //#region src/recorder/addon/export-timelapse-chunks.ts
3315
+ /** One hour of concatenated footage — not one hour of wall clock. */
3316
+ var TIMELAPSE_CHUNK_MS = 36e5;
3317
+ /**
3318
+ * Split a concat list into hour-sized chunks. A single file longer than the
3319
+ * chunk stays whole — it is already one open, and cutting it would need a
3320
+ * second demux we do not have.
3321
+ */
3322
+ function splitTimelapseChunks(entries, chunkMs = TIMELAPSE_CHUNK_MS) {
3323
+ if (entries.length === 0) return [];
3324
+ const chunks = [];
3325
+ let current = [];
3326
+ let currentDur = 0;
3327
+ let streamStartMs = 0;
3328
+ for (const entry of entries) {
3329
+ if (current.length > 0 && currentDur + entry.durMs > chunkMs) {
3330
+ chunks.push({
3331
+ entries: current,
3332
+ streamStartMs
3333
+ });
3334
+ streamStartMs += currentDur;
3335
+ current = [];
3336
+ currentDur = 0;
3337
+ }
3338
+ current.push(entry);
3339
+ currentDur += entry.durMs;
3340
+ }
3341
+ if (current.length > 0) chunks.push({
3342
+ entries: current,
3343
+ streamStartMs
3344
+ });
3345
+ return chunks;
3346
+ }
3347
+ /**
3348
+ * Move stream-time dense ranges onto a chunk's own `t=0`. Ranges that miss
3349
+ * the chunk disappear; a range that straddles the edge is clipped. Empty →
3350
+ * `undefined` so the select stays the single-rate form.
3351
+ */
3352
+ function denseRangesForChunk(dense, chunkStreamStartMs, chunkDurMs) {
3353
+ if (dense === void 0 || dense.ranges.length === 0) return void 0;
3354
+ const startSec = chunkStreamStartMs / 1e3;
3355
+ const endSec = (chunkStreamStartMs + chunkDurMs) / 1e3;
3356
+ const ranges = [];
3357
+ for (const r of dense.ranges) {
3358
+ const from = Math.max(r.fromSec, startSec);
3359
+ const to = Math.min(r.toSec, endSec);
3360
+ if (to <= from) continue;
3361
+ ranges.push({
3362
+ fromSec: from - startSec,
3363
+ toSec: to - startSec
3364
+ });
3365
+ }
3366
+ if (ranges.length === 0) return void 0;
3367
+ return {
3368
+ everyMs: dense.everyMs,
3369
+ ranges
3370
+ };
3371
+ }
3372
+ //#endregion
3373
3373
  //#region src/recorder/addon/spawn-failure.ts
3374
3374
  /**
3375
3375
  * How the recorder reads a `child_process` `'error'` — ONE owner of the
@@ -3648,12 +3648,11 @@ var ExportEngine = class {
3648
3648
  const survivors = [];
3649
3649
  const sidecars = [];
3650
3650
  try {
3651
- for (let i = 0; i < chunks.length; i++) {
3651
+ for (const [i, chunk] of chunks.entries()) {
3652
3652
  if (this.cancelled.has(id)) {
3653
3653
  await this.fail(id, "cancelled");
3654
3654
  return;
3655
3655
  }
3656
- const chunk = chunks[i];
3657
3656
  const chunkDur = chunk.entries.reduce((s, e) => s + e.durMs, 0);
3658
3657
  const listPath = `${outPath}.chunk${i}.ffconcat`;
3659
3658
  const concatPath = `${outPath}.chunk${i}.concat.ts`;
@@ -3686,8 +3685,8 @@ var ExportEngine = class {
3686
3685
  await this.fail(id, "every timelapse hour failed");
3687
3686
  return;
3688
3687
  }
3689
- if (survivors.length === 1) {
3690
- const only = survivors[0];
3688
+ const only = survivors[0];
3689
+ if (survivors.length === 1 && only !== void 0) {
3691
3690
  const copyArgs = buildExportArgs({
3692
3691
  inputPlaylist: only,
3693
3692
  outPath,
@@ -12043,6 +12042,13 @@ var RecorderV2Addon = class extends BaseAddon {
12043
12042
  /**
12044
12043
  * Opt-in fleet walk — NOT armed at boot (2026-08-22: starved scrub/play).
12045
12044
  * Kept so an operator path can still request a full reconcile later.
12045
+ *
12046
+ * ⚠ That operator path DOES NOT EXIST YET. This is the only caller of
12047
+ * `runFullArchiveWalk`, so today the whole walk is unreachable — `tsc` was
12048
+ * flagging it as unused and it was one of the errors keeping this package
12049
+ * red. Made public rather than deleted, because deleting it would have taken
12050
+ * the 100-line walk with it; but a comment promising a path nobody built is
12051
+ * exactly the leftover that reads as verification. Wire it or drop it.
12046
12052
  */
12047
12053
  scheduleDeferredFullWalk(deviceIds) {
12048
12054
  if (this.fullWalkTimer !== null || this.fullWalkInFlight) return;
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_worker_protocol = require("../worker-protocol-BxkCoVjw.js");
3
- const require_lazy_sharp = require("../lazy-sharp-BxMwCkCA.js");
3
+ const require_lazy_sharp = require("../lazy-sharp-DTDXrhMG.js");
4
4
  //#region src/session-decode/color-conversion.ts
5
5
  /**
6
6
  * Explicit YUV→RGB colorspace/range resolution for the session-decode worker.
@@ -1,5 +1,5 @@
1
1
  import { i as formatNativeLeaseKnobs, n as isWorkerRequest, o as resolveNativeLeaseKnobs, r as logLevelForLine } from "../worker-protocol-h7Smb_0i.mjs";
2
- import { n as setSharpWarnSink, r as hostExternalEntryUrls, t as getSharp } from "../lazy-sharp-6oymT_yf.mjs";
2
+ import { n as setSharpWarnSink, r as hostExternalEntryUrls, t as getSharp } from "../lazy-sharp-BtT5x-qP.mjs";
3
3
  //#region src/session-decode/color-conversion.ts
4
4
  /**
5
5
  * Explicit YUV→RGB colorspace/range resolution for the session-decode worker.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.125",
3
+ "version": "1.2.127",
4
4
  "description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",