@camstack/system 1.2.80 → 1.2.82
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-bindings-store.d.ts +21 -0
- package/dist/builtins/device-manager/device-manager.addon.js +23 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +23 -2
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +571 -504
- package/dist/builtins/snapshot/index.mjs +571 -504
- package/dist/builtins/snapshot/snapshot-coalescing.d.ts +6 -1
- package/dist/builtins/snapshot/snapshot.addon.d.ts +29 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-CA2mim74.js → dist-DUDXaubL.js} +144 -6
- package/dist/{dist-DZOZtn-0.mjs → dist-DpL4mUA4.mjs} +144 -6
- package/dist/index.js +165 -36
- package/dist/index.mjs +165 -36
- package/dist/kernel/addon-installer.d.ts +54 -2
- package/dist/kernel/addon-loader.d.ts +33 -0
- package/package.json +1 -1
|
@@ -42,8 +42,13 @@ export declare const SWR_COLD_WAIT_MS = 10000;
|
|
|
42
42
|
* Max simultaneous ffmpeg keyframe grabs — the WRAPPER path, the common case for
|
|
43
43
|
* cameras with no vendor snapshot URL. A 20-cam grid mount spawns at most this
|
|
44
44
|
* many ffmpeg processes; the rest serve cached frames (SWR) or queue.
|
|
45
|
+
*
|
|
46
|
+
* Raised 3 → 6 on 2026-08-12 (operator): a 17-camera cold start serialised
|
|
47
|
+
* into 6 waves and the client-side "image displayed" clustering matched the
|
|
48
|
+
* pool exactly (p90 10.8 s). Measured client-side after the raise — if the
|
|
49
|
+
* hub shows decode pressure, this is the first knob to lower.
|
|
45
50
|
*/
|
|
46
|
-
export declare const MAX_CONCURRENT_GRABS =
|
|
51
|
+
export declare const MAX_CONCURRENT_GRABS = 6;
|
|
47
52
|
/**
|
|
48
53
|
* Max simultaneous cheap native snapshot fetches (vendor HTTP/ONVIF). Higher than
|
|
49
54
|
* the ffmpeg bound because a native fetch is a lightweight HTTP round-trip, not a
|
|
@@ -217,6 +217,16 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
|
|
|
217
217
|
* and no cache propagates.
|
|
218
218
|
*/
|
|
219
219
|
private resolveOutcome;
|
|
220
|
+
/**
|
|
221
|
+
* Reconcile backstop for a stale hit: is the frame we are about to serve the
|
|
222
|
+
* property of a device that no longer exists?
|
|
223
|
+
*
|
|
224
|
+
* Asked ONLY past `staleTtlMs`, which is already the "something is wrong"
|
|
225
|
+
* threshold — so the common path costs nothing. A `true` answer evicts and
|
|
226
|
+
* the caller returns null; anything else leaves the existing
|
|
227
|
+
* keep-the-UI-from-going-blank contract untouched.
|
|
228
|
+
*/
|
|
229
|
+
private staleHitBelongsToRemovedDevice;
|
|
220
230
|
/**
|
|
221
231
|
* Run the capture ladder ONCE for a device: native provider first, then the
|
|
222
232
|
* stream-broker ffmpeg fallback. Never rejects — resolves a {@link
|
|
@@ -289,6 +299,25 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
|
|
|
289
299
|
*/
|
|
290
300
|
private runGrabWithResumeRetry;
|
|
291
301
|
private invalidateCache;
|
|
302
|
+
/** Forget every cached artefact of one device: frames, derived variants and
|
|
303
|
+
* any settled single-flight result that would answer the next request. */
|
|
304
|
+
private dropDeviceCaches;
|
|
305
|
+
/**
|
|
306
|
+
* The device is GONE — drop its cached frames and say so.
|
|
307
|
+
*
|
|
308
|
+
* Distinct from `invalidateCache` (a refresh, expected and silent): this is
|
|
309
|
+
* the terminal case, and a deleted camera that keeps answering with its last
|
|
310
|
+
* JPEG looks to an operator exactly like a camera that was never deleted.
|
|
311
|
+
*/
|
|
312
|
+
private evictRemovedDevice;
|
|
313
|
+
/**
|
|
314
|
+
* Does device-manager still know this id?
|
|
315
|
+
*
|
|
316
|
+
* `null` means the question could not be answered — a transport hiccup is
|
|
317
|
+
* not a deletion (D49), and the caller keeps serving what it has. Only an
|
|
318
|
+
* explicit "no such device" evicts.
|
|
319
|
+
*/
|
|
320
|
+
private deviceStillExists;
|
|
292
321
|
/**
|
|
293
322
|
* Sleep state from the device-state MIRROR, not from a cap round-trip.
|
|
294
323
|
*
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DUDXaubL.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let node_fs_promises = require("node:fs/promises");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { gt as BaseAddon, ut as storageProviderCapability, z as filesystemBrowseCapability } from "../../dist-
|
|
1
|
+
import { gt as BaseAddon, ut as storageProviderCapability, z as filesystemBrowseCapability } from "../../dist-DpL4mUA4.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DUDXaubL.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
let node_module = require("node:module");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as decodeVectorBase64, Et as asJsonObject, Lt as parseJsonUnknown, f as RUNTIME_DEFAULTS, gt as BaseAddon, ht as errMsg, k as dataStoreProviderCapability, mt as vectorStoreCapability, pt as vectorDimFromBase64, w as bareAddonId } from "../../dist-
|
|
1
|
+
import { A as decodeVectorBase64, Et as asJsonObject, Lt as parseJsonUnknown, f as RUNTIME_DEFAULTS, gt as BaseAddon, ht as errMsg, k as dataStoreProviderCapability, mt as vectorStoreCapability, pt as vectorDimFromBase64, w as bareAddonId } from "../../dist-DpL4mUA4.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { statSync } from "node:fs";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DUDXaubL.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let node_fs_promises = require("node:fs/promises");
|
|
9
9
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { It as parseJsonObject, ct as storageCapability, g as StorageMigrationJobSchema, gt as BaseAddon, h as StorageLocationTypeSchema, lt as storageMigrationCapability, ot as settingsStoreCapability } from "../../dist-
|
|
1
|
+
import { It as parseJsonObject, ct as storageCapability, g as StorageMigrationJobSchema, gt as BaseAddon, h as StorageLocationTypeSchema, lt as storageMigrationCapability, ot as settingsStoreCapability } from "../../dist-DpL4mUA4.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DUDXaubL.js");
|
|
7
7
|
//#region src/builtins/system-config/system-config.addon.ts
|
|
8
8
|
/**
|
|
9
9
|
* Built-in `system-config` addon — Phase 4 of the settings redesign.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Nt as hydrateSchema, gt as BaseAddon, ht as errMsg } from "../../dist-
|
|
1
|
+
import { Nt as hydrateSchema, gt as BaseAddon, ht as errMsg } from "../../dist-DpL4mUA4.mjs";
|
|
2
2
|
//#region src/builtins/system-config/system-config.addon.ts
|
|
3
3
|
/**
|
|
4
4
|
* Built-in `system-config` addon — Phase 4 of the settings redesign.
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-DUDXaubL.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
let node_path = require("node:path");
|
|
9
9
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { J as logDestinationCapability, gt as BaseAddon } from "../../dist-
|
|
1
|
+
import { J as logDestinationCapability, gt as BaseAddon } from "../../dist-DpL4mUA4.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
import * as path$1 from "node:path";
|
|
4
4
|
import path from "node:path";
|
|
@@ -3305,8 +3305,31 @@ var AdoptionJobSchema = zod.z.object({
|
|
|
3305
3305
|
error: zod.z.string().nullable()
|
|
3306
3306
|
});
|
|
3307
3307
|
/**
|
|
3308
|
-
* Per-camera FUNCTION SWITCHES
|
|
3309
|
-
*
|
|
3308
|
+
* Per-camera FUNCTION SWITCHES.
|
|
3309
|
+
*
|
|
3310
|
+
* ## The aggregate group is being withdrawn — the BADGE is not (D113)
|
|
3311
|
+
*
|
|
3312
|
+
* This file shipped as "the one coherent on/off surface over the pipeline
|
|
3313
|
+
* functions an operator thinks in terms of". The operator's verdict on
|
|
3314
|
+
* 2026-08-12 was that the coherent surface bought complexity and no clarity:
|
|
3315
|
+
* every function already had a settings page of its own, and a second place to
|
|
3316
|
+
* turn it off is a second place to look. Each switch is going back to its own
|
|
3317
|
+
* component's original options — detection to the detection-pipeline wrapper
|
|
3318
|
+
* binding, audio analysis to its own, recording to `RecordingConfig.enabled`
|
|
3319
|
+
* (which was always first-class; the switch was a veneer over
|
|
3320
|
+
* `recording.setDeviceConfig`), notifications to a notification-center
|
|
3321
|
+
* per-device setting, the two camera planes to their own components.
|
|
3322
|
+
*
|
|
3323
|
+
* What survives is {@link composeSwitchedOff}: `CameraStatus.switchedOff`, the
|
|
3324
|
+
* thing that lets a status surface say DISABLED instead of BROKEN, recomposed
|
|
3325
|
+
* straight from the authorities with no group in the middle. That rule was
|
|
3326
|
+
* never about a control panel.
|
|
3327
|
+
*
|
|
3328
|
+
* Everything else here — {@link CAMERA_SWITCH_CATALOG}, {@link CameraSwitch},
|
|
3329
|
+
* {@link deriveCameraSwitches}, the `pipelineOrchestrator.getCameraSwitches` /
|
|
3330
|
+
* `setCameraSwitch` pair — is a COMPATIBILITY surface for as long as deployed
|
|
3331
|
+
* viewers (v1.0.305) and the admin UI still call it. It is deleted when they
|
|
3332
|
+
* stop; nothing new may be built on it.
|
|
3310
3333
|
*
|
|
3311
3334
|
* ## This file adds no state
|
|
3312
3335
|
*
|
|
@@ -16538,9 +16561,16 @@ var CameraStatusSchema = zod.z.object({
|
|
|
16538
16561
|
audio: CameraAudioStatusSchema.nullable(),
|
|
16539
16562
|
recording: CameraRecordingStatusSchema.nullable(),
|
|
16540
16563
|
/**
|
|
16541
|
-
* Per-camera
|
|
16564
|
+
* Per-camera functions an OPERATOR has turned off
|
|
16542
16565
|
* ([D61](../../../../docs/decisions/adr-0067.md)).
|
|
16543
16566
|
*
|
|
16567
|
+
* Composed from the AUTHORITIES themselves — the wrapper bindings,
|
|
16568
|
+
* `RecordingConfig.enabled`, the notification mute, the broker's audio
|
|
16569
|
+
* policy, the camera's own microphone — via `composeSwitchedOff`, not from
|
|
16570
|
+
* the deprecated `getCameraSwitches` group ([D113](../../../../docs/decisions/adr-0113.md)).
|
|
16571
|
+
* The badge outlives the control panel: the panel was a convenience, this is
|
|
16572
|
+
* the difference between a camera being off and a camera being dead.
|
|
16573
|
+
*
|
|
16544
16574
|
* This is the difference between DISABLED and BROKEN. A camera whose
|
|
16545
16575
|
* `detection` block reports zero fps and whose `switchedOff` contains
|
|
16546
16576
|
* `'object-detection'` was switched off by a person; the same camera with an
|
|
@@ -16979,6 +17009,10 @@ var pipelineOrchestratorCapability = {
|
|
|
16979
17009
|
agentNodeId: zod.z.string().optional()
|
|
16980
17010
|
}), CameraPipelineConfigSchema),
|
|
16981
17011
|
/**
|
|
17012
|
+
* @deprecated The aggregate switch group is being withdrawn
|
|
17013
|
+
* ([D113](../../../../docs/decisions/adr-0113.md)). Build nothing new on
|
|
17014
|
+
* this pair; read the authority directly.
|
|
17015
|
+
*
|
|
16982
17016
|
* The whole per-camera function switch group, DERIVED — never a stored
|
|
16983
17017
|
* list ([D61](../../../../docs/decisions/adr-0067.md)).
|
|
16984
17018
|
*
|
|
@@ -16992,9 +17026,23 @@ var pipelineOrchestratorCapability = {
|
|
|
16992
17026
|
* `auth: 'view'` deliberately — a NON-admin must be able to see that a
|
|
16993
17027
|
* camera is quiet because somebody switched it off. Only the mutation is
|
|
16994
17028
|
* admin-gated.
|
|
17029
|
+
*
|
|
17030
|
+
* **Removal plan.** It stays and it KEEPS WORKING while shipped viewers
|
|
17031
|
+
* (v1.0.305) and the admin UI still call it — removing it now is a broken
|
|
17032
|
+
* app on a device nobody can redeploy from here. It is served by a thin
|
|
17033
|
+
* shim over the same authorities (`camera-switch-service.ts`), so the
|
|
17034
|
+
* behaviour of the pair is the behaviour of the authorities by
|
|
17035
|
+
* construction. It is deleted once every surface reaches its own
|
|
17036
|
+
* component's options and the last caller is gone. Nothing on this server
|
|
17037
|
+
* reads it: `CameraStatus.switchedOff` is composed from the authorities
|
|
17038
|
+
* directly via `composeSwitchedOff`.
|
|
16995
17039
|
*/
|
|
16996
17040
|
getCameraSwitches: method(zod.z.object({ deviceId: zod.z.number() }), CameraSwitchGroupSchema),
|
|
16997
17041
|
/**
|
|
17042
|
+
* @deprecated See {@link getCameraSwitches}. Write the authority — the
|
|
17043
|
+
* wrapper binding, `RecordingConfig.enabled`, the notification mute — not
|
|
17044
|
+
* this ([D113](../../../../docs/decisions/adr-0113.md)).
|
|
17045
|
+
*
|
|
16998
17046
|
* Flip ONE switch, routed to its existing authority.
|
|
16999
17047
|
*
|
|
17000
17048
|
* Never writes a parallel map: `recording` patches `RecordingConfig.enabled`
|
|
@@ -25240,10 +25288,42 @@ var recordingCapability = {
|
|
|
25240
25288
|
*/
|
|
25241
25289
|
/** Playback-speed multiplier for the render (1 = realtime). */
|
|
25242
25290
|
var ExportSpeedSchema = zod.z.number().min(.25).max(32);
|
|
25291
|
+
/**
|
|
25292
|
+
* One dense interval, in SECONDS FROM THE EXPORT'S OWN `fromMs`.
|
|
25293
|
+
*
|
|
25294
|
+
* Relative and not absolute epoch on purpose: the renderer's frame-select
|
|
25295
|
+
* expression sees ffmpeg's `t`, which starts at 0 for the export's source
|
|
25296
|
+
* playlist. Handing it absolute epochs would make every call site responsible
|
|
25297
|
+
* for the same subtraction, and the one that forgot would emit a filter that
|
|
25298
|
+
* selects nothing — silently, as a uniform timelapse.
|
|
25299
|
+
*/
|
|
25300
|
+
var ExportDenseRangeSchema = zod.z.object({
|
|
25301
|
+
fromSec: zod.z.number().nonnegative(),
|
|
25302
|
+
toSec: zod.z.number().nonnegative()
|
|
25303
|
+
}).refine((r) => r.toSec > r.fromSec, { message: "dense range must have toSec > fromSec" });
|
|
25304
|
+
/**
|
|
25305
|
+
* Dense-interval overlay for a timelapse: sample at `dense.everyMs` INSIDE the
|
|
25306
|
+
* listed ranges and at the base `everyMs` everywhere else.
|
|
25307
|
+
*
|
|
25308
|
+
* `everyMs` must be strictly smaller than the base cadence — a dense rate that
|
|
25309
|
+
* is not denser renders a uniform timelapse the operator believes is two-rate.
|
|
25310
|
+
*/
|
|
25311
|
+
var ExportDenseSchema = zod.z.object({
|
|
25312
|
+
everyMs: zod.z.number().int().positive(),
|
|
25313
|
+
ranges: zod.z.array(ExportDenseRangeSchema).min(1).max(200)
|
|
25314
|
+
});
|
|
25243
25315
|
/** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
|
|
25244
25316
|
var ExportTimelapseSchema = zod.z.object({
|
|
25245
25317
|
everyMs: zod.z.number().int().positive(),
|
|
25246
|
-
outputFps: zod.z.number().int().min(1).max(60).optional()
|
|
25318
|
+
outputFps: zod.z.number().int().min(1).max(60).optional(),
|
|
25319
|
+
/** Optional second, FASTER rate over the intervals that matter. */
|
|
25320
|
+
dense: ExportDenseSchema.optional()
|
|
25321
|
+
}).superRefine((v, ctx) => {
|
|
25322
|
+
if (v.dense !== void 0 && v.dense.everyMs >= v.everyMs) ctx.addIssue({
|
|
25323
|
+
code: zod.z.ZodIssueCode.custom,
|
|
25324
|
+
message: "dense.everyMs must be strictly smaller than the base everyMs",
|
|
25325
|
+
path: ["dense", "everyMs"]
|
|
25326
|
+
});
|
|
25247
25327
|
});
|
|
25248
25328
|
/**
|
|
25249
25329
|
* Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
|
|
@@ -25301,6 +25381,19 @@ var ExportDownloadSchema = zod.z.object({
|
|
|
25301
25381
|
url: zod.z.string(),
|
|
25302
25382
|
endpoints: zod.z.array(zod.z.string())
|
|
25303
25383
|
});
|
|
25384
|
+
/**
|
|
25385
|
+
* A finished export's bytes, inline.
|
|
25386
|
+
*
|
|
25387
|
+
* `bytes` is the DECODED length — the number the caller bounds and logs
|
|
25388
|
+
* against, so nobody has to infer it from the base64 length.
|
|
25389
|
+
*/
|
|
25390
|
+
var ExportBytesSchema = zod.z.object({
|
|
25391
|
+
base64: zod.z.string(),
|
|
25392
|
+
contentType: zod.z.string(),
|
|
25393
|
+
/** Suggested filename, extension included. */
|
|
25394
|
+
name: zod.z.string(),
|
|
25395
|
+
bytes: zod.z.number().int().nonnegative()
|
|
25396
|
+
});
|
|
25304
25397
|
var recordingExportCapability = {
|
|
25305
25398
|
name: "recordingExport",
|
|
25306
25399
|
scope: "system",
|
|
@@ -25340,6 +25433,27 @@ var recordingExportCapability = {
|
|
|
25340
25433
|
getDownloadUrl: method(zod.z.object({ exportId: zod.z.string() }), ExportDownloadSchema, {
|
|
25341
25434
|
kind: "query",
|
|
25342
25435
|
auth: "protected"
|
|
25436
|
+
}),
|
|
25437
|
+
/**
|
|
25438
|
+
* The finished file's BYTES, base64, for a caller that must republish them
|
|
25439
|
+
* somewhere a session-less fetcher can reach.
|
|
25440
|
+
*
|
|
25441
|
+
* `getDownloadUrl` is the right answer for a human: the download route is
|
|
25442
|
+
* served `access: 'authenticated'`, which a browser satisfies and a
|
|
25443
|
+
* notifier BACKEND does not. It answers a RELATIVE path, so it is not even
|
|
25444
|
+
* a URL an outside fetcher could try. This method exists for the one case
|
|
25445
|
+
* that needs the other thing — a scheduled timelapse whose video has to
|
|
25446
|
+
* become a public attachment on the notification artifact plane.
|
|
25447
|
+
*
|
|
25448
|
+
* Deliberately narrow: `ready` only (a queued, rendering, failed, expired
|
|
25449
|
+
* or deleted export has no file, and answering "0 bytes" for one is how a
|
|
25450
|
+
* caller ships an empty attachment), still inside its lifetime, and under
|
|
25451
|
+
* {@link RECORDING_EXPORT_MAX_READ_BYTES}. Every refusal throws with the
|
|
25452
|
+
* reason — none of them is silent.
|
|
25453
|
+
*/
|
|
25454
|
+
readExportBytes: method(zod.z.object({ exportId: zod.z.string() }), ExportBytesSchema, {
|
|
25455
|
+
kind: "query",
|
|
25456
|
+
auth: "protected"
|
|
25343
25457
|
})
|
|
25344
25458
|
}
|
|
25345
25459
|
};
|
|
@@ -32063,6 +32177,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
32063
32177
|
addonId: null,
|
|
32064
32178
|
access: "view"
|
|
32065
32179
|
},
|
|
32180
|
+
"recordingExport.readExportBytes": {
|
|
32181
|
+
capName: "recordingExport",
|
|
32182
|
+
capScope: "system",
|
|
32183
|
+
addonId: null,
|
|
32184
|
+
access: "view"
|
|
32185
|
+
},
|
|
32066
32186
|
"sceneMonitor.captureReference": {
|
|
32067
32187
|
capName: "scene-monitor",
|
|
32068
32188
|
capScope: "device",
|
|
@@ -33480,10 +33600,28 @@ TimelapseRuleInputSchema.extend({
|
|
|
33480
33600
|
*/
|
|
33481
33601
|
ownerUserId: zod.z.string().optional(),
|
|
33482
33602
|
/**
|
|
33483
|
-
* Epoch-ms of the
|
|
33484
|
-
*
|
|
33603
|
+
* Epoch-ms of the NEWEST successful generation across every camera of this
|
|
33604
|
+
* rule. What a UI shows, and the compatibility floor for
|
|
33605
|
+
* {@link readTimelapseGeneratedAt}. Absent = never generated.
|
|
33485
33606
|
*/
|
|
33486
33607
|
lastGeneratedAt: zod.z.number().optional(),
|
|
33608
|
+
/**
|
|
33609
|
+
* PER-CAMERA generation state, keyed by `String(deviceId)` — the
|
|
33610
|
+
* re-generation guard's real durable state.
|
|
33611
|
+
*
|
|
33612
|
+
* One rule covers several cameras and each renders its own video, so a rule
|
|
33613
|
+
* -wide stamp is wrong in the direction that DESTROYS work: camera A
|
|
33614
|
+
* succeeding at 06:05 tells camera B, whose render failed, that it is
|
|
33615
|
+
* already done — and B's night is gone for good, because the window will not
|
|
33616
|
+
* come back.
|
|
33617
|
+
*
|
|
33618
|
+
* ADDITIVE, so the migration is free: a row written before this field simply
|
|
33619
|
+
* has no map, and {@link readTimelapseGeneratedAt} falls back to
|
|
33620
|
+
* {@link TimelapseRuleSchema.shape.lastGeneratedAt}. Reading an old row as
|
|
33621
|
+
* "never generated" would re-render and re-notify every camera of every rule
|
|
33622
|
+
* once, on the deploy that shipped the map.
|
|
33623
|
+
*/
|
|
33624
|
+
generatedByDevice: zod.z.record(zod.z.string(), zod.z.number()).optional(),
|
|
33487
33625
|
/** userId of the caller who created the rule (server-stamped). */
|
|
33488
33626
|
createdBy: zod.z.string(),
|
|
33489
33627
|
createdAt: zod.z.number(),
|
|
@@ -3305,8 +3305,31 @@ var AdoptionJobSchema = z.object({
|
|
|
3305
3305
|
error: z.string().nullable()
|
|
3306
3306
|
});
|
|
3307
3307
|
/**
|
|
3308
|
-
* Per-camera FUNCTION SWITCHES
|
|
3309
|
-
*
|
|
3308
|
+
* Per-camera FUNCTION SWITCHES.
|
|
3309
|
+
*
|
|
3310
|
+
* ## The aggregate group is being withdrawn — the BADGE is not (D113)
|
|
3311
|
+
*
|
|
3312
|
+
* This file shipped as "the one coherent on/off surface over the pipeline
|
|
3313
|
+
* functions an operator thinks in terms of". The operator's verdict on
|
|
3314
|
+
* 2026-08-12 was that the coherent surface bought complexity and no clarity:
|
|
3315
|
+
* every function already had a settings page of its own, and a second place to
|
|
3316
|
+
* turn it off is a second place to look. Each switch is going back to its own
|
|
3317
|
+
* component's original options — detection to the detection-pipeline wrapper
|
|
3318
|
+
* binding, audio analysis to its own, recording to `RecordingConfig.enabled`
|
|
3319
|
+
* (which was always first-class; the switch was a veneer over
|
|
3320
|
+
* `recording.setDeviceConfig`), notifications to a notification-center
|
|
3321
|
+
* per-device setting, the two camera planes to their own components.
|
|
3322
|
+
*
|
|
3323
|
+
* What survives is {@link composeSwitchedOff}: `CameraStatus.switchedOff`, the
|
|
3324
|
+
* thing that lets a status surface say DISABLED instead of BROKEN, recomposed
|
|
3325
|
+
* straight from the authorities with no group in the middle. That rule was
|
|
3326
|
+
* never about a control panel.
|
|
3327
|
+
*
|
|
3328
|
+
* Everything else here — {@link CAMERA_SWITCH_CATALOG}, {@link CameraSwitch},
|
|
3329
|
+
* {@link deriveCameraSwitches}, the `pipelineOrchestrator.getCameraSwitches` /
|
|
3330
|
+
* `setCameraSwitch` pair — is a COMPATIBILITY surface for as long as deployed
|
|
3331
|
+
* viewers (v1.0.305) and the admin UI still call it. It is deleted when they
|
|
3332
|
+
* stop; nothing new may be built on it.
|
|
3310
3333
|
*
|
|
3311
3334
|
* ## This file adds no state
|
|
3312
3335
|
*
|
|
@@ -16538,9 +16561,16 @@ var CameraStatusSchema = z.object({
|
|
|
16538
16561
|
audio: CameraAudioStatusSchema.nullable(),
|
|
16539
16562
|
recording: CameraRecordingStatusSchema.nullable(),
|
|
16540
16563
|
/**
|
|
16541
|
-
* Per-camera
|
|
16564
|
+
* Per-camera functions an OPERATOR has turned off
|
|
16542
16565
|
* ([D61](../../../../docs/decisions/adr-0067.md)).
|
|
16543
16566
|
*
|
|
16567
|
+
* Composed from the AUTHORITIES themselves — the wrapper bindings,
|
|
16568
|
+
* `RecordingConfig.enabled`, the notification mute, the broker's audio
|
|
16569
|
+
* policy, the camera's own microphone — via `composeSwitchedOff`, not from
|
|
16570
|
+
* the deprecated `getCameraSwitches` group ([D113](../../../../docs/decisions/adr-0113.md)).
|
|
16571
|
+
* The badge outlives the control panel: the panel was a convenience, this is
|
|
16572
|
+
* the difference between a camera being off and a camera being dead.
|
|
16573
|
+
*
|
|
16544
16574
|
* This is the difference between DISABLED and BROKEN. A camera whose
|
|
16545
16575
|
* `detection` block reports zero fps and whose `switchedOff` contains
|
|
16546
16576
|
* `'object-detection'` was switched off by a person; the same camera with an
|
|
@@ -16979,6 +17009,10 @@ var pipelineOrchestratorCapability = {
|
|
|
16979
17009
|
agentNodeId: z.string().optional()
|
|
16980
17010
|
}), CameraPipelineConfigSchema),
|
|
16981
17011
|
/**
|
|
17012
|
+
* @deprecated The aggregate switch group is being withdrawn
|
|
17013
|
+
* ([D113](../../../../docs/decisions/adr-0113.md)). Build nothing new on
|
|
17014
|
+
* this pair; read the authority directly.
|
|
17015
|
+
*
|
|
16982
17016
|
* The whole per-camera function switch group, DERIVED — never a stored
|
|
16983
17017
|
* list ([D61](../../../../docs/decisions/adr-0067.md)).
|
|
16984
17018
|
*
|
|
@@ -16992,9 +17026,23 @@ var pipelineOrchestratorCapability = {
|
|
|
16992
17026
|
* `auth: 'view'` deliberately — a NON-admin must be able to see that a
|
|
16993
17027
|
* camera is quiet because somebody switched it off. Only the mutation is
|
|
16994
17028
|
* admin-gated.
|
|
17029
|
+
*
|
|
17030
|
+
* **Removal plan.** It stays and it KEEPS WORKING while shipped viewers
|
|
17031
|
+
* (v1.0.305) and the admin UI still call it — removing it now is a broken
|
|
17032
|
+
* app on a device nobody can redeploy from here. It is served by a thin
|
|
17033
|
+
* shim over the same authorities (`camera-switch-service.ts`), so the
|
|
17034
|
+
* behaviour of the pair is the behaviour of the authorities by
|
|
17035
|
+
* construction. It is deleted once every surface reaches its own
|
|
17036
|
+
* component's options and the last caller is gone. Nothing on this server
|
|
17037
|
+
* reads it: `CameraStatus.switchedOff` is composed from the authorities
|
|
17038
|
+
* directly via `composeSwitchedOff`.
|
|
16995
17039
|
*/
|
|
16996
17040
|
getCameraSwitches: method(z.object({ deviceId: z.number() }), CameraSwitchGroupSchema),
|
|
16997
17041
|
/**
|
|
17042
|
+
* @deprecated See {@link getCameraSwitches}. Write the authority — the
|
|
17043
|
+
* wrapper binding, `RecordingConfig.enabled`, the notification mute — not
|
|
17044
|
+
* this ([D113](../../../../docs/decisions/adr-0113.md)).
|
|
17045
|
+
*
|
|
16998
17046
|
* Flip ONE switch, routed to its existing authority.
|
|
16999
17047
|
*
|
|
17000
17048
|
* Never writes a parallel map: `recording` patches `RecordingConfig.enabled`
|
|
@@ -25240,10 +25288,42 @@ var recordingCapability = {
|
|
|
25240
25288
|
*/
|
|
25241
25289
|
/** Playback-speed multiplier for the render (1 = realtime). */
|
|
25242
25290
|
var ExportSpeedSchema = z.number().min(.25).max(32);
|
|
25291
|
+
/**
|
|
25292
|
+
* One dense interval, in SECONDS FROM THE EXPORT'S OWN `fromMs`.
|
|
25293
|
+
*
|
|
25294
|
+
* Relative and not absolute epoch on purpose: the renderer's frame-select
|
|
25295
|
+
* expression sees ffmpeg's `t`, which starts at 0 for the export's source
|
|
25296
|
+
* playlist. Handing it absolute epochs would make every call site responsible
|
|
25297
|
+
* for the same subtraction, and the one that forgot would emit a filter that
|
|
25298
|
+
* selects nothing — silently, as a uniform timelapse.
|
|
25299
|
+
*/
|
|
25300
|
+
var ExportDenseRangeSchema = z.object({
|
|
25301
|
+
fromSec: z.number().nonnegative(),
|
|
25302
|
+
toSec: z.number().nonnegative()
|
|
25303
|
+
}).refine((r) => r.toSec > r.fromSec, { message: "dense range must have toSec > fromSec" });
|
|
25304
|
+
/**
|
|
25305
|
+
* Dense-interval overlay for a timelapse: sample at `dense.everyMs` INSIDE the
|
|
25306
|
+
* listed ranges and at the base `everyMs` everywhere else.
|
|
25307
|
+
*
|
|
25308
|
+
* `everyMs` must be strictly smaller than the base cadence — a dense rate that
|
|
25309
|
+
* is not denser renders a uniform timelapse the operator believes is two-rate.
|
|
25310
|
+
*/
|
|
25311
|
+
var ExportDenseSchema = z.object({
|
|
25312
|
+
everyMs: z.number().int().positive(),
|
|
25313
|
+
ranges: z.array(ExportDenseRangeSchema).min(1).max(200)
|
|
25314
|
+
});
|
|
25243
25315
|
/** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
|
|
25244
25316
|
var ExportTimelapseSchema = z.object({
|
|
25245
25317
|
everyMs: z.number().int().positive(),
|
|
25246
|
-
outputFps: z.number().int().min(1).max(60).optional()
|
|
25318
|
+
outputFps: z.number().int().min(1).max(60).optional(),
|
|
25319
|
+
/** Optional second, FASTER rate over the intervals that matter. */
|
|
25320
|
+
dense: ExportDenseSchema.optional()
|
|
25321
|
+
}).superRefine((v, ctx) => {
|
|
25322
|
+
if (v.dense !== void 0 && v.dense.everyMs >= v.everyMs) ctx.addIssue({
|
|
25323
|
+
code: z.ZodIssueCode.custom,
|
|
25324
|
+
message: "dense.everyMs must be strictly smaller than the base everyMs",
|
|
25325
|
+
path: ["dense", "everyMs"]
|
|
25326
|
+
});
|
|
25247
25327
|
});
|
|
25248
25328
|
/**
|
|
25249
25329
|
* Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
|
|
@@ -25301,6 +25381,19 @@ var ExportDownloadSchema = z.object({
|
|
|
25301
25381
|
url: z.string(),
|
|
25302
25382
|
endpoints: z.array(z.string())
|
|
25303
25383
|
});
|
|
25384
|
+
/**
|
|
25385
|
+
* A finished export's bytes, inline.
|
|
25386
|
+
*
|
|
25387
|
+
* `bytes` is the DECODED length — the number the caller bounds and logs
|
|
25388
|
+
* against, so nobody has to infer it from the base64 length.
|
|
25389
|
+
*/
|
|
25390
|
+
var ExportBytesSchema = z.object({
|
|
25391
|
+
base64: z.string(),
|
|
25392
|
+
contentType: z.string(),
|
|
25393
|
+
/** Suggested filename, extension included. */
|
|
25394
|
+
name: z.string(),
|
|
25395
|
+
bytes: z.number().int().nonnegative()
|
|
25396
|
+
});
|
|
25304
25397
|
var recordingExportCapability = {
|
|
25305
25398
|
name: "recordingExport",
|
|
25306
25399
|
scope: "system",
|
|
@@ -25340,6 +25433,27 @@ var recordingExportCapability = {
|
|
|
25340
25433
|
getDownloadUrl: method(z.object({ exportId: z.string() }), ExportDownloadSchema, {
|
|
25341
25434
|
kind: "query",
|
|
25342
25435
|
auth: "protected"
|
|
25436
|
+
}),
|
|
25437
|
+
/**
|
|
25438
|
+
* The finished file's BYTES, base64, for a caller that must republish them
|
|
25439
|
+
* somewhere a session-less fetcher can reach.
|
|
25440
|
+
*
|
|
25441
|
+
* `getDownloadUrl` is the right answer for a human: the download route is
|
|
25442
|
+
* served `access: 'authenticated'`, which a browser satisfies and a
|
|
25443
|
+
* notifier BACKEND does not. It answers a RELATIVE path, so it is not even
|
|
25444
|
+
* a URL an outside fetcher could try. This method exists for the one case
|
|
25445
|
+
* that needs the other thing — a scheduled timelapse whose video has to
|
|
25446
|
+
* become a public attachment on the notification artifact plane.
|
|
25447
|
+
*
|
|
25448
|
+
* Deliberately narrow: `ready` only (a queued, rendering, failed, expired
|
|
25449
|
+
* or deleted export has no file, and answering "0 bytes" for one is how a
|
|
25450
|
+
* caller ships an empty attachment), still inside its lifetime, and under
|
|
25451
|
+
* {@link RECORDING_EXPORT_MAX_READ_BYTES}. Every refusal throws with the
|
|
25452
|
+
* reason — none of them is silent.
|
|
25453
|
+
*/
|
|
25454
|
+
readExportBytes: method(z.object({ exportId: z.string() }), ExportBytesSchema, {
|
|
25455
|
+
kind: "query",
|
|
25456
|
+
auth: "protected"
|
|
25343
25457
|
})
|
|
25344
25458
|
}
|
|
25345
25459
|
};
|
|
@@ -32063,6 +32177,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
32063
32177
|
addonId: null,
|
|
32064
32178
|
access: "view"
|
|
32065
32179
|
},
|
|
32180
|
+
"recordingExport.readExportBytes": {
|
|
32181
|
+
capName: "recordingExport",
|
|
32182
|
+
capScope: "system",
|
|
32183
|
+
addonId: null,
|
|
32184
|
+
access: "view"
|
|
32185
|
+
},
|
|
32066
32186
|
"sceneMonitor.captureReference": {
|
|
32067
32187
|
capName: "scene-monitor",
|
|
32068
32188
|
capScope: "device",
|
|
@@ -33480,10 +33600,28 @@ TimelapseRuleInputSchema.extend({
|
|
|
33480
33600
|
*/
|
|
33481
33601
|
ownerUserId: z.string().optional(),
|
|
33482
33602
|
/**
|
|
33483
|
-
* Epoch-ms of the
|
|
33484
|
-
*
|
|
33603
|
+
* Epoch-ms of the NEWEST successful generation across every camera of this
|
|
33604
|
+
* rule. What a UI shows, and the compatibility floor for
|
|
33605
|
+
* {@link readTimelapseGeneratedAt}. Absent = never generated.
|
|
33485
33606
|
*/
|
|
33486
33607
|
lastGeneratedAt: z.number().optional(),
|
|
33608
|
+
/**
|
|
33609
|
+
* PER-CAMERA generation state, keyed by `String(deviceId)` — the
|
|
33610
|
+
* re-generation guard's real durable state.
|
|
33611
|
+
*
|
|
33612
|
+
* One rule covers several cameras and each renders its own video, so a rule
|
|
33613
|
+
* -wide stamp is wrong in the direction that DESTROYS work: camera A
|
|
33614
|
+
* succeeding at 06:05 tells camera B, whose render failed, that it is
|
|
33615
|
+
* already done — and B's night is gone for good, because the window will not
|
|
33616
|
+
* come back.
|
|
33617
|
+
*
|
|
33618
|
+
* ADDITIVE, so the migration is free: a row written before this field simply
|
|
33619
|
+
* has no map, and {@link readTimelapseGeneratedAt} falls back to
|
|
33620
|
+
* {@link TimelapseRuleSchema.shape.lastGeneratedAt}. Reading an old row as
|
|
33621
|
+
* "never generated" would re-render and re-notify every camera of every rule
|
|
33622
|
+
* once, on the deploy that shipped the map.
|
|
33623
|
+
*/
|
|
33624
|
+
generatedByDevice: z.record(z.string(), z.number()).optional(),
|
|
33487
33625
|
/** userId of the caller who created the rule (server-stamped). */
|
|
33488
33626
|
createdBy: z.string(),
|
|
33489
33627
|
createdAt: z.number(),
|