@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
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_clip_model_registry = require("../clip-model-registry-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-DQmmIYXS.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_clip_model_registry.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -659,7 +659,8 @@ var NcAlarmArmOutcomeSchema = require_clip_model_registry.discriminatedUnion("ou
|
|
|
659
659
|
require_clip_model_registry.object({
|
|
660
660
|
outcome: require_clip_model_registry.literal("rejected"),
|
|
661
661
|
reason: NcAlarmArmAnywayRejectionSchema
|
|
662
|
-
}).readonly()
|
|
662
|
+
}).readonly(),
|
|
663
|
+
require_clip_model_registry.object({ outcome: require_clip_model_registry.literal("outside-schedule") }).readonly()
|
|
663
664
|
]);
|
|
664
665
|
/**
|
|
665
666
|
* The devices a mode covers, ascending and de-duplicated.
|
|
@@ -1518,7 +1519,25 @@ function modeDelaysFromPersisted(raw) {
|
|
|
1518
1519
|
}
|
|
1519
1520
|
kept[field] = value;
|
|
1520
1521
|
}
|
|
1521
|
-
if (
|
|
1522
|
+
if (item.postTriggeredAction !== void 0) {
|
|
1523
|
+
const action = require_clip_model_registry.NcAlarmPostTriggeredActionSchema.safeParse(item.postTriggeredAction);
|
|
1524
|
+
if (action.success) kept.postTriggeredAction = action.data;
|
|
1525
|
+
else dropped.push({
|
|
1526
|
+
mode: item.mode,
|
|
1527
|
+
why: "out-of-bounds",
|
|
1528
|
+
field: "triggeredDurationSec"
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
if (item.schedule !== void 0) {
|
|
1532
|
+
const schedule = require_clip_model_registry.NcScheduleSchema.safeParse(item.schedule);
|
|
1533
|
+
if (schedule.success) kept.schedule = schedule.data;
|
|
1534
|
+
else dropped.push({
|
|
1535
|
+
mode: item.mode,
|
|
1536
|
+
why: "out-of-bounds",
|
|
1537
|
+
field: "schedule"
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
if (kept.exitDelaySec !== void 0 || kept.entryDelaySec !== void 0 || kept.triggeredDurationSec !== void 0 || kept.postTriggeredAction !== void 0 || kept.schedule !== void 0) entries.push(kept);
|
|
1522
1541
|
}
|
|
1523
1542
|
return {
|
|
1524
1543
|
entries,
|
|
@@ -1534,16 +1553,15 @@ function modeDelaysFromPersisted(raw) {
|
|
|
1534
1553
|
* (`resolveModeDelays`): `home` has no exit delay on purpose — nobody is
|
|
1535
1554
|
* leaving — and says so with its own entry, not by this shared value.
|
|
1536
1555
|
*
|
|
1537
|
-
*
|
|
1538
|
-
*
|
|
1539
|
-
*
|
|
1540
|
-
* every existing alarm after N seconds would be a behaviour change nobody
|
|
1541
|
-
* asked for.
|
|
1556
|
+
* Default post-triggered action is `rearmAfter` 10 s — every mode inherits
|
|
1557
|
+
* that unless the operator overrides. Older blobs that still store the stock
|
|
1558
|
+
* `0` + absent/`doNothing` seed are migrated once at panel boot (D701).
|
|
1542
1559
|
*/
|
|
1543
1560
|
var DEFAULT_DELAYS = {
|
|
1544
1561
|
exitDelaySec: 30,
|
|
1545
1562
|
entryDelaySec: 20,
|
|
1546
|
-
triggeredDurationSec:
|
|
1563
|
+
triggeredDurationSec: 10,
|
|
1564
|
+
postTriggeredAction: "rearmAfter"
|
|
1547
1565
|
};
|
|
1548
1566
|
var DISARMED = {
|
|
1549
1567
|
target: "disarmed",
|
|
@@ -1561,7 +1579,7 @@ function armedState(mode) {
|
|
|
1561
1579
|
* answer is still correct the moment anyone asks.
|
|
1562
1580
|
*/
|
|
1563
1581
|
function stateAt(m, now) {
|
|
1564
|
-
if (m.clearAt !== void 0 && now >= m.clearAt) return m.target;
|
|
1582
|
+
if (m.clearAt !== void 0 && now >= m.clearAt) return m.clearToDisarmed === true ? "disarmed" : m.target;
|
|
1565
1583
|
if (m.fired) return "triggered";
|
|
1566
1584
|
if (m.triggerAt !== void 0) return now >= m.triggerAt ? "triggered" : "pending";
|
|
1567
1585
|
if (m.armedAt !== void 0) return now >= m.armedAt ? m.target : "arming";
|
|
@@ -1657,10 +1675,18 @@ function trigger(m, delays, now) {
|
|
|
1657
1675
|
changedAt: now
|
|
1658
1676
|
};
|
|
1659
1677
|
}
|
|
1660
|
-
/**
|
|
1678
|
+
/**
|
|
1679
|
+
* `clearAt` (+ optional disarm) when the operator asked for a bounded
|
|
1680
|
+
* post-triggered action — see {@link AlarmDelays}.
|
|
1681
|
+
*/
|
|
1661
1682
|
function autoClear(delays, firesAt) {
|
|
1662
|
-
if (delays.
|
|
1663
|
-
|
|
1683
|
+
if (delays.postTriggeredAction === "doNothing") return {};
|
|
1684
|
+
const clearAt = firesAt + Math.max(0, delays.triggeredDurationSec) * 1e3;
|
|
1685
|
+
if (delays.postTriggeredAction === "disableAfter") return {
|
|
1686
|
+
clearAt,
|
|
1687
|
+
clearToDisarmed: true
|
|
1688
|
+
};
|
|
1689
|
+
return { clearAt };
|
|
1664
1690
|
}
|
|
1665
1691
|
/**
|
|
1666
1692
|
* Settle the machine so a stored value never depends on when it is read.
|
|
@@ -1670,16 +1696,23 @@ function autoClear(delays, firesAt) {
|
|
|
1670
1696
|
* than only in the reading of it.
|
|
1671
1697
|
*/
|
|
1672
1698
|
function settle(m, now) {
|
|
1673
|
-
if (m.clearAt !== void 0 && now >= m.clearAt)
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1699
|
+
if (m.clearAt !== void 0 && now >= m.clearAt) {
|
|
1700
|
+
if (m.clearToDisarmed === true) return {
|
|
1701
|
+
...DISARMED,
|
|
1702
|
+
changedAt: m.clearAt
|
|
1703
|
+
};
|
|
1704
|
+
return {
|
|
1705
|
+
target: m.target,
|
|
1706
|
+
fired: false,
|
|
1707
|
+
changedAt: m.clearAt
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1678
1710
|
if (m.triggerAt !== void 0 && now >= m.triggerAt) return {
|
|
1679
1711
|
target: m.target,
|
|
1680
1712
|
fired: true,
|
|
1681
1713
|
changedAt: m.triggerAt,
|
|
1682
|
-
...m.clearAt !== void 0 ? { clearAt: m.clearAt } : {}
|
|
1714
|
+
...m.clearAt !== void 0 ? { clearAt: m.clearAt } : {},
|
|
1715
|
+
...m.clearToDisarmed === true ? { clearToDisarmed: true } : {}
|
|
1683
1716
|
};
|
|
1684
1717
|
if (m.armedAt !== void 0 && now >= m.armedAt) return {
|
|
1685
1718
|
target: m.target,
|
|
@@ -1826,7 +1859,8 @@ function alarmTransition(input) {
|
|
|
1826
1859
|
kind: "alarm-disarmed",
|
|
1827
1860
|
state: next,
|
|
1828
1861
|
previous,
|
|
1829
|
-
at
|
|
1862
|
+
at,
|
|
1863
|
+
...input.autoCleared === true ? { autoCleared: true } : {}
|
|
1830
1864
|
};
|
|
1831
1865
|
return null;
|
|
1832
1866
|
}
|
|
@@ -1917,6 +1951,17 @@ var ncAlarmPanelSchema = require_clip_model_registry.object({
|
|
|
1917
1951
|
entryDelaySec: require_clip_model_registry.number().int().min(0).max(600).default(DEFAULT_DELAYS.entryDelaySec),
|
|
1918
1952
|
triggeredDurationSec: require_clip_model_registry.number().int().min(0).max(3600).default(DEFAULT_DELAYS.triggeredDurationSec),
|
|
1919
1953
|
/**
|
|
1954
|
+
* What happens when the triggered window ends. Absent on older blobs →
|
|
1955
|
+
* derived from {@link triggeredDurationSec} at read time (never defaulted
|
|
1956
|
+
* here: a Zod default would force `doNothing` onto installs that already
|
|
1957
|
+
* had a positive `triggeredDurationSec` and silently stop re-arming).
|
|
1958
|
+
*/
|
|
1959
|
+
postTriggeredAction: require_clip_model_registry._enum([
|
|
1960
|
+
"rearmAfter",
|
|
1961
|
+
"disableAfter",
|
|
1962
|
+
"doNothing"
|
|
1963
|
+
]).optional(),
|
|
1964
|
+
/**
|
|
1920
1965
|
* VESTIGIAL (D630): nothing reads it to decide anything any more — every
|
|
1921
1966
|
* transition is announced to `announceTargets`. Kept so a persisted blob
|
|
1922
1967
|
* and the settings cap still round-trip it, and read once at boot to tell
|
|
@@ -1975,7 +2020,14 @@ var ncAlarmPanelSchema = require_clip_model_registry.object({
|
|
|
1975
2020
|
mode: require_clip_model_registry.string(),
|
|
1976
2021
|
exitDelaySec: require_clip_model_registry.number().optional(),
|
|
1977
2022
|
entryDelaySec: require_clip_model_registry.number().optional(),
|
|
1978
|
-
triggeredDurationSec: require_clip_model_registry.number().optional()
|
|
2023
|
+
triggeredDurationSec: require_clip_model_registry.number().optional(),
|
|
2024
|
+
postTriggeredAction: require_clip_model_registry._enum([
|
|
2025
|
+
"rearmAfter",
|
|
2026
|
+
"disableAfter",
|
|
2027
|
+
"doNothing"
|
|
2028
|
+
]).optional(),
|
|
2029
|
+
/** Loose — re-validated through {@link modeDelaysFromPersisted}. */
|
|
2030
|
+
schedule: require_clip_model_registry.unknown().optional()
|
|
1979
2031
|
})).default([]),
|
|
1980
2032
|
/**
|
|
1981
2033
|
* Active alarm configuration profile id. Empty / absent on a blob older than
|
|
@@ -2047,12 +2099,18 @@ function settingsBlobFromPersisted(raw) {
|
|
|
2047
2099
|
if (typeof rec["exitDelaySec"] === "number") entry.exitDelaySec = rec["exitDelaySec"];
|
|
2048
2100
|
if (typeof rec["entryDelaySec"] === "number") entry.entryDelaySec = rec["entryDelaySec"];
|
|
2049
2101
|
if (typeof rec["triggeredDurationSec"] === "number") entry.triggeredDurationSec = rec["triggeredDurationSec"];
|
|
2102
|
+
if (typeof rec["postTriggeredAction"] === "string") entry.postTriggeredAction = rec["postTriggeredAction"];
|
|
2103
|
+
if (rec["schedule"] !== void 0) entry.schedule = rec["schedule"];
|
|
2050
2104
|
modeDelaysLoose.push(entry);
|
|
2051
2105
|
}
|
|
2106
|
+
const truncatedTriggered = Math.max(0, Math.min(3600, Math.trunc(triggered)));
|
|
2107
|
+
const rawAction = raw["postTriggeredAction"];
|
|
2108
|
+
const postTriggeredAction = rawAction === "rearmAfter" || rawAction === "disableAfter" || rawAction === "doNothing" ? rawAction : void 0;
|
|
2052
2109
|
return {
|
|
2053
2110
|
exitDelaySec: Math.max(0, Math.min(600, Math.trunc(exit))),
|
|
2054
2111
|
entryDelaySec: Math.max(0, Math.min(600, Math.trunc(entry))),
|
|
2055
|
-
triggeredDurationSec:
|
|
2112
|
+
triggeredDurationSec: truncatedTriggered,
|
|
2113
|
+
...postTriggeredAction !== void 0 ? { postTriggeredAction } : {},
|
|
2056
2114
|
announceTargets,
|
|
2057
2115
|
nonBlocking: lists("nonBlocking"),
|
|
2058
2116
|
blocking: lists("blocking"),
|
|
@@ -2065,6 +2123,7 @@ function settingsToPersisted(settings) {
|
|
|
2065
2123
|
exitDelaySec: settings.exitDelaySec,
|
|
2066
2124
|
entryDelaySec: settings.entryDelaySec,
|
|
2067
2125
|
triggeredDurationSec: settings.triggeredDurationSec,
|
|
2126
|
+
...settings.postTriggeredAction !== void 0 ? { postTriggeredAction: settings.postTriggeredAction } : {},
|
|
2068
2127
|
announceTargets: [...settings.announceTargets],
|
|
2069
2128
|
nonBlocking: settings.nonBlocking.map((l) => ({
|
|
2070
2129
|
mode: l.mode,
|
|
@@ -2149,11 +2208,13 @@ function machineFromPersisted(raw) {
|
|
|
2149
2208
|
const armedAt = optionalInstant(rec["armedAt"]);
|
|
2150
2209
|
const triggerAt = optionalInstant(rec["triggerAt"]);
|
|
2151
2210
|
const clearAt = optionalInstant(rec["clearAt"]);
|
|
2211
|
+
const clearToDisarmed = rec["clearToDisarmed"] === true;
|
|
2152
2212
|
return {
|
|
2153
2213
|
target: rec["target"],
|
|
2154
2214
|
...armedAt !== void 0 ? { armedAt } : {},
|
|
2155
2215
|
...triggerAt !== void 0 ? { triggerAt } : {},
|
|
2156
2216
|
...clearAt !== void 0 ? { clearAt } : {},
|
|
2217
|
+
...clearToDisarmed ? { clearToDisarmed: true } : {},
|
|
2157
2218
|
fired: rec["fired"] === true,
|
|
2158
2219
|
changedAt: rec["changedAt"]
|
|
2159
2220
|
};
|
|
@@ -2193,14 +2254,48 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
2193
2254
|
super(ctx, ncAlarmPanelSchema, { type: require_clip_model_registry.DeviceType.AlarmPanel });
|
|
2194
2255
|
this.logDroppedModeDelays();
|
|
2195
2256
|
this.restore();
|
|
2257
|
+
this.migrateStockAfterTriggerDefaults();
|
|
2196
2258
|
this.registerAlarmCap();
|
|
2197
2259
|
this.publish();
|
|
2198
2260
|
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 } });
|
|
2261
|
+
if (this.config.get("announceTargets").length > 0) {
|
|
2262
|
+
this.ctx.logger.info("alarm announceTargets cleared — deliver via alarm rules only", {
|
|
2263
|
+
tags: { deviceId: this.id },
|
|
2264
|
+
meta: { cleared: this.config.get("announceTargets").length }
|
|
2265
|
+
});
|
|
2266
|
+
this.config.set("announceTargets", []);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* One-shot: the pre-D701 stock seed was `triggeredDurationSec: 0` + absent /
|
|
2271
|
+
* `doNothing` (stay triggered forever). That left After trigger looking dead
|
|
2272
|
+
* in the UI. Flip only that seed to {@link DEFAULT_DELAYS} — never a custom
|
|
2273
|
+
* duration or an explicit `disableAfter`.
|
|
2274
|
+
*/
|
|
2275
|
+
migrateStockAfterTriggerDefaults() {
|
|
2276
|
+
const duration = this.config.get("triggeredDurationSec");
|
|
2277
|
+
const action = this.config.get("postTriggeredAction");
|
|
2278
|
+
if (!(duration === 0 && (action === void 0 || action === "doNothing"))) return;
|
|
2279
|
+
this.config.set("triggeredDurationSec", DEFAULT_DELAYS.triggeredDurationSec);
|
|
2280
|
+
this.config.set("postTriggeredAction", DEFAULT_DELAYS.postTriggeredAction);
|
|
2281
|
+
this.ctx.logger.info("alarm after-trigger defaults migrated to rearmAfter 10s", {
|
|
2282
|
+
tags: { deviceId: this.id },
|
|
2283
|
+
meta: {
|
|
2284
|
+
fromDurationSec: duration,
|
|
2285
|
+
fromAction: action ?? "absent",
|
|
2286
|
+
toDurationSec: DEFAULT_DELAYS.triggeredDurationSec,
|
|
2287
|
+
toAction: DEFAULT_DELAYS.postTriggeredAction
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2199
2290
|
}
|
|
2200
2291
|
/** The operator-visible configuration, as the cap serves it. */
|
|
2201
2292
|
settings() {
|
|
2293
|
+
const storedAction = this.config.get("postTriggeredAction");
|
|
2202
2294
|
return {
|
|
2203
|
-
|
|
2295
|
+
exitDelaySec: this.config.get("exitDelaySec"),
|
|
2296
|
+
entryDelaySec: this.config.get("entryDelaySec"),
|
|
2297
|
+
triggeredDurationSec: this.config.get("triggeredDurationSec"),
|
|
2298
|
+
...storedAction !== void 0 ? { postTriggeredAction: storedAction } : {},
|
|
2204
2299
|
announceArm: this.config.get("announceArm"),
|
|
2205
2300
|
announceTargets: [...this.config.get("announceTargets")],
|
|
2206
2301
|
nonBlocking: modeDeviceListFromPersisted(this.config.get("nonBlocking")),
|
|
@@ -2349,6 +2444,7 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
2349
2444
|
if (patch.exitDelaySec !== void 0) await this.config.set("exitDelaySec", patch.exitDelaySec);
|
|
2350
2445
|
if (patch.entryDelaySec !== void 0) await this.config.set("entryDelaySec", patch.entryDelaySec);
|
|
2351
2446
|
if (patch.triggeredDurationSec !== void 0) await this.config.set("triggeredDurationSec", patch.triggeredDurationSec);
|
|
2447
|
+
if (patch.postTriggeredAction !== void 0) await this.config.set("postTriggeredAction", patch.postTriggeredAction);
|
|
2352
2448
|
if (patch.announceArm !== void 0) await this.config.set("announceArm", patch.announceArm);
|
|
2353
2449
|
if (patch.announceTargets !== void 0) await this.config.set("announceTargets", [...patch.announceTargets]);
|
|
2354
2450
|
if (membership !== void 0 && membershipChanged !== null) this.ctx.logger.info("alarm non-blocking list changed", {
|
|
@@ -2524,6 +2620,29 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
2524
2620
|
}
|
|
2525
2621
|
});
|
|
2526
2622
|
}
|
|
2623
|
+
if (edit.postTriggeredAction !== void 0) {
|
|
2624
|
+
const from = was?.postTriggeredAction ?? null;
|
|
2625
|
+
const to = now?.postTriggeredAction ?? null;
|
|
2626
|
+
if (from !== to) this.ctx.logger.info("alarm mode delays changed", {
|
|
2627
|
+
tags: { deviceId: this.id },
|
|
2628
|
+
meta: {
|
|
2629
|
+
mode: edit.mode,
|
|
2630
|
+
field: "postTriggeredAction",
|
|
2631
|
+
from,
|
|
2632
|
+
to
|
|
2633
|
+
}
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
if (edit.schedule !== void 0) {
|
|
2637
|
+
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", {
|
|
2638
|
+
tags: { deviceId: this.id },
|
|
2639
|
+
meta: {
|
|
2640
|
+
mode: edit.mode,
|
|
2641
|
+
cleared: edit.schedule === null,
|
|
2642
|
+
windows: now?.schedule?.windows.length ?? 0
|
|
2643
|
+
}
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2527
2646
|
}
|
|
2528
2647
|
}
|
|
2529
2648
|
/** One INFO line per persisted per-mode delay entry the guard refused to trust. */
|
|
@@ -2582,6 +2701,13 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
2582
2701
|
*/
|
|
2583
2702
|
async armWith(mode) {
|
|
2584
2703
|
const now = Date.now();
|
|
2704
|
+
if (!this.modeScheduleAllows(mode, now)) {
|
|
2705
|
+
this.ctx.logger.info("alarm arm refused — outside activation schedule", {
|
|
2706
|
+
tags: { deviceId: this.id },
|
|
2707
|
+
meta: { mode }
|
|
2708
|
+
});
|
|
2709
|
+
return { outcome: "outside-schedule" };
|
|
2710
|
+
}
|
|
2585
2711
|
const read = await this.applyArmGuard(mode);
|
|
2586
2712
|
if (read.kind === "unguarded") return this.settleArm(mode, null, now);
|
|
2587
2713
|
if (read.kind === "check-failed") {
|
|
@@ -2595,6 +2721,11 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
2595
2721
|
why: this.exclusionWhyFor(mode, device)
|
|
2596
2722
|
})), now);
|
|
2597
2723
|
}
|
|
2724
|
+
/** Absent schedule = always allowed (`isScheduleActive`). */
|
|
2725
|
+
modeScheduleAllows(mode, now) {
|
|
2726
|
+
const schedule = modeDelaysFromPersisted(this.config.get("modeDelays")).entries.find((e) => e.mode === mode)?.schedule;
|
|
2727
|
+
return require_clip_model_registry.isScheduleActive(schedule, now);
|
|
2728
|
+
}
|
|
2598
2729
|
/**
|
|
2599
2730
|
* The Arm anyway on ONE refusal's notification — the only path that arms
|
|
2600
2731
|
* around a device that blocks.
|
|
@@ -2620,6 +2751,17 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
2620
2751
|
const reason = refusalId === this.spentRefusalId ? "used" : "unknown-refusal";
|
|
2621
2752
|
return this.rejectArmAnyway(refusalId, reason);
|
|
2622
2753
|
}
|
|
2754
|
+
if (!this.modeScheduleAllows(held.mode, now)) {
|
|
2755
|
+
this.refusal = null;
|
|
2756
|
+
this.ctx.logger.info("alarm arm-anyway refused — outside activation schedule", {
|
|
2757
|
+
tags: { deviceId: this.id },
|
|
2758
|
+
meta: {
|
|
2759
|
+
mode: held.mode,
|
|
2760
|
+
refusalId
|
|
2761
|
+
}
|
|
2762
|
+
});
|
|
2763
|
+
return { outcome: "outside-schedule" };
|
|
2764
|
+
}
|
|
2623
2765
|
if (this.machine.changedAt !== held.panelChangedAt) {
|
|
2624
2766
|
this.refusal = null;
|
|
2625
2767
|
return this.rejectArmAnyway(refusalId, "stale");
|
|
@@ -3015,11 +3157,12 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
3015
3157
|
* rather than fired only once somebody looks.
|
|
3016
3158
|
*/
|
|
3017
3159
|
publish(now = Date.now()) {
|
|
3160
|
+
const autoCleared = this.machine.clearAt !== void 0 && now >= this.machine.clearAt && this.machine.clearToDisarmed === true;
|
|
3018
3161
|
this.machine = settle(this.machine, now);
|
|
3019
3162
|
const state = stateAt(this.machine, now);
|
|
3020
3163
|
this.runtimeState.setCapState("alarm-panel", this.status(now));
|
|
3021
3164
|
this.persist();
|
|
3022
|
-
this.report(state, now);
|
|
3165
|
+
this.report(state, now, autoCleared);
|
|
3023
3166
|
if (this.machine.armedAt !== void 0 || this.machine.triggerAt !== void 0 || this.machine.clearAt !== void 0) this.startTicking();
|
|
3024
3167
|
else this.stopTicking();
|
|
3025
3168
|
}
|
|
@@ -3031,7 +3174,7 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
3031
3174
|
* panel's own state update with it would make a delivery failure look like
|
|
3032
3175
|
* an alarm failure.
|
|
3033
3176
|
*/
|
|
3034
|
-
report(state, now) {
|
|
3177
|
+
report(state, now, autoCleared = false) {
|
|
3035
3178
|
const previous = this.lastPublished;
|
|
3036
3179
|
this.lastPublished = state;
|
|
3037
3180
|
const hook = this.onPublish;
|
|
@@ -3046,7 +3189,8 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
3046
3189
|
at: now,
|
|
3047
3190
|
...source !== null ? { source } : {},
|
|
3048
3191
|
...arming !== void 0 ? { arming } : {},
|
|
3049
|
-
...pending !== void 0 ? { pending } : {}
|
|
3192
|
+
...pending !== void 0 ? { pending } : {},
|
|
3193
|
+
...autoCleared ? { autoCleared: true } : {}
|
|
3050
3194
|
});
|
|
3051
3195
|
if (transition?.kind === "alarm-triggered") this.attribution.clear();
|
|
3052
3196
|
try {
|
|
@@ -3132,8 +3276,12 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
3132
3276
|
}
|
|
3133
3277
|
/** The global durations plus the per-mode overrides, read from the persisted config NOW. */
|
|
3134
3278
|
delaySettings() {
|
|
3279
|
+
const storedAction = this.config.get("postTriggeredAction");
|
|
3135
3280
|
return {
|
|
3136
|
-
|
|
3281
|
+
exitDelaySec: this.config.get("exitDelaySec"),
|
|
3282
|
+
entryDelaySec: this.config.get("entryDelaySec"),
|
|
3283
|
+
triggeredDurationSec: this.config.get("triggeredDurationSec"),
|
|
3284
|
+
...storedAction !== void 0 ? { postTriggeredAction: storedAction } : {},
|
|
3137
3285
|
modeDelays: modeDelaysFromPersisted(this.config.get("modeDelays")).entries
|
|
3138
3286
|
};
|
|
3139
3287
|
}
|
|
@@ -3143,24 +3291,32 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
3143
3291
|
*/
|
|
3144
3292
|
delaysFor(mode) {
|
|
3145
3293
|
const settings = this.delaySettings();
|
|
3146
|
-
if (mode === null)
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3294
|
+
if (mode === null) {
|
|
3295
|
+
const delays = this.readDelays();
|
|
3296
|
+
return {
|
|
3297
|
+
exitDelaySec: delays.exitDelaySec,
|
|
3298
|
+
entryDelaySec: delays.entryDelaySec,
|
|
3299
|
+
triggeredDurationSec: delays.triggeredDurationSec,
|
|
3300
|
+
postTriggeredAction: delays.postTriggeredAction,
|
|
3301
|
+
source: {
|
|
3302
|
+
exitDelaySec: "default",
|
|
3303
|
+
entryDelaySec: "default",
|
|
3304
|
+
triggeredDurationSec: "default",
|
|
3305
|
+
postTriggeredAction: "default"
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3156
3309
|
return require_clip_model_registry.resolveModeDelays(settings, mode);
|
|
3157
3310
|
}
|
|
3158
|
-
/** The
|
|
3311
|
+
/** The global durations + post-triggered action, read from the persisted config. */
|
|
3159
3312
|
readDelays() {
|
|
3313
|
+
const triggeredDurationSec = this.config.get("triggeredDurationSec");
|
|
3314
|
+
const stored = this.config.get("postTriggeredAction");
|
|
3160
3315
|
return {
|
|
3161
3316
|
exitDelaySec: this.config.get("exitDelaySec"),
|
|
3162
3317
|
entryDelaySec: this.config.get("entryDelaySec"),
|
|
3163
|
-
triggeredDurationSec
|
|
3318
|
+
triggeredDurationSec,
|
|
3319
|
+
postTriggeredAction: require_clip_model_registry.derivePostTriggeredAction(stored, triggeredDurationSec)
|
|
3164
3320
|
};
|
|
3165
3321
|
}
|
|
3166
3322
|
startTicking() {
|
|
@@ -5141,7 +5297,7 @@ function ncClassNoun(texts, className, count) {
|
|
|
5141
5297
|
count,
|
|
5142
5298
|
vars: { name: className }
|
|
5143
5299
|
});
|
|
5144
|
-
if (require_clip_model_registry.isEventKindLabelKey(normalised)) return require_clip_model_registry.eventKindLabel(normalised, texts.language);
|
|
5300
|
+
if (require_clip_model_registry.isEventKindLabelKey(normalised)) return require_clip_model_registry.eventKindLabel(normalised, texts.language).toLocaleLowerCase(texts.language);
|
|
5145
5301
|
return texts.text({
|
|
5146
5302
|
key: "class.other",
|
|
5147
5303
|
count,
|
|
@@ -5149,13 +5305,24 @@ function ncClassNoun(texts, className, count) {
|
|
|
5149
5305
|
});
|
|
5150
5306
|
}
|
|
5151
5307
|
/**
|
|
5152
|
-
* The
|
|
5153
|
-
*
|
|
5154
|
-
*
|
|
5308
|
+
* The ONE capitalisation the notification body gets: its first letter, after
|
|
5309
|
+
* every catalog fragment and placeholder has already been rendered lowercase.
|
|
5310
|
+
* Title, fragments and mid-sentence summaries are never passed through here.
|
|
5155
5311
|
*/
|
|
5156
|
-
function
|
|
5157
|
-
if (
|
|
5158
|
-
|
|
5312
|
+
function ncSentenceCaseBody(body) {
|
|
5313
|
+
if (body.length === 0) return body;
|
|
5314
|
+
const first = body[0];
|
|
5315
|
+
const upper = first.toLocaleUpperCase();
|
|
5316
|
+
if (upper === first) return body;
|
|
5317
|
+
return `${upper}${body.slice(1)}`;
|
|
5318
|
+
}
|
|
5319
|
+
/**
|
|
5320
|
+
* True when `label` is a taxonomy / classification id (species, COCO sub, …)
|
|
5321
|
+
* rather than a person name or plate. Those REPLACE the coarse class in the
|
|
5322
|
+
* subject — "dog" not "animal (dog)", "cane" not "animale (cane)".
|
|
5323
|
+
*/
|
|
5324
|
+
function isClassificationLabel(label) {
|
|
5325
|
+
return require_clip_model_registry.isEventKindLabelKey(label.trim().toLowerCase());
|
|
5159
5326
|
}
|
|
5160
5327
|
/** `person (John)` / `persona (John)` — the thing the notification is about. */
|
|
5161
5328
|
function ncSubjectText(texts, input) {
|
|
@@ -5164,11 +5331,15 @@ function ncSubjectText(texts, input) {
|
|
|
5164
5331
|
key: "detection.subject",
|
|
5165
5332
|
vars: { classNoun }
|
|
5166
5333
|
});
|
|
5334
|
+
if (input.labelKind !== "identity" && input.labelKind !== "plate" && isClassificationLabel(input.label)) return texts.text({
|
|
5335
|
+
key: "detection.subject",
|
|
5336
|
+
vars: { classNoun: ncClassNoun(texts, input.label, 1) }
|
|
5337
|
+
});
|
|
5167
5338
|
return texts.text({
|
|
5168
5339
|
key: subjectLabelKey(input.labelKind),
|
|
5169
5340
|
vars: {
|
|
5170
5341
|
classNoun,
|
|
5171
|
-
label:
|
|
5342
|
+
label: input.label
|
|
5172
5343
|
}
|
|
5173
5344
|
});
|
|
5174
5345
|
}
|
|
@@ -5758,7 +5929,7 @@ function isAdoptable(record, nowMs) {
|
|
|
5758
5929
|
* what `localDayMinute` does for the same input — one behaviour for a bad
|
|
5759
5930
|
* zone, not two.
|
|
5760
5931
|
*/
|
|
5761
|
-
function clockOf$
|
|
5932
|
+
function clockOf$7(atMs, timeZone) {
|
|
5762
5933
|
if (timeZone !== void 0) try {
|
|
5763
5934
|
return new Intl.DateTimeFormat("en-GB", {
|
|
5764
5935
|
timeZone,
|
|
@@ -5894,7 +6065,7 @@ var TimelapseRenderer = class {
|
|
|
5894
6065
|
includeAudio: false,
|
|
5895
6066
|
maxLifeMs: await this.ports.exportLifeMs(),
|
|
5896
6067
|
deleteAfterDownload: false,
|
|
5897
|
-
title: `${request.ruleName} ${clockOf$
|
|
6068
|
+
title: `${request.ruleName} ${clockOf$7(window.startMs, request.timezone)}→${clockOf$7(window.endMs, request.timezone)}`,
|
|
5898
6069
|
timelapse: {
|
|
5899
6070
|
everyMs,
|
|
5900
6071
|
outputFps: request.framerate,
|
|
@@ -6225,7 +6396,7 @@ var TimelapseRenderer = class {
|
|
|
6225
6396
|
...meta,
|
|
6226
6397
|
unservedWindowCount: verdict.unservedWindows.length,
|
|
6227
6398
|
unservedWindows: verdict.unservedWindows.slice(0, 12).map((w) => ({
|
|
6228
|
-
at: clockOf$
|
|
6399
|
+
at: clockOf$7(request.window.startMs + w.fromSec * 1e3, request.timezone),
|
|
6229
6400
|
fromSec: Math.round(w.fromSec),
|
|
6230
6401
|
toSec: Math.round(w.toSec),
|
|
6231
6402
|
expectedDenseFrames: w.expectedDenseFrames,
|
|
@@ -8806,6 +8977,7 @@ function buildActionButtons(input) {
|
|
|
8806
8977
|
sequenceId: button.sequenceId,
|
|
8807
8978
|
actionId: button.id
|
|
8808
8979
|
}),
|
|
8980
|
+
mode: "background",
|
|
8809
8981
|
...button.icon !== void 0 ? { icon: button.icon } : {},
|
|
8810
8982
|
...button.destructive !== void 0 ? { destructive: button.destructive } : {}
|
|
8811
8983
|
});
|
|
@@ -8921,10 +9093,9 @@ var MAX_ARM_BUTTONS = 3;
|
|
|
8921
9093
|
* (`ALARM_DISARM_TTL_MS`). Both exist: the hint is for the person, the TTL is
|
|
8922
9094
|
* for the forwarded screenshot.
|
|
8923
9095
|
*
|
|
8924
|
-
*
|
|
8925
|
-
*
|
|
8926
|
-
*
|
|
8927
|
-
* confirmed.
|
|
9096
|
+
* Every alarm button is `background` — Zentik (and ntfy) fire a POST and leave
|
|
9097
|
+
* the lock screen alone. Opening a browser to arm or disarm costs more
|
|
9098
|
+
* attention than the notification did.
|
|
8928
9099
|
*
|
|
8929
9100
|
* `panel.availableModes` ARE the enabled modes: CamStack has no per-mode
|
|
8930
9101
|
* disable setting, so the Arm buttons offer exactly what the panel declares.
|
|
@@ -8958,14 +9129,14 @@ function buildAlarmButtons(input) {
|
|
|
8958
9129
|
}),
|
|
8959
9130
|
icon: "disarm",
|
|
8960
9131
|
destructive: true,
|
|
8961
|
-
mode: "
|
|
9132
|
+
mode: "background"
|
|
8962
9133
|
}];
|
|
8963
9134
|
}
|
|
8964
9135
|
/**
|
|
8965
9136
|
* The one Arm anyway a refusal notification carries — or none, when the row
|
|
8966
|
-
* names no refusal.
|
|
8967
|
-
*
|
|
8968
|
-
*
|
|
9137
|
+
* names no refusal. Destructive + background: it arms around an open door via
|
|
9138
|
+
* POST (Zentik BACKGROUND_CALL), same as disarm. Its TTL is the refusal's own
|
|
9139
|
+
* (`ARM_ANYWAY_TTL_MS`, priced by the minter).
|
|
8969
9140
|
*/
|
|
8970
9141
|
function armAnywayButton(refusalId, input) {
|
|
8971
9142
|
if (refusalId === void 0) return [];
|
|
@@ -8980,7 +9151,7 @@ function armAnywayButton(refusalId, input) {
|
|
|
8980
9151
|
}),
|
|
8981
9152
|
icon: "arm",
|
|
8982
9153
|
destructive: true,
|
|
8983
|
-
mode: "
|
|
9154
|
+
mode: "background"
|
|
8984
9155
|
}];
|
|
8985
9156
|
}
|
|
8986
9157
|
//#endregion
|
|
@@ -12902,6 +13073,10 @@ function alarmTextKeys(transition, body) {
|
|
|
12902
13073
|
titleKey,
|
|
12903
13074
|
bodyKey: `system.${kind}.body.${transition.sensor.event}`
|
|
12904
13075
|
};
|
|
13076
|
+
if (kind === "alarm-disarmed" && transition.autoCleared === true) return {
|
|
13077
|
+
titleKey,
|
|
13078
|
+
bodyKey: `system.${kind}.body.autoCleared`
|
|
13079
|
+
};
|
|
12905
13080
|
if (kind === "alarm-disarmed" && (transition.previous === "triggered" || transition.previous === "pending")) return {
|
|
12906
13081
|
titleKey,
|
|
12907
13082
|
bodyKey: `system.${kind}.body.defused`
|
|
@@ -14621,13 +14796,14 @@ var en_default = {
|
|
|
14621
14796
|
"system.alarm-arming.title.away": "Arming away — {{seconds}}s",
|
|
14622
14797
|
"system.alarm-arming.title.night": "Arming night — {{seconds}}s",
|
|
14623
14798
|
"system.alarm-arming.body": "Arming",
|
|
14624
|
-
"system.alarm-armed.title.home": "
|
|
14625
|
-
"system.alarm-armed.title.away": "
|
|
14626
|
-
"system.alarm-armed.title.night": "
|
|
14799
|
+
"system.alarm-armed.title.home": "Alarm armed: home",
|
|
14800
|
+
"system.alarm-armed.title.away": "Alarm armed: away",
|
|
14801
|
+
"system.alarm-armed.title.night": "Alarm armed: night",
|
|
14627
14802
|
"system.alarm-armed.body": "Alarm armed",
|
|
14628
14803
|
"system.alarm-disarmed.title": "Disarmed",
|
|
14629
14804
|
"system.alarm-disarmed.body": "Alarm disarmed",
|
|
14630
14805
|
"system.alarm-disarmed.body.defused": "Alarm disarmed during an alarm",
|
|
14806
|
+
"system.alarm-disarmed.body.autoCleared": "Alarm disarmed automatically after the siren",
|
|
14631
14807
|
"system.alarm-triggered.title": "ALARM",
|
|
14632
14808
|
"system.alarm-triggered.body": "Alarm triggered",
|
|
14633
14809
|
"system.alarm-triggered.body.rule": "Triggered by {{rule}} on {{deviceName}}",
|
|
@@ -14949,13 +15125,14 @@ var it_default = {
|
|
|
14949
15125
|
"system.alarm-arming.title.away": "Inserimento Fuori — {{seconds}}s",
|
|
14950
15126
|
"system.alarm-arming.title.night": "Inserimento Notte — {{seconds}}s",
|
|
14951
15127
|
"system.alarm-arming.body": "Inserimento in corso",
|
|
14952
|
-
"system.alarm-armed.title.home": "
|
|
14953
|
-
"system.alarm-armed.title.away": "
|
|
14954
|
-
"system.alarm-armed.title.night": "
|
|
15128
|
+
"system.alarm-armed.title.home": "Allarme inserito: Casa",
|
|
15129
|
+
"system.alarm-armed.title.away": "Allarme inserito: Fuori",
|
|
15130
|
+
"system.alarm-armed.title.night": "Allarme inserito: Notte",
|
|
14955
15131
|
"system.alarm-armed.body": "Allarme inserito",
|
|
14956
15132
|
"system.alarm-disarmed.title": "Disinserito",
|
|
14957
15133
|
"system.alarm-disarmed.body": "Allarme disinserito",
|
|
14958
15134
|
"system.alarm-disarmed.body.defused": "Allarme disinserito durante un allarme",
|
|
15135
|
+
"system.alarm-disarmed.body.autoCleared": "Allarme disinserito automaticamente dopo la sirena",
|
|
14959
15136
|
"system.alarm-triggered.title": "ALLARME",
|
|
14960
15137
|
"system.alarm-triggered.body": "Allarme scattato",
|
|
14961
15138
|
"system.alarm-triggered.body.rule": "Scattato da {{rule}} su {{deviceName}}",
|
|
@@ -23958,7 +24135,7 @@ var NcDispatcher = class {
|
|
|
23958
24135
|
...systemCount,
|
|
23959
24136
|
...systemEvent?.body !== void 0 ? { frozen: systemEvent.body } : {}
|
|
23960
24137
|
});
|
|
23961
|
-
return composeRuleAiBody({
|
|
24138
|
+
return ncSentenceCaseBody(composeRuleAiBody({
|
|
23962
24139
|
aiText,
|
|
23963
24140
|
body: entry.payload.synthetic === true ? this.renderText({
|
|
23964
24141
|
key: "test.prefix",
|
|
@@ -23966,7 +24143,7 @@ var NcDispatcher = class {
|
|
|
23966
24143
|
frozen: rendered
|
|
23967
24144
|
}) : rendered,
|
|
23968
24145
|
templateNamesAi
|
|
23969
|
-
});
|
|
24146
|
+
}));
|
|
23970
24147
|
};
|
|
23971
24148
|
const body = renderBody("");
|
|
23972
24149
|
const resolved = await this.resolveAttachments(entry);
|
|
@@ -25146,15 +25323,18 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
|
|
|
25146
25323
|
const systemEvent = subject.systemEvent;
|
|
25147
25324
|
const detection = systemEvent?.alarmSource?.className !== void 0 ? systemEvent.alarmSource : void 0;
|
|
25148
25325
|
const group = readOutboxGroup(entry.payload.group);
|
|
25326
|
+
const locale = texts.language;
|
|
25327
|
+
const cameraName = deviceName.toLocaleLowerCase(locale);
|
|
25328
|
+
const zonesLower = zoneLabels.map((z) => z.toLocaleLowerCase(locale));
|
|
25149
25329
|
return {
|
|
25150
|
-
camera:
|
|
25330
|
+
camera: cameraName,
|
|
25151
25331
|
class: detection?.className ?? (systemEvent !== void 0 ? "" : subject.className),
|
|
25152
25332
|
classLabel: (() => {
|
|
25153
25333
|
const wire = detection?.className ?? (systemEvent !== void 0 ? "" : subject.className);
|
|
25154
|
-
return wire.length === 0 ? "" : require_clip_model_registry.eventKindLabel(wire,
|
|
25334
|
+
return wire.length === 0 ? "" : require_clip_model_registry.eventKindLabel(wire, locale).toLocaleLowerCase(locale);
|
|
25155
25335
|
})(),
|
|
25156
25336
|
label: detection?.label ?? subject.label ?? "",
|
|
25157
|
-
zones:
|
|
25337
|
+
zones: zonesLower.join(", "),
|
|
25158
25338
|
subject: detection?.className !== void 0 ? ncSubjectText(texts, {
|
|
25159
25339
|
className: detection.className,
|
|
25160
25340
|
...detection.label !== void 0 ? { label: detection.label } : {}
|
|
@@ -25164,9 +25344,9 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
|
|
|
25164
25344
|
...subject.labelKind !== void 0 ? { labelKind: subject.labelKind } : {}
|
|
25165
25345
|
}),
|
|
25166
25346
|
detectionSummary: group !== void 0 && group.members.length > 1 ? ncDetectionSummary(texts, classCounts(group.members)) : "",
|
|
25167
|
-
inZones: ncInZonesText(texts,
|
|
25168
|
-
scope: occupancy !== void 0 ? occupancy.zone ??
|
|
25169
|
-
zone: occupancy?.zone ?? zoneLabels[0] ?? "",
|
|
25347
|
+
inZones: ncInZonesText(texts, zonesLower),
|
|
25348
|
+
scope: occupancy !== void 0 ? occupancy.zone?.toLocaleLowerCase(locale) ?? cameraName : "",
|
|
25349
|
+
zone: (occupancy?.zone ?? zoneLabels[0] ?? "").toLocaleLowerCase(locale),
|
|
25170
25350
|
confidence: percentOf(detection?.confidence ?? subject.confidence),
|
|
25171
25351
|
time: ncClockText(texts.language, subject.timestamp, timeZone),
|
|
25172
25352
|
sounds: subject.audio?.labels.join(", ") ?? "",
|
|
@@ -29664,7 +29844,7 @@ function buildRuleAiSystemPrompt(shape, language, maxWords, grouped = false) {
|
|
|
29664
29844
|
].join("");
|
|
29665
29845
|
}
|
|
29666
29846
|
/** `HH:MM` in the host timezone. */
|
|
29667
|
-
function clockOf$
|
|
29847
|
+
function clockOf$6(atMs) {
|
|
29668
29848
|
const d = new Date(atMs);
|
|
29669
29849
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
29670
29850
|
}
|
|
@@ -29702,7 +29882,7 @@ function buildRuleAiPrompt(input) {
|
|
|
29702
29882
|
if (shape === "detection-track") {
|
|
29703
29883
|
lines.push(`Camera: ${camera}.${located} Subject: ${subject}. The stills, in order:`);
|
|
29704
29884
|
for (const [i, frame] of input.frames.entries()) {
|
|
29705
|
-
const when = frame.atMs !== void 0 ? ` · ${clockOf$
|
|
29885
|
+
const when = frame.atMs !== void 0 ? ` · ${clockOf$6(frame.atMs)}` : "";
|
|
29706
29886
|
lines.push(`${String(i + 1)}.${when}`);
|
|
29707
29887
|
}
|
|
29708
29888
|
return lines.join("\n");
|
|
@@ -31473,7 +31653,7 @@ var DEFAULT_PROMPT_EN = "What happened during this period? Summarise it for a ho
|
|
|
31473
31653
|
*/
|
|
31474
31654
|
var SummaryAnswerSchema = require_clip_model_registry.object({ summary: require_clip_model_registry.string() });
|
|
31475
31655
|
/** `HH:MM` in the host timezone — the two numbers a frame label needs. */
|
|
31476
|
-
function clockOf$
|
|
31656
|
+
function clockOf$5(atMs) {
|
|
31477
31657
|
const d = new Date(atMs);
|
|
31478
31658
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
31479
31659
|
}
|
|
@@ -31496,7 +31676,7 @@ function buildSummaryAiPrompt(input, frames) {
|
|
|
31496
31676
|
const roster = frames.map((f, i) => {
|
|
31497
31677
|
const location = plainLabel(f.location ?? "");
|
|
31498
31678
|
const where = location.length > 0 ? ` (${location})` : "";
|
|
31499
|
-
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$
|
|
31679
|
+
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$5(f.atMs)} · ${plainVocabulary(f.className) || "subject"}`;
|
|
31500
31680
|
}).join("\n");
|
|
31501
31681
|
const described = /* @__PURE__ */ new Map();
|
|
31502
31682
|
for (const f of frames) {
|
|
@@ -32099,7 +32279,7 @@ var NO_DETECTIONS$1 = {
|
|
|
32099
32279
|
* has to check is a key somebody can get wrong. */
|
|
32100
32280
|
var BODY_SEPARATOR$1 = " · ";
|
|
32101
32281
|
/** `HH:MM` in the host timezone. */
|
|
32102
|
-
function clockOf$
|
|
32282
|
+
function clockOf$4(atMs) {
|
|
32103
32283
|
const d = new Date(atMs);
|
|
32104
32284
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
32105
32285
|
}
|
|
@@ -32113,8 +32293,8 @@ function clockOf$3(atMs) {
|
|
|
32113
32293
|
function summaryTemplateVars(input) {
|
|
32114
32294
|
return {
|
|
32115
32295
|
rule: input.ruleName,
|
|
32116
|
-
from: clockOf$
|
|
32117
|
-
to: clockOf$
|
|
32296
|
+
from: clockOf$4(input.window.startMs),
|
|
32297
|
+
to: clockOf$4(input.window.endMs),
|
|
32118
32298
|
time: ncClockText(input.texts.language, input.generatedAt),
|
|
32119
32299
|
events: String(input.tileCount),
|
|
32120
32300
|
cameras: String(input.cameraCount),
|
|
@@ -32209,11 +32389,11 @@ function summaryRecordId(ruleId, windowEndMs) {
|
|
|
32209
32389
|
/** The caption burned onto the mosaic's title band, with the same `{{var}}`
|
|
32210
32390
|
* vocabulary the title and body use. `''` is a real answer ("no caption"). */
|
|
32211
32391
|
function summaryCaptionText(input) {
|
|
32212
|
-
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$
|
|
32392
|
+
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$4(input.window.startMs)}→${clockOf$4(input.window.endMs)}`;
|
|
32213
32393
|
return renderTemplate(input.captionText, {
|
|
32214
32394
|
rule: input.ruleName,
|
|
32215
|
-
from: clockOf$
|
|
32216
|
-
to: clockOf$
|
|
32395
|
+
from: clockOf$4(input.window.startMs),
|
|
32396
|
+
to: clockOf$4(input.window.endMs)
|
|
32217
32397
|
}) ?? "";
|
|
32218
32398
|
}
|
|
32219
32399
|
/**
|
|
@@ -33145,7 +33325,7 @@ var NcSummaryProducer = class {
|
|
|
33145
33325
|
purpose: "summary",
|
|
33146
33326
|
tiles: tiles.map(({ candidate, jpeg }) => ({
|
|
33147
33327
|
jpeg,
|
|
33148
|
-
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$
|
|
33328
|
+
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$3(candidate.firstSeen)} · ${candidate.className}` } : {},
|
|
33149
33329
|
deviceId: candidate.deviceId,
|
|
33150
33330
|
sourceId: candidate.trackId
|
|
33151
33331
|
})),
|
|
@@ -33368,7 +33548,7 @@ var NcSummaryProducer = class {
|
|
|
33368
33548
|
}
|
|
33369
33549
|
};
|
|
33370
33550
|
/** `HH:MM` in the host timezone — the two numbers a tile label needs. */
|
|
33371
|
-
function clockOf$
|
|
33551
|
+
function clockOf$3(atMs) {
|
|
33372
33552
|
const d = new Date(atMs);
|
|
33373
33553
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
33374
33554
|
}
|
|
@@ -33868,6 +34048,99 @@ var NcCalendarEventsStore = class {
|
|
|
33868
34048
|
}
|
|
33869
34049
|
};
|
|
33870
34050
|
//#endregion
|
|
34051
|
+
//#region src/notification-center/calendar-events/calendar-events-body.ts
|
|
34052
|
+
/** Visual weight on the tomorrow header — Telegram/Zentik plain text. */
|
|
34053
|
+
var CALENDAR_EVENTS_TOMORROW_MARK = "★";
|
|
34054
|
+
/** Separates the tomorrow block from the rest of the horizon. */
|
|
34055
|
+
var CALENDAR_EVENTS_DAY_DELIMITER = "────────";
|
|
34056
|
+
var MAX_EVENT_LINES = 40;
|
|
34057
|
+
function pad2(n) {
|
|
34058
|
+
return String(n).padStart(2, "0");
|
|
34059
|
+
}
|
|
34060
|
+
/** Local calendar day key `YYYY-MM-DD`. */
|
|
34061
|
+
function localDayKey(atMs) {
|
|
34062
|
+
const d = new Date(atMs);
|
|
34063
|
+
return `${String(d.getFullYear())}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
34064
|
+
}
|
|
34065
|
+
function startOfLocalDay(atMs) {
|
|
34066
|
+
const d = new Date(atMs);
|
|
34067
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
34068
|
+
}
|
|
34069
|
+
function addLocalDays(atMs, days) {
|
|
34070
|
+
const d = new Date(atMs);
|
|
34071
|
+
d.setDate(d.getDate() + days);
|
|
34072
|
+
return d.getTime();
|
|
34073
|
+
}
|
|
34074
|
+
function dayHeadingFor(dayStartMs, todayKey, tomorrowKey) {
|
|
34075
|
+
const key = localDayKey(dayStartMs);
|
|
34076
|
+
const label = new Date(dayStartMs).toLocaleDateString(void 0, {
|
|
34077
|
+
weekday: "short",
|
|
34078
|
+
month: "short",
|
|
34079
|
+
day: "numeric"
|
|
34080
|
+
});
|
|
34081
|
+
if (key === todayKey) return `Today — ${label}`;
|
|
34082
|
+
if (key === tomorrowKey) return `${CALENDAR_EVENTS_TOMORROW_MARK} Tomorrow — ${label}`;
|
|
34083
|
+
return label;
|
|
34084
|
+
}
|
|
34085
|
+
/** `HH:MM` local, or `YYYY-MM-DD HH:MM` when the end falls on another day. */
|
|
34086
|
+
function clockOf$2(atMs) {
|
|
34087
|
+
const d = new Date(atMs);
|
|
34088
|
+
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
34089
|
+
}
|
|
34090
|
+
function formatEventLine(event) {
|
|
34091
|
+
const when = event.allDay === true ? "All day" : `${clockOf$2(event.startMs)}–${clockOf$2(event.endMs)}`;
|
|
34092
|
+
const where = event.location !== void 0 && event.location.length > 0 ? ` @ ${event.location}` : "";
|
|
34093
|
+
return `• ${when} ${event.title}${where}`;
|
|
34094
|
+
}
|
|
34095
|
+
function groupByLocalDay(events) {
|
|
34096
|
+
const map = /* @__PURE__ */ new Map();
|
|
34097
|
+
for (const ev of events) {
|
|
34098
|
+
const dayKey = localDayKey(ev.startMs);
|
|
34099
|
+
const existing = map.get(dayKey);
|
|
34100
|
+
if (existing !== void 0) {
|
|
34101
|
+
existing.events.push(ev);
|
|
34102
|
+
continue;
|
|
34103
|
+
}
|
|
34104
|
+
map.set(dayKey, {
|
|
34105
|
+
dayKey,
|
|
34106
|
+
dayStartMs: startOfLocalDay(ev.startMs),
|
|
34107
|
+
events: [ev]
|
|
34108
|
+
});
|
|
34109
|
+
}
|
|
34110
|
+
return [...map.values()].toSorted((a, b) => a.dayStartMs - b.dayStartMs);
|
|
34111
|
+
}
|
|
34112
|
+
/**
|
|
34113
|
+
* Build the digest body.
|
|
34114
|
+
*
|
|
34115
|
+
* @param nowMs — fire time; tomorrow is the local calendar day after this.
|
|
34116
|
+
*/
|
|
34117
|
+
function formatCalendarEventsBody(events, nowMs, horizonDays) {
|
|
34118
|
+
if (events.length === 0) return `No events in the next ${String(horizonDays)} day(s).`;
|
|
34119
|
+
const todayKey = localDayKey(nowMs);
|
|
34120
|
+
const tomorrowKey = localDayKey(addLocalDays(startOfLocalDay(nowMs), 1));
|
|
34121
|
+
const buckets = groupByLocalDay(events);
|
|
34122
|
+
const today = buckets.filter((b) => b.dayKey === todayKey);
|
|
34123
|
+
const tomorrow = buckets.filter((b) => b.dayKey === tomorrowKey);
|
|
34124
|
+
const later = buckets.filter((b) => b.dayKey !== todayKey && b.dayKey !== tomorrowKey);
|
|
34125
|
+
const lead = [...today, ...tomorrow];
|
|
34126
|
+
const lines = [];
|
|
34127
|
+
let emitted = 0;
|
|
34128
|
+
const pushBucket = (bucket) => {
|
|
34129
|
+
if (emitted >= MAX_EVENT_LINES) return;
|
|
34130
|
+
lines.push(dayHeadingFor(bucket.dayStartMs, todayKey, tomorrowKey));
|
|
34131
|
+
for (const ev of bucket.events) {
|
|
34132
|
+
if (emitted >= MAX_EVENT_LINES) break;
|
|
34133
|
+
lines.push(formatEventLine(ev));
|
|
34134
|
+
emitted += 1;
|
|
34135
|
+
}
|
|
34136
|
+
};
|
|
34137
|
+
for (const bucket of lead) pushBucket(bucket);
|
|
34138
|
+
if (lead.length > 0 && later.length > 0) lines.push(CALENDAR_EVENTS_DAY_DELIMITER);
|
|
34139
|
+
for (const bucket of later) pushBucket(bucket);
|
|
34140
|
+
if (events.length > MAX_EVENT_LINES) lines.push(`…and ${String(events.length - MAX_EVENT_LINES)} more`);
|
|
34141
|
+
return lines.join("\n");
|
|
34142
|
+
}
|
|
34143
|
+
//#endregion
|
|
33871
34144
|
//#region src/notification-center/calendar-events/calendar-events-delivery.ts
|
|
33872
34145
|
function calendarEventsRecordId(ruleId, windowEndMs) {
|
|
33873
34146
|
return `calendar-events:${ruleId}:${windowEndMs}`;
|
|
@@ -33877,16 +34150,8 @@ function clockOf$1(atMs) {
|
|
|
33877
34150
|
const d = new Date(atMs);
|
|
33878
34151
|
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")}`}`;
|
|
33879
34152
|
}
|
|
33880
|
-
function formatEventLine(event) {
|
|
33881
|
-
const when = event.allDay ? clockOf$1(event.startMs).slice(0, 10) : `${clockOf$1(event.startMs)}–${clockOf$1(event.endMs)}`;
|
|
33882
|
-
const where = event.location !== void 0 && event.location.length > 0 ? ` @ ${event.location}` : "";
|
|
33883
|
-
return `• ${when} ${event.title}${where}`;
|
|
33884
|
-
}
|
|
33885
34153
|
function derivedBody$1(input) {
|
|
33886
|
-
|
|
33887
|
-
const lines = input.events.slice(0, 40).map(formatEventLine);
|
|
33888
|
-
const extra = input.events.length > 40 ? `\n…and ${String(input.events.length - 40)} more` : "";
|
|
33889
|
-
return lines.join("\n") + extra;
|
|
34154
|
+
return formatCalendarEventsBody(input.events, input.eventWindow.startMs, input.horizonDays);
|
|
33890
34155
|
}
|
|
33891
34156
|
function templateVars(input) {
|
|
33892
34157
|
return {
|
|
@@ -39332,19 +39597,21 @@ var NotificationCenter = class {
|
|
|
39332
39597
|
}
|
|
39333
39598
|
/**
|
|
39334
39599
|
* Start photographing every camera that links `sensorId`, and return the
|
|
39335
|
-
* owners the sensor-level evaluation names — or `undefined` when there is
|
|
39336
|
-
*
|
|
39337
|
-
*
|
|
39338
|
-
*
|
|
39339
|
-
*
|
|
39340
|
-
*
|
|
39341
|
-
*
|
|
39342
|
-
*
|
|
39343
|
-
* never fires on a sensor).
|
|
39344
|
-
*
|
|
39345
|
-
*
|
|
39346
|
-
*
|
|
39347
|
-
*
|
|
39600
|
+
* owners the sensor-level evaluation names — or `undefined` when there is
|
|
39601
|
+
* nothing to photograph (`cameraIds` empty), nothing to show (every linked
|
|
39602
|
+
* camera muted), the event will be dropped as muted (the SENSOR is muted,
|
|
39603
|
+
* or there were row cameras and every one is muted — see the evaluation's
|
|
39604
|
+
* mute gate), or no enabled device-event rule that could fire on this
|
|
39605
|
+
* sensor KIND wants a picture (the gate is the RULE's media policy, scope
|
|
39606
|
+
* and kind conditions, read from the in-memory mirror — D49, as
|
|
39607
|
+
* `audioStillOwner`; an occupancy rule, which rides the same delivery,
|
|
39608
|
+
* never fires on a sensor). One linked camera is enough: link alone owns
|
|
39609
|
+
* notification media; tracked rows are a separate timeline question. Coarse
|
|
39610
|
+
* on purpose, in one direction only: a capture for a rule whose other
|
|
39611
|
+
* conditions then fail is one wasted snapshot, a missing capture is a
|
|
39612
|
+
* notification without its picture. The kind check is what keeps a chatty
|
|
39613
|
+
* multi-linked motion sensor from composing a mosaic on every edge for a
|
|
39614
|
+
* doorbell rule.
|
|
39348
39615
|
*
|
|
39349
39616
|
* A MUTED camera (D62) is never dialled: its tile is left out, said on that
|
|
39350
39617
|
* camera (once per window), and the primary is the first linked camera that
|
|
@@ -39357,7 +39624,7 @@ var NotificationCenter = class {
|
|
|
39357
39624
|
beginLinkedStills(request) {
|
|
39358
39625
|
const { sensorId, sensorKind, cameraIds, atMs, rowCameraIds, sensorRaised } = request;
|
|
39359
39626
|
const shelf = this.linkedStills;
|
|
39360
|
-
if (shelf === null || !this.evaluationActive || cameraIds.length
|
|
39627
|
+
if (shelf === null || !this.evaluationActive || cameraIds.length === 0) return void 0;
|
|
39361
39628
|
if (this.deviceMutes.isMuted(sensorId)) return void 0;
|
|
39362
39629
|
const rowShown = this.unmutedCameras(rowCameraIds);
|
|
39363
39630
|
if (rowCameraIds.length > 0 && rowShown.length === 0) return void 0;
|
|
@@ -40656,10 +40923,8 @@ var NotificationCenter = class {
|
|
|
40656
40923
|
* On the SAME serialized chain as every other evaluation, because both of
|
|
40657
40924
|
* those follow-ups read what the evaluation just enqueued:
|
|
40658
40925
|
*
|
|
40659
|
-
* -
|
|
40660
|
-
*
|
|
40661
|
-
* rule has not already delivered it, or a target that is both would get
|
|
40662
|
-
* the same sentence twice;
|
|
40926
|
+
* - Panel `announceTargets` are NOT addressed (retired 2026-10-03): only
|
|
40927
|
+
* alarm-family rules deliver. The announce enqueue is a no-op.
|
|
40663
40928
|
* - a TRIGGERED with an attribution supersedes the triggering rule's own
|
|
40664
40929
|
* still-pending rows, for exactly the targets the alarm reached. That is
|
|
40665
40930
|
* the combined notification: one row carrying the alarm's words and that
|
|
@@ -40692,11 +40957,11 @@ var NotificationCenter = class {
|
|
|
40692
40957
|
});
|
|
40693
40958
|
}
|
|
40694
40959
|
/**
|
|
40695
|
-
* The
|
|
40696
|
-
*
|
|
40697
|
-
* `
|
|
40698
|
-
*
|
|
40699
|
-
*
|
|
40960
|
+
* The composed body for an alarm transition (when the words depend on the
|
|
40961
|
+
* rule set). Delivery targets are ALWAYS empty here: the panel no longer
|
|
40962
|
+
* fans out to `announceTargets` — an alarm-family rule is the only notifier
|
|
40963
|
+
* (operator ruling 2026-10-03). `announceTargets` remains on the settings
|
|
40964
|
+
* blob for schema compatibility and is ignored at send time.
|
|
40700
40965
|
*
|
|
40701
40966
|
* - ARM and ARMING compose from the SAME coverage — "what does this mode
|
|
40702
40967
|
* actually watch" — because they are the same answer given thirty seconds
|
|
@@ -40707,14 +40972,9 @@ var NotificationCenter = class {
|
|
|
40707
40972
|
* Every other kind is `null`: the intake picks its catalog body and params.
|
|
40708
40973
|
*/
|
|
40709
40974
|
alarmAnnouncement(panel, transition) {
|
|
40710
|
-
const targetIds = panel.settings().announceTargets;
|
|
40711
|
-
if (targetIds.length === 0) this.logger.info("alarm transition has no announcement target", {
|
|
40712
|
-
tags: { deviceId: panel.deviceId },
|
|
40713
|
-
meta: { kind: transition.kind }
|
|
40714
|
-
});
|
|
40715
40975
|
return {
|
|
40716
40976
|
text: this.alarmAnnouncementText(panel, transition),
|
|
40717
|
-
targetIds
|
|
40977
|
+
targetIds: []
|
|
40718
40978
|
};
|
|
40719
40979
|
}
|
|
40720
40980
|
/** The composed body of {@link alarmAnnouncement}, or null for a catalog body. */
|
|
@@ -41721,14 +41981,19 @@ var NotificationCenter = class {
|
|
|
41721
41981
|
}
|
|
41722
41982
|
sequenceActionsOf = (sequenceId) => this.sequences.get(sequenceId)?.actions;
|
|
41723
41983
|
/**
|
|
41724
|
-
* Run this rule's onTrigger / onTriggerOff bindings for this match
|
|
41984
|
+
* Run this rule's onTrigger / onTriggerOff bindings for this match, and —
|
|
41985
|
+
* when the rule is gated on `armed_*` — auto-trigger the CamStack
|
|
41986
|
+
* alarm-panel (never sirens).
|
|
41725
41987
|
*
|
|
41726
41988
|
* Called BEFORE the cooldown gate — see the call site for why. Everything
|
|
41727
41989
|
* here is fire-and-forget and nothing may throw into the evaluation.
|
|
41728
41990
|
*/
|
|
41729
41991
|
runRuleActions(rule, subject, kind, controls) {
|
|
41730
|
-
if (
|
|
41731
|
-
const
|
|
41992
|
+
if (controls.runActions === false) return;
|
|
41993
|
+
const triggerOff = this.isTriggerOffSubject(subject);
|
|
41994
|
+
if (!triggerOff) this.autoTriggerAlarmPanel(rule, subject, kind);
|
|
41995
|
+
if (this.actionRunner === null) return;
|
|
41996
|
+
const bindings = triggerOff ? rule.actions?.onTriggerOff : rule.actions?.onTrigger;
|
|
41732
41997
|
if (bindings === void 0 || bindings.length === 0) return;
|
|
41733
41998
|
this.actionRunner.runBindings({
|
|
41734
41999
|
ruleId: rule.id,
|
|
@@ -41740,6 +42005,41 @@ var NotificationCenter = class {
|
|
|
41740
42005
|
});
|
|
41741
42006
|
}
|
|
41742
42007
|
/**
|
|
42008
|
+
* A rule gated on `armed_*` fires the CamStack panel on match — no
|
|
42009
|
+
* `onTrigger` sequence required. Sirens are never actuated here.
|
|
42010
|
+
*/
|
|
42011
|
+
autoTriggerAlarmPanel(rule, subject, kind) {
|
|
42012
|
+
if (!require_clip_model_registry.ruleAutoTriggersAlarm(rule)) return;
|
|
42013
|
+
const panel = this.alarmPanel;
|
|
42014
|
+
if (panel === null) {
|
|
42015
|
+
this.logger.warn("alarm rule matched but no alarm panel is wired — panel not triggered", {
|
|
42016
|
+
tags: { deviceId: subject.deviceId },
|
|
42017
|
+
meta: {
|
|
42018
|
+
ruleId: rule.id,
|
|
42019
|
+
rule: rule.name
|
|
42020
|
+
}
|
|
42021
|
+
});
|
|
42022
|
+
return;
|
|
42023
|
+
}
|
|
42024
|
+
this.actuateWithAttribution({
|
|
42025
|
+
deviceId: panel.deviceId,
|
|
42026
|
+
cap: ALARM_PANEL_CAP,
|
|
42027
|
+
method: "trigger",
|
|
42028
|
+
args: {},
|
|
42029
|
+
origin: fireRefFor(rule, subject, kind)
|
|
42030
|
+
}).catch((err) => {
|
|
42031
|
+
this.logger.warn("alarm panel auto-trigger failed", {
|
|
42032
|
+
tags: { deviceId: subject.deviceId },
|
|
42033
|
+
meta: {
|
|
42034
|
+
ruleId: rule.id,
|
|
42035
|
+
rule: rule.name,
|
|
42036
|
+
panelId: panel.deviceId,
|
|
42037
|
+
error: err instanceof Error ? err.message : String(err)
|
|
42038
|
+
}
|
|
42039
|
+
});
|
|
42040
|
+
});
|
|
42041
|
+
}
|
|
42042
|
+
/**
|
|
41743
42043
|
* The muted device that drops this subject, or `undefined` when none does.
|
|
41744
42044
|
* A per-camera subject: its camera. A linked set: the sensor; else, when
|
|
41745
42045
|
* the change took rows and every row camera is muted, the sensor too — the
|
|
@@ -104137,8 +104437,22 @@ var NO_PUBLICATION = {
|
|
|
104137
104437
|
url: null,
|
|
104138
104438
|
artifactId: null
|
|
104139
104439
|
};
|
|
104140
|
-
|
|
104141
|
-
|
|
104440
|
+
/**
|
|
104441
|
+
* A tRPC-shaped router or procedure reached by DYNAMIC name off `ctx.api`.
|
|
104442
|
+
*
|
|
104443
|
+
* A rule names its cap and method as strings, so the generated client cannot be
|
|
104444
|
+
* indexed statically. The walk down `api → router → procedure → mutate|query`
|
|
104445
|
+
* is therefore guarded hop by hop rather than asserted: every hop that is not
|
|
104446
|
+
* what it must be raises the named error the rule author needs to see.
|
|
104447
|
+
*
|
|
104448
|
+
* Forked-addon `ctx.api` namespaces are tRPC v11 Proxies over a noop function
|
|
104449
|
+
* (`typeof === 'function'`). Treating only plain objects as routers made every
|
|
104450
|
+
* sequence step fail with `no router for capability "alarm-panel"` while the
|
|
104451
|
+
* same method answered fine over HTTP — the panel never left `armed_*`, so
|
|
104452
|
+
* HomeKit stayed quiet.
|
|
104453
|
+
*/
|
|
104454
|
+
function isCapNode(value) {
|
|
104455
|
+
return typeof value === "object" && value !== null || typeof value === "function";
|
|
104142
104456
|
}
|
|
104143
104457
|
function isCapCall(value) {
|
|
104144
104458
|
return typeof value === "function";
|
|
@@ -107257,10 +107571,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
107257
107571
|
},
|
|
107258
107572
|
actuate: async ({ deviceId, cap, method, args }) => {
|
|
107259
107573
|
const router = Reflect.get(api, require_clip_model_registry.kebabToCamel(cap));
|
|
107260
|
-
if (!
|
|
107261
|
-
const proc = router
|
|
107262
|
-
const
|
|
107263
|
-
|
|
107574
|
+
if (!isCapNode(router)) throw new Error(`rule action: no router for capability "${cap}"`);
|
|
107575
|
+
const proc = Reflect.get(router, method);
|
|
107576
|
+
const mutate = isCapNode(proc) ? Reflect.get(proc, "mutate") : void 0;
|
|
107577
|
+
const query = isCapNode(proc) ? Reflect.get(proc, "query") : void 0;
|
|
107578
|
+
const fn = isCapCall(mutate) ? mutate : isCapCall(query) ? query : void 0;
|
|
107579
|
+
if (fn === void 0) throw new Error(`rule action: "${cap}.${method}" is not a callable cap method`);
|
|
107264
107580
|
await fn({
|
|
107265
107581
|
deviceId,
|
|
107266
107582
|
...args
|
|
@@ -113513,7 +113829,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
113513
113829
|
});
|
|
113514
113830
|
return NO_SENSOR_NOTIFICATION_CAMERAS;
|
|
113515
113831
|
});
|
|
113516
|
-
const linkedSet = cameras.mediaCameraIds.length > 1;
|
|
113832
|
+
const linkedSet = cameras.mediaCameraIds.length > 1 || cameras.mediaCameraIds.length === 1 && cameras.rowCameraIds.length === 0;
|
|
113517
113833
|
const linkedStills = linkedSet ? this.startLinkedStills(data, timestamp, cameras) : void 0;
|
|
113518
113834
|
const persisted = [];
|
|
113519
113835
|
const unowned = [];
|
|
@@ -113693,14 +114009,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
113693
114009
|
* which verdict swallowed it.
|
|
113694
114010
|
*/
|
|
113695
114011
|
flushSensorTransitionDrops() {
|
|
113696
|
-
for (const drop of this.sensorTransitions.takeDropSummary(Date.now()))
|
|
113697
|
-
|
|
113698
|
-
|
|
113699
|
-
|
|
113700
|
-
|
|
113701
|
-
|
|
113702
|
-
|
|
113703
|
-
|
|
114012
|
+
for (const drop of this.sensorTransitions.takeDropSummary(Date.now())) {
|
|
114013
|
+
const level = drop.reason === "baseline-seeded-inactive" || drop.reason === "no-change" ? "debug" : "info";
|
|
114014
|
+
this.ctx.logger[level]("sensor state changes dropped — no transition", {
|
|
114015
|
+
tags: { deviceId: drop.deviceId },
|
|
114016
|
+
meta: {
|
|
114017
|
+
capName: drop.capName,
|
|
114018
|
+
reason: drop.reason,
|
|
114019
|
+
count: drop.count
|
|
114020
|
+
}
|
|
114021
|
+
});
|
|
114022
|
+
}
|
|
113704
114023
|
}
|
|
113705
114024
|
/**
|
|
113706
114025
|
* One `info` line per (device, cap) per window for the evaluations refused
|