@camstack/addon-pipeline 1.2.126 → 1.2.128
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/detection-pipeline/index.js +1 -1
- package/dist/detection-pipeline/index.mjs +1 -1
- package/dist/{lazy-sharp-6oymT_yf.mjs → lazy-sharp-BtT5x-qP.mjs} +37 -4
- package/dist/{lazy-sharp-BxMwCkCA.js → lazy-sharp-DTDXrhMG.js} +37 -4
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +1 -1
- package/dist/pipeline-runner/index.mjs +1 -1
- package/dist/recorder/index.js +119 -31
- package/dist/recorder/index.mjs +119 -31
- package/dist/session-decode/decode-worker-child.js +1 -1
- package/dist/session-decode/decode-worker-child.mjs +1 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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-
|
|
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
|
|
51
|
-
* (`<resources>/
|
|
52
|
-
* `app.asar.unpacked`
|
|
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 =
|
|
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
|
|
53
|
-
* (`<resources>/
|
|
54
|
-
* `app.asar.unpacked`
|
|
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 =
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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";
|
package/dist/recorder/index.js
CHANGED
|
@@ -6061,13 +6061,20 @@ async function readPlacementState(state) {
|
|
|
6061
6061
|
* Replace the assignment map, preserving everything else in the blob. The WHOLE
|
|
6062
6062
|
* blob round-trips on every write (the DurableState contract), so the remembered
|
|
6063
6063
|
* volume ids cannot be dropped by an assignment write.
|
|
6064
|
+
*
|
|
6065
|
+
* `base` is REQUIRED, and it is the blob the caller already read. This used to
|
|
6066
|
+
* re-read the store itself, which made every write two round trips instead of
|
|
6067
|
+
* one — and every one of them goes over the UDS door to `hub/sqlite-settings`
|
|
6068
|
+
* with the 60 s default bound. The placement pass read the blob, then wrote it,
|
|
6069
|
+
* and paid for the read TWICE: 4 × 60 s = the `durationMs: 240003` pass measured
|
|
6070
|
+
* on the live hub against a 30 s budget. Taking the blob as an argument makes
|
|
6071
|
+
* "one plan, one read, one write" a shape the type system enforces.
|
|
6064
6072
|
*/
|
|
6065
|
-
async function writeAssignments(state, assignments) {
|
|
6066
|
-
const blob = await state.get();
|
|
6073
|
+
async function writeAssignments(state, base, assignments) {
|
|
6067
6074
|
const next = {};
|
|
6068
6075
|
for (const [key, locationId] of assignments) next[key] = locationId;
|
|
6069
6076
|
await state.set({
|
|
6070
|
-
...
|
|
6077
|
+
...base,
|
|
6071
6078
|
assignments: next,
|
|
6072
6079
|
updatedAt: Date.now()
|
|
6073
6080
|
});
|
|
@@ -6583,9 +6590,45 @@ var fsVolumeProbeDeps = {
|
|
|
6583
6590
|
newVolumeId: () => (0, node_crypto.randomUUID)()
|
|
6584
6591
|
};
|
|
6585
6592
|
//#endregion
|
|
6593
|
+
//#region src/recorder/addon/with-deadline.ts
|
|
6594
|
+
/**
|
|
6595
|
+
* Race a promise against a deadline.
|
|
6596
|
+
*
|
|
6597
|
+
* Every cross-process call the recorder makes goes over a UDS door whose
|
|
6598
|
+
* DEFAULT timeout is 60 s (`DEFAULT_UDS_REQUEST_TIMEOUT_MS`). That default is a
|
|
6599
|
+
* backstop, not a budget: a pass that makes four of them serially and bounds
|
|
6600
|
+
* none of them costs four minutes, and the recorder's periodic passes SKIP
|
|
6601
|
+
* every fire while a run is in flight — so the unbounded call does not make one
|
|
6602
|
+
* pass late, it cancels the seven behind it. Measured on the live hub as a
|
|
6603
|
+
* `durationMs: 240003` placement pass against a 30 s budget (2026-08-18/19).
|
|
6604
|
+
*
|
|
6605
|
+
* Handlers stay attached to the losing promise, so a post-deadline settlement
|
|
6606
|
+
* can never surface as a process-level `unhandledRejection`.
|
|
6607
|
+
*/
|
|
6608
|
+
function withDeadline(promise, ms, label) {
|
|
6609
|
+
return new Promise((resolve, reject) => {
|
|
6610
|
+
const timer = setTimeout(() => {
|
|
6611
|
+
reject(/* @__PURE__ */ new Error(`${label} timed out after ${ms}ms`));
|
|
6612
|
+
}, ms);
|
|
6613
|
+
timer.unref?.();
|
|
6614
|
+
promise.then((value) => {
|
|
6615
|
+
clearTimeout(timer);
|
|
6616
|
+
resolve(value);
|
|
6617
|
+
}, (err) => {
|
|
6618
|
+
clearTimeout(timer);
|
|
6619
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
6620
|
+
});
|
|
6621
|
+
});
|
|
6622
|
+
}
|
|
6623
|
+
//#endregion
|
|
6586
6624
|
//#region src/recorder/addon/placement-service.ts
|
|
6587
6625
|
/** Free space a location keeps in reserve when its config names no floor. */
|
|
6588
6626
|
var DEFAULT_MIN_FREE_PERCENT = 10;
|
|
6627
|
+
/** Nothing planned, nothing to persist. */
|
|
6628
|
+
var NO_PLAN = {
|
|
6629
|
+
assignments: /* @__PURE__ */ new Map(),
|
|
6630
|
+
changes: []
|
|
6631
|
+
};
|
|
6589
6632
|
var PlacementService = class {
|
|
6590
6633
|
deps;
|
|
6591
6634
|
/** Locations whose volume has been probed since the last location change. */
|
|
@@ -6614,6 +6657,9 @@ var PlacementService = class {
|
|
|
6614
6657
|
get reprobeMs() {
|
|
6615
6658
|
return this.deps.reprobeMs ?? 6e4;
|
|
6616
6659
|
}
|
|
6660
|
+
get storeTimeoutMs() {
|
|
6661
|
+
return this.deps.storeTimeoutMs ?? 5e3;
|
|
6662
|
+
}
|
|
6617
6663
|
/**
|
|
6618
6664
|
* Forget every probe verdict. Called whenever the location set is re-resolved
|
|
6619
6665
|
* (rescan, storage migration) — a remount is exactly the event the probe
|
|
@@ -6756,6 +6802,10 @@ var PlacementService = class {
|
|
|
6756
6802
|
* controller's existing 30s tick — it changes the PLAN only; a running writer
|
|
6757
6803
|
* keeps its root until it next attaches. Returns the plan so a caller can log
|
|
6758
6804
|
* or assert on it.
|
|
6805
|
+
*
|
|
6806
|
+
* ONE read and, at most, ONE write. Both bounded
|
|
6807
|
+
* ({@link PLACEMENT_STORE_TIMEOUT_MS}) and both against the same blob: the
|
|
6808
|
+
* plan the pass reads is the plan it writes back.
|
|
6759
6809
|
*/
|
|
6760
6810
|
async recompute(work) {
|
|
6761
6811
|
const pool = this.pool();
|
|
@@ -6765,12 +6815,17 @@ var PlacementService = class {
|
|
|
6765
6815
|
type: item.profile === "low" ? "recordingsLow" : "recordings",
|
|
6766
6816
|
dailyBytes: this.deps.dailyBytesFor?.(item.deviceId, item.profile) ?? 0
|
|
6767
6817
|
}));
|
|
6768
|
-
let
|
|
6818
|
+
let blob;
|
|
6769
6819
|
try {
|
|
6770
|
-
|
|
6820
|
+
blob = await this.bounded(readPlacementState(this.deps.state), "placement plan read");
|
|
6771
6821
|
} catch (err) {
|
|
6772
|
-
this.deps.logger.warn("recorder placement: plan read failed —
|
|
6822
|
+
this.deps.logger.warn("recorder placement: plan read failed — this tick plans NOTHING and the stored plan stands", { meta: {
|
|
6823
|
+
error: require_dist.errMsg(err),
|
|
6824
|
+
timeoutMs: this.storeTimeoutMs
|
|
6825
|
+
} });
|
|
6826
|
+
return NO_PLAN;
|
|
6773
6827
|
}
|
|
6828
|
+
const current = new Map(Object.entries(blob.assignments));
|
|
6774
6829
|
const evacuate = new Set(candidates.filter((c) => c.headroomBytes <= 0).map((c) => c.locationId));
|
|
6775
6830
|
for (const location of pool) if (location.readOnly) evacuate.add(location.id);
|
|
6776
6831
|
const plan = planPlacement({
|
|
@@ -6794,12 +6849,19 @@ var PlacementService = class {
|
|
|
6794
6849
|
});
|
|
6795
6850
|
}
|
|
6796
6851
|
try {
|
|
6797
|
-
await writeAssignments(this.deps.state, plan.assignments);
|
|
6852
|
+
await this.bounded(writeAssignments(this.deps.state, blob, plan.assignments), "placement plan write");
|
|
6798
6853
|
} catch (err) {
|
|
6799
|
-
this.deps.logger.warn("recorder placement: plan persist failed", { meta: {
|
|
6854
|
+
this.deps.logger.warn("recorder placement: plan persist failed", { meta: {
|
|
6855
|
+
error: require_dist.errMsg(err),
|
|
6856
|
+
timeoutMs: this.storeTimeoutMs
|
|
6857
|
+
} });
|
|
6800
6858
|
}
|
|
6801
6859
|
return plan;
|
|
6802
6860
|
}
|
|
6861
|
+
/** Bound one addon-store round trip. See {@link PLACEMENT_STORE_TIMEOUT_MS}. */
|
|
6862
|
+
bounded(promise, label) {
|
|
6863
|
+
return withDeadline(promise, this.storeTimeoutMs, label);
|
|
6864
|
+
}
|
|
6803
6865
|
/**
|
|
6804
6866
|
* The write targets and what each can still absorb — the SAME pool and the
|
|
6805
6867
|
* SAME headroom `recompute` plans over.
|
|
@@ -6970,7 +7032,7 @@ var PlacementService = class {
|
|
|
6970
7032
|
const blob = await readPlacementState(this.deps.state);
|
|
6971
7033
|
const next = new Map(Object.entries(blob.assignments));
|
|
6972
7034
|
next.set(key, location.id);
|
|
6973
|
-
await writeAssignments(this.deps.state, next);
|
|
7035
|
+
await writeAssignments(this.deps.state, blob, next);
|
|
6974
7036
|
} catch (err) {
|
|
6975
7037
|
this.deps.logger.warn("recorder placement: assignment persist failed", {
|
|
6976
7038
|
tags: { deviceId },
|
|
@@ -8282,6 +8344,40 @@ var PLACEMENT_TICK_MS = 3e4;
|
|
|
8282
8344
|
*/
|
|
8283
8345
|
var RECONCILE_TICK_MS = 12e4;
|
|
8284
8346
|
/**
|
|
8347
|
+
* Bound on the ONE store read at the head of every placement and reconcile pass
|
|
8348
|
+
* (`enabledDeviceIds` → `readDeviceConfigs`).
|
|
8349
|
+
*
|
|
8350
|
+
* It is a cross-process read of `hub/sqlite-settings` and it was unbounded, so
|
|
8351
|
+
* it fell through to the 60 s UDS default. Sixty seconds is TWICE the placement
|
|
8352
|
+
* pass's whole budget spent before the pass has looked at a single location, and
|
|
8353
|
+
* it is a third of the reconcile budget spent before a single camera is
|
|
8354
|
+
* evaluated. Measured on the live hub on 2026-08-25: the pass that logged
|
|
8355
|
+
* `durationMs: 181056` spent its first 59 952 ms here and then logged
|
|
8356
|
+
* `could not enumerate persisted devices`.
|
|
8357
|
+
*
|
|
8358
|
+
* Failing this read is already a handled, LOGGED degradation — the pass covers
|
|
8359
|
+
* the active devices only — so a bound costs one pass a narrower scope and buys
|
|
8360
|
+
* back 55 s. Five seconds is three orders of magnitude above the healthy read.
|
|
8361
|
+
*/
|
|
8362
|
+
var TRACKED_DEVICES_READ_TIMEOUT_MS = 5e3;
|
|
8363
|
+
/**
|
|
8364
|
+
* A placement pass slower than this is reported WITH ITS PHASES, long before it
|
|
8365
|
+
* reaches the 30 s budget that only says "it was slow".
|
|
8366
|
+
*
|
|
8367
|
+
* The budget line is a tombstone: by the time a pass trips it, it has already
|
|
8368
|
+
* skipped the ticks behind it, and it names no phase — every 240 s pass in this
|
|
8369
|
+
* repo's history had to be attributed by hand-correlating adjacent lines. This
|
|
8370
|
+
* says which half was slow, so a store that is degrading is visible while it is
|
|
8371
|
+
* still only degrading.
|
|
8372
|
+
*
|
|
8373
|
+
* Deliberately BELOW every bound the pass applies (5 s), so a pass that trips
|
|
8374
|
+
* one of them always reports its phases — a threshold equal to the bound misses
|
|
8375
|
+
* the trip by a rounding error. A healthy pass is single-digit milliseconds
|
|
8376
|
+
* (`placementMs` 3–18 ms on the live hub), so 2 s is still two orders of
|
|
8377
|
+
* magnitude of headroom.
|
|
8378
|
+
*/
|
|
8379
|
+
var PLACEMENT_PHASE_WARN_MS = 2e3;
|
|
8380
|
+
/**
|
|
8285
8381
|
* Bound on the broker lease-release RPC (`releaseStreamWithCodec`) during
|
|
8286
8382
|
* teardown. The UDS request default is 60s, but the runner supervisor's
|
|
8287
8383
|
* SIGTERM→SIGKILL grace (`$process.stop`/`restart` in the kernel
|
|
@@ -8329,11 +8425,6 @@ function passSlowAfterMs(intervalMs) {
|
|
|
8329
8425
|
return intervalMs;
|
|
8330
8426
|
}
|
|
8331
8427
|
/**
|
|
8332
|
-
* Race `promise` against a deadline. Handlers stay attached to the losing
|
|
8333
|
-
* promise, so a post-deadline settlement can never surface as a process-level
|
|
8334
|
-
* `unhandledRejection`.
|
|
8335
|
-
*/
|
|
8336
|
-
/**
|
|
8337
8428
|
* Bound on the two registry reads the battery-wake policy makes. They run
|
|
8338
8429
|
* before any broker call on every attach, battery camera or not, so they are
|
|
8339
8430
|
* the cheapest place for an unresponsive durable store to cost a recording.
|
|
@@ -8348,21 +8439,6 @@ var BATTERY_READ_TIMEOUT_MS = 3e3;
|
|
|
8348
8439
|
* the shipped 30 s post-buffer.
|
|
8349
8440
|
*/
|
|
8350
8441
|
var TRIGGER_LAG_WARN_MS = 2e3;
|
|
8351
|
-
function withDeadline(promise, ms, label) {
|
|
8352
|
-
return new Promise((resolve, reject) => {
|
|
8353
|
-
const timer = setTimeout(() => {
|
|
8354
|
-
reject(/* @__PURE__ */ new Error(`${label} timed out after ${ms}ms`));
|
|
8355
|
-
}, ms);
|
|
8356
|
-
timer.unref?.();
|
|
8357
|
-
promise.then((value) => {
|
|
8358
|
-
clearTimeout(timer);
|
|
8359
|
-
resolve(value);
|
|
8360
|
-
}, (err) => {
|
|
8361
|
-
clearTimeout(timer);
|
|
8362
|
-
reject(err instanceof Error ? err : new Error(String(err)));
|
|
8363
|
-
});
|
|
8364
|
-
});
|
|
8365
|
-
}
|
|
8366
8442
|
/**
|
|
8367
8443
|
* Floor for "this writer is pinned but producing nothing".
|
|
8368
8444
|
*
|
|
@@ -8609,7 +8685,8 @@ var RecordingController = class {
|
|
|
8609
8685
|
async trackedDeviceIds() {
|
|
8610
8686
|
const ids = new Set(this.active.keys());
|
|
8611
8687
|
try {
|
|
8612
|
-
|
|
8688
|
+
const enabled = await withDeadline(this.deps.enabledDeviceIds(), TRACKED_DEVICES_READ_TIMEOUT_MS, "recorder: enabledDeviceIds");
|
|
8689
|
+
for (const id of enabled) ids.add(id);
|
|
8613
8690
|
} catch (err) {
|
|
8614
8691
|
this.deps.logger.warn("recorder controller: could not enumerate persisted devices — this pass covers the active ones only", { meta: {
|
|
8615
8692
|
error: require_dist.errMsg(err),
|
|
@@ -8627,12 +8704,22 @@ var RecordingController = class {
|
|
|
8627
8704
|
async replanPlacement() {
|
|
8628
8705
|
if (this.stopped || this.paused) return;
|
|
8629
8706
|
if (this.deps.onPlacementTick === void 0) return;
|
|
8707
|
+
const startedAt = this.now();
|
|
8630
8708
|
const ids = await this.trackedDeviceIds();
|
|
8709
|
+
const enumeratedAt = this.now();
|
|
8631
8710
|
try {
|
|
8632
8711
|
await this.deps.onPlacementTick(ids);
|
|
8633
8712
|
} catch (err) {
|
|
8634
8713
|
this.deps.logger.warn("recorder controller: placement recompute failed", { meta: { error: require_dist.errMsg(err) } });
|
|
8635
8714
|
}
|
|
8715
|
+
const finishedAt = this.now();
|
|
8716
|
+
if (finishedAt - startedAt > PLACEMENT_PHASE_WARN_MS) this.deps.logger.warn("recorder: placement pass ran long — phase breakdown", { meta: {
|
|
8717
|
+
totalMs: finishedAt - startedAt,
|
|
8718
|
+
enumerateMs: enumeratedAt - startedAt,
|
|
8719
|
+
recomputeMs: finishedAt - enumeratedAt,
|
|
8720
|
+
devices: ids.length,
|
|
8721
|
+
budgetMs: PLACEMENT_TICK_MS
|
|
8722
|
+
} });
|
|
8636
8723
|
}
|
|
8637
8724
|
/**
|
|
8638
8725
|
* The backstop pass: re-evaluate every tracked-or-configured device.
|
|
@@ -11808,7 +11895,8 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
|
|
|
11808
11895
|
*/
|
|
11809
11896
|
async resetPlacementPlan() {
|
|
11810
11897
|
try {
|
|
11811
|
-
|
|
11898
|
+
const store = this.placementStore();
|
|
11899
|
+
await writeAssignments(store, await readPlacementState(store), /* @__PURE__ */ new Map());
|
|
11812
11900
|
this.ctx.logger.info("recorder: placement plan reset after storage migration");
|
|
11813
11901
|
} catch (err) {
|
|
11814
11902
|
this.ctx.logger.warn("recorder: placement plan reset failed", { meta: { error: require_dist.errMsg(err) } });
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -6060,13 +6060,20 @@ async function readPlacementState(state) {
|
|
|
6060
6060
|
* Replace the assignment map, preserving everything else in the blob. The WHOLE
|
|
6061
6061
|
* blob round-trips on every write (the DurableState contract), so the remembered
|
|
6062
6062
|
* volume ids cannot be dropped by an assignment write.
|
|
6063
|
+
*
|
|
6064
|
+
* `base` is REQUIRED, and it is the blob the caller already read. This used to
|
|
6065
|
+
* re-read the store itself, which made every write two round trips instead of
|
|
6066
|
+
* one — and every one of them goes over the UDS door to `hub/sqlite-settings`
|
|
6067
|
+
* with the 60 s default bound. The placement pass read the blob, then wrote it,
|
|
6068
|
+
* and paid for the read TWICE: 4 × 60 s = the `durationMs: 240003` pass measured
|
|
6069
|
+
* on the live hub against a 30 s budget. Taking the blob as an argument makes
|
|
6070
|
+
* "one plan, one read, one write" a shape the type system enforces.
|
|
6063
6071
|
*/
|
|
6064
|
-
async function writeAssignments(state, assignments) {
|
|
6065
|
-
const blob = await state.get();
|
|
6072
|
+
async function writeAssignments(state, base, assignments) {
|
|
6066
6073
|
const next = {};
|
|
6067
6074
|
for (const [key, locationId] of assignments) next[key] = locationId;
|
|
6068
6075
|
await state.set({
|
|
6069
|
-
...
|
|
6076
|
+
...base,
|
|
6070
6077
|
assignments: next,
|
|
6071
6078
|
updatedAt: Date.now()
|
|
6072
6079
|
});
|
|
@@ -6582,9 +6589,45 @@ var fsVolumeProbeDeps = {
|
|
|
6582
6589
|
newVolumeId: () => randomUUID()
|
|
6583
6590
|
};
|
|
6584
6591
|
//#endregion
|
|
6592
|
+
//#region src/recorder/addon/with-deadline.ts
|
|
6593
|
+
/**
|
|
6594
|
+
* Race a promise against a deadline.
|
|
6595
|
+
*
|
|
6596
|
+
* Every cross-process call the recorder makes goes over a UDS door whose
|
|
6597
|
+
* DEFAULT timeout is 60 s (`DEFAULT_UDS_REQUEST_TIMEOUT_MS`). That default is a
|
|
6598
|
+
* backstop, not a budget: a pass that makes four of them serially and bounds
|
|
6599
|
+
* none of them costs four minutes, and the recorder's periodic passes SKIP
|
|
6600
|
+
* every fire while a run is in flight — so the unbounded call does not make one
|
|
6601
|
+
* pass late, it cancels the seven behind it. Measured on the live hub as a
|
|
6602
|
+
* `durationMs: 240003` placement pass against a 30 s budget (2026-08-18/19).
|
|
6603
|
+
*
|
|
6604
|
+
* Handlers stay attached to the losing promise, so a post-deadline settlement
|
|
6605
|
+
* can never surface as a process-level `unhandledRejection`.
|
|
6606
|
+
*/
|
|
6607
|
+
function withDeadline(promise, ms, label) {
|
|
6608
|
+
return new Promise((resolve, reject) => {
|
|
6609
|
+
const timer = setTimeout(() => {
|
|
6610
|
+
reject(/* @__PURE__ */ new Error(`${label} timed out after ${ms}ms`));
|
|
6611
|
+
}, ms);
|
|
6612
|
+
timer.unref?.();
|
|
6613
|
+
promise.then((value) => {
|
|
6614
|
+
clearTimeout(timer);
|
|
6615
|
+
resolve(value);
|
|
6616
|
+
}, (err) => {
|
|
6617
|
+
clearTimeout(timer);
|
|
6618
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
6619
|
+
});
|
|
6620
|
+
});
|
|
6621
|
+
}
|
|
6622
|
+
//#endregion
|
|
6585
6623
|
//#region src/recorder/addon/placement-service.ts
|
|
6586
6624
|
/** Free space a location keeps in reserve when its config names no floor. */
|
|
6587
6625
|
var DEFAULT_MIN_FREE_PERCENT = 10;
|
|
6626
|
+
/** Nothing planned, nothing to persist. */
|
|
6627
|
+
var NO_PLAN = {
|
|
6628
|
+
assignments: /* @__PURE__ */ new Map(),
|
|
6629
|
+
changes: []
|
|
6630
|
+
};
|
|
6588
6631
|
var PlacementService = class {
|
|
6589
6632
|
deps;
|
|
6590
6633
|
/** Locations whose volume has been probed since the last location change. */
|
|
@@ -6613,6 +6656,9 @@ var PlacementService = class {
|
|
|
6613
6656
|
get reprobeMs() {
|
|
6614
6657
|
return this.deps.reprobeMs ?? 6e4;
|
|
6615
6658
|
}
|
|
6659
|
+
get storeTimeoutMs() {
|
|
6660
|
+
return this.deps.storeTimeoutMs ?? 5e3;
|
|
6661
|
+
}
|
|
6616
6662
|
/**
|
|
6617
6663
|
* Forget every probe verdict. Called whenever the location set is re-resolved
|
|
6618
6664
|
* (rescan, storage migration) — a remount is exactly the event the probe
|
|
@@ -6755,6 +6801,10 @@ var PlacementService = class {
|
|
|
6755
6801
|
* controller's existing 30s tick — it changes the PLAN only; a running writer
|
|
6756
6802
|
* keeps its root until it next attaches. Returns the plan so a caller can log
|
|
6757
6803
|
* or assert on it.
|
|
6804
|
+
*
|
|
6805
|
+
* ONE read and, at most, ONE write. Both bounded
|
|
6806
|
+
* ({@link PLACEMENT_STORE_TIMEOUT_MS}) and both against the same blob: the
|
|
6807
|
+
* plan the pass reads is the plan it writes back.
|
|
6758
6808
|
*/
|
|
6759
6809
|
async recompute(work) {
|
|
6760
6810
|
const pool = this.pool();
|
|
@@ -6764,12 +6814,17 @@ var PlacementService = class {
|
|
|
6764
6814
|
type: item.profile === "low" ? "recordingsLow" : "recordings",
|
|
6765
6815
|
dailyBytes: this.deps.dailyBytesFor?.(item.deviceId, item.profile) ?? 0
|
|
6766
6816
|
}));
|
|
6767
|
-
let
|
|
6817
|
+
let blob;
|
|
6768
6818
|
try {
|
|
6769
|
-
|
|
6819
|
+
blob = await this.bounded(readPlacementState(this.deps.state), "placement plan read");
|
|
6770
6820
|
} catch (err) {
|
|
6771
|
-
this.deps.logger.warn("recorder placement: plan read failed —
|
|
6821
|
+
this.deps.logger.warn("recorder placement: plan read failed — this tick plans NOTHING and the stored plan stands", { meta: {
|
|
6822
|
+
error: errMsg(err),
|
|
6823
|
+
timeoutMs: this.storeTimeoutMs
|
|
6824
|
+
} });
|
|
6825
|
+
return NO_PLAN;
|
|
6772
6826
|
}
|
|
6827
|
+
const current = new Map(Object.entries(blob.assignments));
|
|
6773
6828
|
const evacuate = new Set(candidates.filter((c) => c.headroomBytes <= 0).map((c) => c.locationId));
|
|
6774
6829
|
for (const location of pool) if (location.readOnly) evacuate.add(location.id);
|
|
6775
6830
|
const plan = planPlacement({
|
|
@@ -6793,12 +6848,19 @@ var PlacementService = class {
|
|
|
6793
6848
|
});
|
|
6794
6849
|
}
|
|
6795
6850
|
try {
|
|
6796
|
-
await writeAssignments(this.deps.state, plan.assignments);
|
|
6851
|
+
await this.bounded(writeAssignments(this.deps.state, blob, plan.assignments), "placement plan write");
|
|
6797
6852
|
} catch (err) {
|
|
6798
|
-
this.deps.logger.warn("recorder placement: plan persist failed", { meta: {
|
|
6853
|
+
this.deps.logger.warn("recorder placement: plan persist failed", { meta: {
|
|
6854
|
+
error: errMsg(err),
|
|
6855
|
+
timeoutMs: this.storeTimeoutMs
|
|
6856
|
+
} });
|
|
6799
6857
|
}
|
|
6800
6858
|
return plan;
|
|
6801
6859
|
}
|
|
6860
|
+
/** Bound one addon-store round trip. See {@link PLACEMENT_STORE_TIMEOUT_MS}. */
|
|
6861
|
+
bounded(promise, label) {
|
|
6862
|
+
return withDeadline(promise, this.storeTimeoutMs, label);
|
|
6863
|
+
}
|
|
6802
6864
|
/**
|
|
6803
6865
|
* The write targets and what each can still absorb — the SAME pool and the
|
|
6804
6866
|
* SAME headroom `recompute` plans over.
|
|
@@ -6969,7 +7031,7 @@ var PlacementService = class {
|
|
|
6969
7031
|
const blob = await readPlacementState(this.deps.state);
|
|
6970
7032
|
const next = new Map(Object.entries(blob.assignments));
|
|
6971
7033
|
next.set(key, location.id);
|
|
6972
|
-
await writeAssignments(this.deps.state, next);
|
|
7034
|
+
await writeAssignments(this.deps.state, blob, next);
|
|
6973
7035
|
} catch (err) {
|
|
6974
7036
|
this.deps.logger.warn("recorder placement: assignment persist failed", {
|
|
6975
7037
|
tags: { deviceId },
|
|
@@ -8281,6 +8343,40 @@ var PLACEMENT_TICK_MS = 3e4;
|
|
|
8281
8343
|
*/
|
|
8282
8344
|
var RECONCILE_TICK_MS = 12e4;
|
|
8283
8345
|
/**
|
|
8346
|
+
* Bound on the ONE store read at the head of every placement and reconcile pass
|
|
8347
|
+
* (`enabledDeviceIds` → `readDeviceConfigs`).
|
|
8348
|
+
*
|
|
8349
|
+
* It is a cross-process read of `hub/sqlite-settings` and it was unbounded, so
|
|
8350
|
+
* it fell through to the 60 s UDS default. Sixty seconds is TWICE the placement
|
|
8351
|
+
* pass's whole budget spent before the pass has looked at a single location, and
|
|
8352
|
+
* it is a third of the reconcile budget spent before a single camera is
|
|
8353
|
+
* evaluated. Measured on the live hub on 2026-08-25: the pass that logged
|
|
8354
|
+
* `durationMs: 181056` spent its first 59 952 ms here and then logged
|
|
8355
|
+
* `could not enumerate persisted devices`.
|
|
8356
|
+
*
|
|
8357
|
+
* Failing this read is already a handled, LOGGED degradation — the pass covers
|
|
8358
|
+
* the active devices only — so a bound costs one pass a narrower scope and buys
|
|
8359
|
+
* back 55 s. Five seconds is three orders of magnitude above the healthy read.
|
|
8360
|
+
*/
|
|
8361
|
+
var TRACKED_DEVICES_READ_TIMEOUT_MS = 5e3;
|
|
8362
|
+
/**
|
|
8363
|
+
* A placement pass slower than this is reported WITH ITS PHASES, long before it
|
|
8364
|
+
* reaches the 30 s budget that only says "it was slow".
|
|
8365
|
+
*
|
|
8366
|
+
* The budget line is a tombstone: by the time a pass trips it, it has already
|
|
8367
|
+
* skipped the ticks behind it, and it names no phase — every 240 s pass in this
|
|
8368
|
+
* repo's history had to be attributed by hand-correlating adjacent lines. This
|
|
8369
|
+
* says which half was slow, so a store that is degrading is visible while it is
|
|
8370
|
+
* still only degrading.
|
|
8371
|
+
*
|
|
8372
|
+
* Deliberately BELOW every bound the pass applies (5 s), so a pass that trips
|
|
8373
|
+
* one of them always reports its phases — a threshold equal to the bound misses
|
|
8374
|
+
* the trip by a rounding error. A healthy pass is single-digit milliseconds
|
|
8375
|
+
* (`placementMs` 3–18 ms on the live hub), so 2 s is still two orders of
|
|
8376
|
+
* magnitude of headroom.
|
|
8377
|
+
*/
|
|
8378
|
+
var PLACEMENT_PHASE_WARN_MS = 2e3;
|
|
8379
|
+
/**
|
|
8284
8380
|
* Bound on the broker lease-release RPC (`releaseStreamWithCodec`) during
|
|
8285
8381
|
* teardown. The UDS request default is 60s, but the runner supervisor's
|
|
8286
8382
|
* SIGTERM→SIGKILL grace (`$process.stop`/`restart` in the kernel
|
|
@@ -8328,11 +8424,6 @@ function passSlowAfterMs(intervalMs) {
|
|
|
8328
8424
|
return intervalMs;
|
|
8329
8425
|
}
|
|
8330
8426
|
/**
|
|
8331
|
-
* Race `promise` against a deadline. Handlers stay attached to the losing
|
|
8332
|
-
* promise, so a post-deadline settlement can never surface as a process-level
|
|
8333
|
-
* `unhandledRejection`.
|
|
8334
|
-
*/
|
|
8335
|
-
/**
|
|
8336
8427
|
* Bound on the two registry reads the battery-wake policy makes. They run
|
|
8337
8428
|
* before any broker call on every attach, battery camera or not, so they are
|
|
8338
8429
|
* the cheapest place for an unresponsive durable store to cost a recording.
|
|
@@ -8347,21 +8438,6 @@ var BATTERY_READ_TIMEOUT_MS = 3e3;
|
|
|
8347
8438
|
* the shipped 30 s post-buffer.
|
|
8348
8439
|
*/
|
|
8349
8440
|
var TRIGGER_LAG_WARN_MS = 2e3;
|
|
8350
|
-
function withDeadline(promise, ms, label) {
|
|
8351
|
-
return new Promise((resolve, reject) => {
|
|
8352
|
-
const timer = setTimeout(() => {
|
|
8353
|
-
reject(/* @__PURE__ */ new Error(`${label} timed out after ${ms}ms`));
|
|
8354
|
-
}, ms);
|
|
8355
|
-
timer.unref?.();
|
|
8356
|
-
promise.then((value) => {
|
|
8357
|
-
clearTimeout(timer);
|
|
8358
|
-
resolve(value);
|
|
8359
|
-
}, (err) => {
|
|
8360
|
-
clearTimeout(timer);
|
|
8361
|
-
reject(err instanceof Error ? err : new Error(String(err)));
|
|
8362
|
-
});
|
|
8363
|
-
});
|
|
8364
|
-
}
|
|
8365
8441
|
/**
|
|
8366
8442
|
* Floor for "this writer is pinned but producing nothing".
|
|
8367
8443
|
*
|
|
@@ -8608,7 +8684,8 @@ var RecordingController = class {
|
|
|
8608
8684
|
async trackedDeviceIds() {
|
|
8609
8685
|
const ids = new Set(this.active.keys());
|
|
8610
8686
|
try {
|
|
8611
|
-
|
|
8687
|
+
const enabled = await withDeadline(this.deps.enabledDeviceIds(), TRACKED_DEVICES_READ_TIMEOUT_MS, "recorder: enabledDeviceIds");
|
|
8688
|
+
for (const id of enabled) ids.add(id);
|
|
8612
8689
|
} catch (err) {
|
|
8613
8690
|
this.deps.logger.warn("recorder controller: could not enumerate persisted devices — this pass covers the active ones only", { meta: {
|
|
8614
8691
|
error: errMsg(err),
|
|
@@ -8626,12 +8703,22 @@ var RecordingController = class {
|
|
|
8626
8703
|
async replanPlacement() {
|
|
8627
8704
|
if (this.stopped || this.paused) return;
|
|
8628
8705
|
if (this.deps.onPlacementTick === void 0) return;
|
|
8706
|
+
const startedAt = this.now();
|
|
8629
8707
|
const ids = await this.trackedDeviceIds();
|
|
8708
|
+
const enumeratedAt = this.now();
|
|
8630
8709
|
try {
|
|
8631
8710
|
await this.deps.onPlacementTick(ids);
|
|
8632
8711
|
} catch (err) {
|
|
8633
8712
|
this.deps.logger.warn("recorder controller: placement recompute failed", { meta: { error: errMsg(err) } });
|
|
8634
8713
|
}
|
|
8714
|
+
const finishedAt = this.now();
|
|
8715
|
+
if (finishedAt - startedAt > PLACEMENT_PHASE_WARN_MS) this.deps.logger.warn("recorder: placement pass ran long — phase breakdown", { meta: {
|
|
8716
|
+
totalMs: finishedAt - startedAt,
|
|
8717
|
+
enumerateMs: enumeratedAt - startedAt,
|
|
8718
|
+
recomputeMs: finishedAt - enumeratedAt,
|
|
8719
|
+
devices: ids.length,
|
|
8720
|
+
budgetMs: PLACEMENT_TICK_MS
|
|
8721
|
+
} });
|
|
8635
8722
|
}
|
|
8636
8723
|
/**
|
|
8637
8724
|
* The backstop pass: re-evaluate every tracked-or-configured device.
|
|
@@ -11807,7 +11894,8 @@ var RecorderV2Addon = class extends BaseAddon {
|
|
|
11807
11894
|
*/
|
|
11808
11895
|
async resetPlacementPlan() {
|
|
11809
11896
|
try {
|
|
11810
|
-
|
|
11897
|
+
const store = this.placementStore();
|
|
11898
|
+
await writeAssignments(store, await readPlacementState(store), /* @__PURE__ */ new Map());
|
|
11811
11899
|
this.ctx.logger.info("recorder: placement plan reset after storage migration");
|
|
11812
11900
|
} catch (err) {
|
|
11813
11901
|
this.ctx.logger.warn("recorder: placement plan reset failed", { meta: { error: errMsg(err) } });
|
|
@@ -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-
|
|
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-
|
|
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.
|
|
3
|
+
"version": "1.2.128",
|
|
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",
|