@camstack/addon-post-analysis 1.2.325 → 1.2.330
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/{clip-model-registry-CshjlVTF.mjs → clip-model-registry-CGaaUvAD.mjs} +198 -20
- package/dist/{clip-model-registry-BFLeI0dr.js → clip-model-registry-DQmmIYXS.js} +215 -19
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BiD7k7AM.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BNJdXVq8.mjs} +3 -3
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BdFzCOK8.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-n9xCtjjZ.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-D0L1yv82.mjs → hostInit-Bj7szOak.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +460 -141
- package/dist/pipeline-analytics/index.mjs +460 -141
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CBG_lEGV.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Bnb-yu_h.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as NC_ALARM_DELAY_MAX_SEC, $n as
|
|
1
|
+
import { $ as NC_ALARM_DELAY_MAX_SEC, $n as failureContributionCapability, $r as canonicalHash, $t as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, A as EMBEDDING_REQUIREMENTS_FAILED, Ai as unknown, An as assertTimelapseCadences, At as NcRuleInputSchema, B as ListTrackRelationsInputSchema, Bn as deriveRecordingMode, Br as storageOccupancyCapability, Bt as NcStateSummarySectionIdSchema, C as DEFAULT_EVENT_COLOR, Ci as partialRecord, Cn as addonWidgetsSourceCapability, Cr as readDeviceStateFrom, Ct as NcAlarmSensorsSchema, D as DeclaredDevices, Di as templateLiteral, Dn as applyModeDelaysEdits, Dr as resolveClipSearchMinScore, Dt as NcLegacyRuleActionSequenceSchema, E as DETECTION_PIPELINE_CAP_NAME, Ei as string, En as applyBlockingEdit, Er as renameAlarmProfile, Et as NcConditionsSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as consumableLowPercentOf, Fr as ruleKindOf, Ft as NcScheduleSchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as embeddingRefusalOf, Gr as systemEventFilterApplies, Gt as NcTemplateVarDescriptorSchema, H as LockStateSchema, Hn as describeSystemEventFamilyProblem, Hr as syncActiveAlarmProfileSettings, Ht as NcTaxonomySchema, I as FIRST_LEVEL_MACRO_CLASSES, In as cosineSimilarity$1, Ir as sceneMonitorCapability, It as NcSharedSequenceSchema, J as MOTION_CLOSE_AFTER_MS, Jn as encodeVectorBase64, Jr as templateCarriesSummary, K as MAX_TRACK_PURITIES, Kr as systemEventSummaryScheduleOf, Kt as OPS_LOG_DEFAULT_LIMIT, L as FULL_IMAGE_BBOX, Ln as createAlarmProfile, Lr as secureActuationFor, Lt as NcSnoozeInputSchema, M as EVENT_DENSITY_BATCH_MAX, Mn as audioModeOf, Mr as ruleBelongsToAlarm$1, Mt as NcRuleSchema, N as EVENT_KIND_BY_CAP, Nn as buildEventKindDescriptor, Nr as ruleDeviceCompatCapQueries, Nt as NcRuleTargetSchema, O as DetectionSourceSchema, Oi as tuple, On as applyNonBlockingEdit, Or as resolveLocationMode, Ot as NcRuleActionSchema, P as EVENT_OWNER_TYPES, Pn as cloneAlarmSettings, Pr as ruleGroupingApplies, Pt as NcRuleUpdateInputSchema, Q as NC_ALARM_DELAY_FIELDS, Qn as faceGalleryCapability, Qr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Qt as RetrainStatusSchema, R as FailureCounters, Rn as deleteAlarmProfile, Rr as sensorRaisedOf, Rt as NcSnoozeSchema, S as CoverStateSchema, Si as object, Sn as activateAlarmProfile, Sr as plateGalleryCapability, St as NcAlarmSensorsInputSchema, T as DETECTION_MACRO_CLASSES, Ti as strictObject, Tn as alarmPanelCapability, Tr as readTimelapseGeneratedAt, Tt as NcConditionDescriptorSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Ur as systemEventDeliveryProblems, Ut as NcTemplatePreviewInputSchema, V as ListTrackRelationsResultSchema, Vn as describeSystemEventDeliveryProblem, Vr as subKindsOf, Vt as NcSystemEventKindSchema, W as MAX_BIRTH_DECISION_RECORDS, Wr as systemEventFamilyProblems, Wt as NcTemplatePreviewSchema, X as MotionTriggeredBySchema, Xn as eventKindLabel, Xr as vectorDimFromBase64, Xt as RECORDING_SOURCE_CAMSTACK_ADDON, Y as MediaFileKindEnum, Yn as ensureAlarmProfiles, Yr as templateVarsFor, Yt as RECORDING_EXPORT_MAX_READ_BYTES, Z as NC_ALARM_DEFAULT_PROFILE_ID, Zn as evictionPolicyOfLocation, Zr as zoneAnalyticsCapability, Zt as RebuildObjectEmbeddingsRefusalSchema, _ as BaseDevice, _i as boolean, _n as TrackPuritySchema, _r as memoizePinnedFfmpeg, _t as NC_TANK_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, ai as CamProfileSchema, an as SCENE_DEFAULT_UNCOVERED_POLICY, ar as isDetectionMacroClass, at as NC_CLIP_PRE_ROLL_DEFAULT_SEC, b as ClearTrackRelationInputSchema, bi as looseObject, bn as TrackSideSchema, bt as NcAlarmConfigSchema, c as resolveClusterModelPin, ci as defineCustomActions, cn as SceneMonitorSchema, cr as isKnownTemplateVar, ct as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, di as nodePin, dn as SetTrackRelationInputSchema, dr as isScheduleActive, dt as NC_GROUPING_DEFAULT_SEC, ei as evaluateSensorEdge, en as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, er as gifDeriveSpeed, et as NC_ALARM_SYSTEM_EVENT_KINDS, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as TIMELAPSE_DENSE_FLOOR_SEC, fr as isSystemDelivery, ft as NC_GROUPING_MAX_SEC, g as BIRTH_DECISION_DEFAULT_LIMIT, gi as array, gn as TimelapseRuleSchema, gr as mayWriteToLocation, gt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, h as ArchivedDebugNoteSchema, hi as _enum, hn as TimelapseRulePatchSchema, hr as kebabToCamel, ht as NC_SNOOZE_MAX_MINUTES, ii as BaseAddon, in as SCENE_DEFAULT_QUIET_SECONDS, ir as isDefaultAlarmProfileId, it as NC_CLIP_POST_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, ji as EventCategory, jn as audioMetricsCapability, jr as ruleAutoTriggersAlarm, jt as NcRulePatchSchema, k as EMBEDDING_MODEL_FAILED, ki as union, kn as applySecureOnArmEdit, kr as resolveModeDelays, kt as NcRuleActionsSchema, l as APPEARANCE_BIN_COUNT, li as hydrateSchema, ln as SetTrackPurityInputSchema, lr as isMediaOwnerType, lt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, m as AlarmArmModeSchema, mi as ZodObject, mn as TimelapseRuleInputSchema, mr as isVectorIndexDimConflict, mt as NC_SENSOR_TRANSITION_KINDS, ni as DeviceType, nn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, nr as isAlarmControllableCap, nt as NC_AUDIO_CONFIRM_HITS_DEFAULT, o as FACE_EMBEDDING_STEP_ID, oi as createEvent, on as SCENE_DIVERGED, or as isEventKindLabelKey, ot as NC_CLIP_RETENTION_MAX_SEC, p as AUDIO_MACRO_LABELS, pi as sleep$1, pn as TRACK_RELATIONS_DEFAULT_LIMIT, pr as isSystemEventSummaryRule, pt as NC_GROUPING_MIN_SEC, q as MAX_TRACK_RELATIONS, qr as systemEventSummarySplitOf, qt as OpsLogEntrySchema, ri as errMsg, rn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, rr as isAudioClassName, rt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, s as readClusterModelPinStrict, si as customAction, sn as SCENE_RESET_RECAPTURES, sr as isEventOwnerType, st as NC_CONDITION_CATALOG, t as BUILTIN_CLIP_MODELS, ti as isSourceCap, tn as SCENE_DEFAULT_ANCHOR_THRESHOLD, tr as gifFromMp4Args, tt as NC_ALARM_TRIGGERED_MAX_SEC, u as APPEARANCE_BLOCKS, ui as isDeviceScopedCap, un as SetTrackPurityResultSchema, ur as isRetiredClearKey, ut as NC_DEFAULT_SNOOZE_MINUTES, v as BirthDecisionRecordSchema, vi as discriminatedUnion, vn as TrackRelationKindSchema, vr as notificationRulesCapability, vt as NC_TAXONOMY, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wi as record, wn as alarmDeviceSecureOnArm, wr as readMotionTriggeredBy, wt as NcAlarmSettingsPatchSchema, x as ClearTrackRelationResultSchema, xi as number, xn as TrackSourceSchema, xr as pipelineAnalyticsCapability, xt as NcAlarmPostTriggeredActionSchema, y as COCO_TO_MACRO, yi as literal, yn as TrackRelationSchema, yr as occupancyScope, yt as NC_TEMPLATE_VARS, z as LabelAttributionSchema, zn as derivePostTriggeredAction, zr as servedTemplateVars, zt as NcSnoozeSuppressedSchema } from "../clip-model-registry-CGaaUvAD.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs$1 from "node:fs";
|
|
4
4
|
import { createWriteStream, promises } from "node:fs";
|
|
@@ -652,7 +652,8 @@ var NcAlarmArmOutcomeSchema = discriminatedUnion("outcome", [
|
|
|
652
652
|
object({
|
|
653
653
|
outcome: literal("rejected"),
|
|
654
654
|
reason: NcAlarmArmAnywayRejectionSchema
|
|
655
|
-
}).readonly()
|
|
655
|
+
}).readonly(),
|
|
656
|
+
object({ outcome: literal("outside-schedule") }).readonly()
|
|
656
657
|
]);
|
|
657
658
|
/**
|
|
658
659
|
* The devices a mode covers, ascending and de-duplicated.
|
|
@@ -1511,7 +1512,25 @@ function modeDelaysFromPersisted(raw) {
|
|
|
1511
1512
|
}
|
|
1512
1513
|
kept[field] = value;
|
|
1513
1514
|
}
|
|
1514
|
-
if (
|
|
1515
|
+
if (item.postTriggeredAction !== void 0) {
|
|
1516
|
+
const action = NcAlarmPostTriggeredActionSchema.safeParse(item.postTriggeredAction);
|
|
1517
|
+
if (action.success) kept.postTriggeredAction = action.data;
|
|
1518
|
+
else dropped.push({
|
|
1519
|
+
mode: item.mode,
|
|
1520
|
+
why: "out-of-bounds",
|
|
1521
|
+
field: "triggeredDurationSec"
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
if (item.schedule !== void 0) {
|
|
1525
|
+
const schedule = NcScheduleSchema.safeParse(item.schedule);
|
|
1526
|
+
if (schedule.success) kept.schedule = schedule.data;
|
|
1527
|
+
else dropped.push({
|
|
1528
|
+
mode: item.mode,
|
|
1529
|
+
why: "out-of-bounds",
|
|
1530
|
+
field: "schedule"
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
if (kept.exitDelaySec !== void 0 || kept.entryDelaySec !== void 0 || kept.triggeredDurationSec !== void 0 || kept.postTriggeredAction !== void 0 || kept.schedule !== void 0) entries.push(kept);
|
|
1515
1534
|
}
|
|
1516
1535
|
return {
|
|
1517
1536
|
entries,
|
|
@@ -1527,16 +1546,15 @@ function modeDelaysFromPersisted(raw) {
|
|
|
1527
1546
|
* (`resolveModeDelays`): `home` has no exit delay on purpose — nobody is
|
|
1528
1547
|
* leaving — and says so with its own entry, not by this shared value.
|
|
1529
1548
|
*
|
|
1530
|
-
*
|
|
1531
|
-
*
|
|
1532
|
-
*
|
|
1533
|
-
* every existing alarm after N seconds would be a behaviour change nobody
|
|
1534
|
-
* asked for.
|
|
1549
|
+
* Default post-triggered action is `rearmAfter` 10 s — every mode inherits
|
|
1550
|
+
* that unless the operator overrides. Older blobs that still store the stock
|
|
1551
|
+
* `0` + absent/`doNothing` seed are migrated once at panel boot (D701).
|
|
1535
1552
|
*/
|
|
1536
1553
|
var DEFAULT_DELAYS = {
|
|
1537
1554
|
exitDelaySec: 30,
|
|
1538
1555
|
entryDelaySec: 20,
|
|
1539
|
-
triggeredDurationSec:
|
|
1556
|
+
triggeredDurationSec: 10,
|
|
1557
|
+
postTriggeredAction: "rearmAfter"
|
|
1540
1558
|
};
|
|
1541
1559
|
var DISARMED = {
|
|
1542
1560
|
target: "disarmed",
|
|
@@ -1554,7 +1572,7 @@ function armedState(mode) {
|
|
|
1554
1572
|
* answer is still correct the moment anyone asks.
|
|
1555
1573
|
*/
|
|
1556
1574
|
function stateAt(m, now) {
|
|
1557
|
-
if (m.clearAt !== void 0 && now >= m.clearAt) return m.target;
|
|
1575
|
+
if (m.clearAt !== void 0 && now >= m.clearAt) return m.clearToDisarmed === true ? "disarmed" : m.target;
|
|
1558
1576
|
if (m.fired) return "triggered";
|
|
1559
1577
|
if (m.triggerAt !== void 0) return now >= m.triggerAt ? "triggered" : "pending";
|
|
1560
1578
|
if (m.armedAt !== void 0) return now >= m.armedAt ? m.target : "arming";
|
|
@@ -1650,10 +1668,18 @@ function trigger(m, delays, now) {
|
|
|
1650
1668
|
changedAt: now
|
|
1651
1669
|
};
|
|
1652
1670
|
}
|
|
1653
|
-
/**
|
|
1671
|
+
/**
|
|
1672
|
+
* `clearAt` (+ optional disarm) when the operator asked for a bounded
|
|
1673
|
+
* post-triggered action — see {@link AlarmDelays}.
|
|
1674
|
+
*/
|
|
1654
1675
|
function autoClear(delays, firesAt) {
|
|
1655
|
-
if (delays.
|
|
1656
|
-
|
|
1676
|
+
if (delays.postTriggeredAction === "doNothing") return {};
|
|
1677
|
+
const clearAt = firesAt + Math.max(0, delays.triggeredDurationSec) * 1e3;
|
|
1678
|
+
if (delays.postTriggeredAction === "disableAfter") return {
|
|
1679
|
+
clearAt,
|
|
1680
|
+
clearToDisarmed: true
|
|
1681
|
+
};
|
|
1682
|
+
return { clearAt };
|
|
1657
1683
|
}
|
|
1658
1684
|
/**
|
|
1659
1685
|
* Settle the machine so a stored value never depends on when it is read.
|
|
@@ -1663,16 +1689,23 @@ function autoClear(delays, firesAt) {
|
|
|
1663
1689
|
* than only in the reading of it.
|
|
1664
1690
|
*/
|
|
1665
1691
|
function settle(m, now) {
|
|
1666
|
-
if (m.clearAt !== void 0 && now >= m.clearAt)
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1692
|
+
if (m.clearAt !== void 0 && now >= m.clearAt) {
|
|
1693
|
+
if (m.clearToDisarmed === true) return {
|
|
1694
|
+
...DISARMED,
|
|
1695
|
+
changedAt: m.clearAt
|
|
1696
|
+
};
|
|
1697
|
+
return {
|
|
1698
|
+
target: m.target,
|
|
1699
|
+
fired: false,
|
|
1700
|
+
changedAt: m.clearAt
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1671
1703
|
if (m.triggerAt !== void 0 && now >= m.triggerAt) return {
|
|
1672
1704
|
target: m.target,
|
|
1673
1705
|
fired: true,
|
|
1674
1706
|
changedAt: m.triggerAt,
|
|
1675
|
-
...m.clearAt !== void 0 ? { clearAt: m.clearAt } : {}
|
|
1707
|
+
...m.clearAt !== void 0 ? { clearAt: m.clearAt } : {},
|
|
1708
|
+
...m.clearToDisarmed === true ? { clearToDisarmed: true } : {}
|
|
1676
1709
|
};
|
|
1677
1710
|
if (m.armedAt !== void 0 && now >= m.armedAt) return {
|
|
1678
1711
|
target: m.target,
|
|
@@ -1819,7 +1852,8 @@ function alarmTransition(input) {
|
|
|
1819
1852
|
kind: "alarm-disarmed",
|
|
1820
1853
|
state: next,
|
|
1821
1854
|
previous,
|
|
1822
|
-
at
|
|
1855
|
+
at,
|
|
1856
|
+
...input.autoCleared === true ? { autoCleared: true } : {}
|
|
1823
1857
|
};
|
|
1824
1858
|
return null;
|
|
1825
1859
|
}
|
|
@@ -1910,6 +1944,17 @@ var ncAlarmPanelSchema = object({
|
|
|
1910
1944
|
entryDelaySec: number().int().min(0).max(600).default(DEFAULT_DELAYS.entryDelaySec),
|
|
1911
1945
|
triggeredDurationSec: number().int().min(0).max(3600).default(DEFAULT_DELAYS.triggeredDurationSec),
|
|
1912
1946
|
/**
|
|
1947
|
+
* What happens when the triggered window ends. Absent on older blobs →
|
|
1948
|
+
* derived from {@link triggeredDurationSec} at read time (never defaulted
|
|
1949
|
+
* here: a Zod default would force `doNothing` onto installs that already
|
|
1950
|
+
* had a positive `triggeredDurationSec` and silently stop re-arming).
|
|
1951
|
+
*/
|
|
1952
|
+
postTriggeredAction: _enum([
|
|
1953
|
+
"rearmAfter",
|
|
1954
|
+
"disableAfter",
|
|
1955
|
+
"doNothing"
|
|
1956
|
+
]).optional(),
|
|
1957
|
+
/**
|
|
1913
1958
|
* VESTIGIAL (D630): nothing reads it to decide anything any more — every
|
|
1914
1959
|
* transition is announced to `announceTargets`. Kept so a persisted blob
|
|
1915
1960
|
* and the settings cap still round-trip it, and read once at boot to tell
|
|
@@ -1968,7 +2013,14 @@ var ncAlarmPanelSchema = object({
|
|
|
1968
2013
|
mode: string(),
|
|
1969
2014
|
exitDelaySec: number().optional(),
|
|
1970
2015
|
entryDelaySec: number().optional(),
|
|
1971
|
-
triggeredDurationSec: number().optional()
|
|
2016
|
+
triggeredDurationSec: number().optional(),
|
|
2017
|
+
postTriggeredAction: _enum([
|
|
2018
|
+
"rearmAfter",
|
|
2019
|
+
"disableAfter",
|
|
2020
|
+
"doNothing"
|
|
2021
|
+
]).optional(),
|
|
2022
|
+
/** Loose — re-validated through {@link modeDelaysFromPersisted}. */
|
|
2023
|
+
schedule: unknown().optional()
|
|
1972
2024
|
})).default([]),
|
|
1973
2025
|
/**
|
|
1974
2026
|
* Active alarm configuration profile id. Empty / absent on a blob older than
|
|
@@ -2040,12 +2092,18 @@ function settingsBlobFromPersisted(raw) {
|
|
|
2040
2092
|
if (typeof rec["exitDelaySec"] === "number") entry.exitDelaySec = rec["exitDelaySec"];
|
|
2041
2093
|
if (typeof rec["entryDelaySec"] === "number") entry.entryDelaySec = rec["entryDelaySec"];
|
|
2042
2094
|
if (typeof rec["triggeredDurationSec"] === "number") entry.triggeredDurationSec = rec["triggeredDurationSec"];
|
|
2095
|
+
if (typeof rec["postTriggeredAction"] === "string") entry.postTriggeredAction = rec["postTriggeredAction"];
|
|
2096
|
+
if (rec["schedule"] !== void 0) entry.schedule = rec["schedule"];
|
|
2043
2097
|
modeDelaysLoose.push(entry);
|
|
2044
2098
|
}
|
|
2099
|
+
const truncatedTriggered = Math.max(0, Math.min(3600, Math.trunc(triggered)));
|
|
2100
|
+
const rawAction = raw["postTriggeredAction"];
|
|
2101
|
+
const postTriggeredAction = rawAction === "rearmAfter" || rawAction === "disableAfter" || rawAction === "doNothing" ? rawAction : void 0;
|
|
2045
2102
|
return {
|
|
2046
2103
|
exitDelaySec: Math.max(0, Math.min(600, Math.trunc(exit))),
|
|
2047
2104
|
entryDelaySec: Math.max(0, Math.min(600, Math.trunc(entry))),
|
|
2048
|
-
triggeredDurationSec:
|
|
2105
|
+
triggeredDurationSec: truncatedTriggered,
|
|
2106
|
+
...postTriggeredAction !== void 0 ? { postTriggeredAction } : {},
|
|
2049
2107
|
announceTargets,
|
|
2050
2108
|
nonBlocking: lists("nonBlocking"),
|
|
2051
2109
|
blocking: lists("blocking"),
|
|
@@ -2058,6 +2116,7 @@ function settingsToPersisted(settings) {
|
|
|
2058
2116
|
exitDelaySec: settings.exitDelaySec,
|
|
2059
2117
|
entryDelaySec: settings.entryDelaySec,
|
|
2060
2118
|
triggeredDurationSec: settings.triggeredDurationSec,
|
|
2119
|
+
...settings.postTriggeredAction !== void 0 ? { postTriggeredAction: settings.postTriggeredAction } : {},
|
|
2061
2120
|
announceTargets: [...settings.announceTargets],
|
|
2062
2121
|
nonBlocking: settings.nonBlocking.map((l) => ({
|
|
2063
2122
|
mode: l.mode,
|
|
@@ -2142,11 +2201,13 @@ function machineFromPersisted(raw) {
|
|
|
2142
2201
|
const armedAt = optionalInstant(rec["armedAt"]);
|
|
2143
2202
|
const triggerAt = optionalInstant(rec["triggerAt"]);
|
|
2144
2203
|
const clearAt = optionalInstant(rec["clearAt"]);
|
|
2204
|
+
const clearToDisarmed = rec["clearToDisarmed"] === true;
|
|
2145
2205
|
return {
|
|
2146
2206
|
target: rec["target"],
|
|
2147
2207
|
...armedAt !== void 0 ? { armedAt } : {},
|
|
2148
2208
|
...triggerAt !== void 0 ? { triggerAt } : {},
|
|
2149
2209
|
...clearAt !== void 0 ? { clearAt } : {},
|
|
2210
|
+
...clearToDisarmed ? { clearToDisarmed: true } : {},
|
|
2150
2211
|
fired: rec["fired"] === true,
|
|
2151
2212
|
changedAt: rec["changedAt"]
|
|
2152
2213
|
};
|
|
@@ -2186,14 +2247,48 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2186
2247
|
super(ctx, ncAlarmPanelSchema, { type: DeviceType.AlarmPanel });
|
|
2187
2248
|
this.logDroppedModeDelays();
|
|
2188
2249
|
this.restore();
|
|
2250
|
+
this.migrateStockAfterTriggerDefaults();
|
|
2189
2251
|
this.registerAlarmCap();
|
|
2190
2252
|
this.publish();
|
|
2191
2253
|
if (this.config.get("announceArm") === false && this.config.get("announceTargets").length > 0) this.ctx.logger.info("alarm announcements now cover every transition (D630) — the announceArm switch is gone", { tags: { deviceId: this.id } });
|
|
2254
|
+
if (this.config.get("announceTargets").length > 0) {
|
|
2255
|
+
this.ctx.logger.info("alarm announceTargets cleared — deliver via alarm rules only", {
|
|
2256
|
+
tags: { deviceId: this.id },
|
|
2257
|
+
meta: { cleared: this.config.get("announceTargets").length }
|
|
2258
|
+
});
|
|
2259
|
+
this.config.set("announceTargets", []);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* One-shot: the pre-D701 stock seed was `triggeredDurationSec: 0` + absent /
|
|
2264
|
+
* `doNothing` (stay triggered forever). That left After trigger looking dead
|
|
2265
|
+
* in the UI. Flip only that seed to {@link DEFAULT_DELAYS} — never a custom
|
|
2266
|
+
* duration or an explicit `disableAfter`.
|
|
2267
|
+
*/
|
|
2268
|
+
migrateStockAfterTriggerDefaults() {
|
|
2269
|
+
const duration = this.config.get("triggeredDurationSec");
|
|
2270
|
+
const action = this.config.get("postTriggeredAction");
|
|
2271
|
+
if (!(duration === 0 && (action === void 0 || action === "doNothing"))) return;
|
|
2272
|
+
this.config.set("triggeredDurationSec", DEFAULT_DELAYS.triggeredDurationSec);
|
|
2273
|
+
this.config.set("postTriggeredAction", DEFAULT_DELAYS.postTriggeredAction);
|
|
2274
|
+
this.ctx.logger.info("alarm after-trigger defaults migrated to rearmAfter 10s", {
|
|
2275
|
+
tags: { deviceId: this.id },
|
|
2276
|
+
meta: {
|
|
2277
|
+
fromDurationSec: duration,
|
|
2278
|
+
fromAction: action ?? "absent",
|
|
2279
|
+
toDurationSec: DEFAULT_DELAYS.triggeredDurationSec,
|
|
2280
|
+
toAction: DEFAULT_DELAYS.postTriggeredAction
|
|
2281
|
+
}
|
|
2282
|
+
});
|
|
2192
2283
|
}
|
|
2193
2284
|
/** The operator-visible configuration, as the cap serves it. */
|
|
2194
2285
|
settings() {
|
|
2286
|
+
const storedAction = this.config.get("postTriggeredAction");
|
|
2195
2287
|
return {
|
|
2196
|
-
|
|
2288
|
+
exitDelaySec: this.config.get("exitDelaySec"),
|
|
2289
|
+
entryDelaySec: this.config.get("entryDelaySec"),
|
|
2290
|
+
triggeredDurationSec: this.config.get("triggeredDurationSec"),
|
|
2291
|
+
...storedAction !== void 0 ? { postTriggeredAction: storedAction } : {},
|
|
2197
2292
|
announceArm: this.config.get("announceArm"),
|
|
2198
2293
|
announceTargets: [...this.config.get("announceTargets")],
|
|
2199
2294
|
nonBlocking: modeDeviceListFromPersisted(this.config.get("nonBlocking")),
|
|
@@ -2342,6 +2437,7 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2342
2437
|
if (patch.exitDelaySec !== void 0) await this.config.set("exitDelaySec", patch.exitDelaySec);
|
|
2343
2438
|
if (patch.entryDelaySec !== void 0) await this.config.set("entryDelaySec", patch.entryDelaySec);
|
|
2344
2439
|
if (patch.triggeredDurationSec !== void 0) await this.config.set("triggeredDurationSec", patch.triggeredDurationSec);
|
|
2440
|
+
if (patch.postTriggeredAction !== void 0) await this.config.set("postTriggeredAction", patch.postTriggeredAction);
|
|
2345
2441
|
if (patch.announceArm !== void 0) await this.config.set("announceArm", patch.announceArm);
|
|
2346
2442
|
if (patch.announceTargets !== void 0) await this.config.set("announceTargets", [...patch.announceTargets]);
|
|
2347
2443
|
if (membership !== void 0 && membershipChanged !== null) this.ctx.logger.info("alarm non-blocking list changed", {
|
|
@@ -2517,6 +2613,29 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2517
2613
|
}
|
|
2518
2614
|
});
|
|
2519
2615
|
}
|
|
2616
|
+
if (edit.postTriggeredAction !== void 0) {
|
|
2617
|
+
const from = was?.postTriggeredAction ?? null;
|
|
2618
|
+
const to = now?.postTriggeredAction ?? null;
|
|
2619
|
+
if (from !== to) this.ctx.logger.info("alarm mode delays changed", {
|
|
2620
|
+
tags: { deviceId: this.id },
|
|
2621
|
+
meta: {
|
|
2622
|
+
mode: edit.mode,
|
|
2623
|
+
field: "postTriggeredAction",
|
|
2624
|
+
from,
|
|
2625
|
+
to
|
|
2626
|
+
}
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
if (edit.schedule !== void 0) {
|
|
2630
|
+
if ((was?.schedule === void 0 ? null : "set") !== (now?.schedule === void 0 ? null : "set") || edit.schedule !== null) this.ctx.logger.info("alarm mode activation schedule changed", {
|
|
2631
|
+
tags: { deviceId: this.id },
|
|
2632
|
+
meta: {
|
|
2633
|
+
mode: edit.mode,
|
|
2634
|
+
cleared: edit.schedule === null,
|
|
2635
|
+
windows: now?.schedule?.windows.length ?? 0
|
|
2636
|
+
}
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2520
2639
|
}
|
|
2521
2640
|
}
|
|
2522
2641
|
/** One INFO line per persisted per-mode delay entry the guard refused to trust. */
|
|
@@ -2575,6 +2694,13 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2575
2694
|
*/
|
|
2576
2695
|
async armWith(mode) {
|
|
2577
2696
|
const now = Date.now();
|
|
2697
|
+
if (!this.modeScheduleAllows(mode, now)) {
|
|
2698
|
+
this.ctx.logger.info("alarm arm refused — outside activation schedule", {
|
|
2699
|
+
tags: { deviceId: this.id },
|
|
2700
|
+
meta: { mode }
|
|
2701
|
+
});
|
|
2702
|
+
return { outcome: "outside-schedule" };
|
|
2703
|
+
}
|
|
2578
2704
|
const read = await this.applyArmGuard(mode);
|
|
2579
2705
|
if (read.kind === "unguarded") return this.settleArm(mode, null, now);
|
|
2580
2706
|
if (read.kind === "check-failed") {
|
|
@@ -2588,6 +2714,11 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2588
2714
|
why: this.exclusionWhyFor(mode, device)
|
|
2589
2715
|
})), now);
|
|
2590
2716
|
}
|
|
2717
|
+
/** Absent schedule = always allowed (`isScheduleActive`). */
|
|
2718
|
+
modeScheduleAllows(mode, now) {
|
|
2719
|
+
const schedule = modeDelaysFromPersisted(this.config.get("modeDelays")).entries.find((e) => e.mode === mode)?.schedule;
|
|
2720
|
+
return isScheduleActive(schedule, now);
|
|
2721
|
+
}
|
|
2591
2722
|
/**
|
|
2592
2723
|
* The Arm anyway on ONE refusal's notification — the only path that arms
|
|
2593
2724
|
* around a device that blocks.
|
|
@@ -2613,6 +2744,17 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2613
2744
|
const reason = refusalId === this.spentRefusalId ? "used" : "unknown-refusal";
|
|
2614
2745
|
return this.rejectArmAnyway(refusalId, reason);
|
|
2615
2746
|
}
|
|
2747
|
+
if (!this.modeScheduleAllows(held.mode, now)) {
|
|
2748
|
+
this.refusal = null;
|
|
2749
|
+
this.ctx.logger.info("alarm arm-anyway refused — outside activation schedule", {
|
|
2750
|
+
tags: { deviceId: this.id },
|
|
2751
|
+
meta: {
|
|
2752
|
+
mode: held.mode,
|
|
2753
|
+
refusalId
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
return { outcome: "outside-schedule" };
|
|
2757
|
+
}
|
|
2616
2758
|
if (this.machine.changedAt !== held.panelChangedAt) {
|
|
2617
2759
|
this.refusal = null;
|
|
2618
2760
|
return this.rejectArmAnyway(refusalId, "stale");
|
|
@@ -3008,11 +3150,12 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3008
3150
|
* rather than fired only once somebody looks.
|
|
3009
3151
|
*/
|
|
3010
3152
|
publish(now = Date.now()) {
|
|
3153
|
+
const autoCleared = this.machine.clearAt !== void 0 && now >= this.machine.clearAt && this.machine.clearToDisarmed === true;
|
|
3011
3154
|
this.machine = settle(this.machine, now);
|
|
3012
3155
|
const state = stateAt(this.machine, now);
|
|
3013
3156
|
this.runtimeState.setCapState("alarm-panel", this.status(now));
|
|
3014
3157
|
this.persist();
|
|
3015
|
-
this.report(state, now);
|
|
3158
|
+
this.report(state, now, autoCleared);
|
|
3016
3159
|
if (this.machine.armedAt !== void 0 || this.machine.triggerAt !== void 0 || this.machine.clearAt !== void 0) this.startTicking();
|
|
3017
3160
|
else this.stopTicking();
|
|
3018
3161
|
}
|
|
@@ -3024,7 +3167,7 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3024
3167
|
* panel's own state update with it would make a delivery failure look like
|
|
3025
3168
|
* an alarm failure.
|
|
3026
3169
|
*/
|
|
3027
|
-
report(state, now) {
|
|
3170
|
+
report(state, now, autoCleared = false) {
|
|
3028
3171
|
const previous = this.lastPublished;
|
|
3029
3172
|
this.lastPublished = state;
|
|
3030
3173
|
const hook = this.onPublish;
|
|
@@ -3039,7 +3182,8 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3039
3182
|
at: now,
|
|
3040
3183
|
...source !== null ? { source } : {},
|
|
3041
3184
|
...arming !== void 0 ? { arming } : {},
|
|
3042
|
-
...pending !== void 0 ? { pending } : {}
|
|
3185
|
+
...pending !== void 0 ? { pending } : {},
|
|
3186
|
+
...autoCleared ? { autoCleared: true } : {}
|
|
3043
3187
|
});
|
|
3044
3188
|
if (transition?.kind === "alarm-triggered") this.attribution.clear();
|
|
3045
3189
|
try {
|
|
@@ -3125,8 +3269,12 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3125
3269
|
}
|
|
3126
3270
|
/** The global durations plus the per-mode overrides, read from the persisted config NOW. */
|
|
3127
3271
|
delaySettings() {
|
|
3272
|
+
const storedAction = this.config.get("postTriggeredAction");
|
|
3128
3273
|
return {
|
|
3129
|
-
|
|
3274
|
+
exitDelaySec: this.config.get("exitDelaySec"),
|
|
3275
|
+
entryDelaySec: this.config.get("entryDelaySec"),
|
|
3276
|
+
triggeredDurationSec: this.config.get("triggeredDurationSec"),
|
|
3277
|
+
...storedAction !== void 0 ? { postTriggeredAction: storedAction } : {},
|
|
3130
3278
|
modeDelays: modeDelaysFromPersisted(this.config.get("modeDelays")).entries
|
|
3131
3279
|
};
|
|
3132
3280
|
}
|
|
@@ -3136,24 +3284,32 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3136
3284
|
*/
|
|
3137
3285
|
delaysFor(mode) {
|
|
3138
3286
|
const settings = this.delaySettings();
|
|
3139
|
-
if (mode === null)
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3287
|
+
if (mode === null) {
|
|
3288
|
+
const delays = this.readDelays();
|
|
3289
|
+
return {
|
|
3290
|
+
exitDelaySec: delays.exitDelaySec,
|
|
3291
|
+
entryDelaySec: delays.entryDelaySec,
|
|
3292
|
+
triggeredDurationSec: delays.triggeredDurationSec,
|
|
3293
|
+
postTriggeredAction: delays.postTriggeredAction,
|
|
3294
|
+
source: {
|
|
3295
|
+
exitDelaySec: "default",
|
|
3296
|
+
entryDelaySec: "default",
|
|
3297
|
+
triggeredDurationSec: "default",
|
|
3298
|
+
postTriggeredAction: "default"
|
|
3299
|
+
}
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3149
3302
|
return resolveModeDelays(settings, mode);
|
|
3150
3303
|
}
|
|
3151
|
-
/** The
|
|
3304
|
+
/** The global durations + post-triggered action, read from the persisted config. */
|
|
3152
3305
|
readDelays() {
|
|
3306
|
+
const triggeredDurationSec = this.config.get("triggeredDurationSec");
|
|
3307
|
+
const stored = this.config.get("postTriggeredAction");
|
|
3153
3308
|
return {
|
|
3154
3309
|
exitDelaySec: this.config.get("exitDelaySec"),
|
|
3155
3310
|
entryDelaySec: this.config.get("entryDelaySec"),
|
|
3156
|
-
triggeredDurationSec
|
|
3311
|
+
triggeredDurationSec,
|
|
3312
|
+
postTriggeredAction: derivePostTriggeredAction(stored, triggeredDurationSec)
|
|
3157
3313
|
};
|
|
3158
3314
|
}
|
|
3159
3315
|
startTicking() {
|
|
@@ -5133,7 +5289,7 @@ function ncClassNoun(texts, className, count) {
|
|
|
5133
5289
|
count,
|
|
5134
5290
|
vars: { name: className }
|
|
5135
5291
|
});
|
|
5136
|
-
if (isEventKindLabelKey(normalised)) return eventKindLabel(normalised, texts.language);
|
|
5292
|
+
if (isEventKindLabelKey(normalised)) return eventKindLabel(normalised, texts.language).toLocaleLowerCase(texts.language);
|
|
5137
5293
|
return texts.text({
|
|
5138
5294
|
key: "class.other",
|
|
5139
5295
|
count,
|
|
@@ -5141,13 +5297,24 @@ function ncClassNoun(texts, className, count) {
|
|
|
5141
5297
|
});
|
|
5142
5298
|
}
|
|
5143
5299
|
/**
|
|
5144
|
-
* The
|
|
5145
|
-
*
|
|
5146
|
-
*
|
|
5300
|
+
* The ONE capitalisation the notification body gets: its first letter, after
|
|
5301
|
+
* every catalog fragment and placeholder has already been rendered lowercase.
|
|
5302
|
+
* Title, fragments and mid-sentence summaries are never passed through here.
|
|
5147
5303
|
*/
|
|
5148
|
-
function
|
|
5149
|
-
if (
|
|
5150
|
-
|
|
5304
|
+
function ncSentenceCaseBody(body) {
|
|
5305
|
+
if (body.length === 0) return body;
|
|
5306
|
+
const first = body[0];
|
|
5307
|
+
const upper = first.toLocaleUpperCase();
|
|
5308
|
+
if (upper === first) return body;
|
|
5309
|
+
return `${upper}${body.slice(1)}`;
|
|
5310
|
+
}
|
|
5311
|
+
/**
|
|
5312
|
+
* True when `label` is a taxonomy / classification id (species, COCO sub, …)
|
|
5313
|
+
* rather than a person name or plate. Those REPLACE the coarse class in the
|
|
5314
|
+
* subject — "dog" not "animal (dog)", "cane" not "animale (cane)".
|
|
5315
|
+
*/
|
|
5316
|
+
function isClassificationLabel(label) {
|
|
5317
|
+
return isEventKindLabelKey(label.trim().toLowerCase());
|
|
5151
5318
|
}
|
|
5152
5319
|
/** `person (John)` / `persona (John)` — the thing the notification is about. */
|
|
5153
5320
|
function ncSubjectText(texts, input) {
|
|
@@ -5156,11 +5323,15 @@ function ncSubjectText(texts, input) {
|
|
|
5156
5323
|
key: "detection.subject",
|
|
5157
5324
|
vars: { classNoun }
|
|
5158
5325
|
});
|
|
5326
|
+
if (input.labelKind !== "identity" && input.labelKind !== "plate" && isClassificationLabel(input.label)) return texts.text({
|
|
5327
|
+
key: "detection.subject",
|
|
5328
|
+
vars: { classNoun: ncClassNoun(texts, input.label, 1) }
|
|
5329
|
+
});
|
|
5159
5330
|
return texts.text({
|
|
5160
5331
|
key: subjectLabelKey(input.labelKind),
|
|
5161
5332
|
vars: {
|
|
5162
5333
|
classNoun,
|
|
5163
|
-
label:
|
|
5334
|
+
label: input.label
|
|
5164
5335
|
}
|
|
5165
5336
|
});
|
|
5166
5337
|
}
|
|
@@ -5750,7 +5921,7 @@ function isAdoptable(record, nowMs) {
|
|
|
5750
5921
|
* what `localDayMinute` does for the same input — one behaviour for a bad
|
|
5751
5922
|
* zone, not two.
|
|
5752
5923
|
*/
|
|
5753
|
-
function clockOf$
|
|
5924
|
+
function clockOf$7(atMs, timeZone) {
|
|
5754
5925
|
if (timeZone !== void 0) try {
|
|
5755
5926
|
return new Intl.DateTimeFormat("en-GB", {
|
|
5756
5927
|
timeZone,
|
|
@@ -5886,7 +6057,7 @@ var TimelapseRenderer = class {
|
|
|
5886
6057
|
includeAudio: false,
|
|
5887
6058
|
maxLifeMs: await this.ports.exportLifeMs(),
|
|
5888
6059
|
deleteAfterDownload: false,
|
|
5889
|
-
title: `${request.ruleName} ${clockOf$
|
|
6060
|
+
title: `${request.ruleName} ${clockOf$7(window.startMs, request.timezone)}→${clockOf$7(window.endMs, request.timezone)}`,
|
|
5890
6061
|
timelapse: {
|
|
5891
6062
|
everyMs,
|
|
5892
6063
|
outputFps: request.framerate,
|
|
@@ -6217,7 +6388,7 @@ var TimelapseRenderer = class {
|
|
|
6217
6388
|
...meta,
|
|
6218
6389
|
unservedWindowCount: verdict.unservedWindows.length,
|
|
6219
6390
|
unservedWindows: verdict.unservedWindows.slice(0, 12).map((w) => ({
|
|
6220
|
-
at: clockOf$
|
|
6391
|
+
at: clockOf$7(request.window.startMs + w.fromSec * 1e3, request.timezone),
|
|
6221
6392
|
fromSec: Math.round(w.fromSec),
|
|
6222
6393
|
toSec: Math.round(w.toSec),
|
|
6223
6394
|
expectedDenseFrames: w.expectedDenseFrames,
|
|
@@ -8798,6 +8969,7 @@ function buildActionButtons(input) {
|
|
|
8798
8969
|
sequenceId: button.sequenceId,
|
|
8799
8970
|
actionId: button.id
|
|
8800
8971
|
}),
|
|
8972
|
+
mode: "background",
|
|
8801
8973
|
...button.icon !== void 0 ? { icon: button.icon } : {},
|
|
8802
8974
|
...button.destructive !== void 0 ? { destructive: button.destructive } : {}
|
|
8803
8975
|
});
|
|
@@ -8913,10 +9085,9 @@ var MAX_ARM_BUTTONS = 3;
|
|
|
8913
9085
|
* (`ALARM_DISARM_TTL_MS`). Both exist: the hint is for the person, the TTL is
|
|
8914
9086
|
* for the forwarded screenshot.
|
|
8915
9087
|
*
|
|
8916
|
-
*
|
|
8917
|
-
*
|
|
8918
|
-
*
|
|
8919
|
-
* confirmed.
|
|
9088
|
+
* Every alarm button is `background` — Zentik (and ntfy) fire a POST and leave
|
|
9089
|
+
* the lock screen alone. Opening a browser to arm or disarm costs more
|
|
9090
|
+
* attention than the notification did.
|
|
8920
9091
|
*
|
|
8921
9092
|
* `panel.availableModes` ARE the enabled modes: CamStack has no per-mode
|
|
8922
9093
|
* disable setting, so the Arm buttons offer exactly what the panel declares.
|
|
@@ -8950,14 +9121,14 @@ function buildAlarmButtons(input) {
|
|
|
8950
9121
|
}),
|
|
8951
9122
|
icon: "disarm",
|
|
8952
9123
|
destructive: true,
|
|
8953
|
-
mode: "
|
|
9124
|
+
mode: "background"
|
|
8954
9125
|
}];
|
|
8955
9126
|
}
|
|
8956
9127
|
/**
|
|
8957
9128
|
* The one Arm anyway a refusal notification carries — or none, when the row
|
|
8958
|
-
* names no refusal.
|
|
8959
|
-
*
|
|
8960
|
-
*
|
|
9129
|
+
* names no refusal. Destructive + background: it arms around an open door via
|
|
9130
|
+
* POST (Zentik BACKGROUND_CALL), same as disarm. Its TTL is the refusal's own
|
|
9131
|
+
* (`ARM_ANYWAY_TTL_MS`, priced by the minter).
|
|
8961
9132
|
*/
|
|
8962
9133
|
function armAnywayButton(refusalId, input) {
|
|
8963
9134
|
if (refusalId === void 0) return [];
|
|
@@ -8972,7 +9143,7 @@ function armAnywayButton(refusalId, input) {
|
|
|
8972
9143
|
}),
|
|
8973
9144
|
icon: "arm",
|
|
8974
9145
|
destructive: true,
|
|
8975
|
-
mode: "
|
|
9146
|
+
mode: "background"
|
|
8976
9147
|
}];
|
|
8977
9148
|
}
|
|
8978
9149
|
//#endregion
|
|
@@ -12894,6 +13065,10 @@ function alarmTextKeys(transition, body) {
|
|
|
12894
13065
|
titleKey,
|
|
12895
13066
|
bodyKey: `system.${kind}.body.${transition.sensor.event}`
|
|
12896
13067
|
};
|
|
13068
|
+
if (kind === "alarm-disarmed" && transition.autoCleared === true) return {
|
|
13069
|
+
titleKey,
|
|
13070
|
+
bodyKey: `system.${kind}.body.autoCleared`
|
|
13071
|
+
};
|
|
12897
13072
|
if (kind === "alarm-disarmed" && (transition.previous === "triggered" || transition.previous === "pending")) return {
|
|
12898
13073
|
titleKey,
|
|
12899
13074
|
bodyKey: `system.${kind}.body.defused`
|
|
@@ -14613,13 +14788,14 @@ var en_default = {
|
|
|
14613
14788
|
"system.alarm-arming.title.away": "Arming away — {{seconds}}s",
|
|
14614
14789
|
"system.alarm-arming.title.night": "Arming night — {{seconds}}s",
|
|
14615
14790
|
"system.alarm-arming.body": "Arming",
|
|
14616
|
-
"system.alarm-armed.title.home": "
|
|
14617
|
-
"system.alarm-armed.title.away": "
|
|
14618
|
-
"system.alarm-armed.title.night": "
|
|
14791
|
+
"system.alarm-armed.title.home": "Alarm armed: home",
|
|
14792
|
+
"system.alarm-armed.title.away": "Alarm armed: away",
|
|
14793
|
+
"system.alarm-armed.title.night": "Alarm armed: night",
|
|
14619
14794
|
"system.alarm-armed.body": "Alarm armed",
|
|
14620
14795
|
"system.alarm-disarmed.title": "Disarmed",
|
|
14621
14796
|
"system.alarm-disarmed.body": "Alarm disarmed",
|
|
14622
14797
|
"system.alarm-disarmed.body.defused": "Alarm disarmed during an alarm",
|
|
14798
|
+
"system.alarm-disarmed.body.autoCleared": "Alarm disarmed automatically after the siren",
|
|
14623
14799
|
"system.alarm-triggered.title": "ALARM",
|
|
14624
14800
|
"system.alarm-triggered.body": "Alarm triggered",
|
|
14625
14801
|
"system.alarm-triggered.body.rule": "Triggered by {{rule}} on {{deviceName}}",
|
|
@@ -14941,13 +15117,14 @@ var it_default = {
|
|
|
14941
15117
|
"system.alarm-arming.title.away": "Inserimento Fuori — {{seconds}}s",
|
|
14942
15118
|
"system.alarm-arming.title.night": "Inserimento Notte — {{seconds}}s",
|
|
14943
15119
|
"system.alarm-arming.body": "Inserimento in corso",
|
|
14944
|
-
"system.alarm-armed.title.home": "
|
|
14945
|
-
"system.alarm-armed.title.away": "
|
|
14946
|
-
"system.alarm-armed.title.night": "
|
|
15120
|
+
"system.alarm-armed.title.home": "Allarme inserito: Casa",
|
|
15121
|
+
"system.alarm-armed.title.away": "Allarme inserito: Fuori",
|
|
15122
|
+
"system.alarm-armed.title.night": "Allarme inserito: Notte",
|
|
14947
15123
|
"system.alarm-armed.body": "Allarme inserito",
|
|
14948
15124
|
"system.alarm-disarmed.title": "Disinserito",
|
|
14949
15125
|
"system.alarm-disarmed.body": "Allarme disinserito",
|
|
14950
15126
|
"system.alarm-disarmed.body.defused": "Allarme disinserito durante un allarme",
|
|
15127
|
+
"system.alarm-disarmed.body.autoCleared": "Allarme disinserito automaticamente dopo la sirena",
|
|
14951
15128
|
"system.alarm-triggered.title": "ALLARME",
|
|
14952
15129
|
"system.alarm-triggered.body": "Allarme scattato",
|
|
14953
15130
|
"system.alarm-triggered.body.rule": "Scattato da {{rule}} su {{deviceName}}",
|
|
@@ -23935,7 +24112,7 @@ var NcDispatcher = class {
|
|
|
23935
24112
|
...systemCount,
|
|
23936
24113
|
...systemEvent?.body !== void 0 ? { frozen: systemEvent.body } : {}
|
|
23937
24114
|
});
|
|
23938
|
-
return composeRuleAiBody({
|
|
24115
|
+
return ncSentenceCaseBody(composeRuleAiBody({
|
|
23939
24116
|
aiText,
|
|
23940
24117
|
body: entry.payload.synthetic === true ? this.renderText({
|
|
23941
24118
|
key: "test.prefix",
|
|
@@ -23943,7 +24120,7 @@ var NcDispatcher = class {
|
|
|
23943
24120
|
frozen: rendered
|
|
23944
24121
|
}) : rendered,
|
|
23945
24122
|
templateNamesAi
|
|
23946
|
-
});
|
|
24123
|
+
}));
|
|
23947
24124
|
};
|
|
23948
24125
|
const body = renderBody("");
|
|
23949
24126
|
const resolved = await this.resolveAttachments(entry);
|
|
@@ -25123,15 +25300,18 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
|
|
|
25123
25300
|
const systemEvent = subject.systemEvent;
|
|
25124
25301
|
const detection = systemEvent?.alarmSource?.className !== void 0 ? systemEvent.alarmSource : void 0;
|
|
25125
25302
|
const group = readOutboxGroup(entry.payload.group);
|
|
25303
|
+
const locale = texts.language;
|
|
25304
|
+
const cameraName = deviceName.toLocaleLowerCase(locale);
|
|
25305
|
+
const zonesLower = zoneLabels.map((z) => z.toLocaleLowerCase(locale));
|
|
25126
25306
|
return {
|
|
25127
|
-
camera:
|
|
25307
|
+
camera: cameraName,
|
|
25128
25308
|
class: detection?.className ?? (systemEvent !== void 0 ? "" : subject.className),
|
|
25129
25309
|
classLabel: (() => {
|
|
25130
25310
|
const wire = detection?.className ?? (systemEvent !== void 0 ? "" : subject.className);
|
|
25131
|
-
return wire.length === 0 ? "" : eventKindLabel(wire,
|
|
25311
|
+
return wire.length === 0 ? "" : eventKindLabel(wire, locale).toLocaleLowerCase(locale);
|
|
25132
25312
|
})(),
|
|
25133
25313
|
label: detection?.label ?? subject.label ?? "",
|
|
25134
|
-
zones:
|
|
25314
|
+
zones: zonesLower.join(", "),
|
|
25135
25315
|
subject: detection?.className !== void 0 ? ncSubjectText(texts, {
|
|
25136
25316
|
className: detection.className,
|
|
25137
25317
|
...detection.label !== void 0 ? { label: detection.label } : {}
|
|
@@ -25141,9 +25321,9 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
|
|
|
25141
25321
|
...subject.labelKind !== void 0 ? { labelKind: subject.labelKind } : {}
|
|
25142
25322
|
}),
|
|
25143
25323
|
detectionSummary: group !== void 0 && group.members.length > 1 ? ncDetectionSummary(texts, classCounts(group.members)) : "",
|
|
25144
|
-
inZones: ncInZonesText(texts,
|
|
25145
|
-
scope: occupancy !== void 0 ? occupancy.zone ??
|
|
25146
|
-
zone: occupancy?.zone ?? zoneLabels[0] ?? "",
|
|
25324
|
+
inZones: ncInZonesText(texts, zonesLower),
|
|
25325
|
+
scope: occupancy !== void 0 ? occupancy.zone?.toLocaleLowerCase(locale) ?? cameraName : "",
|
|
25326
|
+
zone: (occupancy?.zone ?? zoneLabels[0] ?? "").toLocaleLowerCase(locale),
|
|
25147
25327
|
confidence: percentOf(detection?.confidence ?? subject.confidence),
|
|
25148
25328
|
time: ncClockText(texts.language, subject.timestamp, timeZone),
|
|
25149
25329
|
sounds: subject.audio?.labels.join(", ") ?? "",
|
|
@@ -29641,7 +29821,7 @@ function buildRuleAiSystemPrompt(shape, language, maxWords, grouped = false) {
|
|
|
29641
29821
|
].join("");
|
|
29642
29822
|
}
|
|
29643
29823
|
/** `HH:MM` in the host timezone. */
|
|
29644
|
-
function clockOf$
|
|
29824
|
+
function clockOf$6(atMs) {
|
|
29645
29825
|
const d = new Date(atMs);
|
|
29646
29826
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
29647
29827
|
}
|
|
@@ -29679,7 +29859,7 @@ function buildRuleAiPrompt(input) {
|
|
|
29679
29859
|
if (shape === "detection-track") {
|
|
29680
29860
|
lines.push(`Camera: ${camera}.${located} Subject: ${subject}. The stills, in order:`);
|
|
29681
29861
|
for (const [i, frame] of input.frames.entries()) {
|
|
29682
|
-
const when = frame.atMs !== void 0 ? ` · ${clockOf$
|
|
29862
|
+
const when = frame.atMs !== void 0 ? ` · ${clockOf$6(frame.atMs)}` : "";
|
|
29683
29863
|
lines.push(`${String(i + 1)}.${when}`);
|
|
29684
29864
|
}
|
|
29685
29865
|
return lines.join("\n");
|
|
@@ -31450,7 +31630,7 @@ var DEFAULT_PROMPT_EN = "What happened during this period? Summarise it for a ho
|
|
|
31450
31630
|
*/
|
|
31451
31631
|
var SummaryAnswerSchema = object({ summary: string() });
|
|
31452
31632
|
/** `HH:MM` in the host timezone — the two numbers a frame label needs. */
|
|
31453
|
-
function clockOf$
|
|
31633
|
+
function clockOf$5(atMs) {
|
|
31454
31634
|
const d = new Date(atMs);
|
|
31455
31635
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
31456
31636
|
}
|
|
@@ -31473,7 +31653,7 @@ function buildSummaryAiPrompt(input, frames) {
|
|
|
31473
31653
|
const roster = frames.map((f, i) => {
|
|
31474
31654
|
const location = plainLabel(f.location ?? "");
|
|
31475
31655
|
const where = location.length > 0 ? ` (${location})` : "";
|
|
31476
|
-
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$
|
|
31656
|
+
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$5(f.atMs)} · ${plainVocabulary(f.className) || "subject"}`;
|
|
31477
31657
|
}).join("\n");
|
|
31478
31658
|
const described = /* @__PURE__ */ new Map();
|
|
31479
31659
|
for (const f of frames) {
|
|
@@ -32076,7 +32256,7 @@ var NO_DETECTIONS$1 = {
|
|
|
32076
32256
|
* has to check is a key somebody can get wrong. */
|
|
32077
32257
|
var BODY_SEPARATOR$1 = " · ";
|
|
32078
32258
|
/** `HH:MM` in the host timezone. */
|
|
32079
|
-
function clockOf$
|
|
32259
|
+
function clockOf$4(atMs) {
|
|
32080
32260
|
const d = new Date(atMs);
|
|
32081
32261
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
32082
32262
|
}
|
|
@@ -32090,8 +32270,8 @@ function clockOf$3(atMs) {
|
|
|
32090
32270
|
function summaryTemplateVars(input) {
|
|
32091
32271
|
return {
|
|
32092
32272
|
rule: input.ruleName,
|
|
32093
|
-
from: clockOf$
|
|
32094
|
-
to: clockOf$
|
|
32273
|
+
from: clockOf$4(input.window.startMs),
|
|
32274
|
+
to: clockOf$4(input.window.endMs),
|
|
32095
32275
|
time: ncClockText(input.texts.language, input.generatedAt),
|
|
32096
32276
|
events: String(input.tileCount),
|
|
32097
32277
|
cameras: String(input.cameraCount),
|
|
@@ -32186,11 +32366,11 @@ function summaryRecordId(ruleId, windowEndMs) {
|
|
|
32186
32366
|
/** The caption burned onto the mosaic's title band, with the same `{{var}}`
|
|
32187
32367
|
* vocabulary the title and body use. `''` is a real answer ("no caption"). */
|
|
32188
32368
|
function summaryCaptionText(input) {
|
|
32189
|
-
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$
|
|
32369
|
+
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$4(input.window.startMs)}→${clockOf$4(input.window.endMs)}`;
|
|
32190
32370
|
return renderTemplate(input.captionText, {
|
|
32191
32371
|
rule: input.ruleName,
|
|
32192
|
-
from: clockOf$
|
|
32193
|
-
to: clockOf$
|
|
32372
|
+
from: clockOf$4(input.window.startMs),
|
|
32373
|
+
to: clockOf$4(input.window.endMs)
|
|
32194
32374
|
}) ?? "";
|
|
32195
32375
|
}
|
|
32196
32376
|
/**
|
|
@@ -33122,7 +33302,7 @@ var NcSummaryProducer = class {
|
|
|
33122
33302
|
purpose: "summary",
|
|
33123
33303
|
tiles: tiles.map(({ candidate, jpeg }) => ({
|
|
33124
33304
|
jpeg,
|
|
33125
|
-
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$
|
|
33305
|
+
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$3(candidate.firstSeen)} · ${candidate.className}` } : {},
|
|
33126
33306
|
deviceId: candidate.deviceId,
|
|
33127
33307
|
sourceId: candidate.trackId
|
|
33128
33308
|
})),
|
|
@@ -33345,7 +33525,7 @@ var NcSummaryProducer = class {
|
|
|
33345
33525
|
}
|
|
33346
33526
|
};
|
|
33347
33527
|
/** `HH:MM` in the host timezone — the two numbers a tile label needs. */
|
|
33348
|
-
function clockOf$
|
|
33528
|
+
function clockOf$3(atMs) {
|
|
33349
33529
|
const d = new Date(atMs);
|
|
33350
33530
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
33351
33531
|
}
|
|
@@ -33845,6 +34025,99 @@ var NcCalendarEventsStore = class {
|
|
|
33845
34025
|
}
|
|
33846
34026
|
};
|
|
33847
34027
|
//#endregion
|
|
34028
|
+
//#region src/notification-center/calendar-events/calendar-events-body.ts
|
|
34029
|
+
/** Visual weight on the tomorrow header — Telegram/Zentik plain text. */
|
|
34030
|
+
var CALENDAR_EVENTS_TOMORROW_MARK = "★";
|
|
34031
|
+
/** Separates the tomorrow block from the rest of the horizon. */
|
|
34032
|
+
var CALENDAR_EVENTS_DAY_DELIMITER = "────────";
|
|
34033
|
+
var MAX_EVENT_LINES = 40;
|
|
34034
|
+
function pad2(n) {
|
|
34035
|
+
return String(n).padStart(2, "0");
|
|
34036
|
+
}
|
|
34037
|
+
/** Local calendar day key `YYYY-MM-DD`. */
|
|
34038
|
+
function localDayKey(atMs) {
|
|
34039
|
+
const d = new Date(atMs);
|
|
34040
|
+
return `${String(d.getFullYear())}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
34041
|
+
}
|
|
34042
|
+
function startOfLocalDay(atMs) {
|
|
34043
|
+
const d = new Date(atMs);
|
|
34044
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
34045
|
+
}
|
|
34046
|
+
function addLocalDays(atMs, days) {
|
|
34047
|
+
const d = new Date(atMs);
|
|
34048
|
+
d.setDate(d.getDate() + days);
|
|
34049
|
+
return d.getTime();
|
|
34050
|
+
}
|
|
34051
|
+
function dayHeadingFor(dayStartMs, todayKey, tomorrowKey) {
|
|
34052
|
+
const key = localDayKey(dayStartMs);
|
|
34053
|
+
const label = new Date(dayStartMs).toLocaleDateString(void 0, {
|
|
34054
|
+
weekday: "short",
|
|
34055
|
+
month: "short",
|
|
34056
|
+
day: "numeric"
|
|
34057
|
+
});
|
|
34058
|
+
if (key === todayKey) return `Today — ${label}`;
|
|
34059
|
+
if (key === tomorrowKey) return `${CALENDAR_EVENTS_TOMORROW_MARK} Tomorrow — ${label}`;
|
|
34060
|
+
return label;
|
|
34061
|
+
}
|
|
34062
|
+
/** `HH:MM` local, or `YYYY-MM-DD HH:MM` when the end falls on another day. */
|
|
34063
|
+
function clockOf$2(atMs) {
|
|
34064
|
+
const d = new Date(atMs);
|
|
34065
|
+
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
34066
|
+
}
|
|
34067
|
+
function formatEventLine(event) {
|
|
34068
|
+
const when = event.allDay === true ? "All day" : `${clockOf$2(event.startMs)}–${clockOf$2(event.endMs)}`;
|
|
34069
|
+
const where = event.location !== void 0 && event.location.length > 0 ? ` @ ${event.location}` : "";
|
|
34070
|
+
return `• ${when} ${event.title}${where}`;
|
|
34071
|
+
}
|
|
34072
|
+
function groupByLocalDay(events) {
|
|
34073
|
+
const map = /* @__PURE__ */ new Map();
|
|
34074
|
+
for (const ev of events) {
|
|
34075
|
+
const dayKey = localDayKey(ev.startMs);
|
|
34076
|
+
const existing = map.get(dayKey);
|
|
34077
|
+
if (existing !== void 0) {
|
|
34078
|
+
existing.events.push(ev);
|
|
34079
|
+
continue;
|
|
34080
|
+
}
|
|
34081
|
+
map.set(dayKey, {
|
|
34082
|
+
dayKey,
|
|
34083
|
+
dayStartMs: startOfLocalDay(ev.startMs),
|
|
34084
|
+
events: [ev]
|
|
34085
|
+
});
|
|
34086
|
+
}
|
|
34087
|
+
return [...map.values()].toSorted((a, b) => a.dayStartMs - b.dayStartMs);
|
|
34088
|
+
}
|
|
34089
|
+
/**
|
|
34090
|
+
* Build the digest body.
|
|
34091
|
+
*
|
|
34092
|
+
* @param nowMs — fire time; tomorrow is the local calendar day after this.
|
|
34093
|
+
*/
|
|
34094
|
+
function formatCalendarEventsBody(events, nowMs, horizonDays) {
|
|
34095
|
+
if (events.length === 0) return `No events in the next ${String(horizonDays)} day(s).`;
|
|
34096
|
+
const todayKey = localDayKey(nowMs);
|
|
34097
|
+
const tomorrowKey = localDayKey(addLocalDays(startOfLocalDay(nowMs), 1));
|
|
34098
|
+
const buckets = groupByLocalDay(events);
|
|
34099
|
+
const today = buckets.filter((b) => b.dayKey === todayKey);
|
|
34100
|
+
const tomorrow = buckets.filter((b) => b.dayKey === tomorrowKey);
|
|
34101
|
+
const later = buckets.filter((b) => b.dayKey !== todayKey && b.dayKey !== tomorrowKey);
|
|
34102
|
+
const lead = [...today, ...tomorrow];
|
|
34103
|
+
const lines = [];
|
|
34104
|
+
let emitted = 0;
|
|
34105
|
+
const pushBucket = (bucket) => {
|
|
34106
|
+
if (emitted >= MAX_EVENT_LINES) return;
|
|
34107
|
+
lines.push(dayHeadingFor(bucket.dayStartMs, todayKey, tomorrowKey));
|
|
34108
|
+
for (const ev of bucket.events) {
|
|
34109
|
+
if (emitted >= MAX_EVENT_LINES) break;
|
|
34110
|
+
lines.push(formatEventLine(ev));
|
|
34111
|
+
emitted += 1;
|
|
34112
|
+
}
|
|
34113
|
+
};
|
|
34114
|
+
for (const bucket of lead) pushBucket(bucket);
|
|
34115
|
+
if (lead.length > 0 && later.length > 0) lines.push(CALENDAR_EVENTS_DAY_DELIMITER);
|
|
34116
|
+
for (const bucket of later) pushBucket(bucket);
|
|
34117
|
+
if (events.length > MAX_EVENT_LINES) lines.push(`…and ${String(events.length - MAX_EVENT_LINES)} more`);
|
|
34118
|
+
return lines.join("\n");
|
|
34119
|
+
}
|
|
34120
|
+
//#endregion
|
|
33848
34121
|
//#region src/notification-center/calendar-events/calendar-events-delivery.ts
|
|
33849
34122
|
function calendarEventsRecordId(ruleId, windowEndMs) {
|
|
33850
34123
|
return `calendar-events:${ruleId}:${windowEndMs}`;
|
|
@@ -33854,16 +34127,8 @@ function clockOf$1(atMs) {
|
|
|
33854
34127
|
const d = new Date(atMs);
|
|
33855
34128
|
return `${`${String(d.getFullYear())}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`} ${`${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`}`;
|
|
33856
34129
|
}
|
|
33857
|
-
function formatEventLine(event) {
|
|
33858
|
-
const when = event.allDay ? clockOf$1(event.startMs).slice(0, 10) : `${clockOf$1(event.startMs)}–${clockOf$1(event.endMs)}`;
|
|
33859
|
-
const where = event.location !== void 0 && event.location.length > 0 ? ` @ ${event.location}` : "";
|
|
33860
|
-
return `• ${when} ${event.title}${where}`;
|
|
33861
|
-
}
|
|
33862
34130
|
function derivedBody$1(input) {
|
|
33863
|
-
|
|
33864
|
-
const lines = input.events.slice(0, 40).map(formatEventLine);
|
|
33865
|
-
const extra = input.events.length > 40 ? `\n…and ${String(input.events.length - 40)} more` : "";
|
|
33866
|
-
return lines.join("\n") + extra;
|
|
34131
|
+
return formatCalendarEventsBody(input.events, input.eventWindow.startMs, input.horizonDays);
|
|
33867
34132
|
}
|
|
33868
34133
|
function templateVars(input) {
|
|
33869
34134
|
return {
|
|
@@ -39309,19 +39574,21 @@ var NotificationCenter = class {
|
|
|
39309
39574
|
}
|
|
39310
39575
|
/**
|
|
39311
39576
|
* Start photographing every camera that links `sensorId`, and return the
|
|
39312
|
-
* owners the sensor-level evaluation names — or `undefined` when there is
|
|
39313
|
-
*
|
|
39314
|
-
*
|
|
39315
|
-
*
|
|
39316
|
-
*
|
|
39317
|
-
*
|
|
39318
|
-
*
|
|
39319
|
-
*
|
|
39320
|
-
* never fires on a sensor).
|
|
39321
|
-
*
|
|
39322
|
-
*
|
|
39323
|
-
*
|
|
39324
|
-
*
|
|
39577
|
+
* owners the sensor-level evaluation names — or `undefined` when there is
|
|
39578
|
+
* nothing to photograph (`cameraIds` empty), nothing to show (every linked
|
|
39579
|
+
* camera muted), the event will be dropped as muted (the SENSOR is muted,
|
|
39580
|
+
* or there were row cameras and every one is muted — see the evaluation's
|
|
39581
|
+
* mute gate), or no enabled device-event rule that could fire on this
|
|
39582
|
+
* sensor KIND wants a picture (the gate is the RULE's media policy, scope
|
|
39583
|
+
* and kind conditions, read from the in-memory mirror — D49, as
|
|
39584
|
+
* `audioStillOwner`; an occupancy rule, which rides the same delivery,
|
|
39585
|
+
* never fires on a sensor). One linked camera is enough: link alone owns
|
|
39586
|
+
* notification media; tracked rows are a separate timeline question. Coarse
|
|
39587
|
+
* on purpose, in one direction only: a capture for a rule whose other
|
|
39588
|
+
* conditions then fail is one wasted snapshot, a missing capture is a
|
|
39589
|
+
* notification without its picture. The kind check is what keeps a chatty
|
|
39590
|
+
* multi-linked motion sensor from composing a mosaic on every edge for a
|
|
39591
|
+
* doorbell rule.
|
|
39325
39592
|
*
|
|
39326
39593
|
* A MUTED camera (D62) is never dialled: its tile is left out, said on that
|
|
39327
39594
|
* camera (once per window), and the primary is the first linked camera that
|
|
@@ -39334,7 +39601,7 @@ var NotificationCenter = class {
|
|
|
39334
39601
|
beginLinkedStills(request) {
|
|
39335
39602
|
const { sensorId, sensorKind, cameraIds, atMs, rowCameraIds, sensorRaised } = request;
|
|
39336
39603
|
const shelf = this.linkedStills;
|
|
39337
|
-
if (shelf === null || !this.evaluationActive || cameraIds.length
|
|
39604
|
+
if (shelf === null || !this.evaluationActive || cameraIds.length === 0) return void 0;
|
|
39338
39605
|
if (this.deviceMutes.isMuted(sensorId)) return void 0;
|
|
39339
39606
|
const rowShown = this.unmutedCameras(rowCameraIds);
|
|
39340
39607
|
if (rowCameraIds.length > 0 && rowShown.length === 0) return void 0;
|
|
@@ -40633,10 +40900,8 @@ var NotificationCenter = class {
|
|
|
40633
40900
|
* On the SAME serialized chain as every other evaluation, because both of
|
|
40634
40901
|
* those follow-ups read what the evaluation just enqueued:
|
|
40635
40902
|
*
|
|
40636
|
-
* -
|
|
40637
|
-
*
|
|
40638
|
-
* rule has not already delivered it, or a target that is both would get
|
|
40639
|
-
* the same sentence twice;
|
|
40903
|
+
* - Panel `announceTargets` are NOT addressed (retired 2026-10-03): only
|
|
40904
|
+
* alarm-family rules deliver. The announce enqueue is a no-op.
|
|
40640
40905
|
* - a TRIGGERED with an attribution supersedes the triggering rule's own
|
|
40641
40906
|
* still-pending rows, for exactly the targets the alarm reached. That is
|
|
40642
40907
|
* the combined notification: one row carrying the alarm's words and that
|
|
@@ -40669,11 +40934,11 @@ var NotificationCenter = class {
|
|
|
40669
40934
|
});
|
|
40670
40935
|
}
|
|
40671
40936
|
/**
|
|
40672
|
-
* The
|
|
40673
|
-
*
|
|
40674
|
-
* `
|
|
40675
|
-
*
|
|
40676
|
-
*
|
|
40937
|
+
* The composed body for an alarm transition (when the words depend on the
|
|
40938
|
+
* rule set). Delivery targets are ALWAYS empty here: the panel no longer
|
|
40939
|
+
* fans out to `announceTargets` — an alarm-family rule is the only notifier
|
|
40940
|
+
* (operator ruling 2026-10-03). `announceTargets` remains on the settings
|
|
40941
|
+
* blob for schema compatibility and is ignored at send time.
|
|
40677
40942
|
*
|
|
40678
40943
|
* - ARM and ARMING compose from the SAME coverage — "what does this mode
|
|
40679
40944
|
* actually watch" — because they are the same answer given thirty seconds
|
|
@@ -40684,14 +40949,9 @@ var NotificationCenter = class {
|
|
|
40684
40949
|
* Every other kind is `null`: the intake picks its catalog body and params.
|
|
40685
40950
|
*/
|
|
40686
40951
|
alarmAnnouncement(panel, transition) {
|
|
40687
|
-
const targetIds = panel.settings().announceTargets;
|
|
40688
|
-
if (targetIds.length === 0) this.logger.info("alarm transition has no announcement target", {
|
|
40689
|
-
tags: { deviceId: panel.deviceId },
|
|
40690
|
-
meta: { kind: transition.kind }
|
|
40691
|
-
});
|
|
40692
40952
|
return {
|
|
40693
40953
|
text: this.alarmAnnouncementText(panel, transition),
|
|
40694
|
-
targetIds
|
|
40954
|
+
targetIds: []
|
|
40695
40955
|
};
|
|
40696
40956
|
}
|
|
40697
40957
|
/** The composed body of {@link alarmAnnouncement}, or null for a catalog body. */
|
|
@@ -41698,14 +41958,19 @@ var NotificationCenter = class {
|
|
|
41698
41958
|
}
|
|
41699
41959
|
sequenceActionsOf = (sequenceId) => this.sequences.get(sequenceId)?.actions;
|
|
41700
41960
|
/**
|
|
41701
|
-
* Run this rule's onTrigger / onTriggerOff bindings for this match
|
|
41961
|
+
* Run this rule's onTrigger / onTriggerOff bindings for this match, and —
|
|
41962
|
+
* when the rule is gated on `armed_*` — auto-trigger the CamStack
|
|
41963
|
+
* alarm-panel (never sirens).
|
|
41702
41964
|
*
|
|
41703
41965
|
* Called BEFORE the cooldown gate — see the call site for why. Everything
|
|
41704
41966
|
* here is fire-and-forget and nothing may throw into the evaluation.
|
|
41705
41967
|
*/
|
|
41706
41968
|
runRuleActions(rule, subject, kind, controls) {
|
|
41707
|
-
if (
|
|
41708
|
-
const
|
|
41969
|
+
if (controls.runActions === false) return;
|
|
41970
|
+
const triggerOff = this.isTriggerOffSubject(subject);
|
|
41971
|
+
if (!triggerOff) this.autoTriggerAlarmPanel(rule, subject, kind);
|
|
41972
|
+
if (this.actionRunner === null) return;
|
|
41973
|
+
const bindings = triggerOff ? rule.actions?.onTriggerOff : rule.actions?.onTrigger;
|
|
41709
41974
|
if (bindings === void 0 || bindings.length === 0) return;
|
|
41710
41975
|
this.actionRunner.runBindings({
|
|
41711
41976
|
ruleId: rule.id,
|
|
@@ -41717,6 +41982,41 @@ var NotificationCenter = class {
|
|
|
41717
41982
|
});
|
|
41718
41983
|
}
|
|
41719
41984
|
/**
|
|
41985
|
+
* A rule gated on `armed_*` fires the CamStack panel on match — no
|
|
41986
|
+
* `onTrigger` sequence required. Sirens are never actuated here.
|
|
41987
|
+
*/
|
|
41988
|
+
autoTriggerAlarmPanel(rule, subject, kind) {
|
|
41989
|
+
if (!ruleAutoTriggersAlarm(rule)) return;
|
|
41990
|
+
const panel = this.alarmPanel;
|
|
41991
|
+
if (panel === null) {
|
|
41992
|
+
this.logger.warn("alarm rule matched but no alarm panel is wired — panel not triggered", {
|
|
41993
|
+
tags: { deviceId: subject.deviceId },
|
|
41994
|
+
meta: {
|
|
41995
|
+
ruleId: rule.id,
|
|
41996
|
+
rule: rule.name
|
|
41997
|
+
}
|
|
41998
|
+
});
|
|
41999
|
+
return;
|
|
42000
|
+
}
|
|
42001
|
+
this.actuateWithAttribution({
|
|
42002
|
+
deviceId: panel.deviceId,
|
|
42003
|
+
cap: ALARM_PANEL_CAP,
|
|
42004
|
+
method: "trigger",
|
|
42005
|
+
args: {},
|
|
42006
|
+
origin: fireRefFor(rule, subject, kind)
|
|
42007
|
+
}).catch((err) => {
|
|
42008
|
+
this.logger.warn("alarm panel auto-trigger failed", {
|
|
42009
|
+
tags: { deviceId: subject.deviceId },
|
|
42010
|
+
meta: {
|
|
42011
|
+
ruleId: rule.id,
|
|
42012
|
+
rule: rule.name,
|
|
42013
|
+
panelId: panel.deviceId,
|
|
42014
|
+
error: err instanceof Error ? err.message : String(err)
|
|
42015
|
+
}
|
|
42016
|
+
});
|
|
42017
|
+
});
|
|
42018
|
+
}
|
|
42019
|
+
/**
|
|
41720
42020
|
* The muted device that drops this subject, or `undefined` when none does.
|
|
41721
42021
|
* A per-camera subject: its camera. A linked set: the sensor; else, when
|
|
41722
42022
|
* the change took rows and every row camera is muted, the sensor too — the
|
|
@@ -104042,8 +104342,22 @@ var NO_PUBLICATION = {
|
|
|
104042
104342
|
url: null,
|
|
104043
104343
|
artifactId: null
|
|
104044
104344
|
};
|
|
104045
|
-
|
|
104046
|
-
|
|
104345
|
+
/**
|
|
104346
|
+
* A tRPC-shaped router or procedure reached by DYNAMIC name off `ctx.api`.
|
|
104347
|
+
*
|
|
104348
|
+
* A rule names its cap and method as strings, so the generated client cannot be
|
|
104349
|
+
* indexed statically. The walk down `api → router → procedure → mutate|query`
|
|
104350
|
+
* is therefore guarded hop by hop rather than asserted: every hop that is not
|
|
104351
|
+
* what it must be raises the named error the rule author needs to see.
|
|
104352
|
+
*
|
|
104353
|
+
* Forked-addon `ctx.api` namespaces are tRPC v11 Proxies over a noop function
|
|
104354
|
+
* (`typeof === 'function'`). Treating only plain objects as routers made every
|
|
104355
|
+
* sequence step fail with `no router for capability "alarm-panel"` while the
|
|
104356
|
+
* same method answered fine over HTTP — the panel never left `armed_*`, so
|
|
104357
|
+
* HomeKit stayed quiet.
|
|
104358
|
+
*/
|
|
104359
|
+
function isCapNode(value) {
|
|
104360
|
+
return typeof value === "object" && value !== null || typeof value === "function";
|
|
104047
104361
|
}
|
|
104048
104362
|
function isCapCall(value) {
|
|
104049
104363
|
return typeof value === "function";
|
|
@@ -107162,10 +107476,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
107162
107476
|
},
|
|
107163
107477
|
actuate: async ({ deviceId, cap, method, args }) => {
|
|
107164
107478
|
const router = Reflect.get(api, kebabToCamel(cap));
|
|
107165
|
-
if (!
|
|
107166
|
-
const proc = router
|
|
107167
|
-
const
|
|
107168
|
-
|
|
107479
|
+
if (!isCapNode(router)) throw new Error(`rule action: no router for capability "${cap}"`);
|
|
107480
|
+
const proc = Reflect.get(router, method);
|
|
107481
|
+
const mutate = isCapNode(proc) ? Reflect.get(proc, "mutate") : void 0;
|
|
107482
|
+
const query = isCapNode(proc) ? Reflect.get(proc, "query") : void 0;
|
|
107483
|
+
const fn = isCapCall(mutate) ? mutate : isCapCall(query) ? query : void 0;
|
|
107484
|
+
if (fn === void 0) throw new Error(`rule action: "${cap}.${method}" is not a callable cap method`);
|
|
107169
107485
|
await fn({
|
|
107170
107486
|
deviceId,
|
|
107171
107487
|
...args
|
|
@@ -113418,7 +113734,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
113418
113734
|
});
|
|
113419
113735
|
return NO_SENSOR_NOTIFICATION_CAMERAS;
|
|
113420
113736
|
});
|
|
113421
|
-
const linkedSet = cameras.mediaCameraIds.length > 1;
|
|
113737
|
+
const linkedSet = cameras.mediaCameraIds.length > 1 || cameras.mediaCameraIds.length === 1 && cameras.rowCameraIds.length === 0;
|
|
113422
113738
|
const linkedStills = linkedSet ? this.startLinkedStills(data, timestamp, cameras) : void 0;
|
|
113423
113739
|
const persisted = [];
|
|
113424
113740
|
const unowned = [];
|
|
@@ -113598,14 +113914,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
113598
113914
|
* which verdict swallowed it.
|
|
113599
113915
|
*/
|
|
113600
113916
|
flushSensorTransitionDrops() {
|
|
113601
|
-
for (const drop of this.sensorTransitions.takeDropSummary(Date.now()))
|
|
113602
|
-
|
|
113603
|
-
|
|
113604
|
-
|
|
113605
|
-
|
|
113606
|
-
|
|
113607
|
-
|
|
113608
|
-
|
|
113917
|
+
for (const drop of this.sensorTransitions.takeDropSummary(Date.now())) {
|
|
113918
|
+
const level = drop.reason === "baseline-seeded-inactive" || drop.reason === "no-change" ? "debug" : "info";
|
|
113919
|
+
this.ctx.logger[level]("sensor state changes dropped — no transition", {
|
|
113920
|
+
tags: { deviceId: drop.deviceId },
|
|
113921
|
+
meta: {
|
|
113922
|
+
capName: drop.capName,
|
|
113923
|
+
reason: drop.reason,
|
|
113924
|
+
count: drop.count
|
|
113925
|
+
}
|
|
113926
|
+
});
|
|
113927
|
+
}
|
|
113609
113928
|
}
|
|
113610
113929
|
/**
|
|
113611
113930
|
* One `info` line per (device, cap) per window for the evaluations refused
|