@camstack/types 1.2.63 → 1.2.65
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/capabilities/index.d.ts +3 -3
- package/dist/capabilities/notification-rules.cap.d.ts +791 -164
- package/dist/capabilities/osd-manager.cap.d.ts +108 -12
- package/dist/capabilities/pipeline-analytics.cap.d.ts +1 -0
- package/dist/capabilities/recording-export.cap.d.ts +17 -6
- package/dist/capabilities/recording.cap.d.ts +189 -0
- package/dist/capabilities/storage-migration.cap.d.ts +1 -0
- package/dist/generated/addon-api.d.ts +35 -0
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/system-proxy.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +742 -60
- package/dist/index.mjs +718 -59
- package/dist/interfaces/addon.d.ts +32 -0
- package/dist/interfaces/event-bus.d.ts +29 -3
- package/dist/interfaces/relocate.d.ts +15 -0
- package/dist/interfaces/stream-broker.d.ts +14 -0
- package/dist/notification/timelapse-rule.d.ts +83 -0
- package/dist/pipeline/native-lease.d.ts +36 -19
- package/package.json +1 -1
|
@@ -1141,6 +1141,27 @@ export interface AddonExecution {
|
|
|
1141
1141
|
* former hard-coded addon-name list in `process-service.ts`.
|
|
1142
1142
|
*/
|
|
1143
1143
|
readonly heapProfile?: AddonHeapProfile;
|
|
1144
|
+
/**
|
|
1145
|
+
* `@camstack/system` builtin ONLY: run this builtin in its OWN forked runner
|
|
1146
|
+
* instead of in-process on the hub root.
|
|
1147
|
+
*
|
|
1148
|
+
* The hub drops `@camstack/system` builtins from the runner plan because the
|
|
1149
|
+
* infrastructure ones (storage, settings, logging, device-manager) must be
|
|
1150
|
+
* resident in the process every runner depends on. That is a reason to keep
|
|
1151
|
+
* THOSE in-process — not a property of the package. A builtin that owns
|
|
1152
|
+
* capture, transcode and byte caches (`snapshot`) charges all of it to the
|
|
1153
|
+
* event loop that also answers the tRPC API.
|
|
1154
|
+
*
|
|
1155
|
+
* Declaring `isolate: true` opts a single builtin out of that blanket rule:
|
|
1156
|
+
* it is planned, spawned, restarted and route-mounted exactly like any other
|
|
1157
|
+
* forked addon, and `resolveRunnerId` still owns its runner id (D2 — one
|
|
1158
|
+
* addon, one process). Nothing else changes: the manifest entry keeps its
|
|
1159
|
+
* `entry` path inside the `@camstack/system` closure, and the runner loads it
|
|
1160
|
+
* from there.
|
|
1161
|
+
*
|
|
1162
|
+
* Ignored for non-system packages — they already fork.
|
|
1163
|
+
*/
|
|
1164
|
+
readonly isolate?: boolean;
|
|
1144
1165
|
}
|
|
1145
1166
|
export type AddonHeapProfile = 'light' | 'heavy';
|
|
1146
1167
|
export declare const DEFAULT_ADDON_PLACEMENT: AddonPlacement;
|
|
@@ -1184,6 +1205,17 @@ export declare function resolveAddonGroup(decl: Pick<AddonDeclaration, 'executio
|
|
|
1184
1205
|
export declare function resolveRunnerId(decl: Pick<AddonDeclaration, 'execution'>, addonId: string): string;
|
|
1185
1206
|
/** Convenience accessor — the resolved placement (defaults to `hub-only`). */
|
|
1186
1207
|
export declare function resolveAddonPlacement(decl: Pick<AddonDeclaration, 'execution'>): AddonPlacement;
|
|
1208
|
+
/**
|
|
1209
|
+
* True when a `@camstack/system` builtin opted out of the in-process rule and
|
|
1210
|
+
* must be planned as a forked runner (`execution.isolate`).
|
|
1211
|
+
*
|
|
1212
|
+
* ONE predicate, because the hub asks this question in three places that must
|
|
1213
|
+
* agree: the runner plan (`buildAddonGroupPlan`), the "does this boot
|
|
1214
|
+
* in-process" filter, and `isForkedAddonEntry` (which decides route mounts,
|
|
1215
|
+
* data-plane mounts, restart and uninstall). They diverged once before for
|
|
1216
|
+
* `auth-oidc` and the addon's routes were mounted against an async UDS proxy.
|
|
1217
|
+
*/
|
|
1218
|
+
export declare function isIsolatedBuiltin(decl: Pick<AddonDeclaration, 'execution'>): boolean;
|
|
1187
1219
|
/**
|
|
1188
1220
|
* One entry of a `RunnerPlan` — an addon assigned to a runner, paired
|
|
1189
1221
|
* with the on-disk directory the runner subprocess loads it from.
|
|
@@ -526,6 +526,29 @@ export interface TopologyNode {
|
|
|
526
526
|
readonly version: string;
|
|
527
527
|
} | null;
|
|
528
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Why a broker's stream health flipped — the discriminator that separates an
|
|
531
|
+
* OUTAGE from an intentional state change.
|
|
532
|
+
*
|
|
533
|
+
* A `stream.offline` on its own says nothing about whether anything is wrong:
|
|
534
|
+
* the same event is fired when a camera stops sending video AND when the last
|
|
535
|
+
* consumer left and the broker suspended its dial on purpose. A consumer that
|
|
536
|
+
* reads only the category cannot tell them apart, and the Notification Center
|
|
537
|
+
* shipped exactly that bug — an operator notified that a stream went down
|
|
538
|
+
* every time nobody was watching it.
|
|
539
|
+
*
|
|
540
|
+
* - `stale-timeout` — no video packet for the broker's stale window
|
|
541
|
+
* (120 s). The ONLY outage. This is what a notification may act on.
|
|
542
|
+
* - `recovered` — packets resumed after a `stale-timeout`. The
|
|
543
|
+
* matching end of an outage.
|
|
544
|
+
* - `first-packet` — a broker's first ever packet. A stream coming up, not
|
|
545
|
+
* a recovery: there was no outage before it.
|
|
546
|
+
* - `suspended` — the lazy-dial idle suspend (no demand). Deliberate.
|
|
547
|
+
* - `resumed` — demand returned and the dial came back. The matching
|
|
548
|
+
* end of a `suspended`, and equally deliberate.
|
|
549
|
+
* - `broker-destroyed` — the stream was unpublished/released. Deliberate.
|
|
550
|
+
*/
|
|
551
|
+
export type StreamHealthReason = 'stale-timeout' | 'recovered' | 'first-packet' | 'suspended' | 'resumed' | 'broker-destroyed';
|
|
529
552
|
/** All known event categories with their typed payloads */
|
|
530
553
|
export interface EventCatalog {
|
|
531
554
|
'system.boot': {
|
|
@@ -1011,7 +1034,7 @@ export interface EventCatalog {
|
|
|
1011
1034
|
readonly engine: PipelineEngineChoice;
|
|
1012
1035
|
readonly modelsLoaded: readonly string[];
|
|
1013
1036
|
readonly inUseByCameras: readonly number[];
|
|
1014
|
-
readonly kind: 'runtime' | 'warm-override';
|
|
1037
|
+
readonly kind: 'runtime' | 'warm-override' | 'device-pool';
|
|
1015
1038
|
readonly poolPid: number | null;
|
|
1016
1039
|
readonly idleMs: number | null;
|
|
1017
1040
|
readonly idleTtlMs: number | null;
|
|
@@ -1159,6 +1182,9 @@ export interface EventCatalog {
|
|
|
1159
1182
|
* (every broker is keyed by its cam stream now); `profile` is the
|
|
1160
1183
|
* profile slot currently bound to it (or `null` if the broker is
|
|
1161
1184
|
* only kept alive by a manual activation).
|
|
1185
|
+
*
|
|
1186
|
+
* `reason` is the discriminator a consumer MUST read before treating a flip
|
|
1187
|
+
* as an outage — see {@link StreamHealthReason}.
|
|
1162
1188
|
*/
|
|
1163
1189
|
'stream.online': {
|
|
1164
1190
|
readonly deviceId: number;
|
|
@@ -1167,7 +1193,7 @@ export interface EventCatalog {
|
|
|
1167
1193
|
readonly brokerId: string;
|
|
1168
1194
|
readonly sourceType: string;
|
|
1169
1195
|
readonly lastPacketAt: number;
|
|
1170
|
-
readonly reason?:
|
|
1196
|
+
readonly reason?: StreamHealthReason;
|
|
1171
1197
|
};
|
|
1172
1198
|
'stream.offline': {
|
|
1173
1199
|
readonly deviceId: number;
|
|
@@ -1176,7 +1202,7 @@ export interface EventCatalog {
|
|
|
1176
1202
|
readonly brokerId: string;
|
|
1177
1203
|
readonly sourceType: string;
|
|
1178
1204
|
readonly lastPacketAt: number;
|
|
1179
|
-
readonly reason?:
|
|
1205
|
+
readonly reason?: StreamHealthReason;
|
|
1180
1206
|
};
|
|
1181
1207
|
'retention.cleanup': {
|
|
1182
1208
|
readonly deletedEvents?: number;
|
|
@@ -8,7 +8,15 @@ import { z } from 'zod';
|
|
|
8
8
|
* Each completed/failed run also lands one durable ops-log row on its owning
|
|
9
9
|
* addon surface.
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* `queued` exists because the recorder mover is SINGLE-FLIGHT and an operator
|
|
13
|
+
* rebalance enqueues one job per (camera, profile). Refusing the second job —
|
|
14
|
+
* what the engine did before — turned a fifteen-camera rebalance into fifteen
|
|
15
|
+
* manual retries. Queued jobs run FIFO; a queued job that is cancelled never
|
|
16
|
+
* runs at all.
|
|
17
|
+
*/
|
|
11
18
|
export declare const RelocateJobStateSchema: z.ZodEnum<{
|
|
19
|
+
queued: "queued";
|
|
12
20
|
done: "done";
|
|
13
21
|
failed: "failed";
|
|
14
22
|
running: "running";
|
|
@@ -18,6 +26,7 @@ export type RelocateJobState = z.infer<typeof RelocateJobStateSchema>;
|
|
|
18
26
|
export declare const RelocateJobSchema: z.ZodObject<{
|
|
19
27
|
jobId: z.ZodString;
|
|
20
28
|
state: z.ZodEnum<{
|
|
29
|
+
queued: "queued";
|
|
21
30
|
done: "done";
|
|
22
31
|
failed: "failed";
|
|
23
32
|
running: "running";
|
|
@@ -52,6 +61,8 @@ export declare const RelocateFootageInputSchema: z.ZodObject<{
|
|
|
52
61
|
recordings: "recordings";
|
|
53
62
|
recordingsLow: "recordingsLow";
|
|
54
63
|
}>>;
|
|
64
|
+
deviceId: z.ZodOptional<z.ZodNumber>;
|
|
65
|
+
profiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
55
66
|
throttleMbps: z.ZodOptional<z.ZodNumber>;
|
|
56
67
|
}, z.core.$strip>;
|
|
57
68
|
export type RelocateFootageInput = z.infer<typeof RelocateFootageInputSchema>;
|
|
@@ -72,6 +83,8 @@ export declare const StorageMigrationFootageMoveInputSchema: z.ZodObject<{
|
|
|
72
83
|
recordings: "recordings";
|
|
73
84
|
recordingsLow: "recordingsLow";
|
|
74
85
|
}>>;
|
|
86
|
+
deviceId: z.ZodOptional<z.ZodNumber>;
|
|
87
|
+
profiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
75
88
|
throttleMbps: z.ZodOptional<z.ZodNumber>;
|
|
76
89
|
leaseId: z.ZodString;
|
|
77
90
|
}, z.core.$strip>;
|
|
@@ -147,6 +160,7 @@ export declare const StorageMigrationMoveSchema: z.ZodObject<{
|
|
|
147
160
|
toLocationId: z.ZodString;
|
|
148
161
|
moverJobId: z.ZodNullable<z.ZodString>;
|
|
149
162
|
state: z.ZodNullable<z.ZodEnum<{
|
|
163
|
+
queued: "queued";
|
|
150
164
|
done: "done";
|
|
151
165
|
failed: "failed";
|
|
152
166
|
running: "running";
|
|
@@ -185,6 +199,7 @@ export declare const StorageMigrationJobSchema: z.ZodObject<{
|
|
|
185
199
|
toLocationId: z.ZodString;
|
|
186
200
|
moverJobId: z.ZodNullable<z.ZodString>;
|
|
187
201
|
state: z.ZodNullable<z.ZodEnum<{
|
|
202
|
+
queued: "queued";
|
|
188
203
|
done: "done";
|
|
189
204
|
failed: "failed";
|
|
190
205
|
running: "running";
|
|
@@ -265,6 +265,20 @@ export interface IStreamBroker {
|
|
|
265
265
|
* `H265Repacketizer` once the camera's first RTP+SDP arrive.
|
|
266
266
|
*/
|
|
267
267
|
onSdpParameterSets(callback: (ps: ReadonlyArray<Buffer>) => void): Unsubscribe;
|
|
268
|
+
/**
|
|
269
|
+
* Subscribe to SOURCE RESTARTS — every dial after the first, carrying a
|
|
270
|
+
* short reason (`dial#2`, …). The WebRTC server subscribes for EVERY
|
|
271
|
+
* session shape (RTP passthrough, AnnexB, transcode): each holds parameter
|
|
272
|
+
* sets and a bootstrap latch belonging to the connection that ended, so a
|
|
273
|
+
* re-dial must re-sync them rather than leave the session decoding against
|
|
274
|
+
* a dead mapping.
|
|
275
|
+
*
|
|
276
|
+
* Fires on the DIAL, not on the first packet: a restart that never delivers
|
|
277
|
+
* must still be observable rather than silently indistinguishable from a
|
|
278
|
+
* healthy stream. The returned function detaches ONE subscriber (a broker
|
|
279
|
+
* fans out to many live sessions).
|
|
280
|
+
*/
|
|
281
|
+
onSourceRestart(observer: (reason: string) => void): Unsubscribe;
|
|
268
282
|
/**
|
|
269
283
|
* Source type as configured at `start()` — see `StreamSourceType`
|
|
270
284
|
* for the supported values. `null` before `start()` resolves.
|
|
@@ -28,6 +28,35 @@ export declare const TimelapseTemplateSchema: z.ZodObject<{
|
|
|
28
28
|
body: z.ZodOptional<z.ZodString>;
|
|
29
29
|
}, z.core.$strip>;
|
|
30
30
|
export type TimelapseTemplate = z.infer<typeof TimelapseTemplateSchema>;
|
|
31
|
+
/**
|
|
32
|
+
* Floor on any dense cadence, seconds — the recording's own frame interval.
|
|
33
|
+
*
|
|
34
|
+
* Declared here because it bounds BOTH the rule field and the renderer's
|
|
35
|
+
* derivation, and two copies of a floor are two floors that can drift.
|
|
36
|
+
*/
|
|
37
|
+
export declare const TIMELAPSE_DENSE_FLOOR_SEC = 0.1;
|
|
38
|
+
/**
|
|
39
|
+
* Whether the notification's preview is a STILL or a short animation.
|
|
40
|
+
*
|
|
41
|
+
* The operator's ask, verbatim: *"inviato come gif o video (come per le altre
|
|
42
|
+
* rule)"* — his Scrypted advanced-notifier has a `gifRule`, and a ten-hour
|
|
43
|
+
* night reads better as three seconds of motion than as one frame of it. Both
|
|
44
|
+
* modes get the SAME treatment (blurred frame, large centred title); `'gif'`
|
|
45
|
+
* simply applies it to a dozen frames sampled across the render and assembles
|
|
46
|
+
* them.
|
|
47
|
+
*
|
|
48
|
+
* `'image'` is the default and stays the default: a GIF costs a dozen ffmpeg
|
|
49
|
+
* seeks and a palette pass, and no rule that never asked for one should start
|
|
50
|
+
* paying that on the deploy that shipped it.
|
|
51
|
+
*
|
|
52
|
+
* A GIF that cannot be assembled DEGRADES to the still — never to nothing.
|
|
53
|
+
*/
|
|
54
|
+
declare const PreviewModeField: z.ZodEnum<{
|
|
55
|
+
image: "image";
|
|
56
|
+
gif: "gif";
|
|
57
|
+
}>;
|
|
58
|
+
/** Still or animation — the rule field, as a type the renderer can take. */
|
|
59
|
+
export type TimelapsePreviewMode = z.infer<typeof PreviewModeField>;
|
|
31
60
|
/**
|
|
32
61
|
* Client-supplied timelapse-rule fields. The server stamps id / createdBy /
|
|
33
62
|
* createdAt / updatedAt / ownerUserId / lastGeneratedAt — none of them appear
|
|
@@ -48,6 +77,8 @@ export declare const TimelapseRuleInputSchema: z.ZodObject<{
|
|
|
48
77
|
}, z.core.$strip>;
|
|
49
78
|
cadenceSec: z.ZodDefault<z.ZodNumber>;
|
|
50
79
|
framerate: z.ZodDefault<z.ZodNumber>;
|
|
80
|
+
denseCadenceSec: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
minDwellSec: z.ZodOptional<z.ZodNumber>;
|
|
51
82
|
targets: z.ZodArray<z.ZodObject<{
|
|
52
83
|
targetId: z.ZodString;
|
|
53
84
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -56,9 +87,23 @@ export declare const TimelapseRuleInputSchema: z.ZodObject<{
|
|
|
56
87
|
title: z.ZodOptional<z.ZodString>;
|
|
57
88
|
body: z.ZodOptional<z.ZodString>;
|
|
58
89
|
}, z.core.$strip>>;
|
|
90
|
+
previewText: z.ZodOptional<z.ZodString>;
|
|
91
|
+
previewMode: z.ZodDefault<z.ZodEnum<{
|
|
92
|
+
image: "image";
|
|
93
|
+
gif: "gif";
|
|
94
|
+
}>>;
|
|
95
|
+
reportClasses: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
59
96
|
priority: z.ZodDefault<z.ZodNumber>;
|
|
60
97
|
}, z.core.$strip>;
|
|
61
98
|
export type TimelapseRuleInput = z.infer<typeof TimelapseRuleInputSchema>;
|
|
99
|
+
/**
|
|
100
|
+
* The caption a rule that never set one gets.
|
|
101
|
+
*
|
|
102
|
+
* Rendered through the ordinary `{{var}}` pass at delivery, so a rule created
|
|
103
|
+
* before this field existed still reads "Timelapse Videocamera ingresso" and
|
|
104
|
+
* not the literal braces.
|
|
105
|
+
*/
|
|
106
|
+
export declare const DEFAULT_TIMELAPSE_PREVIEW_TEXT = "Timelapse {{camera}}";
|
|
62
107
|
/**
|
|
63
108
|
* Partial patch for an update — any subset of the INPUT fields, with NO
|
|
64
109
|
* defaults (an absent key means "leave unchanged", never "reset to default").
|
|
@@ -90,6 +135,8 @@ export declare const TimelapseRulePatchSchema: z.ZodObject<{
|
|
|
90
135
|
}, z.core.$strip>>;
|
|
91
136
|
cadenceSec: z.ZodOptional<z.ZodNumber>;
|
|
92
137
|
framerate: z.ZodOptional<z.ZodNumber>;
|
|
138
|
+
denseCadenceSec: z.ZodOptional<z.ZodNumber>;
|
|
139
|
+
minDwellSec: z.ZodOptional<z.ZodNumber>;
|
|
93
140
|
targets: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
94
141
|
targetId: z.ZodString;
|
|
95
142
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -98,6 +145,12 @@ export declare const TimelapseRulePatchSchema: z.ZodObject<{
|
|
|
98
145
|
title: z.ZodOptional<z.ZodString>;
|
|
99
146
|
body: z.ZodOptional<z.ZodString>;
|
|
100
147
|
}, z.core.$strip>>>;
|
|
148
|
+
previewText: z.ZodOptional<z.ZodString>;
|
|
149
|
+
previewMode: z.ZodOptional<z.ZodEnum<{
|
|
150
|
+
image: "image";
|
|
151
|
+
gif: "gif";
|
|
152
|
+
}>>;
|
|
153
|
+
reportClasses: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
101
154
|
priority: z.ZodOptional<z.ZodNumber>;
|
|
102
155
|
}, z.core.$strip>;
|
|
103
156
|
export type TimelapseRulePatch = z.infer<typeof TimelapseRulePatchSchema>;
|
|
@@ -117,6 +170,8 @@ export declare const TimelapseRuleSchema: z.ZodObject<{
|
|
|
117
170
|
}, z.core.$strip>;
|
|
118
171
|
cadenceSec: z.ZodDefault<z.ZodNumber>;
|
|
119
172
|
framerate: z.ZodDefault<z.ZodNumber>;
|
|
173
|
+
denseCadenceSec: z.ZodOptional<z.ZodNumber>;
|
|
174
|
+
minDwellSec: z.ZodOptional<z.ZodNumber>;
|
|
120
175
|
targets: z.ZodArray<z.ZodObject<{
|
|
121
176
|
targetId: z.ZodString;
|
|
122
177
|
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -125,6 +180,12 @@ export declare const TimelapseRuleSchema: z.ZodObject<{
|
|
|
125
180
|
title: z.ZodOptional<z.ZodString>;
|
|
126
181
|
body: z.ZodOptional<z.ZodString>;
|
|
127
182
|
}, z.core.$strip>>;
|
|
183
|
+
previewText: z.ZodOptional<z.ZodString>;
|
|
184
|
+
previewMode: z.ZodDefault<z.ZodEnum<{
|
|
185
|
+
image: "image";
|
|
186
|
+
gif: "gif";
|
|
187
|
+
}>>;
|
|
188
|
+
reportClasses: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
128
189
|
priority: z.ZodDefault<z.ZodNumber>;
|
|
129
190
|
id: z.ZodString;
|
|
130
191
|
ownerUserId: z.ZodOptional<z.ZodString>;
|
|
@@ -135,6 +196,27 @@ export declare const TimelapseRuleSchema: z.ZodObject<{
|
|
|
135
196
|
updatedAt: z.ZodNumber;
|
|
136
197
|
}, z.core.$strip>;
|
|
137
198
|
export type TimelapseRule = z.infer<typeof TimelapseRuleSchema>;
|
|
199
|
+
/** The cadence pair a rule must satisfy — the guard's whole input. */
|
|
200
|
+
export interface TimelapseCadencePair {
|
|
201
|
+
readonly cadenceSec: number;
|
|
202
|
+
readonly denseCadenceSec?: number;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Refuse a dense cadence that is not denser than the base.
|
|
206
|
+
*
|
|
207
|
+
* Called at UPSERT, on the MERGED rule — a patch that lowers `cadenceSec`
|
|
208
|
+
* alone can invalidate a `denseCadenceSec` set months earlier, so checking the
|
|
209
|
+
* patch in isolation would let the bad pair through.
|
|
210
|
+
*
|
|
211
|
+
* Refusing here and not at render time is the whole point: `ExportTimelapseSchema`
|
|
212
|
+
* also rejects the pair, but it does so when the window has already closed and
|
|
213
|
+
* the footage is being cut — the operator learns at 06:05 that last night was
|
|
214
|
+
* never going to render, and a closed window does not come back. This turns
|
|
215
|
+
* that into a failed edit he can see and correct.
|
|
216
|
+
*
|
|
217
|
+
* @throws Error naming both numbers, so the message is actionable in a toast.
|
|
218
|
+
*/
|
|
219
|
+
export declare function assertTimelapseCadences(pair: TimelapseCadencePair): void;
|
|
138
220
|
/**
|
|
139
221
|
* The last successful generation for ONE camera of a rule, epoch-ms.
|
|
140
222
|
*
|
|
@@ -148,3 +230,4 @@ export type TimelapseRule = z.infer<typeof TimelapseRuleSchema>;
|
|
|
148
230
|
* entire rule set once.
|
|
149
231
|
*/
|
|
150
232
|
export declare function readTimelapseGeneratedAt(rule: Pick<TimelapseRule, 'lastGeneratedAt' | 'generatedByDevice'>, deviceId: number): number;
|
|
233
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* THE native-frame **lease** knobs —
|
|
3
|
-
* decode worker's native-resolution
|
|
2
|
+
* THE native-frame **lease** knobs — hold depth, RAM budget, demand window and
|
|
3
|
+
* subject-tile budget for the decode worker's native-resolution retention.
|
|
4
4
|
*
|
|
5
5
|
* ## Why they live here and not in the addon that reads them
|
|
6
6
|
*
|
|
@@ -16,20 +16,25 @@
|
|
|
16
16
|
* The lease is a per-decode-worker RAM window. Its purpose — the late
|
|
17
17
|
* cross-process native crop landing on a full-resolution frame rather than the
|
|
18
18
|
* ≤640 detection fallback — is a property of the PIPELINE, not of a node's
|
|
19
|
-
* hardware: a per-node
|
|
19
|
+
* hardware: a per-node window would mean the same camera produces different crop
|
|
20
20
|
* quality depending on which node the balancer placed it on, and nobody could
|
|
21
21
|
* tell that from the stored media. Node-level RAM pressure is already handled
|
|
22
22
|
* by the per-session budget ceiling, which is itself one of these knobs.
|
|
23
23
|
*
|
|
24
24
|
* ## What each knob costs
|
|
25
25
|
*
|
|
26
|
-
* A
|
|
26
|
+
* A HELD frame is a full NATIVE-resolution copy in system RAM. With the
|
|
27
27
|
* default pinned-RGB24 lease path (`CAMSTACK_SESSION_PINNED_RGB_CROP`, on):
|
|
28
28
|
* 4K ≈ 24.9 MB/frame, 1080p ≈ 6.2 MB/frame. On the YUV420P path (flag off, and
|
|
29
29
|
* for software-decoded sessions): 4K ≈ 12.4 MB, 1080p ≈ 3.1 MB. Worst-case
|
|
30
|
-
* resident RAM for ONE busy camera
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* resident RAM for ONE busy camera is now `frameBytes × holdFrames`, clamped by
|
|
31
|
+
* the budget ceiling — bounded by a COUNT because a held frame is waiting for
|
|
32
|
+
* one specific event (its own detection result), not for a clock.
|
|
33
|
+
*
|
|
34
|
+
* A TILE is one subject at native resolution, JPEG-encoded: ~60-120 KB on 4K,
|
|
35
|
+
* and nothing at all on a frame that detected nothing. That is the asymmetry
|
|
36
|
+
* this whole shape exists for — see
|
|
37
|
+
* `docs/design/2026-08-13-native-lease-two-tier-redesign.md`.
|
|
33
38
|
*/
|
|
34
39
|
import { z } from 'zod';
|
|
35
40
|
import type { HydratedSettingsView } from './detail-crop.js';
|
|
@@ -39,10 +44,11 @@ import type { HydratedSettingsView } from './detail-crop.js';
|
|
|
39
44
|
* the reader can walk every section instead of trusting the section id.
|
|
40
45
|
*/
|
|
41
46
|
export declare const NATIVE_LEASE_SECTION_ID = "native-lease";
|
|
42
|
-
export declare const
|
|
47
|
+
export declare const NATIVE_LEASE_HOLD_KEY = "nativeLeaseHoldFrames";
|
|
43
48
|
export declare const NATIVE_LEASE_BUDGET_KEY = "nativeLeaseBudgetMb";
|
|
44
49
|
export declare const NATIVE_LEASE_ACTIVITY_KEY = "nativeLeaseActivityMs";
|
|
45
50
|
export declare const NATIVE_LEASE_ADMISSION_KEY = "nativeLeaseAdmission";
|
|
51
|
+
export declare const NATIVE_LEASE_TILE_BUDGET_KEY = "nativeTileBudgetMb";
|
|
46
52
|
/**
|
|
47
53
|
* WHICH delivered frames the decode worker retains a native copy of.
|
|
48
54
|
*
|
|
@@ -72,19 +78,24 @@ export type NativeLeaseAdmission = z.infer<typeof NativeLeaseAdmissionSchema>;
|
|
|
72
78
|
* junk number would silently become a 0-length or unbounded retention window.
|
|
73
79
|
*/
|
|
74
80
|
export declare const NativeLeaseSettingsSchema: z.ZodObject<{
|
|
75
|
-
|
|
81
|
+
holdFrames: z.ZodNumber;
|
|
76
82
|
budgetMb: z.ZodNumber;
|
|
77
83
|
activityMs: z.ZodNumber;
|
|
78
84
|
admission: z.ZodEnum<{
|
|
79
85
|
all: "all";
|
|
80
86
|
inferred: "inferred";
|
|
81
87
|
}>;
|
|
88
|
+
tileBudgetMb: z.ZodNumber;
|
|
82
89
|
}, z.core.$strip>;
|
|
83
90
|
export type NativeLeaseSettings = z.infer<typeof NativeLeaseSettingsSchema>;
|
|
84
91
|
/**
|
|
85
|
-
* The values in force when the operator has set nothing
|
|
86
|
-
*
|
|
87
|
-
*
|
|
92
|
+
* The values in force when the operator has set nothing.
|
|
93
|
+
*
|
|
94
|
+
* `budgetMb` stays at 1024 on the day the hold landed, deliberately: it stopped
|
|
95
|
+
* being the retention window and became the OOM ceiling, and lowering a ceiling
|
|
96
|
+
* in the same change that redefines it would make a regression and a retune
|
|
97
|
+
* indistinguishable. Cut it once `tileHits` / `holdOverflow` have been read on
|
|
98
|
+
* live traffic.
|
|
88
99
|
*/
|
|
89
100
|
export declare const DEFAULT_NATIVE_LEASE_SETTINGS: NativeLeaseSettings;
|
|
90
101
|
/**
|
|
@@ -94,10 +105,10 @@ export declare const DEFAULT_NATIVE_LEASE_SETTINGS: NativeLeaseSettings;
|
|
|
94
105
|
*/
|
|
95
106
|
export type NativeLeaseSettingsOverride = Partial<NativeLeaseSettings>;
|
|
96
107
|
/** Slider bounds for the operator-facing knobs (orchestrator settings UI). */
|
|
97
|
-
export declare const
|
|
98
|
-
readonly min:
|
|
99
|
-
readonly max:
|
|
100
|
-
readonly step:
|
|
108
|
+
export declare const NATIVE_LEASE_HOLD_FIELD: {
|
|
109
|
+
readonly min: 1;
|
|
110
|
+
readonly max: 64;
|
|
111
|
+
readonly step: 1;
|
|
101
112
|
readonly default: number;
|
|
102
113
|
};
|
|
103
114
|
export declare const NATIVE_LEASE_BUDGET_FIELD: {
|
|
@@ -112,6 +123,12 @@ export declare const NATIVE_LEASE_ACTIVITY_FIELD: {
|
|
|
112
123
|
readonly step: 1000;
|
|
113
124
|
readonly default: number;
|
|
114
125
|
};
|
|
126
|
+
export declare const NATIVE_LEASE_TILE_BUDGET_FIELD: {
|
|
127
|
+
readonly min: 0;
|
|
128
|
+
readonly max: 1024;
|
|
129
|
+
readonly step: 16;
|
|
130
|
+
readonly default: number;
|
|
131
|
+
};
|
|
115
132
|
/** Select options for the admission knob (orchestrator settings UI). */
|
|
116
133
|
export declare const NATIVE_LEASE_ADMISSION_FIELD: {
|
|
117
134
|
readonly options: readonly [{
|
|
@@ -123,10 +140,10 @@ export declare const NATIVE_LEASE_ADMISSION_FIELD: {
|
|
|
123
140
|
}];
|
|
124
141
|
readonly default: "all" | "inferred";
|
|
125
142
|
};
|
|
126
|
-
/**
|
|
143
|
+
/** Every knob, as a key union. */
|
|
127
144
|
export type NativeLeaseKnob = keyof NativeLeaseSettings;
|
|
128
|
-
/** The
|
|
129
|
-
export type NativeLeaseNumberKnob = '
|
|
145
|
+
/** The knobs whose value is a bounded integer (everything but `admission`). */
|
|
146
|
+
export type NativeLeaseNumberKnob = 'holdFrames' | 'budgetMb' | 'activityMs' | 'tileBudgetMb';
|
|
130
147
|
/**
|
|
131
148
|
* Narrow a FLAT settings record to the knobs the operator set.
|
|
132
149
|
*
|