@camstack/types 1.2.87 → 1.2.89

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.
@@ -109,7 +109,7 @@ export { PasskeySummarySchema, userPasskeysCapability } from './user-passkeys.ca
109
109
  export type { IVectorStoreProvider } from './vector-store.cap.js';
110
110
  export { VectorDeclareIndexInputSchema, VectorDeleteByFilterInputSchema, VectorDeleteInputSchema, VectorDeleteResultSchema, type VectorFilter, VectorFilterSchema, VectorGetInputSchema, VectorGetResultSchema, type VectorItem, VectorItemSchema, type VectorMatch, VectorMatchSchema, type VectorMetadata, VectorMetadataSchema, type VectorMetric, VectorMetricSchema, VectorQueryInputSchema, VectorQueryResultSchema, VectorStatsInputSchema, VectorStatsResultSchema, VectorUpsertInputSchema, VectorUpsertResultSchema, vectorStoreCapability, } from './vector-store.cap.js';
111
111
  export type { Clip, ClipPlayback, IVideoclipsProvider } from './videoclips.cap.js';
112
- export { ClipPlaybackSchema, ClipSchema, MAX_CLIP_EVENT_IDS, videoclipsCapability, } from './videoclips.cap.js';
112
+ export { ClipPlaybackSchema, ClipSchema, MAX_CLIP_EVENT_IDS, MAX_CLIP_LABELS, videoclipsCapability, } from './videoclips.cap.js';
113
113
  export type { IViewerUiProvider } from './viewer-ui.cap.js';
114
114
  export { viewerUiCapability } from './viewer-ui.cap.js';
115
115
  export { type IWebrtcSessionProvider, type WebrtcClientHints, type WebrtcStreamChoice, WebrtcStreamChoiceSchema, type WebrtcStreamTarget, WebrtcStreamTargetSchema, webrtcClientHintsSchema, webrtcSessionCapability, } from './webrtc-session.cap.js';
@@ -60,6 +60,7 @@ export type RecordingDeviceUsage = z.infer<typeof RecordingDeviceUsageSchema>;
60
60
  export declare const RecordingLocationUsageSchema: z.ZodObject<{
61
61
  locationId: z.ZodNullable<z.ZodString>;
62
62
  locationIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
63
+ root: z.ZodOptional<z.ZodString>;
63
64
  usedBytes: z.ZodNumber;
64
65
  availableBytes: z.ZodNullable<z.ZodNumber>;
65
66
  totalBytes: z.ZodNullable<z.ZodNumber>;
@@ -76,13 +77,14 @@ export declare const RecordingStorageUsageSchema: z.ZodObject<{
76
77
  usedBytes: z.ZodNumber;
77
78
  oldestMs: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
78
79
  }, z.core.$strip>>;
79
- locations: z.ZodArray<z.ZodObject<{
80
+ locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
80
81
  locationId: z.ZodNullable<z.ZodString>;
81
82
  locationIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
83
+ root: z.ZodOptional<z.ZodString>;
82
84
  usedBytes: z.ZodNumber;
83
85
  availableBytes: z.ZodNullable<z.ZodNumber>;
84
86
  totalBytes: z.ZodNullable<z.ZodNumber>;
85
- }, z.core.$strip>>;
87
+ }, z.core.$strip>>>;
86
88
  }, z.core.$strip>;
87
89
  export type RecordingStorageUsage = z.infer<typeof RecordingStorageUsageSchema>;
88
90
  /**
@@ -263,13 +265,14 @@ export declare const recordingCapability: {
263
265
  usedBytes: z.ZodNumber;
264
266
  oldestMs: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
265
267
  }, z.core.$strip>>;
266
- locations: z.ZodArray<z.ZodObject<{
268
+ locations: z.ZodOptional<z.ZodArray<z.ZodObject<{
267
269
  locationId: z.ZodNullable<z.ZodString>;
268
270
  locationIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
271
+ root: z.ZodOptional<z.ZodString>;
269
272
  usedBytes: z.ZodNumber;
270
273
  availableBytes: z.ZodNullable<z.ZodNumber>;
271
274
  totalBytes: z.ZodNullable<z.ZodNumber>;
272
- }, z.core.$strip>>;
275
+ }, z.core.$strip>>>;
273
276
  }, z.core.$strip>, "query">;
274
277
  readonly getDeviceConfig: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
275
278
  deviceId: z.ZodNumber;
@@ -22,6 +22,8 @@ import { type InferProvider } from './capability-definition.js';
22
22
  * while preserving `eventIds[0]` — the surface's thumbnail fallback.
23
23
  */
24
24
  export declare const MAX_CLIP_EVENT_IDS = 24;
25
+ /** Cap on {@link ClipSchema.labels} — the ribbon row has space for ~3 words. */
26
+ export declare const MAX_CLIP_LABELS = 3;
25
27
  export declare const ClipSchema: z.ZodObject<{
26
28
  id: z.ZodString;
27
29
  source: z.ZodString;
@@ -35,6 +37,7 @@ export declare const ClipSchema: z.ZodObject<{
35
37
  startMs: z.ZodNumber;
36
38
  endMs: z.ZodNumber;
37
39
  }, z.core.$strip>;
40
+ labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
38
41
  thumbnail: z.ZodOptional<z.ZodString>;
39
42
  stillAtMs: z.ZodOptional<z.ZodNumber>;
40
43
  eventIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -80,6 +83,7 @@ export declare const videoclipsCapability: {
80
83
  startMs: z.ZodNumber;
81
84
  endMs: z.ZodNumber;
82
85
  }, z.core.$strip>;
86
+ labels: z.ZodOptional<z.ZodArray<z.ZodString>>;
83
87
  thumbnail: z.ZodOptional<z.ZodString>;
84
88
  stillAtMs: z.ZodOptional<z.ZodNumber>;
85
89
  eventIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
package/dist/index.d.ts CHANGED
@@ -194,6 +194,7 @@ export { audioIsFailClosed, audioLabelChoices, audioModeOf, audioOrDefaults, isA
194
194
  export { isBaseConditionKey, knownValues, NC_BASE_CONDITION_KEYS, type NcBaseConditionKey, pickerForCondition, type TaxonomyGroup, type TaxonomyOption, type TaxonomyPicker, } from './notification/condition-taxonomy.js';
195
195
  export { type PreparedAction, type PreparedAttachment, type PreparedNotification, prepareNotification, type ResolvedLevel, } from './notification/degrade-engine.js';
196
196
  export { htmlToText, markdownToHtmlLite, markdownToText, type NotificationFormat as NotificationBodyFormat, resolveFormat, textToHtml, transcodeBody, } from './notification/format-transcode.js';
197
+ export { conditionExclusionReason, conditionVisibleForKind, defaultDeliveryForSection, droppedConditionsForKind, isAudioRule, isOccupancyRule, isSystemDelivery, NC_AUDIO_SEED, NC_OCCUPANCY_DEFAULTS, NC_RULE_EDITOR_SECTION_ORDER, NC_RULE_KIND_SPECS, NC_RULE_SECTIONS, NC_SYSTEM_DELIVERY, type NcDroppedCondition, type NcKindVisibilitySubject, type NcRuleEditorSection, type NcRuleEditorSectionKey, type NcRuleKind, type NcRuleKindSpec, type NcRuleOwningSectionKey, type NcRuleSection, type NcRuleSectionKey, type NcRuleSectionSubject, type NcRuleSeed, parseRuleSection, ruleEditorSectionsForKind, ruleKindOf, ruleKindSpec, ruleMatchesSection, ruleSection, ruleSectionOf, ruleSeedForSection, } from './notification/rule-kinds.js';
197
198
  export { isScheduleActive } from './notification/schedule.js';
198
199
  export { NC_SYSTEM_EVENT_FILTER_KEYS, type NcSystemEventFilterKey, systemEventFilterApplies, systemEventFilterAppliesToAnyKind, } from './notification/system-event-filters.js';
199
200
  export type { TimelapseCadencePair, TimelapsePreviewMode, TimelapseRule, TimelapseRuleInput, TimelapseRulePatch, TimelapseTemplate, } from './notification/timelapse-rule.js';
package/dist/index.js CHANGED
@@ -20220,6 +20220,8 @@ var vectorStoreCapability = {
20220
20220
  * while preserving `eventIds[0]` — the surface's thumbnail fallback.
20221
20221
  */
20222
20222
  var MAX_CLIP_EVENT_IDS = 24;
20223
+ /** Cap on {@link ClipSchema.labels} — the ribbon row has space for ~3 words. */
20224
+ var MAX_CLIP_LABELS = 3;
20223
20225
  var ClipSchema = zod.z.object({
20224
20226
  /** Opaque, provider-namespaced id. The default provider encodes the time
20225
20227
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -20237,6 +20239,14 @@ var ClipSchema = zod.z.object({
20237
20239
  endMs: zod.z.number()
20238
20240
  }),
20239
20241
  /**
20242
+ * Distinct object classes attached to this visit (`person`, `car`, …),
20243
+ * dominant first, capped at {@link MAX_CLIP_LABELS}. The ribbon renders these
20244
+ * instead of the bare kind — "Object" tells the operator nothing a colour bar
20245
+ * did not. Absent (never empty) when the visit attached no classified object
20246
+ * event, so a motion/audio-only visit keeps its kind label.
20247
+ */
20248
+ labels: zod.z.array(zod.z.string()).max(3).optional(),
20249
+ /**
20240
20250
  * Lazy thumbnail URL, never inlined.
20241
20251
  *
20242
20252
  * Recording-derived clips (events-mode keep-window, and the prepared
@@ -27435,9 +27445,21 @@ var RecordingLocationUsageSchema = zod.z.object({
27435
27445
  * previously rendered as two identical "disks" with a nonsensical used
27436
27446
  * split — hydrate attribution across aliases is arbitrary by nature. */
27437
27447
  locationIds: zod.z.array(zod.z.string()).optional(),
27438
- /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
27448
+ /**
27449
+ * Filesystem root of the physical volume this row accounts — the only
27450
+ * operator-legible identity when a location has no `displayName` yet, and
27451
+ * what a "which disk is full" alert has to name. OPTIONAL for the same
27452
+ * train-skew reason as `oldestMs`: a recorder that predates the field omits
27453
+ * it, and this schema must keep validating that older provider's payload.
27454
+ */
27455
+ root: zod.z.string().optional(),
27456
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
27457
+ * the footage's SHARE of the volume, not how full the volume is — the disk
27458
+ * also holds everything that is not recordings. Volume fill is
27459
+ * `(totalBytes - availableBytes) / totalBytes`. */
27439
27460
  usedBytes: zod.z.number(),
27440
- /** Free bytes on the location's volume; null when capacity is unknown (remote). */
27461
+ /** Free bytes on the location's volume; null when capacity is unknown
27462
+ * (remote/unstattable — the recorder logs the statfs failure with the root). */
27441
27463
  availableBytes: zod.z.number().nullable(),
27442
27464
  /** Total bytes of the location's volume; null when unknown. */
27443
27465
  totalBytes: zod.z.number().nullable()
@@ -27449,7 +27471,15 @@ var RecordingStorageUsageSchema = zod.z.object({
27449
27471
  nodeId: zod.z.string(),
27450
27472
  totalUsedBytes: zod.z.number(),
27451
27473
  devices: zod.z.array(RecordingDeviceUsageSchema),
27452
- locations: zod.z.array(RecordingLocationUsageSchema)
27474
+ /**
27475
+ * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
27476
+ * of the payload without it: the hub serves this method through a BAKED
27477
+ * router, so a hub whose framework train predates a change to this array
27478
+ * strips it out of an answer a newer recorder happily produced. A UI that
27479
+ * dereferences it unconditionally crashes the whole Storage page over a
27480
+ * missing disk bar.
27481
+ */
27482
+ locations: zod.z.array(RecordingLocationUsageSchema).optional()
27453
27483
  });
27454
27484
  /**
27455
27485
  * The OPERATOR-ARMED half of multi-location recordings (D116).
@@ -43550,6 +43580,340 @@ function prepareNotification(caps, n) {
43550
43580
  };
43551
43581
  }
43552
43582
  //#endregion
43583
+ //#region src/notification/rule-kinds.ts
43584
+ /**
43585
+ * Reasons, written once. Each names the SUBJECT FIELD the engine reads and
43586
+ * finds absent — that is what makes the exclusion checkable against the engine
43587
+ * rather than an opinion about tidiness.
43588
+ */
43589
+ var NO_SENSOR_KIND = "An occupancy edge is not a sensor event: it carries no sensor kind, so this condition can never match one.";
43590
+ var NO_EVENT_TOKEN = "An occupancy edge carries no device event-type token — that comes from an event-emitter slice, which a count crossing has none of.";
43591
+ var OCCUPANCY_SOURCE = "An occupancy edge is always produced by the pipeline, so any other source can never match and “pipeline” narrows nothing.";
43592
+ var AUDIO_SOURCE = "A confirmed sound window is always stamped as an audio subject, so any other source can never match.";
43593
+ var SOUND_NOT_A_PICTURE = "A sound rule fires on what the camera HEARD. Its subject carries no detection, so this condition can never match one — name the sounds in the Sound condition instead.";
43594
+ var SOUND_NO_CONFIDENCE = "A sound window’s evidence is a percentage of samples, not a detector score, so a confidence threshold can never be satisfied.";
43595
+ var SOUND_NO_GEOMETRY = "A sound has no position in the frame, so a zone or a drawn polygon can never match it.";
43596
+ var NC_RULE_KIND_SPECS = [
43597
+ {
43598
+ kind: "detection",
43599
+ label: "Conditions",
43600
+ excluded: {},
43601
+ carriesMedia: true,
43602
+ carriesSubjectCrop: true
43603
+ },
43604
+ {
43605
+ kind: "sensor",
43606
+ label: "Conditions",
43607
+ /** Same argument as `detection`: `occupancy` converts, it does not confuse. */
43608
+ excluded: {},
43609
+ carriesMedia: true,
43610
+ carriesSubjectCrop: true,
43611
+ blurb: "A doorbell press or a linked sensor changing state. The picture comes from the camera the sensor is attributed to."
43612
+ },
43613
+ {
43614
+ kind: "occupancy",
43615
+ label: "Occupancy",
43616
+ discriminator: "occupancy",
43617
+ excluded: {
43618
+ sensorKinds: NO_SENSOR_KIND,
43619
+ eventTypeTokens: NO_EVENT_TOKEN,
43620
+ source: OCCUPANCY_SOURCE
43621
+ },
43622
+ carriesMedia: true,
43623
+ carriesSubjectCrop: true,
43624
+ blurb: "Fires when the count in a camera frame or a zone crosses your threshold and holds. Pick the camera first — the zone list is that camera’s."
43625
+ },
43626
+ {
43627
+ kind: "sound",
43628
+ label: "Sound",
43629
+ discriminator: "audio",
43630
+ excluded: {
43631
+ classes: SOUND_NOT_A_PICTURE,
43632
+ classesExclude: SOUND_NOT_A_PICTURE,
43633
+ minConfidence: SOUND_NO_CONFIDENCE,
43634
+ zones: SOUND_NO_GEOMETRY,
43635
+ zonesExclude: SOUND_NO_GEOMETRY,
43636
+ customZones: SOUND_NO_GEOMETRY,
43637
+ crossing: SOUND_NO_GEOMETRY,
43638
+ labelEquals: SOUND_NOT_A_PICTURE,
43639
+ identities: SOUND_NOT_A_PICTURE,
43640
+ identitiesExclude: SOUND_NOT_A_PICTURE,
43641
+ plates: SOUND_NOT_A_PICTURE,
43642
+ source: AUDIO_SOURCE
43643
+ },
43644
+ carriesMedia: true,
43645
+ carriesSubjectCrop: false,
43646
+ blurb: "Fires on a confirmed sampling window of audio, never on a picture. Name the sounds and/or keep a level threshold — with neither it can never match.",
43647
+ mediaBlurb: "A sound owns no frame, so the picture is a photograph of the camera taken at the moment of the match — the whole scene, never a crop. The GIF and the clip are cut around the same instant, from the camera."
43648
+ },
43649
+ {
43650
+ kind: "system",
43651
+ label: "Events",
43652
+ discriminator: "systemEvent",
43653
+ excluded: {},
43654
+ carriesMedia: false,
43655
+ carriesSubjectCrop: false,
43656
+ blurb: "The installation itself: a camera or stream dropping, a node leaving, an update landing. There is no detection behind it and no frame to attach."
43657
+ }
43658
+ ];
43659
+ /** The spec for a kind. Total by construction — every member has an entry. */
43660
+ function ruleKindSpec(kind) {
43661
+ return NC_RULE_KIND_SPECS.find((s) => s.kind === kind) ?? NC_RULE_KIND_SPECS[0];
43662
+ }
43663
+ /** The delivery a system rule rides — named once, compared everywhere. */
43664
+ var NC_SYSTEM_DELIVERY = "system-event";
43665
+ /**
43666
+ * Is this a SYSTEM rule — one about the installation rather than about anything
43667
+ * a camera saw? Takes a plain `string` so a rule carrying a delivery this build
43668
+ * has never heard of still gets an answer instead of a type error.
43669
+ */
43670
+ function isSystemDelivery(delivery) {
43671
+ return delivery === NC_SYSTEM_DELIVERY;
43672
+ }
43673
+ /**
43674
+ * Which kind of rule is this?
43675
+ *
43676
+ * The CONDITION wins over the delivery wherever the engine says it does, and
43677
+ * the order below is the engine's own: `evaluateRule` checks the system branch
43678
+ * first, then the occupancy and audio gates. Takes a plain `delivery` string so
43679
+ * a trigger this build does not know still classifies (as `detection`, which is
43680
+ * the read-only path's own fallback).
43681
+ */
43682
+ function ruleKindOf(rule) {
43683
+ if (isSystemDelivery(rule.delivery)) return "system";
43684
+ if (rule.conditions.occupancy !== void 0 && rule.delivery === "device-event") return "occupancy";
43685
+ if (rule.conditions.audio !== void 0 && rule.delivery === "immediate") return "sound";
43686
+ if (rule.delivery === "device-event") return "sensor";
43687
+ return "detection";
43688
+ }
43689
+ /**
43690
+ * Is this condition visible — and authorable — for a rule of this kind?
43691
+ *
43692
+ * The COMPOSITION, and the only function that decides it: `appliesTo` is the
43693
+ * server's answer about the trigger, the kind's `excluded` map is this build's
43694
+ * answer about the sub-type, and the discriminator overrides both (a sound rule
43695
+ * must be able to edit its sound even though `audio` is excluded for every
43696
+ * OTHER kind that rides `immediate`).
43697
+ */
43698
+ function conditionVisibleForKind(descriptor, delivery, kind) {
43699
+ const spec = ruleKindSpec(kind);
43700
+ if (descriptor.id === spec.discriminator) return true;
43701
+ if (!descriptor.appliesTo.includes(delivery)) return false;
43702
+ return spec.excluded[descriptor.id] === void 0;
43703
+ }
43704
+ /** Why a condition is not offered for this kind, or `null` when it IS. */
43705
+ function conditionExclusionReason(descriptorId, kind) {
43706
+ const spec = ruleKindSpec(kind);
43707
+ if (descriptorId === spec.discriminator) return null;
43708
+ return spec.excluded[descriptorId] ?? null;
43709
+ }
43710
+ function droppedConditionsForKind(conditions, kind) {
43711
+ const spec = ruleKindSpec(kind);
43712
+ const out = [];
43713
+ for (const [conditionId, value] of Object.entries(conditions)) {
43714
+ if (value === void 0) continue;
43715
+ if (Array.isArray(value) && value.length === 0) continue;
43716
+ const reason = spec.excluded[conditionId];
43717
+ if (reason !== void 0) out.push({
43718
+ conditionId,
43719
+ reason
43720
+ });
43721
+ }
43722
+ return out;
43723
+ }
43724
+ var NC_RULE_SECTIONS = [
43725
+ {
43726
+ key: "all",
43727
+ label: "All rules",
43728
+ blurb: "Every notification rule, whatever its trigger. Each rule picks a trigger, matches conditions and fans out to one or more delivery targets.",
43729
+ newRuleLabel: "New rule",
43730
+ emptyHint: "No notification rules yet. Create one to be told what your cameras see."
43731
+ },
43732
+ {
43733
+ key: "detection",
43734
+ label: "Detections & devices",
43735
+ blurb: "Rules driven by what the cameras and sensors SEE — detections, tracks, doorbell presses and package events.",
43736
+ newRuleLabel: "New detection rule",
43737
+ emptyHint: "No detection rules yet. Create one to be told what your cameras see."
43738
+ },
43739
+ {
43740
+ key: "audio",
43741
+ label: "Sound",
43742
+ blurb: "Rules on what the cameras HEAR: a sustained window of sound loud enough, or classified as one of the sounds you name. A sound rule fires only on a confirmed sampling window, never on a picture.",
43743
+ newRuleLabel: "New sound rule",
43744
+ emptyHint: "No sound rules yet. A new sound rule opens on the sound condition already filled in with a level threshold, so you only have to name the sounds and the targets."
43745
+ },
43746
+ {
43747
+ key: "occupancy",
43748
+ label: "Occupancy",
43749
+ blurb: "Rules on how many objects are in a camera frame or a zone, and for how long: \"the driveway became occupied\", \"the garden emptied\".",
43750
+ newRuleLabel: "New occupancy rule",
43751
+ emptyHint: "No occupancy rules yet. A new occupancy rule opens on the occupancy condition; pick the camera first, then the zone and the count."
43752
+ },
43753
+ {
43754
+ key: "system",
43755
+ label: "System events",
43756
+ blurb: "Rules about the installation itself: a camera or stream dropping, a node leaving the cluster, an addon or server update becoming available. These carry no image and are not tied to a detection.",
43757
+ newRuleLabel: "New system rule",
43758
+ emptyHint: "No system-event rules yet. These tell you when a camera drops, a node leaves or an update lands."
43759
+ }
43760
+ ];
43761
+ /** The section for a key, or `undefined` — the lookup behind every label. */
43762
+ function ruleSection(key) {
43763
+ return NC_RULE_SECTIONS.find((s) => s.key === key) ?? NC_RULE_SECTIONS[0];
43764
+ }
43765
+ /** Narrow a raw section string back to a known key (no cast). */
43766
+ function parseRuleSection(value) {
43767
+ return NC_RULE_SECTIONS.find((s) => s.key === value)?.key;
43768
+ }
43769
+ /** True for a rule whose sound condition makes it a SOUND rule. */
43770
+ function isAudioRule(subject) {
43771
+ return subject.conditions.audio !== void 0;
43772
+ }
43773
+ /** True for a rule whose occupancy condition makes it an OCCUPANCY rule. */
43774
+ function isOccupancyRule(subject) {
43775
+ return subject.conditions.occupancy !== void 0;
43776
+ }
43777
+ /**
43778
+ * Does a rule belong in this section?
43779
+ *
43780
+ * `all` always matches — a section that hides a rule is how an operator
43781
+ * concludes it was deleted. The other four PARTITION the list: sound and
43782
+ * occupancy are claimed by their condition, system by its trigger, and
43783
+ * everything left over — including a trigger this build has never heard of —
43784
+ * counts as a detection rule so it stays visible somewhere.
43785
+ *
43786
+ * Keyed on the CONDITION for two of them, because the trigger alone cannot
43787
+ * answer: a sound rule is `immediate` and an occupancy rule is `device-event`,
43788
+ * and in each case the condition is the discriminator the engine itself uses.
43789
+ * A section keyed on delivery would put both back in the pile they are
43790
+ * invisible in.
43791
+ */
43792
+ function ruleMatchesSection(subject, section) {
43793
+ if (section === "all") return true;
43794
+ if (section === "system") return isSystemDelivery(subject.delivery);
43795
+ if (section === "audio") return isAudioRule(subject);
43796
+ if (section === "occupancy") return isOccupancyRule(subject);
43797
+ return !isSystemDelivery(subject.delivery) && !isAudioRule(subject) && !isOccupancyRule(subject);
43798
+ }
43799
+ /**
43800
+ * Which section OWNS this rule — the counter behind a section strip.
43801
+ *
43802
+ * Derived from {@link ruleMatchesSection} rather than restated, so the strip's
43803
+ * counts and the strip's contents can never disagree.
43804
+ */
43805
+ function ruleSectionOf(subject) {
43806
+ if (ruleMatchesSection(subject, "system")) return "system";
43807
+ if (ruleMatchesSection(subject, "audio")) return "audio";
43808
+ if (ruleMatchesSection(subject, "occupancy")) return "occupancy";
43809
+ return "detection";
43810
+ }
43811
+ /**
43812
+ * The occupancy schema's own defaults, in one place.
43813
+ *
43814
+ * Both the WIDGET (what an untouched sub-field shows) and the CREATION PRESET
43815
+ * (what "New occupancy rule" seeds) have to author exactly these numbers, and
43816
+ * two copies of a default is how a preset starts writing a rule that does not
43817
+ * match what the editor then displays. Mirrors `NcOccupancyConditionSchema`'s
43818
+ * `.default(...)` clauses, and the spec asserts the mirror.
43819
+ */
43820
+ var NC_OCCUPANCY_DEFAULTS = {
43821
+ op: "became-occupied",
43822
+ count: 1,
43823
+ sustainSeconds: 15
43824
+ };
43825
+ /**
43826
+ * The seeded sound condition.
43827
+ *
43828
+ * Carries a level floor rather than the bare defaults: with neither a level nor
43829
+ * a label the engine returns false for every window (`matchesAudio`), so a
43830
+ * preset without one would hand the operator a rule that can never fire. The
43831
+ * offered level is the same one the widget writes, so switching to SOUNDS
43832
+ * (label mode — the one that fires on the first labelled frame, D157) is one
43833
+ * tap on the mode picker.
43834
+ *
43835
+ * The seed cannot be a label rule instead: a labels-only condition with no
43836
+ * labels chosen yet is fail-closed too, and it would hand the operator the same
43837
+ * dead rule from the other side.
43838
+ */
43839
+ var NC_AUDIO_SEED = {
43840
+ hitPercent: NC_AUDIO_DEFAULTS.hitPercent,
43841
+ samplingSeconds: NC_AUDIO_DEFAULTS.samplingSeconds,
43842
+ dbThreshold: -55
43843
+ };
43844
+ function ruleSeedForSection(section) {
43845
+ switch (section) {
43846
+ case "system": return {
43847
+ delivery: NC_SYSTEM_DELIVERY,
43848
+ conditions: {}
43849
+ };
43850
+ case "audio": return {
43851
+ delivery: "immediate",
43852
+ conditions: { audio: NC_AUDIO_SEED }
43853
+ };
43854
+ case "occupancy": return {
43855
+ delivery: "device-event",
43856
+ conditions: { occupancy: NC_OCCUPANCY_DEFAULTS }
43857
+ };
43858
+ default: return {
43859
+ delivery: "immediate",
43860
+ conditions: {}
43861
+ };
43862
+ }
43863
+ }
43864
+ /** The trigger a "New rule" in this section should open on. */
43865
+ function defaultDeliveryForSection(section) {
43866
+ return ruleSeedForSection(section).delivery;
43867
+ }
43868
+ var NC_RULE_EDITOR_SECTION_ORDER = [
43869
+ "rule",
43870
+ "devices",
43871
+ "conditions",
43872
+ "notification",
43873
+ "actions",
43874
+ "advanced"
43875
+ ];
43876
+ /** The English fallback name of each section, before the kind renames one. */
43877
+ var EDITOR_SECTION_LABELS = {
43878
+ rule: "Rule",
43879
+ devices: "Cameras",
43880
+ conditions: "Conditions",
43881
+ notification: "Notification",
43882
+ actions: "Actions",
43883
+ advanced: "Advanced"
43884
+ };
43885
+ /**
43886
+ * The sections a rule of THIS kind actually shows.
43887
+ *
43888
+ * Two rules, both of them about a control that would decide nothing:
43889
+ *
43890
+ * - a `system-event` rule has no device scope — `conditions.devices` is not
43891
+ * evaluated for it and the input builder refuses to write it — so the
43892
+ * Cameras section is DROPPED rather than shown and ignored;
43893
+ * - a sound or occupancy rule is NAMED on its conditions section. Neither has
43894
+ * a trigger of its own, so that section is the only place either feature
43895
+ * exists, and a heading that never mentions sound is exactly why an operator
43896
+ * with a working audio engine reported there was no way to create an audio
43897
+ * rule.
43898
+ *
43899
+ * The NOTIFICATION section always survives: priority and the template still
43900
+ * matter even for a kind that carries no picture, and dropping the section to
43901
+ * hide the media controls would take the template with it. Whether the media
43902
+ * half renders is `carriesMedia` / `carriesSubjectCrop`, asked separately.
43903
+ */
43904
+ function ruleEditorSectionsForKind(kind) {
43905
+ const spec = ruleKindSpec(kind);
43906
+ const sections = [];
43907
+ for (const key of NC_RULE_EDITOR_SECTION_ORDER) {
43908
+ if (key === "devices" && kind === "system") continue;
43909
+ sections.push({
43910
+ key,
43911
+ label: key === "conditions" ? spec.label : EDITOR_SECTION_LABELS[key]
43912
+ });
43913
+ }
43914
+ return sections;
43915
+ }
43916
+ //#endregion
43553
43917
  //#region src/notification/schedule.ts
43554
43918
  var WEEKDAY_TO_DAY = {
43555
43919
  Sun: 0,
@@ -46682,6 +47046,7 @@ exports.LoginMethodContributionSchema = LoginMethodContributionSchema;
46682
47046
  exports.LoginStageEnum = LoginStageEnum;
46683
47047
  exports.MACRO_LABELS = MACRO_LABELS;
46684
47048
  exports.MAX_CLIP_EVENT_IDS = MAX_CLIP_EVENT_IDS;
47049
+ exports.MAX_CLIP_LABELS = MAX_CLIP_LABELS;
46685
47050
  exports.MAX_CONDITION_DEPTH = MAX_CONDITION_DEPTH;
46686
47051
  exports.MAX_CONDITION_LEAVES = MAX_CONDITION_LEAVES;
46687
47052
  exports.MAX_EXPRESSION_AST_NODES = MAX_EXPRESSION_AST_NODES;
@@ -46761,6 +47126,7 @@ exports.NC_AUDIO_HIT_PERCENT_MAX = NC_AUDIO_HIT_PERCENT_MAX;
46761
47126
  exports.NC_AUDIO_HIT_PERCENT_MIN = NC_AUDIO_HIT_PERCENT_MIN;
46762
47127
  exports.NC_AUDIO_SAMPLING_MAX_SEC = NC_AUDIO_SAMPLING_MAX_SEC;
46763
47128
  exports.NC_AUDIO_SAMPLING_MIN_SEC = NC_AUDIO_SAMPLING_MIN_SEC;
47129
+ exports.NC_AUDIO_SEED = NC_AUDIO_SEED;
46764
47130
  exports.NC_AUTHORABLE_SYSTEM_EVENT_KINDS = NC_AUTHORABLE_SYSTEM_EVENT_KINDS;
46765
47131
  exports.NC_BASE_CONDITION_KEYS = NC_BASE_CONDITION_KEYS;
46766
47132
  exports.NC_CONDITION_CATALOG = NC_CONDITION_CATALOG;
@@ -46772,7 +47138,12 @@ exports.NC_DEFAULT_SNOOZE_MINUTES = NC_DEFAULT_SNOOZE_MINUTES;
46772
47138
  exports.NC_HISTORY_LIMIT_DEFAULT = NC_HISTORY_LIMIT_DEFAULT;
46773
47139
  exports.NC_HISTORY_LIMIT_MAX = NC_HISTORY_LIMIT_MAX;
46774
47140
  exports.NC_MAX_PER_TRACK_IMMEDIATE = NC_MAX_PER_TRACK_IMMEDIATE;
47141
+ exports.NC_OCCUPANCY_DEFAULTS = NC_OCCUPANCY_DEFAULTS;
47142
+ exports.NC_RULE_EDITOR_SECTION_ORDER = NC_RULE_EDITOR_SECTION_ORDER;
47143
+ exports.NC_RULE_KIND_SPECS = NC_RULE_KIND_SPECS;
47144
+ exports.NC_RULE_SECTIONS = NC_RULE_SECTIONS;
46775
47145
  exports.NC_SNOOZE_MAX_MINUTES = NC_SNOOZE_MAX_MINUTES;
47146
+ exports.NC_SYSTEM_DELIVERY = NC_SYSTEM_DELIVERY;
46776
47147
  exports.NC_SYSTEM_EVENT_FILTER_KEYS = NC_SYSTEM_EVENT_FILTER_KEYS;
46777
47148
  exports.NC_TAXONOMY = NC_TAXONOMY;
46778
47149
  exports.NativeCropBboxSchema = NativeCropBboxSchema;
@@ -47260,6 +47631,8 @@ exports.compileExpression = compileExpression;
47260
47631
  exports.compileExpressionSafe = compileExpressionSafe;
47261
47632
  exports.composeSwitchedOff = composeSwitchedOff;
47262
47633
  exports.conditionDepth = conditionDepth;
47634
+ exports.conditionExclusionReason = conditionExclusionReason;
47635
+ exports.conditionVisibleForKind = conditionVisibleForKind;
47263
47636
  exports.connectionTestCapability = connectionTestCapability;
47264
47637
  exports.connectivityCapability = connectivityCapability;
47265
47638
  exports.consumablesCapability = consumablesCapability;
@@ -47289,6 +47662,7 @@ exports.dayNightCapability = dayNightCapability;
47289
47662
  exports.declarationOwnerNodeId = declarationOwnerNodeId;
47290
47663
  exports.decodeVectorBase64 = decodeVectorBase64;
47291
47664
  exports.decoderCapability = decoderCapability;
47665
+ exports.defaultDeliveryForSection = defaultDeliveryForSection;
47292
47666
  exports.defaultDeviceFor = defaultDeviceFor;
47293
47667
  exports.defineCustomActions = defineCustomActions;
47294
47668
  exports.deriveBatteryPresence = deriveBatteryPresence;
@@ -47311,6 +47685,7 @@ exports.deviceSelectorMatches = deviceSelectorMatches;
47311
47685
  exports.deviceStateCapability = deviceStateCapability;
47312
47686
  exports.deviceStatusCapability = deviceStatusCapability;
47313
47687
  exports.doorbellCapability = doorbellCapability;
47688
+ exports.droppedConditionsForKind = droppedConditionsForKind;
47314
47689
  exports.egressTranscodeSharingKey = egressTranscodeSharingKey;
47315
47690
  exports.egressTransportFromRequest = egressTransportFromRequest;
47316
47691
  exports.embeddingEncoderCapability = embeddingEncoderCapability;
@@ -47362,6 +47737,7 @@ exports.invocationFromEncodeProfile = require_canonical_hash.invocationFromEncod
47362
47737
  exports.isAgentOnlyPlacement = isAgentOnlyPlacement;
47363
47738
  exports.isArrayOutputSchema = isArrayOutputSchema;
47364
47739
  exports.isAudioLabelSelected = isAudioLabelSelected;
47740
+ exports.isAudioRule = isAudioRule;
47365
47741
  exports.isBaseConditionKey = isBaseConditionKey;
47366
47742
  exports.isBatteryPresenceFault = isBatteryPresenceFault;
47367
47743
  exports.isCollectionArrayMethod = isCollectionArrayMethod;
@@ -47373,10 +47749,12 @@ exports.isEvent = require_sleep.isEvent;
47373
47749
  exports.isIsolatedBuiltin = isIsolatedBuiltin;
47374
47750
  exports.isNode = isNode;
47375
47751
  exports.isObjectInput = isObjectInput;
47752
+ exports.isOccupancyRule = isOccupancyRule;
47376
47753
  exports.isRestoredCap = isRestoredCap;
47377
47754
  exports.isSameAddonId = isSameAddonId;
47378
47755
  exports.isScheduleActive = isScheduleActive;
47379
47756
  exports.isSoftwareDecode = require_canonical_hash.isSoftwareDecode;
47757
+ exports.isSystemDelivery = isSystemDelivery;
47380
47758
  exports.isVoidInput = isVoidInput;
47381
47759
  exports.jobKindSchema = jobKindSchema;
47382
47760
  exports.kebabToCamel = kebabToCamel;
@@ -47440,6 +47818,7 @@ exports.parseJsonObject = require_sleep.parseJsonObject;
47440
47818
  exports.parseJsonUnknown = require_sleep.parseJsonUnknown;
47441
47819
  exports.parseProcStatus = parseProcStatus;
47442
47820
  exports.parseProfileBrokerId = require_sleep.parseProfileBrokerId;
47821
+ exports.parseRuleSection = parseRuleSection;
47443
47822
  exports.parseStreamParamsFormPatch = parseStreamParamsFormPatch;
47444
47823
  exports.patchAudio = patchAudio;
47445
47824
  exports.petFeederCapability = petFeederCapability;
@@ -47499,6 +47878,13 @@ exports.resolveScrubThumbnailGeometry = resolveScrubThumbnailGeometry;
47499
47878
  exports.resolveVariantModelId = resolveVariantModelId;
47500
47879
  exports.resolveViewableDeviceIds = resolveViewableDeviceIds;
47501
47880
  exports.roleSpec = roleSpec;
47881
+ exports.ruleEditorSectionsForKind = ruleEditorSectionsForKind;
47882
+ exports.ruleKindOf = ruleKindOf;
47883
+ exports.ruleKindSpec = ruleKindSpec;
47884
+ exports.ruleMatchesSection = ruleMatchesSection;
47885
+ exports.ruleSection = ruleSection;
47886
+ exports.ruleSectionOf = ruleSectionOf;
47887
+ exports.ruleSeedForSection = ruleSeedForSection;
47502
47888
  exports.runInferenceStep = runInferenceStep;
47503
47889
  exports.runtimeDevices = runtimeDevices;
47504
47890
  exports.runtimeStatePolicyFor = runtimeStatePolicyFor;