@camstack/addon-post-analysis 1.2.327 → 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-Cn5n0a0Q.mjs → clip-model-registry-CGaaUvAD.mjs} +111 -16
- package/dist/{clip-model-registry-CDG2krro.js → clip-model-registry-DQmmIYXS.js} +128 -15
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-TZMtQBvr.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/{hostInit-B61IObaQ.mjs → hostInit-Bj7szOak.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +434 -118
- package/dist/pipeline-analytics/index.mjs +434 -118
- 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-DK7fUvI-.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,6 +2247,7 @@ 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 } });
|
|
@@ -2197,10 +2259,36 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2197
2259
|
this.config.set("announceTargets", []);
|
|
2198
2260
|
}
|
|
2199
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
|
+
});
|
|
2283
|
+
}
|
|
2200
2284
|
/** The operator-visible configuration, as the cap serves it. */
|
|
2201
2285
|
settings() {
|
|
2286
|
+
const storedAction = this.config.get("postTriggeredAction");
|
|
2202
2287
|
return {
|
|
2203
|
-
|
|
2288
|
+
exitDelaySec: this.config.get("exitDelaySec"),
|
|
2289
|
+
entryDelaySec: this.config.get("entryDelaySec"),
|
|
2290
|
+
triggeredDurationSec: this.config.get("triggeredDurationSec"),
|
|
2291
|
+
...storedAction !== void 0 ? { postTriggeredAction: storedAction } : {},
|
|
2204
2292
|
announceArm: this.config.get("announceArm"),
|
|
2205
2293
|
announceTargets: [...this.config.get("announceTargets")],
|
|
2206
2294
|
nonBlocking: modeDeviceListFromPersisted(this.config.get("nonBlocking")),
|
|
@@ -2349,6 +2437,7 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2349
2437
|
if (patch.exitDelaySec !== void 0) await this.config.set("exitDelaySec", patch.exitDelaySec);
|
|
2350
2438
|
if (patch.entryDelaySec !== void 0) await this.config.set("entryDelaySec", patch.entryDelaySec);
|
|
2351
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);
|
|
2352
2441
|
if (patch.announceArm !== void 0) await this.config.set("announceArm", patch.announceArm);
|
|
2353
2442
|
if (patch.announceTargets !== void 0) await this.config.set("announceTargets", [...patch.announceTargets]);
|
|
2354
2443
|
if (membership !== void 0 && membershipChanged !== null) this.ctx.logger.info("alarm non-blocking list changed", {
|
|
@@ -2524,6 +2613,29 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2524
2613
|
}
|
|
2525
2614
|
});
|
|
2526
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
|
+
}
|
|
2527
2639
|
}
|
|
2528
2640
|
}
|
|
2529
2641
|
/** One INFO line per persisted per-mode delay entry the guard refused to trust. */
|
|
@@ -2582,6 +2694,13 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2582
2694
|
*/
|
|
2583
2695
|
async armWith(mode) {
|
|
2584
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
|
+
}
|
|
2585
2704
|
const read = await this.applyArmGuard(mode);
|
|
2586
2705
|
if (read.kind === "unguarded") return this.settleArm(mode, null, now);
|
|
2587
2706
|
if (read.kind === "check-failed") {
|
|
@@ -2595,6 +2714,11 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2595
2714
|
why: this.exclusionWhyFor(mode, device)
|
|
2596
2715
|
})), now);
|
|
2597
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
|
+
}
|
|
2598
2722
|
/**
|
|
2599
2723
|
* The Arm anyway on ONE refusal's notification — the only path that arms
|
|
2600
2724
|
* around a device that blocks.
|
|
@@ -2620,6 +2744,17 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2620
2744
|
const reason = refusalId === this.spentRefusalId ? "used" : "unknown-refusal";
|
|
2621
2745
|
return this.rejectArmAnyway(refusalId, reason);
|
|
2622
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
|
+
}
|
|
2623
2758
|
if (this.machine.changedAt !== held.panelChangedAt) {
|
|
2624
2759
|
this.refusal = null;
|
|
2625
2760
|
return this.rejectArmAnyway(refusalId, "stale");
|
|
@@ -3015,11 +3150,12 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3015
3150
|
* rather than fired only once somebody looks.
|
|
3016
3151
|
*/
|
|
3017
3152
|
publish(now = Date.now()) {
|
|
3153
|
+
const autoCleared = this.machine.clearAt !== void 0 && now >= this.machine.clearAt && this.machine.clearToDisarmed === true;
|
|
3018
3154
|
this.machine = settle(this.machine, now);
|
|
3019
3155
|
const state = stateAt(this.machine, now);
|
|
3020
3156
|
this.runtimeState.setCapState("alarm-panel", this.status(now));
|
|
3021
3157
|
this.persist();
|
|
3022
|
-
this.report(state, now);
|
|
3158
|
+
this.report(state, now, autoCleared);
|
|
3023
3159
|
if (this.machine.armedAt !== void 0 || this.machine.triggerAt !== void 0 || this.machine.clearAt !== void 0) this.startTicking();
|
|
3024
3160
|
else this.stopTicking();
|
|
3025
3161
|
}
|
|
@@ -3031,7 +3167,7 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3031
3167
|
* panel's own state update with it would make a delivery failure look like
|
|
3032
3168
|
* an alarm failure.
|
|
3033
3169
|
*/
|
|
3034
|
-
report(state, now) {
|
|
3170
|
+
report(state, now, autoCleared = false) {
|
|
3035
3171
|
const previous = this.lastPublished;
|
|
3036
3172
|
this.lastPublished = state;
|
|
3037
3173
|
const hook = this.onPublish;
|
|
@@ -3046,7 +3182,8 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3046
3182
|
at: now,
|
|
3047
3183
|
...source !== null ? { source } : {},
|
|
3048
3184
|
...arming !== void 0 ? { arming } : {},
|
|
3049
|
-
...pending !== void 0 ? { pending } : {}
|
|
3185
|
+
...pending !== void 0 ? { pending } : {},
|
|
3186
|
+
...autoCleared ? { autoCleared: true } : {}
|
|
3050
3187
|
});
|
|
3051
3188
|
if (transition?.kind === "alarm-triggered") this.attribution.clear();
|
|
3052
3189
|
try {
|
|
@@ -3132,8 +3269,12 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3132
3269
|
}
|
|
3133
3270
|
/** The global durations plus the per-mode overrides, read from the persisted config NOW. */
|
|
3134
3271
|
delaySettings() {
|
|
3272
|
+
const storedAction = this.config.get("postTriggeredAction");
|
|
3135
3273
|
return {
|
|
3136
|
-
|
|
3274
|
+
exitDelaySec: this.config.get("exitDelaySec"),
|
|
3275
|
+
entryDelaySec: this.config.get("entryDelaySec"),
|
|
3276
|
+
triggeredDurationSec: this.config.get("triggeredDurationSec"),
|
|
3277
|
+
...storedAction !== void 0 ? { postTriggeredAction: storedAction } : {},
|
|
3137
3278
|
modeDelays: modeDelaysFromPersisted(this.config.get("modeDelays")).entries
|
|
3138
3279
|
};
|
|
3139
3280
|
}
|
|
@@ -3143,24 +3284,32 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3143
3284
|
*/
|
|
3144
3285
|
delaysFor(mode) {
|
|
3145
3286
|
const settings = this.delaySettings();
|
|
3146
|
-
if (mode === null)
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
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
|
+
}
|
|
3156
3302
|
return resolveModeDelays(settings, mode);
|
|
3157
3303
|
}
|
|
3158
|
-
/** The
|
|
3304
|
+
/** The global durations + post-triggered action, read from the persisted config. */
|
|
3159
3305
|
readDelays() {
|
|
3306
|
+
const triggeredDurationSec = this.config.get("triggeredDurationSec");
|
|
3307
|
+
const stored = this.config.get("postTriggeredAction");
|
|
3160
3308
|
return {
|
|
3161
3309
|
exitDelaySec: this.config.get("exitDelaySec"),
|
|
3162
3310
|
entryDelaySec: this.config.get("entryDelaySec"),
|
|
3163
|
-
triggeredDurationSec
|
|
3311
|
+
triggeredDurationSec,
|
|
3312
|
+
postTriggeredAction: derivePostTriggeredAction(stored, triggeredDurationSec)
|
|
3164
3313
|
};
|
|
3165
3314
|
}
|
|
3166
3315
|
startTicking() {
|
|
@@ -5140,7 +5289,7 @@ function ncClassNoun(texts, className, count) {
|
|
|
5140
5289
|
count,
|
|
5141
5290
|
vars: { name: className }
|
|
5142
5291
|
});
|
|
5143
|
-
if (isEventKindLabelKey(normalised)) return eventKindLabel(normalised, texts.language);
|
|
5292
|
+
if (isEventKindLabelKey(normalised)) return eventKindLabel(normalised, texts.language).toLocaleLowerCase(texts.language);
|
|
5144
5293
|
return texts.text({
|
|
5145
5294
|
key: "class.other",
|
|
5146
5295
|
count,
|
|
@@ -5148,13 +5297,24 @@ function ncClassNoun(texts, className, count) {
|
|
|
5148
5297
|
});
|
|
5149
5298
|
}
|
|
5150
5299
|
/**
|
|
5151
|
-
* The
|
|
5152
|
-
*
|
|
5153
|
-
*
|
|
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.
|
|
5154
5303
|
*/
|
|
5155
|
-
function
|
|
5156
|
-
if (
|
|
5157
|
-
|
|
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());
|
|
5158
5318
|
}
|
|
5159
5319
|
/** `person (John)` / `persona (John)` — the thing the notification is about. */
|
|
5160
5320
|
function ncSubjectText(texts, input) {
|
|
@@ -5163,11 +5323,15 @@ function ncSubjectText(texts, input) {
|
|
|
5163
5323
|
key: "detection.subject",
|
|
5164
5324
|
vars: { classNoun }
|
|
5165
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
|
+
});
|
|
5166
5330
|
return texts.text({
|
|
5167
5331
|
key: subjectLabelKey(input.labelKind),
|
|
5168
5332
|
vars: {
|
|
5169
5333
|
classNoun,
|
|
5170
|
-
label:
|
|
5334
|
+
label: input.label
|
|
5171
5335
|
}
|
|
5172
5336
|
});
|
|
5173
5337
|
}
|
|
@@ -5757,7 +5921,7 @@ function isAdoptable(record, nowMs) {
|
|
|
5757
5921
|
* what `localDayMinute` does for the same input — one behaviour for a bad
|
|
5758
5922
|
* zone, not two.
|
|
5759
5923
|
*/
|
|
5760
|
-
function clockOf$
|
|
5924
|
+
function clockOf$7(atMs, timeZone) {
|
|
5761
5925
|
if (timeZone !== void 0) try {
|
|
5762
5926
|
return new Intl.DateTimeFormat("en-GB", {
|
|
5763
5927
|
timeZone,
|
|
@@ -5893,7 +6057,7 @@ var TimelapseRenderer = class {
|
|
|
5893
6057
|
includeAudio: false,
|
|
5894
6058
|
maxLifeMs: await this.ports.exportLifeMs(),
|
|
5895
6059
|
deleteAfterDownload: false,
|
|
5896
|
-
title: `${request.ruleName} ${clockOf$
|
|
6060
|
+
title: `${request.ruleName} ${clockOf$7(window.startMs, request.timezone)}→${clockOf$7(window.endMs, request.timezone)}`,
|
|
5897
6061
|
timelapse: {
|
|
5898
6062
|
everyMs,
|
|
5899
6063
|
outputFps: request.framerate,
|
|
@@ -6224,7 +6388,7 @@ var TimelapseRenderer = class {
|
|
|
6224
6388
|
...meta,
|
|
6225
6389
|
unservedWindowCount: verdict.unservedWindows.length,
|
|
6226
6390
|
unservedWindows: verdict.unservedWindows.slice(0, 12).map((w) => ({
|
|
6227
|
-
at: clockOf$
|
|
6391
|
+
at: clockOf$7(request.window.startMs + w.fromSec * 1e3, request.timezone),
|
|
6228
6392
|
fromSec: Math.round(w.fromSec),
|
|
6229
6393
|
toSec: Math.round(w.toSec),
|
|
6230
6394
|
expectedDenseFrames: w.expectedDenseFrames,
|
|
@@ -8805,6 +8969,7 @@ function buildActionButtons(input) {
|
|
|
8805
8969
|
sequenceId: button.sequenceId,
|
|
8806
8970
|
actionId: button.id
|
|
8807
8971
|
}),
|
|
8972
|
+
mode: "background",
|
|
8808
8973
|
...button.icon !== void 0 ? { icon: button.icon } : {},
|
|
8809
8974
|
...button.destructive !== void 0 ? { destructive: button.destructive } : {}
|
|
8810
8975
|
});
|
|
@@ -8920,10 +9085,9 @@ var MAX_ARM_BUTTONS = 3;
|
|
|
8920
9085
|
* (`ALARM_DISARM_TTL_MS`). Both exist: the hint is for the person, the TTL is
|
|
8921
9086
|
* for the forwarded screenshot.
|
|
8922
9087
|
*
|
|
8923
|
-
*
|
|
8924
|
-
*
|
|
8925
|
-
*
|
|
8926
|
-
* 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.
|
|
8927
9091
|
*
|
|
8928
9092
|
* `panel.availableModes` ARE the enabled modes: CamStack has no per-mode
|
|
8929
9093
|
* disable setting, so the Arm buttons offer exactly what the panel declares.
|
|
@@ -8957,14 +9121,14 @@ function buildAlarmButtons(input) {
|
|
|
8957
9121
|
}),
|
|
8958
9122
|
icon: "disarm",
|
|
8959
9123
|
destructive: true,
|
|
8960
|
-
mode: "
|
|
9124
|
+
mode: "background"
|
|
8961
9125
|
}];
|
|
8962
9126
|
}
|
|
8963
9127
|
/**
|
|
8964
9128
|
* The one Arm anyway a refusal notification carries — or none, when the row
|
|
8965
|
-
* names no refusal.
|
|
8966
|
-
*
|
|
8967
|
-
*
|
|
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).
|
|
8968
9132
|
*/
|
|
8969
9133
|
function armAnywayButton(refusalId, input) {
|
|
8970
9134
|
if (refusalId === void 0) return [];
|
|
@@ -8979,7 +9143,7 @@ function armAnywayButton(refusalId, input) {
|
|
|
8979
9143
|
}),
|
|
8980
9144
|
icon: "arm",
|
|
8981
9145
|
destructive: true,
|
|
8982
|
-
mode: "
|
|
9146
|
+
mode: "background"
|
|
8983
9147
|
}];
|
|
8984
9148
|
}
|
|
8985
9149
|
//#endregion
|
|
@@ -12901,6 +13065,10 @@ function alarmTextKeys(transition, body) {
|
|
|
12901
13065
|
titleKey,
|
|
12902
13066
|
bodyKey: `system.${kind}.body.${transition.sensor.event}`
|
|
12903
13067
|
};
|
|
13068
|
+
if (kind === "alarm-disarmed" && transition.autoCleared === true) return {
|
|
13069
|
+
titleKey,
|
|
13070
|
+
bodyKey: `system.${kind}.body.autoCleared`
|
|
13071
|
+
};
|
|
12904
13072
|
if (kind === "alarm-disarmed" && (transition.previous === "triggered" || transition.previous === "pending")) return {
|
|
12905
13073
|
titleKey,
|
|
12906
13074
|
bodyKey: `system.${kind}.body.defused`
|
|
@@ -14620,13 +14788,14 @@ var en_default = {
|
|
|
14620
14788
|
"system.alarm-arming.title.away": "Arming away — {{seconds}}s",
|
|
14621
14789
|
"system.alarm-arming.title.night": "Arming night — {{seconds}}s",
|
|
14622
14790
|
"system.alarm-arming.body": "Arming",
|
|
14623
|
-
"system.alarm-armed.title.home": "
|
|
14624
|
-
"system.alarm-armed.title.away": "
|
|
14625
|
-
"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",
|
|
14626
14794
|
"system.alarm-armed.body": "Alarm armed",
|
|
14627
14795
|
"system.alarm-disarmed.title": "Disarmed",
|
|
14628
14796
|
"system.alarm-disarmed.body": "Alarm disarmed",
|
|
14629
14797
|
"system.alarm-disarmed.body.defused": "Alarm disarmed during an alarm",
|
|
14798
|
+
"system.alarm-disarmed.body.autoCleared": "Alarm disarmed automatically after the siren",
|
|
14630
14799
|
"system.alarm-triggered.title": "ALARM",
|
|
14631
14800
|
"system.alarm-triggered.body": "Alarm triggered",
|
|
14632
14801
|
"system.alarm-triggered.body.rule": "Triggered by {{rule}} on {{deviceName}}",
|
|
@@ -14948,13 +15117,14 @@ var it_default = {
|
|
|
14948
15117
|
"system.alarm-arming.title.away": "Inserimento Fuori — {{seconds}}s",
|
|
14949
15118
|
"system.alarm-arming.title.night": "Inserimento Notte — {{seconds}}s",
|
|
14950
15119
|
"system.alarm-arming.body": "Inserimento in corso",
|
|
14951
|
-
"system.alarm-armed.title.home": "
|
|
14952
|
-
"system.alarm-armed.title.away": "
|
|
14953
|
-
"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",
|
|
14954
15123
|
"system.alarm-armed.body": "Allarme inserito",
|
|
14955
15124
|
"system.alarm-disarmed.title": "Disinserito",
|
|
14956
15125
|
"system.alarm-disarmed.body": "Allarme disinserito",
|
|
14957
15126
|
"system.alarm-disarmed.body.defused": "Allarme disinserito durante un allarme",
|
|
15127
|
+
"system.alarm-disarmed.body.autoCleared": "Allarme disinserito automaticamente dopo la sirena",
|
|
14958
15128
|
"system.alarm-triggered.title": "ALLARME",
|
|
14959
15129
|
"system.alarm-triggered.body": "Allarme scattato",
|
|
14960
15130
|
"system.alarm-triggered.body.rule": "Scattato da {{rule}} su {{deviceName}}",
|
|
@@ -23942,7 +24112,7 @@ var NcDispatcher = class {
|
|
|
23942
24112
|
...systemCount,
|
|
23943
24113
|
...systemEvent?.body !== void 0 ? { frozen: systemEvent.body } : {}
|
|
23944
24114
|
});
|
|
23945
|
-
return composeRuleAiBody({
|
|
24115
|
+
return ncSentenceCaseBody(composeRuleAiBody({
|
|
23946
24116
|
aiText,
|
|
23947
24117
|
body: entry.payload.synthetic === true ? this.renderText({
|
|
23948
24118
|
key: "test.prefix",
|
|
@@ -23950,7 +24120,7 @@ var NcDispatcher = class {
|
|
|
23950
24120
|
frozen: rendered
|
|
23951
24121
|
}) : rendered,
|
|
23952
24122
|
templateNamesAi
|
|
23953
|
-
});
|
|
24123
|
+
}));
|
|
23954
24124
|
};
|
|
23955
24125
|
const body = renderBody("");
|
|
23956
24126
|
const resolved = await this.resolveAttachments(entry);
|
|
@@ -25130,15 +25300,18 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
|
|
|
25130
25300
|
const systemEvent = subject.systemEvent;
|
|
25131
25301
|
const detection = systemEvent?.alarmSource?.className !== void 0 ? systemEvent.alarmSource : void 0;
|
|
25132
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));
|
|
25133
25306
|
return {
|
|
25134
|
-
camera:
|
|
25307
|
+
camera: cameraName,
|
|
25135
25308
|
class: detection?.className ?? (systemEvent !== void 0 ? "" : subject.className),
|
|
25136
25309
|
classLabel: (() => {
|
|
25137
25310
|
const wire = detection?.className ?? (systemEvent !== void 0 ? "" : subject.className);
|
|
25138
|
-
return wire.length === 0 ? "" : eventKindLabel(wire,
|
|
25311
|
+
return wire.length === 0 ? "" : eventKindLabel(wire, locale).toLocaleLowerCase(locale);
|
|
25139
25312
|
})(),
|
|
25140
25313
|
label: detection?.label ?? subject.label ?? "",
|
|
25141
|
-
zones:
|
|
25314
|
+
zones: zonesLower.join(", "),
|
|
25142
25315
|
subject: detection?.className !== void 0 ? ncSubjectText(texts, {
|
|
25143
25316
|
className: detection.className,
|
|
25144
25317
|
...detection.label !== void 0 ? { label: detection.label } : {}
|
|
@@ -25148,9 +25321,9 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
|
|
|
25148
25321
|
...subject.labelKind !== void 0 ? { labelKind: subject.labelKind } : {}
|
|
25149
25322
|
}),
|
|
25150
25323
|
detectionSummary: group !== void 0 && group.members.length > 1 ? ncDetectionSummary(texts, classCounts(group.members)) : "",
|
|
25151
|
-
inZones: ncInZonesText(texts,
|
|
25152
|
-
scope: occupancy !== void 0 ? occupancy.zone ??
|
|
25153
|
-
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),
|
|
25154
25327
|
confidence: percentOf(detection?.confidence ?? subject.confidence),
|
|
25155
25328
|
time: ncClockText(texts.language, subject.timestamp, timeZone),
|
|
25156
25329
|
sounds: subject.audio?.labels.join(", ") ?? "",
|
|
@@ -29648,7 +29821,7 @@ function buildRuleAiSystemPrompt(shape, language, maxWords, grouped = false) {
|
|
|
29648
29821
|
].join("");
|
|
29649
29822
|
}
|
|
29650
29823
|
/** `HH:MM` in the host timezone. */
|
|
29651
|
-
function clockOf$
|
|
29824
|
+
function clockOf$6(atMs) {
|
|
29652
29825
|
const d = new Date(atMs);
|
|
29653
29826
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
29654
29827
|
}
|
|
@@ -29686,7 +29859,7 @@ function buildRuleAiPrompt(input) {
|
|
|
29686
29859
|
if (shape === "detection-track") {
|
|
29687
29860
|
lines.push(`Camera: ${camera}.${located} Subject: ${subject}. The stills, in order:`);
|
|
29688
29861
|
for (const [i, frame] of input.frames.entries()) {
|
|
29689
|
-
const when = frame.atMs !== void 0 ? ` · ${clockOf$
|
|
29862
|
+
const when = frame.atMs !== void 0 ? ` · ${clockOf$6(frame.atMs)}` : "";
|
|
29690
29863
|
lines.push(`${String(i + 1)}.${when}`);
|
|
29691
29864
|
}
|
|
29692
29865
|
return lines.join("\n");
|
|
@@ -31457,7 +31630,7 @@ var DEFAULT_PROMPT_EN = "What happened during this period? Summarise it for a ho
|
|
|
31457
31630
|
*/
|
|
31458
31631
|
var SummaryAnswerSchema = object({ summary: string() });
|
|
31459
31632
|
/** `HH:MM` in the host timezone — the two numbers a frame label needs. */
|
|
31460
|
-
function clockOf$
|
|
31633
|
+
function clockOf$5(atMs) {
|
|
31461
31634
|
const d = new Date(atMs);
|
|
31462
31635
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
31463
31636
|
}
|
|
@@ -31480,7 +31653,7 @@ function buildSummaryAiPrompt(input, frames) {
|
|
|
31480
31653
|
const roster = frames.map((f, i) => {
|
|
31481
31654
|
const location = plainLabel(f.location ?? "");
|
|
31482
31655
|
const where = location.length > 0 ? ` (${location})` : "";
|
|
31483
|
-
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"}`;
|
|
31484
31657
|
}).join("\n");
|
|
31485
31658
|
const described = /* @__PURE__ */ new Map();
|
|
31486
31659
|
for (const f of frames) {
|
|
@@ -32083,7 +32256,7 @@ var NO_DETECTIONS$1 = {
|
|
|
32083
32256
|
* has to check is a key somebody can get wrong. */
|
|
32084
32257
|
var BODY_SEPARATOR$1 = " · ";
|
|
32085
32258
|
/** `HH:MM` in the host timezone. */
|
|
32086
|
-
function clockOf$
|
|
32259
|
+
function clockOf$4(atMs) {
|
|
32087
32260
|
const d = new Date(atMs);
|
|
32088
32261
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
32089
32262
|
}
|
|
@@ -32097,8 +32270,8 @@ function clockOf$3(atMs) {
|
|
|
32097
32270
|
function summaryTemplateVars(input) {
|
|
32098
32271
|
return {
|
|
32099
32272
|
rule: input.ruleName,
|
|
32100
|
-
from: clockOf$
|
|
32101
|
-
to: clockOf$
|
|
32273
|
+
from: clockOf$4(input.window.startMs),
|
|
32274
|
+
to: clockOf$4(input.window.endMs),
|
|
32102
32275
|
time: ncClockText(input.texts.language, input.generatedAt),
|
|
32103
32276
|
events: String(input.tileCount),
|
|
32104
32277
|
cameras: String(input.cameraCount),
|
|
@@ -32193,11 +32366,11 @@ function summaryRecordId(ruleId, windowEndMs) {
|
|
|
32193
32366
|
/** The caption burned onto the mosaic's title band, with the same `{{var}}`
|
|
32194
32367
|
* vocabulary the title and body use. `''` is a real answer ("no caption"). */
|
|
32195
32368
|
function summaryCaptionText(input) {
|
|
32196
|
-
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)}`;
|
|
32197
32370
|
return renderTemplate(input.captionText, {
|
|
32198
32371
|
rule: input.ruleName,
|
|
32199
|
-
from: clockOf$
|
|
32200
|
-
to: clockOf$
|
|
32372
|
+
from: clockOf$4(input.window.startMs),
|
|
32373
|
+
to: clockOf$4(input.window.endMs)
|
|
32201
32374
|
}) ?? "";
|
|
32202
32375
|
}
|
|
32203
32376
|
/**
|
|
@@ -33129,7 +33302,7 @@ var NcSummaryProducer = class {
|
|
|
33129
33302
|
purpose: "summary",
|
|
33130
33303
|
tiles: tiles.map(({ candidate, jpeg }) => ({
|
|
33131
33304
|
jpeg,
|
|
33132
|
-
...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}` } : {},
|
|
33133
33306
|
deviceId: candidate.deviceId,
|
|
33134
33307
|
sourceId: candidate.trackId
|
|
33135
33308
|
})),
|
|
@@ -33352,7 +33525,7 @@ var NcSummaryProducer = class {
|
|
|
33352
33525
|
}
|
|
33353
33526
|
};
|
|
33354
33527
|
/** `HH:MM` in the host timezone — the two numbers a tile label needs. */
|
|
33355
|
-
function clockOf$
|
|
33528
|
+
function clockOf$3(atMs) {
|
|
33356
33529
|
const d = new Date(atMs);
|
|
33357
33530
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
33358
33531
|
}
|
|
@@ -33852,6 +34025,99 @@ var NcCalendarEventsStore = class {
|
|
|
33852
34025
|
}
|
|
33853
34026
|
};
|
|
33854
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
|
|
33855
34121
|
//#region src/notification-center/calendar-events/calendar-events-delivery.ts
|
|
33856
34122
|
function calendarEventsRecordId(ruleId, windowEndMs) {
|
|
33857
34123
|
return `calendar-events:${ruleId}:${windowEndMs}`;
|
|
@@ -33861,16 +34127,8 @@ function clockOf$1(atMs) {
|
|
|
33861
34127
|
const d = new Date(atMs);
|
|
33862
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")}`}`;
|
|
33863
34129
|
}
|
|
33864
|
-
function formatEventLine(event) {
|
|
33865
|
-
const when = event.allDay ? clockOf$1(event.startMs).slice(0, 10) : `${clockOf$1(event.startMs)}–${clockOf$1(event.endMs)}`;
|
|
33866
|
-
const where = event.location !== void 0 && event.location.length > 0 ? ` @ ${event.location}` : "";
|
|
33867
|
-
return `• ${when} ${event.title}${where}`;
|
|
33868
|
-
}
|
|
33869
34130
|
function derivedBody$1(input) {
|
|
33870
|
-
|
|
33871
|
-
const lines = input.events.slice(0, 40).map(formatEventLine);
|
|
33872
|
-
const extra = input.events.length > 40 ? `\n…and ${String(input.events.length - 40)} more` : "";
|
|
33873
|
-
return lines.join("\n") + extra;
|
|
34131
|
+
return formatCalendarEventsBody(input.events, input.eventWindow.startMs, input.horizonDays);
|
|
33874
34132
|
}
|
|
33875
34133
|
function templateVars(input) {
|
|
33876
34134
|
return {
|
|
@@ -39316,19 +39574,21 @@ var NotificationCenter = class {
|
|
|
39316
39574
|
}
|
|
39317
39575
|
/**
|
|
39318
39576
|
* Start photographing every camera that links `sensorId`, and return the
|
|
39319
|
-
* owners the sensor-level evaluation names — or `undefined` when there is
|
|
39320
|
-
*
|
|
39321
|
-
*
|
|
39322
|
-
*
|
|
39323
|
-
*
|
|
39324
|
-
*
|
|
39325
|
-
*
|
|
39326
|
-
*
|
|
39327
|
-
* never fires on a sensor).
|
|
39328
|
-
*
|
|
39329
|
-
*
|
|
39330
|
-
*
|
|
39331
|
-
*
|
|
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.
|
|
39332
39592
|
*
|
|
39333
39593
|
* A MUTED camera (D62) is never dialled: its tile is left out, said on that
|
|
39334
39594
|
* camera (once per window), and the primary is the first linked camera that
|
|
@@ -39341,7 +39601,7 @@ var NotificationCenter = class {
|
|
|
39341
39601
|
beginLinkedStills(request) {
|
|
39342
39602
|
const { sensorId, sensorKind, cameraIds, atMs, rowCameraIds, sensorRaised } = request;
|
|
39343
39603
|
const shelf = this.linkedStills;
|
|
39344
|
-
if (shelf === null || !this.evaluationActive || cameraIds.length
|
|
39604
|
+
if (shelf === null || !this.evaluationActive || cameraIds.length === 0) return void 0;
|
|
39345
39605
|
if (this.deviceMutes.isMuted(sensorId)) return void 0;
|
|
39346
39606
|
const rowShown = this.unmutedCameras(rowCameraIds);
|
|
39347
39607
|
if (rowCameraIds.length > 0 && rowShown.length === 0) return void 0;
|
|
@@ -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 = [];
|