@camstack/addon-post-analysis 1.2.320 → 1.2.322
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-C_aKxHpy.js → clip-model-registry-BFbP9U7z.js} +427 -0
- package/dist/{clip-model-registry-C-IEAIQQ.mjs → clip-model-registry-CwK2SLQD.mjs} +374 -1
- 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-D553qWb_.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Dmt5eh93.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-mzYK6V9S.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-CuOcvcK7.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-ByDyL2zi.mjs → hostInit-DtKO_7UT.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +916 -50
- package/dist/pipeline-analytics/index.mjs +916 -50
- 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-7e_3hsiC.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-C1V_UEyk.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as NC_ALARM_DELAY_MAX_SEC, $n as isEventOwnerType, $r as defineCustomActions, $t as SCENE_DEFAULT_QUIET_SECONDS, A as EMBEDDING_REQUIREMENTS_FAILED, An as consumableLowPercentOf, Ar as servedTemplateVars, At as NcRuleUpdateInputSchema, B as ListTrackRelationsInputSchema, Br as templateVarsFor, Bt as NcTemplateVarDescriptorSchema, C as DEFAULT_EVENT_COLOR, Cn as applyNonBlockingEdit, Cr as ruleBelongsToAlarm$1, Ct as NcAlarmSettingsPatchSchema, D as DeclaredDevices, Dn as audioModeOf, Dr as sceneMonitorCapability, Dt as NcRulePatchSchema, E as DETECTION_PIPELINE_CAP_NAME, En as audioMetricsCapability, Er as ruleKindOf, Et as NcRuleInputSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as describeSystemEventDeliveryProblem, Fr as systemEventFamilyProblems, Ft as NcStateSummarySectionIdSchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as faceGalleryCapability, Gr as evaluateSensorEdge, Gt as RECORDING_SOURCE_CAMSTACK_ADDON, H as LockStateSchema, Hn as encodeVectorBase64, Hr as zoneAnalyticsCapability, Ht as OpsLogEntrySchema, I as FIRST_LEVEL_MACRO_CLASSES, In as describeSystemEventFamilyProblem, Ir as systemEventFilterApplies, It as NcSystemEventKindSchema, J as MOTION_CLOSE_AFTER_MS, Jn as gifFromMp4Args, Jr as errMsg, Jt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, K as MAX_TRACK_PURITIES, Kn as failureContributionCapability, Kr as isSourceCap, Kt as RebuildObjectEmbeddingsRefusalSchema, L as FULL_IMAGE_BBOX, Lr as systemEventSummaryScheduleOf, Lt as NcTaxonomySchema, M as EVENT_DENSITY_BATCH_MAX, Mn as createAlarmProfile, Mr as subKindsOf, Mt as NcSnoozeInputSchema, N as EVENT_KIND_BY_CAP, Nn as deleteAlarmProfile, Nr as syncActiveAlarmProfileSettings, Nt as NcSnoozeSchema, O as DetectionSourceSchema, On as buildEventKindDescriptor, Or as secureActuationFor, Ot as NcRuleSchema, P as EVENT_OWNER_TYPES, Pn as deriveRecordingMode, Pr as systemEventDeliveryProblems, Pt as NcSnoozeSuppressedSchema, Q as NC_ALARM_DELAY_FIELDS, Qn as isDetectionMacroClass, Qr as customAction, Qt as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, R as FailureCounters, Rr as systemEventSummarySplitOf, Rt as NcTemplatePreviewInputSchema, S as CoverStateSchema, Si as EventCategory, Sn as applyModeDelaysEdits, St as NcAlarmSensorsSchema, T as DETECTION_MACRO_CLASSES, Tn as assertTimelapseCadences, Tr as ruleGroupingApplies, Tt as NcConditionsSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Un as ensureAlarmProfiles, Ur as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, V as ListTrackRelationsResultSchema, Vr as vectorDimFromBase64, Vt as OPS_LOG_DEFAULT_LIMIT, W as MAX_BIRTH_DECISION_RECORDS, Wn as evictionPolicyOfLocation, Wr as canonicalHash, Wt as RECORDING_EXPORT_MAX_READ_BYTES, X as MotionTriggeredBySchema, Xn as isAudioClassName, Xr as CamProfileSchema, Xt as SCENE_DEFAULT_ANCHOR_THRESHOLD, Y as MediaFileKindEnum, Yn as isAlarmControllableCap, Yr as BaseAddon, Yt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, Z as NC_ALARM_DEFAULT_PROFILE_ID, Zn as isDefaultAlarmProfileId, Zr as createEvent, Zt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, _ as BaseDevice, _i as string, _n as activateAlarmProfile, _r as readTimelapseGeneratedAt, _t as NC_TANK_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, ai as ZodObject, an as SetTrackPurityResultSchema, ar as isSystemEventSummaryRule, at as NC_CLIP_PRE_ROLL_DEFAULT_SEC, b as ClearTrackRelationInputSchema, bi as union, bn as alarmPanelCapability, br as resolveLocationMode, bt as NcAlarmConfigSchema, c as resolveClusterModelPin, ci as boolean, cn as TRACK_RELATIONS_DEFAULT_LIMIT, cr as mayWriteToLocation, ct as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, di as looseObject, dn as TimelapseRuleSchema, dr as occupancyScope, dt as NC_GROUPING_DEFAULT_SEC, ei as hydrateSchema, en as SCENE_DEFAULT_UNCOVERED_POLICY, er as isKnownTemplateVar, et as NC_ALARM_SYSTEM_EVENT_KINDS, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fi as number, fn as TrackPuritySchema, ft as NC_GROUPING_MAX_SEC, g as BIRTH_DECISION_DEFAULT_LIMIT, gi as strictObject, gn as TrackSourceSchema, gr as readMotionTriggeredBy, gt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, h as ArchivedDebugNoteSchema, hi as record, hn as TrackSideSchema, hr as readDeviceStateFrom, ht as NC_SNOOZE_MAX_MINUTES, ii as sleep$1, in as SetTrackPurityInputSchema, ir as isSystemDelivery, it as NC_CLIP_POST_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as cosineSimilarity$1, jr as storageOccupancyCapability, jt as NcScheduleSchema, k as EMBEDDING_MODEL_FAILED, kn as cloneAlarmSettings, kr as sensorRaisedOf, kt as NcRuleTargetSchema, l as APPEARANCE_BIN_COUNT, li as discriminatedUnion, ln as TimelapseRuleInputSchema, lr as memoizePinnedFfmpeg, lt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, m as AlarmArmModeSchema, mi as partialRecord, mn as TrackRelationSchema, mr as plateGalleryCapability, mt as NC_SENSOR_TRANSITION_KINDS, ni as nodePin, nn as SCENE_RESET_RECAPTURES, nr as isRetiredClearKey, nt as NC_AUDIO_CONFIRM_HITS_DEFAULT, o as FACE_EMBEDDING_STEP_ID, oi as _enum, on as SetTrackRelationInputSchema, or as isVectorIndexDimConflict, ot as NC_CLIP_RETENTION_MAX_SEC, p as AUDIO_MACRO_LABELS, pi as object, pn as TrackRelationKindSchema, pr as pipelineAnalyticsCapability, pt as NC_GROUPING_MIN_SEC, q as MAX_TRACK_RELATIONS, qn as gifDeriveSpeed, qr as DeviceType, qt as RetrainStatusSchema, rn as SceneMonitorSchema, rr as isScheduleActive, rt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, s as readClusterModelPinStrict, si as array, sn as TIMELAPSE_DENSE_FLOOR_SEC, sr as kebabToCamel, st as NC_CONDITION_CATALOG, t as BUILTIN_CLIP_MODELS, ti as isDeviceScopedCap, tn as SCENE_DIVERGED, tr as isMediaOwnerType, tt as NC_ALARM_TRIGGERED_MAX_SEC, u as APPEARANCE_BLOCKS, ui as literal, un as TimelapseRulePatchSchema, ur as notificationRulesCapability, ut as NC_DEFAULT_SNOOZE_MINUTES, v as BirthDecisionRecordSchema, vi as templateLiteral, vn as addonWidgetsSourceCapability, vr as renameAlarmProfile, vt as NC_TAXONOMY, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as applySecureOnArmEdit, wr as ruleDeviceCompatCapQueries, wt as NcConditionDescriptorSchema, x as ClearTrackRelationResultSchema, xi as unknown, xn as applyBlockingEdit, xr as resolveModeDelays, xt as NcAlarmSensorsInputSchema, y as COCO_TO_MACRO, yi as tuple, yn as alarmDeviceSecureOnArm, yr as resolveClipSearchMinScore, yt as NC_TEMPLATE_VARS, z as LabelAttributionSchema, zn as embeddingRefusalOf, zr as templateCarriesSummary, zt as NcTemplatePreviewSchema } from "../clip-model-registry-CwK2SLQD.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";
|
|
@@ -1941,6 +1941,21 @@ var ncAlarmPanelSchema = object({
|
|
|
1941
1941
|
exitDelaySec: number().optional(),
|
|
1942
1942
|
entryDelaySec: number().optional(),
|
|
1943
1943
|
triggeredDurationSec: number().optional()
|
|
1944
|
+
})).default([]),
|
|
1945
|
+
/**
|
|
1946
|
+
* Active alarm configuration profile id. Empty / absent on a blob older than
|
|
1947
|
+
* profiles → seeded to Default on first read via {@link ensureAlarmProfiles}.
|
|
1948
|
+
*/
|
|
1949
|
+
activeProfileId: string().default(NC_ALARM_DEFAULT_PROFILE_ID),
|
|
1950
|
+
/**
|
|
1951
|
+
* Named settings blobs. Loose on purpose — each profile's settings are
|
|
1952
|
+
* re-validated through {@link settingsBlobFromPersisted} so a corrupt entry
|
|
1953
|
+
* drops rather than taking down the panel.
|
|
1954
|
+
*/
|
|
1955
|
+
profiles: array(object({
|
|
1956
|
+
id: string(),
|
|
1957
|
+
name: string(),
|
|
1958
|
+
settings: record(string(), unknown())
|
|
1944
1959
|
})).default([])
|
|
1945
1960
|
});
|
|
1946
1961
|
/**
|
|
@@ -1963,6 +1978,106 @@ function modeDeviceListFromPersisted(raw) {
|
|
|
1963
1978
|
return lists;
|
|
1964
1979
|
}
|
|
1965
1980
|
/**
|
|
1981
|
+
* Rebuild a trusted {@link NcAlarmSettings} from a persisted profile blob.
|
|
1982
|
+
* Unknown / corrupt fields fall back to system-safe empties via the flat-key
|
|
1983
|
+
* readers the panel already uses for the live settings.
|
|
1984
|
+
*/
|
|
1985
|
+
function settingsBlobFromPersisted(raw) {
|
|
1986
|
+
const exit = typeof raw["exitDelaySec"] === "number" ? raw["exitDelaySec"] : DEFAULT_DELAYS.exitDelaySec;
|
|
1987
|
+
const entry = typeof raw["entryDelaySec"] === "number" ? raw["entryDelaySec"] : DEFAULT_DELAYS.entryDelaySec;
|
|
1988
|
+
const triggered = typeof raw["triggeredDurationSec"] === "number" ? raw["triggeredDurationSec"] : DEFAULT_DELAYS.triggeredDurationSec;
|
|
1989
|
+
const announceTargets = Array.isArray(raw["announceTargets"]) ? raw["announceTargets"].filter((t) => typeof t === "string") : [];
|
|
1990
|
+
const lists = (key) => {
|
|
1991
|
+
const v = raw[key];
|
|
1992
|
+
if (!Array.isArray(v)) return [];
|
|
1993
|
+
const rows = [];
|
|
1994
|
+
for (const item of v) {
|
|
1995
|
+
if (typeof item !== "object" || item === null) continue;
|
|
1996
|
+
const rec = item;
|
|
1997
|
+
if (typeof rec["mode"] !== "string" || !Array.isArray(rec["deviceIds"])) continue;
|
|
1998
|
+
rows.push({
|
|
1999
|
+
mode: rec["mode"],
|
|
2000
|
+
deviceIds: rec["deviceIds"].filter((id) => typeof id === "number")
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
return modeDeviceListFromPersisted(rows);
|
|
2004
|
+
};
|
|
2005
|
+
const modeDelaysRaw = Array.isArray(raw["modeDelays"]) ? raw["modeDelays"] : [];
|
|
2006
|
+
const modeDelaysLoose = [];
|
|
2007
|
+
for (const item of modeDelaysRaw) {
|
|
2008
|
+
if (typeof item !== "object" || item === null) continue;
|
|
2009
|
+
const rec = item;
|
|
2010
|
+
if (typeof rec["mode"] !== "string") continue;
|
|
2011
|
+
const entry = { mode: rec["mode"] };
|
|
2012
|
+
if (typeof rec["exitDelaySec"] === "number") entry.exitDelaySec = rec["exitDelaySec"];
|
|
2013
|
+
if (typeof rec["entryDelaySec"] === "number") entry.entryDelaySec = rec["entryDelaySec"];
|
|
2014
|
+
if (typeof rec["triggeredDurationSec"] === "number") entry.triggeredDurationSec = rec["triggeredDurationSec"];
|
|
2015
|
+
modeDelaysLoose.push(entry);
|
|
2016
|
+
}
|
|
2017
|
+
return {
|
|
2018
|
+
exitDelaySec: Math.max(0, Math.min(600, Math.trunc(exit))),
|
|
2019
|
+
entryDelaySec: Math.max(0, Math.min(600, Math.trunc(entry))),
|
|
2020
|
+
triggeredDurationSec: Math.max(0, Math.min(3600, Math.trunc(triggered))),
|
|
2021
|
+
announceTargets,
|
|
2022
|
+
nonBlocking: lists("nonBlocking"),
|
|
2023
|
+
blocking: lists("blocking"),
|
|
2024
|
+
secureOnArmOff: lists("secureOnArmOff"),
|
|
2025
|
+
modeDelays: modeDelaysFromPersisted(modeDelaysLoose).entries
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
function settingsToPersisted(settings) {
|
|
2029
|
+
return {
|
|
2030
|
+
exitDelaySec: settings.exitDelaySec,
|
|
2031
|
+
entryDelaySec: settings.entryDelaySec,
|
|
2032
|
+
triggeredDurationSec: settings.triggeredDurationSec,
|
|
2033
|
+
announceTargets: [...settings.announceTargets],
|
|
2034
|
+
nonBlocking: settings.nonBlocking.map((l) => ({
|
|
2035
|
+
mode: l.mode,
|
|
2036
|
+
deviceIds: [...l.deviceIds]
|
|
2037
|
+
})),
|
|
2038
|
+
blocking: settings.blocking.map((l) => ({
|
|
2039
|
+
mode: l.mode,
|
|
2040
|
+
deviceIds: [...l.deviceIds]
|
|
2041
|
+
})),
|
|
2042
|
+
secureOnArmOff: settings.secureOnArmOff.map((l) => ({
|
|
2043
|
+
mode: l.mode,
|
|
2044
|
+
deviceIds: [...l.deviceIds]
|
|
2045
|
+
})),
|
|
2046
|
+
modeDelays: settings.modeDelays.map((e) => ({ ...e }))
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
function profilesStateFromPersisted(activeId, rows, current) {
|
|
2050
|
+
const profiles = rows.filter((r) => r.id.length > 0 && r.name.trim().length > 0).map((r) => ({
|
|
2051
|
+
id: r.id,
|
|
2052
|
+
name: r.name,
|
|
2053
|
+
settings: settingsBlobFromPersisted(r.settings)
|
|
2054
|
+
}));
|
|
2055
|
+
return ensureAlarmProfiles(profiles.length > 0 ? {
|
|
2056
|
+
activeId,
|
|
2057
|
+
profiles
|
|
2058
|
+
} : null, current);
|
|
2059
|
+
}
|
|
2060
|
+
function profilesToPersisted(state) {
|
|
2061
|
+
return {
|
|
2062
|
+
activeProfileId: state.activeId,
|
|
2063
|
+
profiles: state.profiles.map((p) => ({
|
|
2064
|
+
id: p.id,
|
|
2065
|
+
name: p.name,
|
|
2066
|
+
settings: settingsToPersisted(p.settings)
|
|
2067
|
+
}))
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
function profilesViewOf(state) {
|
|
2071
|
+
return {
|
|
2072
|
+
activeId: state.activeId,
|
|
2073
|
+
items: state.profiles.map((p) => ({
|
|
2074
|
+
id: p.id,
|
|
2075
|
+
name: p.name,
|
|
2076
|
+
isDefault: isDefaultAlarmProfileId(p.id)
|
|
2077
|
+
}))
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
/**
|
|
1966
2081
|
* The log line for a trigger the state machine ignored, per state. `trigger`
|
|
1967
2082
|
* ignores four: disarmed, arming (the exit delay), and pending / triggered
|
|
1968
2083
|
* (already counting down or going off — a second sensor must not restart it).
|
|
@@ -2210,7 +2325,134 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2210
2325
|
changed: membershipChanged
|
|
2211
2326
|
}
|
|
2212
2327
|
});
|
|
2213
|
-
|
|
2328
|
+
const settled = this.settings();
|
|
2329
|
+
await this.persistProfilesState(syncActiveAlarmProfileSettings(this.readProfilesState(), settled));
|
|
2330
|
+
return settled;
|
|
2331
|
+
}
|
|
2332
|
+
/** Named profiles + which one is active — for {@link NcAlarmConfig.profiles}. */
|
|
2333
|
+
profilesView() {
|
|
2334
|
+
return profilesViewOf(this.readProfilesState());
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Create a named profile (system defaults, or clone). Does not activate.
|
|
2338
|
+
* `newId` is assigned by the caller (usually `randomUUID`).
|
|
2339
|
+
*/
|
|
2340
|
+
async createProfile(input) {
|
|
2341
|
+
const result = createAlarmProfile(this.readProfilesState(), {
|
|
2342
|
+
name: input.name,
|
|
2343
|
+
id: input.newId,
|
|
2344
|
+
source: input.source
|
|
2345
|
+
});
|
|
2346
|
+
if (result.kind === "refused") throw this.profilesRefuseError(result.reason, result.detail);
|
|
2347
|
+
await this.persistProfilesState(result.state);
|
|
2348
|
+
this.ctx.logger.info("alarm profile created", {
|
|
2349
|
+
tags: { deviceId: this.id },
|
|
2350
|
+
meta: {
|
|
2351
|
+
profileId: input.newId,
|
|
2352
|
+
name: input.name.trim(),
|
|
2353
|
+
source: input.source.kind
|
|
2354
|
+
}
|
|
2355
|
+
});
|
|
2356
|
+
return profilesViewOf(result.state);
|
|
2357
|
+
}
|
|
2358
|
+
async renameProfile(id, name) {
|
|
2359
|
+
const result = renameAlarmProfile(this.readProfilesState(), {
|
|
2360
|
+
id,
|
|
2361
|
+
name
|
|
2362
|
+
});
|
|
2363
|
+
if (result.kind === "refused") throw this.profilesRefuseError(result.reason, result.detail);
|
|
2364
|
+
await this.persistProfilesState(result.state);
|
|
2365
|
+
this.ctx.logger.info("alarm profile renamed", {
|
|
2366
|
+
tags: { deviceId: this.id },
|
|
2367
|
+
meta: {
|
|
2368
|
+
profileId: id,
|
|
2369
|
+
name: name.trim()
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
return profilesViewOf(result.state);
|
|
2373
|
+
}
|
|
2374
|
+
/**
|
|
2375
|
+
* Delete a non-default profile. If it was active, Default becomes active and
|
|
2376
|
+
* its settings are applied to the flat keys (arm path).
|
|
2377
|
+
*/
|
|
2378
|
+
async deleteProfile(id) {
|
|
2379
|
+
const before = this.readProfilesState();
|
|
2380
|
+
const result = deleteAlarmProfile(before, id);
|
|
2381
|
+
if (result.kind === "refused") throw this.profilesRefuseError(result.reason, result.detail);
|
|
2382
|
+
const switched = before.activeId === id;
|
|
2383
|
+
await this.persistProfilesState(result.state);
|
|
2384
|
+
if (switched) {
|
|
2385
|
+
const def = result.state.profiles.find((p) => p.id === NC_ALARM_DEFAULT_PROFILE_ID);
|
|
2386
|
+
if (def !== void 0) await this.replaceFlatSettings(def.settings);
|
|
2387
|
+
}
|
|
2388
|
+
this.ctx.logger.info("alarm profile deleted", {
|
|
2389
|
+
tags: { deviceId: this.id },
|
|
2390
|
+
meta: {
|
|
2391
|
+
profileId: id,
|
|
2392
|
+
activatedDefault: switched
|
|
2393
|
+
}
|
|
2394
|
+
});
|
|
2395
|
+
return profilesViewOf(result.state);
|
|
2396
|
+
}
|
|
2397
|
+
/**
|
|
2398
|
+
* Make a profile active and apply its blob onto the flat settings the arm
|
|
2399
|
+
* path reads.
|
|
2400
|
+
*/
|
|
2401
|
+
async activateProfile(id) {
|
|
2402
|
+
const result = activateAlarmProfile(syncActiveAlarmProfileSettings(this.readProfilesState(), this.settings()), id);
|
|
2403
|
+
if (result.kind === "refused") throw this.profilesRefuseError(result.reason, result.detail);
|
|
2404
|
+
const next = result.state.profiles.find((p) => p.id === id);
|
|
2405
|
+
if (next === void 0) throw this.profilesRefuseError("not-found", id);
|
|
2406
|
+
await this.persistProfilesState(result.state);
|
|
2407
|
+
await this.replaceFlatSettings(next.settings);
|
|
2408
|
+
this.ctx.logger.info("alarm profile activated", {
|
|
2409
|
+
tags: { deviceId: this.id },
|
|
2410
|
+
meta: {
|
|
2411
|
+
profileId: id,
|
|
2412
|
+
name: next.name
|
|
2413
|
+
}
|
|
2414
|
+
});
|
|
2415
|
+
return profilesViewOf(result.state);
|
|
2416
|
+
}
|
|
2417
|
+
readProfilesState() {
|
|
2418
|
+
return profilesStateFromPersisted(this.config.get("activeProfileId"), this.config.get("profiles"), this.settings());
|
|
2419
|
+
}
|
|
2420
|
+
async persistProfilesState(state) {
|
|
2421
|
+
const persisted = profilesToPersisted(state);
|
|
2422
|
+
await Promise.all([this.config.set("activeProfileId", persisted.activeProfileId), this.config.set("profiles", persisted.profiles)]);
|
|
2423
|
+
}
|
|
2424
|
+
/** Write a full settings blob onto the flat keys the arm path reads. */
|
|
2425
|
+
async replaceFlatSettings(settings) {
|
|
2426
|
+
const blob = cloneAlarmSettings(settings);
|
|
2427
|
+
await Promise.all([
|
|
2428
|
+
this.config.set("exitDelaySec", blob.exitDelaySec),
|
|
2429
|
+
this.config.set("entryDelaySec", blob.entryDelaySec),
|
|
2430
|
+
this.config.set("triggeredDurationSec", blob.triggeredDurationSec),
|
|
2431
|
+
this.config.set("announceTargets", [...blob.announceTargets]),
|
|
2432
|
+
this.config.set("nonBlocking", blob.nonBlocking.map((l) => ({
|
|
2433
|
+
mode: l.mode,
|
|
2434
|
+
deviceIds: [...l.deviceIds]
|
|
2435
|
+
}))),
|
|
2436
|
+
this.config.set("blocking", blob.blocking.map((l) => ({
|
|
2437
|
+
mode: l.mode,
|
|
2438
|
+
deviceIds: [...l.deviceIds]
|
|
2439
|
+
}))),
|
|
2440
|
+
this.config.set("secureOnArmOff", blob.secureOnArmOff.map((l) => ({
|
|
2441
|
+
mode: l.mode,
|
|
2442
|
+
deviceIds: [...l.deviceIds]
|
|
2443
|
+
}))),
|
|
2444
|
+
this.config.set("modeDelays", blob.modeDelays.map((e) => ({ ...e })))
|
|
2445
|
+
]);
|
|
2446
|
+
}
|
|
2447
|
+
profilesRefuseError(reason, detail) {
|
|
2448
|
+
switch (reason) {
|
|
2449
|
+
case "name-empty": return /* @__PURE__ */ new Error("alarm profile name must not be empty");
|
|
2450
|
+
case "name-taken": return /* @__PURE__ */ new Error(`an alarm profile named '${detail ?? ""}' already exists`);
|
|
2451
|
+
case "cannot-delete-default": return /* @__PURE__ */ new Error("the Default alarm profile cannot be deleted");
|
|
2452
|
+
case "clone-source-missing": return /* @__PURE__ */ new Error(`alarm profile '${detail ?? ""}' was not found to clone`);
|
|
2453
|
+
case "not-found": return /* @__PURE__ */ new Error(`alarm profile '${detail ?? ""}' was not found`);
|
|
2454
|
+
default: return /* @__PURE__ */ new Error(`alarm profile operation refused (${reason})`);
|
|
2455
|
+
}
|
|
2214
2456
|
}
|
|
2215
2457
|
/**
|
|
2216
2458
|
* Refuse a mode this panel does not offer — logged by reason, then thrown. A membership edit
|
|
@@ -2943,6 +3185,11 @@ function ncAlarmPanelPort(panel) {
|
|
|
2943
3185
|
deviceId: panel.id,
|
|
2944
3186
|
settings: () => panel.settings(),
|
|
2945
3187
|
applySettings: (patch) => panel.applySettings(patch),
|
|
3188
|
+
profilesView: () => panel.profilesView(),
|
|
3189
|
+
createProfile: (input) => panel.createProfile(input),
|
|
3190
|
+
renameProfile: (id, name) => panel.renameProfile(id, name),
|
|
3191
|
+
deleteProfile: (id) => panel.deleteProfile(id),
|
|
3192
|
+
activateProfile: (id) => panel.activateProfile(id),
|
|
2946
3193
|
availableModes: () => NC_ALARM_MODES,
|
|
2947
3194
|
noteRuleTrigger: (source) => panel.noteRuleTrigger(source),
|
|
2948
3195
|
excludedDevices: () => panel.excludedDevices(),
|
|
@@ -5440,7 +5687,7 @@ function isAdoptable(record, nowMs) {
|
|
|
5440
5687
|
* what `localDayMinute` does for the same input — one behaviour for a bad
|
|
5441
5688
|
* zone, not two.
|
|
5442
5689
|
*/
|
|
5443
|
-
function clockOf$
|
|
5690
|
+
function clockOf$6(atMs, timeZone) {
|
|
5444
5691
|
if (timeZone !== void 0) try {
|
|
5445
5692
|
return new Intl.DateTimeFormat("en-GB", {
|
|
5446
5693
|
timeZone,
|
|
@@ -5576,7 +5823,7 @@ var TimelapseRenderer = class {
|
|
|
5576
5823
|
includeAudio: false,
|
|
5577
5824
|
maxLifeMs: await this.ports.exportLifeMs(),
|
|
5578
5825
|
deleteAfterDownload: false,
|
|
5579
|
-
title: `${request.ruleName} ${clockOf$
|
|
5826
|
+
title: `${request.ruleName} ${clockOf$6(window.startMs, request.timezone)}→${clockOf$6(window.endMs, request.timezone)}`,
|
|
5580
5827
|
timelapse: {
|
|
5581
5828
|
everyMs,
|
|
5582
5829
|
outputFps: request.framerate,
|
|
@@ -5907,7 +6154,7 @@ var TimelapseRenderer = class {
|
|
|
5907
6154
|
...meta,
|
|
5908
6155
|
unservedWindowCount: verdict.unservedWindows.length,
|
|
5909
6156
|
unservedWindows: verdict.unservedWindows.slice(0, 12).map((w) => ({
|
|
5910
|
-
at: clockOf$
|
|
6157
|
+
at: clockOf$6(request.window.startMs + w.fromSec * 1e3, request.timezone),
|
|
5911
6158
|
fromSec: Math.round(w.fromSec),
|
|
5912
6159
|
toSec: Math.round(w.toSec),
|
|
5913
6160
|
expectedDenseFrames: w.expectedDenseFrames,
|
|
@@ -13892,7 +14139,7 @@ var NcPruneRecordSchema = object({
|
|
|
13892
14139
|
source: _enum(["event", "reconcile"])
|
|
13893
14140
|
});
|
|
13894
14141
|
/** Rules × deleted subjects — small; a generous ceiling. */
|
|
13895
|
-
var LOAD_LIMIT$
|
|
14142
|
+
var LOAD_LIMIT$5 = 1e4;
|
|
13896
14143
|
function keyOf$1(row) {
|
|
13897
14144
|
return `${row.kind}:${row.ruleId}:${row.field}:${row.subject}`;
|
|
13898
14145
|
}
|
|
@@ -13911,7 +14158,7 @@ var NC_DEVICE_PRUNES_SPEC = {
|
|
|
13911
14158
|
return parsed.success ? parsed.data : null;
|
|
13912
14159
|
},
|
|
13913
14160
|
deviceIdOf: (row) => row.subjectKind === "device" ? Number(row.subject) : void 0,
|
|
13914
|
-
loadLimit: LOAD_LIMIT$
|
|
14161
|
+
loadLimit: LOAD_LIMIT$5
|
|
13915
14162
|
};
|
|
13916
14163
|
/** The outcomes that mean "this last reference was already dealt with". */
|
|
13917
14164
|
var KEPT_OUTCOMES = new Set(["kept-disabled", "kept"]);
|
|
@@ -13946,7 +14193,7 @@ var NcPruneLedger = class {
|
|
|
13946
14193
|
try {
|
|
13947
14194
|
const records = await this.store.query.query({
|
|
13948
14195
|
collection: NC_DEVICE_PRUNES_COLLECTION,
|
|
13949
|
-
filter: { limit: LOAD_LIMIT$
|
|
14196
|
+
filter: { limit: LOAD_LIMIT$5 }
|
|
13950
14197
|
});
|
|
13951
14198
|
const next = /* @__PURE__ */ new Map();
|
|
13952
14199
|
for (const record of records) {
|
|
@@ -17271,12 +17518,12 @@ var NcSummaryTemplateSchema = object({
|
|
|
17271
17518
|
title: string().max(500).optional(),
|
|
17272
17519
|
body: string().max(2e3).optional()
|
|
17273
17520
|
});
|
|
17274
|
-
var NameField = string().min(1).max(200);
|
|
17521
|
+
var NameField$1 = string().min(1).max(200);
|
|
17275
17522
|
/** MULTI-CAMERA is the point of the feature — a one-camera digest is a
|
|
17276
17523
|
* timelapse with extra steps, but it is allowed and it is cheap. */
|
|
17277
17524
|
var DeviceIdsField = array(number()).min(1).max(64);
|
|
17278
|
-
var TargetsField = array(NcRuleTargetSchema).min(1);
|
|
17279
|
-
var PriorityField = number().int().min(1).max(5);
|
|
17525
|
+
var TargetsField$1 = array(NcRuleTargetSchema).min(1);
|
|
17526
|
+
var PriorityField$1 = number().int().min(1).max(5);
|
|
17280
17527
|
/**
|
|
17281
17528
|
* The mosaic's tile ceiling. 9 (a 3×3 grid) is the shipped default: it is the
|
|
17282
17529
|
* most a phone lock-screen thumbnail can carry and still be read, and it is the
|
|
@@ -17441,7 +17688,7 @@ var NcSummaryAiSchema = object({
|
|
|
17441
17688
|
onFailure: _enum(["deliver-without-text", "skip"]).optional()
|
|
17442
17689
|
});
|
|
17443
17690
|
var NcSummaryRuleInputSchema = object({
|
|
17444
|
-
name: NameField,
|
|
17691
|
+
name: NameField$1,
|
|
17445
17692
|
enabled: boolean().default(true),
|
|
17446
17693
|
deviceIds: DeviceIdsField,
|
|
17447
17694
|
window: NcSummaryWindowSchema,
|
|
@@ -17450,7 +17697,7 @@ var NcSummaryRuleInputSchema = object({
|
|
|
17450
17697
|
maxAiImages: MaxAiImagesField.default(NC_SUMMARY_DEFAULT_AI_IMAGES),
|
|
17451
17698
|
rank: NcSummaryRankSchema.default("importance"),
|
|
17452
17699
|
barrierSeconds: BarrierSecondsField.default(120),
|
|
17453
|
-
targets: TargetsField,
|
|
17700
|
+
targets: TargetsField$1,
|
|
17454
17701
|
template: NcSummaryTemplateSchema.optional(),
|
|
17455
17702
|
/** Burned onto the mosaic's title band — and ONLY when `captions` is `all`. */
|
|
17456
17703
|
captions: NcSummaryCaptionsSchema.default(NC_SUMMARY_DEFAULT_CAPTIONS),
|
|
@@ -17460,7 +17707,7 @@ var NcSummaryRuleInputSchema = object({
|
|
|
17460
17707
|
/** Deliver a window that matched NOTHING? Default no: a nightly "0 events"
|
|
17461
17708
|
* is a line nobody reads, and it costs the lines beside it their reader. */
|
|
17462
17709
|
deliverEmpty: boolean().default(false),
|
|
17463
|
-
priority: PriorityField.default(3),
|
|
17710
|
+
priority: PriorityField$1.default(3),
|
|
17464
17711
|
ai: NcSummaryAiSchema.optional()
|
|
17465
17712
|
});
|
|
17466
17713
|
/**
|
|
@@ -17472,7 +17719,7 @@ var NcSummaryRuleInputSchema = object({
|
|
|
17472
17719
|
* plain `.optional()`.
|
|
17473
17720
|
*/
|
|
17474
17721
|
var NcSummaryRulePatchSchema = object({
|
|
17475
|
-
name: NameField.optional(),
|
|
17722
|
+
name: NameField$1.optional(),
|
|
17476
17723
|
enabled: boolean().optional(),
|
|
17477
17724
|
deviceIds: DeviceIdsField.optional(),
|
|
17478
17725
|
window: NcSummaryWindowSchema.optional(),
|
|
@@ -17481,12 +17728,12 @@ var NcSummaryRulePatchSchema = object({
|
|
|
17481
17728
|
maxAiImages: MaxAiImagesField.optional(),
|
|
17482
17729
|
rank: NcSummaryRankSchema.optional(),
|
|
17483
17730
|
barrierSeconds: BarrierSecondsField.optional(),
|
|
17484
|
-
targets: TargetsField.optional(),
|
|
17731
|
+
targets: TargetsField$1.optional(),
|
|
17485
17732
|
template: NcSummaryTemplateSchema.nullable().optional(),
|
|
17486
17733
|
captions: NcSummaryCaptionsSchema.optional(),
|
|
17487
17734
|
captionText: string().max(200).optional(),
|
|
17488
17735
|
deliverEmpty: boolean().optional(),
|
|
17489
|
-
priority: PriorityField.optional(),
|
|
17736
|
+
priority: PriorityField$1.optional(),
|
|
17490
17737
|
ai: NcSummaryAiSchema.nullable().optional()
|
|
17491
17738
|
});
|
|
17492
17739
|
/**
|
|
@@ -17519,6 +17766,89 @@ function assertSummaryBudget(rule) {
|
|
|
17519
17766
|
if (rule.maxAiImages > rule.maxTiles) throw new Error(`maxAiImages (${rule.maxAiImages}) cannot exceed maxTiles (${rule.maxTiles}) — the model cannot judge frames the mosaic does not contain`);
|
|
17520
17767
|
}
|
|
17521
17768
|
//#endregion
|
|
17769
|
+
//#region src/notification-center/calendar-events/calendar-events-window.ts
|
|
17770
|
+
/** One day in milliseconds. */
|
|
17771
|
+
var CALENDAR_EVENTS_DAY_MS = 1440 * 60 * 1e3;
|
|
17772
|
+
/**
|
|
17773
|
+
* Floor / ceiling on the horizon days a rule may declare.
|
|
17774
|
+
* 1 day is the minimum useful digest; 30 keeps a single notification bounded.
|
|
17775
|
+
*/
|
|
17776
|
+
var CALENDAR_EVENTS_MIN_HORIZON_DAYS = 1;
|
|
17777
|
+
var CALENDAR_EVENTS_MAX_HORIZON_DAYS = 30;
|
|
17778
|
+
var CALENDAR_EVENTS_DEFAULT_HORIZON_DAYS = 7;
|
|
17779
|
+
/**
|
|
17780
|
+
* Build the producer window for one fire.
|
|
17781
|
+
*
|
|
17782
|
+
* `horizonDays` is clamped into the legal range rather than refused — a
|
|
17783
|
+
* stored rule that predates a tighter bound must still fire.
|
|
17784
|
+
*/
|
|
17785
|
+
function calendarEventsWindow(nowMs, horizonDays) {
|
|
17786
|
+
const days = Math.min(CALENDAR_EVENTS_MAX_HORIZON_DAYS, Math.max(CALENDAR_EVENTS_MIN_HORIZON_DAYS, Math.floor(horizonDays)));
|
|
17787
|
+
return {
|
|
17788
|
+
startMs: nowMs,
|
|
17789
|
+
endMs: nowMs + days * CALENDAR_EVENTS_DAY_MS,
|
|
17790
|
+
horizonDays: days
|
|
17791
|
+
};
|
|
17792
|
+
}
|
|
17793
|
+
//#endregion
|
|
17794
|
+
//#region src/notification-center/calendar-events/calendar-events-rule.ts
|
|
17795
|
+
/**
|
|
17796
|
+
* NcCalendarEventsRule — scheduled "upcoming calendar events" digest.
|
|
17797
|
+
*
|
|
17798
|
+
* Same ownership model as Summaries/Timelapses: lives in the addon (not
|
|
17799
|
+
* `@camstack/types`) so a field is live on the next post-analysis deploy.
|
|
17800
|
+
* Rides no `NcDelivery` member; reaches the outside world through
|
|
17801
|
+
* `nc.*CalendarEvents*` bridge actions.
|
|
17802
|
+
*
|
|
17803
|
+
* Calendar selection mirrors detection classes: `calendars` = Only,
|
|
17804
|
+
* `calendarsExclude` = Except, both empty = All.
|
|
17805
|
+
*/
|
|
17806
|
+
var NcCalendarEventsTemplateSchema = object({
|
|
17807
|
+
title: string().max(500).optional(),
|
|
17808
|
+
body: string().max(4e3).optional()
|
|
17809
|
+
});
|
|
17810
|
+
var NameField = string().min(1).max(200);
|
|
17811
|
+
var TargetsField = array(NcRuleTargetSchema).min(1);
|
|
17812
|
+
var PriorityField = number().int().min(1).max(5);
|
|
17813
|
+
var HorizonDaysField = number().int().min(CALENDAR_EVENTS_MIN_HORIZON_DAYS).max(CALENDAR_EVENTS_MAX_HORIZON_DAYS);
|
|
17814
|
+
var CalendarIdsField = array(string().min(1).max(200)).max(64);
|
|
17815
|
+
var NcCalendarEventsRuleInputSchema = object({
|
|
17816
|
+
name: NameField,
|
|
17817
|
+
enabled: boolean().default(true),
|
|
17818
|
+
schedule: NcScheduleSchema,
|
|
17819
|
+
/** Only these calendars. Empty/absent with no exclude = All. */
|
|
17820
|
+
calendars: CalendarIdsField.optional(),
|
|
17821
|
+
/** All except these. Ignored when `calendars` is non-empty. */
|
|
17822
|
+
calendarsExclude: CalendarIdsField.optional(),
|
|
17823
|
+
horizonDays: HorizonDaysField.default(CALENDAR_EVENTS_DEFAULT_HORIZON_DAYS),
|
|
17824
|
+
/** Deliver when the horizon holds no events? Default no. */
|
|
17825
|
+
deliverEmpty: boolean().default(false),
|
|
17826
|
+
targets: TargetsField,
|
|
17827
|
+
template: NcCalendarEventsTemplateSchema.optional(),
|
|
17828
|
+
priority: PriorityField.default(3)
|
|
17829
|
+
});
|
|
17830
|
+
var NcCalendarEventsRulePatchSchema = object({
|
|
17831
|
+
name: NameField.optional(),
|
|
17832
|
+
enabled: boolean().optional(),
|
|
17833
|
+
schedule: NcScheduleSchema.optional(),
|
|
17834
|
+
calendars: CalendarIdsField.nullable().optional(),
|
|
17835
|
+
calendarsExclude: CalendarIdsField.nullable().optional(),
|
|
17836
|
+
horizonDays: HorizonDaysField.optional(),
|
|
17837
|
+
deliverEmpty: boolean().optional(),
|
|
17838
|
+
targets: TargetsField.optional(),
|
|
17839
|
+
template: NcCalendarEventsTemplateSchema.nullable().optional(),
|
|
17840
|
+
priority: PriorityField.optional()
|
|
17841
|
+
});
|
|
17842
|
+
var NcCalendarEventsRuleSchema = NcCalendarEventsRuleInputSchema.extend({
|
|
17843
|
+
id: string(),
|
|
17844
|
+
ownerUserId: string().optional(),
|
|
17845
|
+
/** Epoch-ms of the schedule window end that was last delivered. */
|
|
17846
|
+
generatedAt: number().optional(),
|
|
17847
|
+
createdBy: string(),
|
|
17848
|
+
createdAt: number(),
|
|
17849
|
+
updatedAt: number()
|
|
17850
|
+
});
|
|
17851
|
+
//#endregion
|
|
17522
17852
|
//#region src/notification-center/unstored-media-id.ts
|
|
17523
17853
|
/**
|
|
17524
17854
|
* An id for media bytes that were NEVER STORED.
|
|
@@ -18664,6 +18994,22 @@ var ncActions = defineCustomActions({
|
|
|
18664
18994
|
kind: "mutation",
|
|
18665
18995
|
caller: "required"
|
|
18666
18996
|
}),
|
|
18997
|
+
"nc.listCalendarEventsRules": customAction(object({}), object({
|
|
18998
|
+
rules: array(NcCalendarEventsRuleSchema.extend({ readOnly: boolean() })),
|
|
18999
|
+
canAuthor: boolean()
|
|
19000
|
+
}), { caller: "required" }),
|
|
19001
|
+
"nc.upsertCalendarEventsRule": customAction(object({
|
|
19002
|
+
ruleId: string().optional(),
|
|
19003
|
+
rule: NcCalendarEventsRuleInputSchema.optional(),
|
|
19004
|
+
patch: NcCalendarEventsRulePatchSchema.optional()
|
|
19005
|
+
}), object({ rule: NcCalendarEventsRuleSchema }), {
|
|
19006
|
+
kind: "mutation",
|
|
19007
|
+
caller: "required"
|
|
19008
|
+
}),
|
|
19009
|
+
"nc.deleteCalendarEventsRule": customAction(object({ ruleId: string() }), object({ success: literal(true) }), {
|
|
19010
|
+
kind: "mutation",
|
|
19011
|
+
caller: "required"
|
|
19012
|
+
}),
|
|
18667
19013
|
/**
|
|
18668
19014
|
* Produce one digest window RIGHT NOW — the operator's Test button.
|
|
18669
19015
|
*
|
|
@@ -18950,6 +19296,11 @@ function makeNcActionHandlers(deps) {
|
|
|
18950
19296
|
if (sum === void 0) throw new Error("this node does not host the summary producer — use the hub");
|
|
18951
19297
|
return sum;
|
|
18952
19298
|
};
|
|
19299
|
+
const requireCalendarEvents = () => {
|
|
19300
|
+
const ce = deps.calendarEvents;
|
|
19301
|
+
if (ce === void 0) throw new Error("this node does not host calendar-events rules — use the hub");
|
|
19302
|
+
return ce;
|
|
19303
|
+
};
|
|
18953
19304
|
/** The artefact index, or an honest refusal. */
|
|
18954
19305
|
const requireArtifacts = () => {
|
|
18955
19306
|
const index = deps.artifacts;
|
|
@@ -18978,6 +19329,13 @@ function makeNcActionHandlers(deps) {
|
|
|
18978
19329
|
if (rule.ownerUserId !== caller.userId) throw new Error(`forbidden: summary rule not owned: ${ruleId}`);
|
|
18979
19330
|
return rule;
|
|
18980
19331
|
};
|
|
19332
|
+
const assertCalendarEventsEditable = (ruleId, caller, ce) => {
|
|
19333
|
+
const rule = ce.store.get(ruleId);
|
|
19334
|
+
if (rule === null) throw new Error(`forbidden: calendar-events rule not found: ${ruleId}`);
|
|
19335
|
+
if (caller.isAdmin) return rule;
|
|
19336
|
+
if (rule.ownerUserId !== caller.userId) throw new Error(`forbidden: calendar-events rule not owned: ${ruleId}`);
|
|
19337
|
+
return rule;
|
|
19338
|
+
};
|
|
18981
19339
|
/** A timelapse rule the caller may EDIT. Same admin bypass as `NcRule`. */
|
|
18982
19340
|
const assertTimelapseEditable = (ruleId, caller, tl) => {
|
|
18983
19341
|
const rule = tl.store.get(ruleId);
|
|
@@ -19519,6 +19877,53 @@ function makeNcActionHandlers(deps) {
|
|
|
19519
19877
|
...out.skippedByAi === true ? { skippedByAi: true } : {}
|
|
19520
19878
|
};
|
|
19521
19879
|
},
|
|
19880
|
+
"nc.listCalendarEventsRules": async (_input, caller) => {
|
|
19881
|
+
const c = requireCaller$1(caller);
|
|
19882
|
+
const ce = requireCalendarEvents();
|
|
19883
|
+
return {
|
|
19884
|
+
rules: (c.isAdmin ? ce.store.list() : ce.store.listForOwner(c.userId)).map((r) => ({
|
|
19885
|
+
...r,
|
|
19886
|
+
readOnly: !c.isAdmin && r.ownerUserId !== c.userId
|
|
19887
|
+
})),
|
|
19888
|
+
canAuthor: true
|
|
19889
|
+
};
|
|
19890
|
+
},
|
|
19891
|
+
"nc.upsertCalendarEventsRule": async (input, caller) => {
|
|
19892
|
+
const c = requireCaller$1(caller);
|
|
19893
|
+
const ce = requireCalendarEvents();
|
|
19894
|
+
if (input.ruleId === void 0) {
|
|
19895
|
+
if (input.rule === void 0) throw new Error("upsertCalendarEventsRule: rule or ruleId required");
|
|
19896
|
+
const parsed = NcCalendarEventsRuleInputSchema.parse(input.rule);
|
|
19897
|
+
await assertTargetsOwned(parsed.targets.map((t) => t.targetId), c);
|
|
19898
|
+
const rule = await ce.store.create(parsed, c.userId, c.isAdmin ? void 0 : c.userId);
|
|
19899
|
+
deps.logger.info("calendar-events rule created", { meta: {
|
|
19900
|
+
ruleId: rule.id,
|
|
19901
|
+
owner: c.userId
|
|
19902
|
+
} });
|
|
19903
|
+
return { rule };
|
|
19904
|
+
}
|
|
19905
|
+
assertCalendarEventsEditable(input.ruleId, c, ce);
|
|
19906
|
+
if (input.patch === void 0) throw new Error("upsertCalendarEventsRule: patch required");
|
|
19907
|
+
const patch = NcCalendarEventsRulePatchSchema.parse(input.patch);
|
|
19908
|
+
if (patch.targets !== void 0) await assertTargetsOwned(patch.targets.map((t) => t.targetId), c);
|
|
19909
|
+
const rule = await ce.store.update(input.ruleId, patch);
|
|
19910
|
+
deps.logger.info("calendar-events rule updated", { meta: {
|
|
19911
|
+
ruleId: rule.id,
|
|
19912
|
+
owner: c.userId
|
|
19913
|
+
} });
|
|
19914
|
+
return { rule };
|
|
19915
|
+
},
|
|
19916
|
+
"nc.deleteCalendarEventsRule": async (input, caller) => {
|
|
19917
|
+
const c = requireCaller$1(caller);
|
|
19918
|
+
const ce = requireCalendarEvents();
|
|
19919
|
+
assertCalendarEventsEditable(input.ruleId, c, ce);
|
|
19920
|
+
await ce.store.delete(input.ruleId);
|
|
19921
|
+
deps.logger.info("calendar-events rule deleted", { meta: {
|
|
19922
|
+
ruleId: input.ruleId,
|
|
19923
|
+
owner: c.userId
|
|
19924
|
+
} });
|
|
19925
|
+
return { success: true };
|
|
19926
|
+
},
|
|
19522
19927
|
"nc.getTexts": async (input) => {
|
|
19523
19928
|
const texts = requireTexts();
|
|
19524
19929
|
return input.language === void 0 ? texts.view() : texts.view(input.language);
|
|
@@ -28382,7 +28787,8 @@ var OUTBOX_RECORD_KINDS = new Set([
|
|
|
28382
28787
|
"audio-window",
|
|
28383
28788
|
"system-event",
|
|
28384
28789
|
"timelapse",
|
|
28385
|
-
"ai-summary"
|
|
28790
|
+
"ai-summary",
|
|
28791
|
+
"calendar-events"
|
|
28386
28792
|
]);
|
|
28387
28793
|
function isOutboxRecordKind(x) {
|
|
28388
28794
|
return typeof x === "string" && OUTBOX_RECORD_KINDS.has(x);
|
|
@@ -28735,7 +29141,7 @@ function buildRuleAiSystemPrompt(shape, language, maxWords, grouped = false) {
|
|
|
28735
29141
|
].join("");
|
|
28736
29142
|
}
|
|
28737
29143
|
/** `HH:MM` in the host timezone. */
|
|
28738
|
-
function clockOf$
|
|
29144
|
+
function clockOf$5(atMs) {
|
|
28739
29145
|
const d = new Date(atMs);
|
|
28740
29146
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
28741
29147
|
}
|
|
@@ -28773,7 +29179,7 @@ function buildRuleAiPrompt(input) {
|
|
|
28773
29179
|
if (shape === "detection-track") {
|
|
28774
29180
|
lines.push(`Camera: ${camera}.${located} Subject: ${subject}. The stills, in order:`);
|
|
28775
29181
|
for (const [i, frame] of input.frames.entries()) {
|
|
28776
|
-
const when = frame.atMs !== void 0 ? ` · ${clockOf$
|
|
29182
|
+
const when = frame.atMs !== void 0 ? ` · ${clockOf$5(frame.atMs)}` : "";
|
|
28777
29183
|
lines.push(`${String(i + 1)}.${when}`);
|
|
28778
29184
|
}
|
|
28779
29185
|
return lines.join("\n");
|
|
@@ -29070,7 +29476,7 @@ var NcRuleAiRowSchema = object({
|
|
|
29070
29476
|
updatedAt: number()
|
|
29071
29477
|
});
|
|
29072
29478
|
/** Operator-authored and tiny; a generous ceiling. */
|
|
29073
|
-
var LOAD_LIMIT$
|
|
29479
|
+
var LOAD_LIMIT$4 = 1e4;
|
|
29074
29480
|
var NC_RULE_AI_SPEC = {
|
|
29075
29481
|
collection: NC_RULE_AI_COLLECTION,
|
|
29076
29482
|
columns: [...NC_RULE_AI_COLUMNS],
|
|
@@ -29086,7 +29492,7 @@ var NC_RULE_AI_SPEC = {
|
|
|
29086
29492
|
const parsed = NcRuleAiRowSchema.safeParse(data["row"]);
|
|
29087
29493
|
return parsed.success ? parsed.data : null;
|
|
29088
29494
|
},
|
|
29089
|
-
loadLimit: LOAD_LIMIT$
|
|
29495
|
+
loadLimit: LOAD_LIMIT$4
|
|
29090
29496
|
};
|
|
29091
29497
|
var NcRuleAiStore = class {
|
|
29092
29498
|
ledger;
|
|
@@ -29423,7 +29829,7 @@ var NC_RULES_INDEXES = [{
|
|
|
29423
29829
|
columns: ["enabled"]
|
|
29424
29830
|
}];
|
|
29425
29831
|
/** Query cap — the rule set is operator-authored and tiny; a generous ceiling. */
|
|
29426
|
-
var LOAD_LIMIT$
|
|
29832
|
+
var LOAD_LIMIT$3 = 1e4;
|
|
29427
29833
|
function isRecord$3(value) {
|
|
29428
29834
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29429
29835
|
}
|
|
@@ -29472,7 +29878,7 @@ function makeRulesSpec(onProjection) {
|
|
|
29472
29878
|
if (projection.kind === "projected") onProjection?.(key, projection);
|
|
29473
29879
|
return parsed.data;
|
|
29474
29880
|
},
|
|
29475
|
-
loadLimit: LOAD_LIMIT$
|
|
29881
|
+
loadLimit: LOAD_LIMIT$3
|
|
29476
29882
|
};
|
|
29477
29883
|
}
|
|
29478
29884
|
/** The spec `declare` needs — no sink: declaring a collection projects nothing. */
|
|
@@ -30544,7 +30950,7 @@ var DEFAULT_PROMPT_EN = "What happened during this period? Summarise it for a ho
|
|
|
30544
30950
|
*/
|
|
30545
30951
|
var SummaryAnswerSchema = object({ summary: string() });
|
|
30546
30952
|
/** `HH:MM` in the host timezone — the two numbers a frame label needs. */
|
|
30547
|
-
function clockOf$
|
|
30953
|
+
function clockOf$4(atMs) {
|
|
30548
30954
|
const d = new Date(atMs);
|
|
30549
30955
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
30550
30956
|
}
|
|
@@ -30567,7 +30973,7 @@ function buildSummaryAiPrompt(input, frames) {
|
|
|
30567
30973
|
const roster = frames.map((f, i) => {
|
|
30568
30974
|
const location = plainLabel(f.location ?? "");
|
|
30569
30975
|
const where = location.length > 0 ? ` (${location})` : "";
|
|
30570
|
-
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$
|
|
30976
|
+
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$4(f.atMs)} · ${plainVocabulary(f.className) || "subject"}`;
|
|
30571
30977
|
}).join("\n");
|
|
30572
30978
|
const described = /* @__PURE__ */ new Map();
|
|
30573
30979
|
for (const f of frames) {
|
|
@@ -31170,7 +31576,7 @@ var NO_DETECTIONS$1 = {
|
|
|
31170
31576
|
* has to check is a key somebody can get wrong. */
|
|
31171
31577
|
var BODY_SEPARATOR$1 = " · ";
|
|
31172
31578
|
/** `HH:MM` in the host timezone. */
|
|
31173
|
-
function clockOf$
|
|
31579
|
+
function clockOf$3(atMs) {
|
|
31174
31580
|
const d = new Date(atMs);
|
|
31175
31581
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
31176
31582
|
}
|
|
@@ -31184,8 +31590,8 @@ function clockOf$2(atMs) {
|
|
|
31184
31590
|
function summaryTemplateVars(input) {
|
|
31185
31591
|
return {
|
|
31186
31592
|
rule: input.ruleName,
|
|
31187
|
-
from: clockOf$
|
|
31188
|
-
to: clockOf$
|
|
31593
|
+
from: clockOf$3(input.window.startMs),
|
|
31594
|
+
to: clockOf$3(input.window.endMs),
|
|
31189
31595
|
time: ncClockText(input.texts.language, input.generatedAt),
|
|
31190
31596
|
events: String(input.tileCount),
|
|
31191
31597
|
cameras: String(input.cameraCount),
|
|
@@ -31229,7 +31635,7 @@ function composeSummaryBody(input) {
|
|
|
31229
31635
|
* string, for the reason the timelapse learned: a hand-rolled `n === 1` shipped
|
|
31230
31636
|
* "1 intervalli ripresi" to a live phone.
|
|
31231
31637
|
*/
|
|
31232
|
-
function derivedBody$
|
|
31638
|
+
function derivedBody$2(input, vars) {
|
|
31233
31639
|
const texts = input.texts;
|
|
31234
31640
|
const parts = [texts.text({
|
|
31235
31641
|
key: "summary.body.window",
|
|
@@ -31264,7 +31670,7 @@ function derivedBody$1(input, vars) {
|
|
|
31264
31670
|
}
|
|
31265
31671
|
/** Per-target params with the derived `clickUrl` filled in where absent. A
|
|
31266
31672
|
* value the operator set on the target WINS — he chose it. */
|
|
31267
|
-
function paramsFor$
|
|
31673
|
+
function paramsFor$2(target, clickUrl) {
|
|
31268
31674
|
if (clickUrl === void 0) return target.params;
|
|
31269
31675
|
if (target.params === void 0) return { clickUrl };
|
|
31270
31676
|
if (typeof target.params["clickUrl"] === "string") return target.params;
|
|
@@ -31280,11 +31686,11 @@ function summaryRecordId(ruleId, windowEndMs) {
|
|
|
31280
31686
|
/** The caption burned onto the mosaic's title band, with the same `{{var}}`
|
|
31281
31687
|
* vocabulary the title and body use. `''` is a real answer ("no caption"). */
|
|
31282
31688
|
function summaryCaptionText(input) {
|
|
31283
|
-
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$
|
|
31689
|
+
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$3(input.window.startMs)}→${clockOf$3(input.window.endMs)}`;
|
|
31284
31690
|
return renderTemplate(input.captionText, {
|
|
31285
31691
|
rule: input.ruleName,
|
|
31286
|
-
from: clockOf$
|
|
31287
|
-
to: clockOf$
|
|
31692
|
+
from: clockOf$3(input.window.startMs),
|
|
31693
|
+
to: clockOf$3(input.window.endMs)
|
|
31288
31694
|
}) ?? "";
|
|
31289
31695
|
}
|
|
31290
31696
|
/**
|
|
@@ -31302,7 +31708,7 @@ function buildSummaryOutboxInputs(input) {
|
|
|
31302
31708
|
const body = composeSummaryBody({
|
|
31303
31709
|
aiText: aiSentence(input),
|
|
31304
31710
|
renderedTemplateBody: renderTemplate(input.template?.body, vars),
|
|
31305
|
-
derivedBody: derivedBody$
|
|
31711
|
+
derivedBody: derivedBody$2(input, vars),
|
|
31306
31712
|
templateNamesAi: templatePlaceholders(input.template?.body ?? "").includes("ai")
|
|
31307
31713
|
});
|
|
31308
31714
|
const recordId = summaryRecordId(input.ruleId, input.window.endMs);
|
|
@@ -31321,7 +31727,7 @@ function buildSummaryOutboxInputs(input) {
|
|
|
31321
31727
|
},
|
|
31322
31728
|
media: "none",
|
|
31323
31729
|
...artifacts.length > 0 ? { artifacts } : {},
|
|
31324
|
-
...paramsFor$
|
|
31730
|
+
...paramsFor$2(target, input.clickUrl) !== void 0 ? { params: paramsFor$2(target, input.clickUrl) } : {},
|
|
31325
31731
|
subject: {
|
|
31326
31732
|
deviceId: input.primaryDeviceId,
|
|
31327
31733
|
className: "summary",
|
|
@@ -32216,7 +32622,7 @@ var NcSummaryProducer = class {
|
|
|
32216
32622
|
purpose: "summary",
|
|
32217
32623
|
tiles: tiles.map(({ candidate, jpeg }) => ({
|
|
32218
32624
|
jpeg,
|
|
32219
|
-
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$
|
|
32625
|
+
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$2(candidate.firstSeen)} · ${candidate.className}` } : {},
|
|
32220
32626
|
deviceId: candidate.deviceId,
|
|
32221
32627
|
sourceId: candidate.trackId
|
|
32222
32628
|
})),
|
|
@@ -32439,7 +32845,7 @@ var NcSummaryProducer = class {
|
|
|
32439
32845
|
}
|
|
32440
32846
|
};
|
|
32441
32847
|
/** `HH:MM` in the host timezone — the two numbers a tile label needs. */
|
|
32442
|
-
function clockOf$
|
|
32848
|
+
function clockOf$2(atMs) {
|
|
32443
32849
|
const d = new Date(atMs);
|
|
32444
32850
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
32445
32851
|
}
|
|
@@ -32515,7 +32921,7 @@ var NC_SUMMARY_RULES_INDEXES = [{
|
|
|
32515
32921
|
columns: ["enabled"]
|
|
32516
32922
|
}];
|
|
32517
32923
|
/** The rule set is operator-authored and tiny; a generous ceiling. */
|
|
32518
|
-
var LOAD_LIMIT$
|
|
32924
|
+
var LOAD_LIMIT$2 = 1e4;
|
|
32519
32925
|
/**
|
|
32520
32926
|
* The ledger contract for the digest rule set.
|
|
32521
32927
|
*
|
|
@@ -32545,7 +32951,7 @@ var NC_SUMMARY_RULES_SPEC = {
|
|
|
32545
32951
|
const parsed = NcSummaryRuleSchema.safeParse(data["rule"]);
|
|
32546
32952
|
return parsed.success ? parsed.data : null;
|
|
32547
32953
|
},
|
|
32548
|
-
loadLimit: LOAD_LIMIT$
|
|
32954
|
+
loadLimit: LOAD_LIMIT$2
|
|
32549
32955
|
};
|
|
32550
32956
|
/**
|
|
32551
32957
|
* The three-way CLEARABLE patch signal, one function per field: `undefined`
|
|
@@ -32556,7 +32962,7 @@ var NC_SUMMARY_RULES_SPEC = {
|
|
|
32556
32962
|
* cast here would be a cast on the exact path that decides whether a persisted
|
|
32557
32963
|
* rule keeps a field. Three literal destructurings are three provable ones.
|
|
32558
32964
|
*/
|
|
32559
|
-
function applyTemplatePatch$
|
|
32965
|
+
function applyTemplatePatch$2(merged, template) {
|
|
32560
32966
|
if (template === void 0) return merged;
|
|
32561
32967
|
if (template !== null) return {
|
|
32562
32968
|
...merged,
|
|
@@ -32702,7 +33108,7 @@ var NcSummaryStore = class {
|
|
|
32702
33108
|
createdAt: existing.createdAt,
|
|
32703
33109
|
updatedAt: this.now()
|
|
32704
33110
|
};
|
|
32705
|
-
return this.write(applyAiPatch(applyFiltersPatch(applyTemplatePatch$
|
|
33111
|
+
return this.write(applyAiPatch(applyFiltersPatch(applyTemplatePatch$2(merged, template), filters), ai));
|
|
32706
33112
|
}
|
|
32707
33113
|
async setEnabled(ruleId, enabled) {
|
|
32708
33114
|
return this.update(ruleId, { enabled });
|
|
@@ -32754,6 +33160,408 @@ var NcSummaryStore = class {
|
|
|
32754
33160
|
}
|
|
32755
33161
|
};
|
|
32756
33162
|
//#endregion
|
|
33163
|
+
//#region src/notification-center/calendar-events/calendar-events-store.ts
|
|
33164
|
+
/**
|
|
33165
|
+
* Durable calendar-events rule set — same DurableLedger mechanics as
|
|
33166
|
+
* Summaries, with a per-rule `generatedAt` stamp (one fire per schedule window).
|
|
33167
|
+
*/
|
|
33168
|
+
/**
|
|
33169
|
+
* @durable class=config owner=notification-center
|
|
33170
|
+
* write="an operator creates or edits a calendar-events rule, or the producer stamps a delivered window via markGenerated"
|
|
33171
|
+
* retention="none — a row goes only when the operator deletes the rule."
|
|
33172
|
+
*/
|
|
33173
|
+
var NC_CALENDAR_EVENTS_RULES_COLLECTION = "notification-center:calendar-events-rules";
|
|
33174
|
+
var NC_CALENDAR_EVENTS_RULES_COLUMNS = [
|
|
33175
|
+
{
|
|
33176
|
+
name: "id",
|
|
33177
|
+
type: "TEXT",
|
|
33178
|
+
primaryKey: true,
|
|
33179
|
+
notNull: true
|
|
33180
|
+
},
|
|
33181
|
+
{
|
|
33182
|
+
name: "name",
|
|
33183
|
+
type: "TEXT",
|
|
33184
|
+
notNull: true
|
|
33185
|
+
},
|
|
33186
|
+
{
|
|
33187
|
+
name: "enabled",
|
|
33188
|
+
type: "BOOLEAN",
|
|
33189
|
+
notNull: true
|
|
33190
|
+
},
|
|
33191
|
+
{
|
|
33192
|
+
name: "updatedAt",
|
|
33193
|
+
type: "INTEGER",
|
|
33194
|
+
notNull: true
|
|
33195
|
+
},
|
|
33196
|
+
{
|
|
33197
|
+
name: "rule",
|
|
33198
|
+
type: "JSON",
|
|
33199
|
+
notNull: true
|
|
33200
|
+
}
|
|
33201
|
+
];
|
|
33202
|
+
var NC_CALENDAR_EVENTS_RULES_INDEXES = [{
|
|
33203
|
+
name: "idx_nc_calendar_events_rules_enabled",
|
|
33204
|
+
columns: ["enabled"]
|
|
33205
|
+
}];
|
|
33206
|
+
var LOAD_LIMIT$1 = 1e4;
|
|
33207
|
+
var NC_CALENDAR_EVENTS_RULES_SPEC = {
|
|
33208
|
+
collection: NC_CALENDAR_EVENTS_RULES_COLLECTION,
|
|
33209
|
+
columns: [...NC_CALENDAR_EVENTS_RULES_COLUMNS],
|
|
33210
|
+
indexes: [...NC_CALENDAR_EVENTS_RULES_INDEXES],
|
|
33211
|
+
writeMode: "write-through",
|
|
33212
|
+
keyOf: (rule) => rule.id,
|
|
33213
|
+
toValue: (rule) => ({
|
|
33214
|
+
name: rule.name,
|
|
33215
|
+
enabled: rule.enabled,
|
|
33216
|
+
updatedAt: rule.updatedAt,
|
|
33217
|
+
rule
|
|
33218
|
+
}),
|
|
33219
|
+
fromRecord: (_key, data) => {
|
|
33220
|
+
const parsed = NcCalendarEventsRuleSchema.safeParse(data["rule"]);
|
|
33221
|
+
return parsed.success ? parsed.data : null;
|
|
33222
|
+
},
|
|
33223
|
+
loadLimit: LOAD_LIMIT$1
|
|
33224
|
+
};
|
|
33225
|
+
function applyTemplatePatch$1(merged, template) {
|
|
33226
|
+
if (template === void 0) return merged;
|
|
33227
|
+
if (template !== null) return {
|
|
33228
|
+
...merged,
|
|
33229
|
+
template
|
|
33230
|
+
};
|
|
33231
|
+
const { template: _cleared, ...rest } = merged;
|
|
33232
|
+
return rest;
|
|
33233
|
+
}
|
|
33234
|
+
function applyOptionalStringList(merged, key, value) {
|
|
33235
|
+
if (value === void 0) return merged;
|
|
33236
|
+
if (value !== null) return {
|
|
33237
|
+
...merged,
|
|
33238
|
+
[key]: [...value]
|
|
33239
|
+
};
|
|
33240
|
+
const { [key]: _cleared, ...rest } = merged;
|
|
33241
|
+
return rest;
|
|
33242
|
+
}
|
|
33243
|
+
var NcCalendarEventsStore = class {
|
|
33244
|
+
ledger;
|
|
33245
|
+
store;
|
|
33246
|
+
logger;
|
|
33247
|
+
now;
|
|
33248
|
+
newId;
|
|
33249
|
+
constructor(deps) {
|
|
33250
|
+
this.store = deps.store;
|
|
33251
|
+
this.ledger = new DurableLedger({
|
|
33252
|
+
spec: NC_CALENDAR_EVENTS_RULES_SPEC,
|
|
33253
|
+
store: deps.store,
|
|
33254
|
+
logger: deps.logger
|
|
33255
|
+
});
|
|
33256
|
+
this.logger = deps.logger;
|
|
33257
|
+
this.now = deps.now ?? (() => Date.now());
|
|
33258
|
+
this.newId = deps.newId ?? (() => randomUUID());
|
|
33259
|
+
}
|
|
33260
|
+
static async declare(store) {
|
|
33261
|
+
await DurableLedger.declare(store, NC_CALENDAR_EVENTS_RULES_SPEC);
|
|
33262
|
+
}
|
|
33263
|
+
async load() {
|
|
33264
|
+
await this.ledger.load();
|
|
33265
|
+
this.logger.debug("calendar-events rules loaded", { meta: { rules: this.ledger.size } });
|
|
33266
|
+
}
|
|
33267
|
+
list() {
|
|
33268
|
+
return this.ledger.snapshot().toSorted((a, b) => b.updatedAt - a.updatedAt);
|
|
33269
|
+
}
|
|
33270
|
+
listEnabled() {
|
|
33271
|
+
return this.list().filter((r) => r.enabled);
|
|
33272
|
+
}
|
|
33273
|
+
get(ruleId) {
|
|
33274
|
+
return this.ledger.get(ruleId) ?? null;
|
|
33275
|
+
}
|
|
33276
|
+
listForOwner(userId) {
|
|
33277
|
+
return this.list().filter((r) => r.ownerUserId === void 0 || r.ownerUserId === userId);
|
|
33278
|
+
}
|
|
33279
|
+
async create(input, createdBy, ownerUserId) {
|
|
33280
|
+
const now = this.now();
|
|
33281
|
+
const rule = NcCalendarEventsRuleSchema.parse({
|
|
33282
|
+
...NcCalendarEventsRuleInputSchema.parse(input),
|
|
33283
|
+
id: this.newId(),
|
|
33284
|
+
...ownerUserId !== void 0 ? { ownerUserId } : {},
|
|
33285
|
+
createdBy,
|
|
33286
|
+
createdAt: now,
|
|
33287
|
+
updatedAt: now
|
|
33288
|
+
});
|
|
33289
|
+
await this.ledger.put(rule);
|
|
33290
|
+
return rule;
|
|
33291
|
+
}
|
|
33292
|
+
async update(ruleId, patch) {
|
|
33293
|
+
const existing = this.ledger.get(ruleId);
|
|
33294
|
+
if (!existing) throw new Error(`calendar-events rule not found: ${ruleId}`);
|
|
33295
|
+
const { template, calendars, calendarsExclude, ...rest } = patch;
|
|
33296
|
+
const merged = {
|
|
33297
|
+
...existing,
|
|
33298
|
+
...rest,
|
|
33299
|
+
id: existing.id,
|
|
33300
|
+
...existing.ownerUserId !== void 0 ? { ownerUserId: existing.ownerUserId } : {},
|
|
33301
|
+
...existing.generatedAt !== void 0 ? { generatedAt: existing.generatedAt } : {},
|
|
33302
|
+
createdBy: existing.createdBy,
|
|
33303
|
+
createdAt: existing.createdAt,
|
|
33304
|
+
updatedAt: this.now()
|
|
33305
|
+
};
|
|
33306
|
+
return this.write(applyOptionalStringList(applyOptionalStringList(applyTemplatePatch$1(merged, template), "calendars", calendars), "calendarsExclude", calendarsExclude));
|
|
33307
|
+
}
|
|
33308
|
+
async setEnabled(ruleId, enabled) {
|
|
33309
|
+
return this.update(ruleId, { enabled });
|
|
33310
|
+
}
|
|
33311
|
+
async markGenerated(ruleId, windowEndMs) {
|
|
33312
|
+
const existing = this.ledger.get(ruleId);
|
|
33313
|
+
if (!existing) throw new Error(`calendar-events rule not found: ${ruleId}`);
|
|
33314
|
+
return this.write({
|
|
33315
|
+
...existing,
|
|
33316
|
+
generatedAt: Math.max(existing.generatedAt ?? 0, windowEndMs)
|
|
33317
|
+
});
|
|
33318
|
+
}
|
|
33319
|
+
/**
|
|
33320
|
+
* Idempotent delete — unknown ids are a no-op in RAM, still attempted on disk.
|
|
33321
|
+
*
|
|
33322
|
+
* Deliberately NOT {@link DurableLedger.forget}: that swallows a failed
|
|
33323
|
+
* durable delete. Mirror eviction first; durable half re-throws on failure
|
|
33324
|
+
* (same shape as Summaries).
|
|
33325
|
+
*/
|
|
33326
|
+
async delete(ruleId) {
|
|
33327
|
+
this.ledger.evict(ruleId);
|
|
33328
|
+
try {
|
|
33329
|
+
await this.store.delete.mutate({
|
|
33330
|
+
collection: NC_CALENDAR_EVENTS_RULES_COLLECTION,
|
|
33331
|
+
key: ruleId
|
|
33332
|
+
});
|
|
33333
|
+
} catch (err) {
|
|
33334
|
+
this.logger.warn("calendar-events rule durable delete failed", { meta: {
|
|
33335
|
+
ruleId,
|
|
33336
|
+
error: err instanceof Error ? err.message : String(err)
|
|
33337
|
+
} });
|
|
33338
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
33339
|
+
}
|
|
33340
|
+
}
|
|
33341
|
+
async write(rule) {
|
|
33342
|
+
const validated = NcCalendarEventsRuleSchema.parse(rule);
|
|
33343
|
+
await this.ledger.put(validated);
|
|
33344
|
+
return validated;
|
|
33345
|
+
}
|
|
33346
|
+
};
|
|
33347
|
+
//#endregion
|
|
33348
|
+
//#region src/notification-center/calendar-events/calendar-events-delivery.ts
|
|
33349
|
+
function calendarEventsRecordId(ruleId, windowEndMs) {
|
|
33350
|
+
return `calendar-events:${ruleId}:${windowEndMs}`;
|
|
33351
|
+
}
|
|
33352
|
+
/** `YYYY-MM-DD HH:MM` in the host timezone. */
|
|
33353
|
+
function clockOf$1(atMs) {
|
|
33354
|
+
const d = new Date(atMs);
|
|
33355
|
+
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")}`}`;
|
|
33356
|
+
}
|
|
33357
|
+
function formatEventLine(event) {
|
|
33358
|
+
const when = event.allDay ? clockOf$1(event.startMs).slice(0, 10) : `${clockOf$1(event.startMs)}–${clockOf$1(event.endMs)}`;
|
|
33359
|
+
const where = event.location !== void 0 && event.location.length > 0 ? ` @ ${event.location}` : "";
|
|
33360
|
+
return `• ${when} ${event.title}${where}`;
|
|
33361
|
+
}
|
|
33362
|
+
function derivedBody$1(input) {
|
|
33363
|
+
if (input.events.length === 0) return `No events in the next ${String(input.horizonDays)} day(s).`;
|
|
33364
|
+
const lines = input.events.slice(0, 40).map(formatEventLine);
|
|
33365
|
+
const extra = input.events.length > 40 ? `\n…and ${String(input.events.length - 40)} more` : "";
|
|
33366
|
+
return lines.join("\n") + extra;
|
|
33367
|
+
}
|
|
33368
|
+
function templateVars(input) {
|
|
33369
|
+
return {
|
|
33370
|
+
rule: input.ruleName,
|
|
33371
|
+
count: String(input.events.length),
|
|
33372
|
+
horizonDays: String(input.horizonDays),
|
|
33373
|
+
from: clockOf$1(input.eventWindow.startMs),
|
|
33374
|
+
to: clockOf$1(input.eventWindow.endMs),
|
|
33375
|
+
events: derivedBody$1(input),
|
|
33376
|
+
calendars: String(input.calendarIds.length)
|
|
33377
|
+
};
|
|
33378
|
+
}
|
|
33379
|
+
function paramsFor$1(target) {
|
|
33380
|
+
const params = target.params;
|
|
33381
|
+
if (params === void 0) return void 0;
|
|
33382
|
+
const out = {};
|
|
33383
|
+
for (const [k, v] of Object.entries(params)) if (typeof v === "string") out[k] = v;
|
|
33384
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
33385
|
+
}
|
|
33386
|
+
/**
|
|
33387
|
+
* One outbox row per enabled target. Ready for `NcOutbox.enqueue`.
|
|
33388
|
+
*/
|
|
33389
|
+
function buildCalendarEventsOutboxInputs(input) {
|
|
33390
|
+
const vars = templateVars(input);
|
|
33391
|
+
const optedOut = new Set(input.disabledTargetIds ?? []);
|
|
33392
|
+
const title = renderTemplate(input.template?.title, vars) ?? `${input.ruleName}: ${String(input.events.length)} upcoming`;
|
|
33393
|
+
const body = renderTemplate(input.template?.body, vars) ?? derivedBody$1(input);
|
|
33394
|
+
const recordId = calendarEventsRecordId(input.ruleId, input.scheduleWindow.endMs);
|
|
33395
|
+
return input.targets.filter((t) => !optedOut.has(t.targetId)).map((target) => {
|
|
33396
|
+
const payload = {
|
|
33397
|
+
ruleName: input.ruleName,
|
|
33398
|
+
delivery: "calendar-events",
|
|
33399
|
+
priority: input.priority,
|
|
33400
|
+
template: {
|
|
33401
|
+
title,
|
|
33402
|
+
body
|
|
33403
|
+
},
|
|
33404
|
+
media: "none",
|
|
33405
|
+
...paramsFor$1(target) !== void 0 ? { params: paramsFor$1(target) } : {},
|
|
33406
|
+
subject: {
|
|
33407
|
+
deviceId: NC_SYSTEM_DEVICE_ID$1,
|
|
33408
|
+
className: "calendar-events",
|
|
33409
|
+
zones: [],
|
|
33410
|
+
timestamp: input.scheduleWindow.endMs
|
|
33411
|
+
}
|
|
33412
|
+
};
|
|
33413
|
+
return {
|
|
33414
|
+
ruleId: input.ruleId,
|
|
33415
|
+
targetId: target.targetId,
|
|
33416
|
+
deviceId: NC_SYSTEM_DEVICE_ID$1,
|
|
33417
|
+
recordKind: "calendar-events",
|
|
33418
|
+
recordId,
|
|
33419
|
+
payload
|
|
33420
|
+
};
|
|
33421
|
+
});
|
|
33422
|
+
}
|
|
33423
|
+
//#endregion
|
|
33424
|
+
//#region src/notification-center/calendar-events/calendar-events-selection.ts
|
|
33425
|
+
/**
|
|
33426
|
+
* Expand a rule's calendar scope against the known calendar ids.
|
|
33427
|
+
*
|
|
33428
|
+
* - All (both lists empty/absent) → every known id
|
|
33429
|
+
* - Only → the include list, intersected with known (unknown ids dropped)
|
|
33430
|
+
* - Except → known minus the exclude list
|
|
33431
|
+
*
|
|
33432
|
+
* An empty known set always yields empty — there is nothing to notify about.
|
|
33433
|
+
*/
|
|
33434
|
+
function expandCalendarScope(scope, knownIds) {
|
|
33435
|
+
const known = [...knownIds];
|
|
33436
|
+
if (known.length === 0) return [];
|
|
33437
|
+
const include = scope?.calendars ?? [];
|
|
33438
|
+
const exclude = scope?.calendarsExclude ?? [];
|
|
33439
|
+
if (include.length > 0) {
|
|
33440
|
+
const wanted = new Set(include);
|
|
33441
|
+
return known.filter((id) => wanted.has(id));
|
|
33442
|
+
}
|
|
33443
|
+
if (exclude.length > 0) {
|
|
33444
|
+
const banned = new Set(exclude);
|
|
33445
|
+
return known.filter((id) => !banned.has(id));
|
|
33446
|
+
}
|
|
33447
|
+
return known;
|
|
33448
|
+
}
|
|
33449
|
+
//#endregion
|
|
33450
|
+
//#region src/notification-center/calendar-events/calendar-events-producer.ts
|
|
33451
|
+
/** How far back a closed schedule window may still be owed. */
|
|
33452
|
+
var CALENDAR_EVENTS_LOOKBACK_MS = 2160 * 60 * 1e3;
|
|
33453
|
+
var NcCalendarEventsProducer = class {
|
|
33454
|
+
deps;
|
|
33455
|
+
inFlight = false;
|
|
33456
|
+
now;
|
|
33457
|
+
constructor(deps) {
|
|
33458
|
+
this.deps = deps;
|
|
33459
|
+
this.now = deps.now ?? (() => Date.now());
|
|
33460
|
+
}
|
|
33461
|
+
async tick() {
|
|
33462
|
+
if (this.inFlight) return {
|
|
33463
|
+
examined: 0,
|
|
33464
|
+
sent: 0,
|
|
33465
|
+
empty: 0,
|
|
33466
|
+
skipped: 0,
|
|
33467
|
+
failed: 0
|
|
33468
|
+
};
|
|
33469
|
+
this.inFlight = true;
|
|
33470
|
+
const report = {
|
|
33471
|
+
examined: 0,
|
|
33472
|
+
sent: 0,
|
|
33473
|
+
empty: 0,
|
|
33474
|
+
skipped: 0,
|
|
33475
|
+
failed: 0
|
|
33476
|
+
};
|
|
33477
|
+
try {
|
|
33478
|
+
const rules = this.deps.store.listEnabled();
|
|
33479
|
+
for (const rule of rules) {
|
|
33480
|
+
report.examined += 1;
|
|
33481
|
+
try {
|
|
33482
|
+
const outcome = await this.runRule(rule);
|
|
33483
|
+
if (outcome === "sent") report.sent += 1;
|
|
33484
|
+
else if (outcome === "empty") report.empty += 1;
|
|
33485
|
+
else report.skipped += 1;
|
|
33486
|
+
} catch (err) {
|
|
33487
|
+
report.failed += 1;
|
|
33488
|
+
this.deps.logger.warn("calendar-events rule failed", { meta: {
|
|
33489
|
+
ruleId: rule.id,
|
|
33490
|
+
error: err instanceof Error ? err.message : String(err)
|
|
33491
|
+
} });
|
|
33492
|
+
}
|
|
33493
|
+
}
|
|
33494
|
+
return report;
|
|
33495
|
+
} finally {
|
|
33496
|
+
this.inFlight = false;
|
|
33497
|
+
}
|
|
33498
|
+
}
|
|
33499
|
+
async runRule(rule) {
|
|
33500
|
+
const nowMs = this.now();
|
|
33501
|
+
const scheduleWindow = findLastClosedWindow({
|
|
33502
|
+
schedule: rule.schedule,
|
|
33503
|
+
nowMs,
|
|
33504
|
+
lookbackMs: CALENDAR_EVENTS_LOOKBACK_MS
|
|
33505
|
+
});
|
|
33506
|
+
if (scheduleWindow === null) return "skipped";
|
|
33507
|
+
if ((rule.generatedAt ?? 0) >= scheduleWindow.endMs) return "skipped";
|
|
33508
|
+
const known = await this.deps.calendars.listCalendars();
|
|
33509
|
+
const calendarIds = expandCalendarScope({
|
|
33510
|
+
calendars: rule.calendars,
|
|
33511
|
+
calendarsExclude: rule.calendarsExclude
|
|
33512
|
+
}, known.map((c) => c.id));
|
|
33513
|
+
if (calendarIds.length === 0) {
|
|
33514
|
+
this.deps.logger.info("calendar-events: no calendars in scope", { meta: { ruleId: rule.id } });
|
|
33515
|
+
if (!rule.deliverEmpty) {
|
|
33516
|
+
await this.deps.store.markGenerated(rule.id, scheduleWindow.endMs);
|
|
33517
|
+
return "empty";
|
|
33518
|
+
}
|
|
33519
|
+
}
|
|
33520
|
+
const eventWindow = calendarEventsWindow(nowMs, rule.horizonDays);
|
|
33521
|
+
const events = calendarIds.length === 0 ? [] : await this.deps.calendars.listEvents({
|
|
33522
|
+
calendarIds,
|
|
33523
|
+
startMs: eventWindow.startMs,
|
|
33524
|
+
endMs: eventWindow.endMs
|
|
33525
|
+
});
|
|
33526
|
+
if (events.length === 0 && !rule.deliverEmpty) {
|
|
33527
|
+
this.deps.logger.info("calendar-events: empty horizon, not delivering", { meta: {
|
|
33528
|
+
ruleId: rule.id,
|
|
33529
|
+
horizonDays: eventWindow.horizonDays
|
|
33530
|
+
} });
|
|
33531
|
+
await this.deps.store.markGenerated(rule.id, scheduleWindow.endMs);
|
|
33532
|
+
return "empty";
|
|
33533
|
+
}
|
|
33534
|
+
const sorted = [...events].toSorted((a, b) => a.startMs - b.startMs);
|
|
33535
|
+
await this.enqueue(rule, scheduleWindow, eventWindow, calendarIds, sorted);
|
|
33536
|
+
await this.deps.store.markGenerated(rule.id, scheduleWindow.endMs);
|
|
33537
|
+
return "sent";
|
|
33538
|
+
}
|
|
33539
|
+
async enqueue(rule, scheduleWindow, eventWindow, calendarIds, events) {
|
|
33540
|
+
const inputs = buildCalendarEventsOutboxInputs({
|
|
33541
|
+
ruleId: rule.id,
|
|
33542
|
+
ruleName: rule.name,
|
|
33543
|
+
scheduleWindow,
|
|
33544
|
+
eventWindow: {
|
|
33545
|
+
startMs: eventWindow.startMs,
|
|
33546
|
+
endMs: eventWindow.endMs
|
|
33547
|
+
},
|
|
33548
|
+
horizonDays: eventWindow.horizonDays,
|
|
33549
|
+
events,
|
|
33550
|
+
calendarIds,
|
|
33551
|
+
targets: rule.targets,
|
|
33552
|
+
priority: rule.priority,
|
|
33553
|
+
...rule.template !== void 0 ? { template: rule.template } : {}
|
|
33554
|
+
});
|
|
33555
|
+
for (const input of inputs) await this.deps.outbox.enqueue(input);
|
|
33556
|
+
this.deps.logger.info("calendar-events delivered", { meta: {
|
|
33557
|
+
ruleId: rule.id,
|
|
33558
|
+
events: events.length,
|
|
33559
|
+
targets: inputs.length,
|
|
33560
|
+
windowEndMs: scheduleWindow.endMs
|
|
33561
|
+
} });
|
|
33562
|
+
}
|
|
33563
|
+
};
|
|
33564
|
+
//#endregion
|
|
32757
33565
|
//#region src/notification-center/text-catalog-editor.ts
|
|
32758
33566
|
var NcTextCatalogEditor = class {
|
|
32759
33567
|
store;
|
|
@@ -35837,7 +36645,7 @@ function packagePhaseOf(ev) {
|
|
|
35837
36645
|
*/
|
|
35838
36646
|
function historyRecordKind(kind) {
|
|
35839
36647
|
if (kind === "audio-event" || kind === "audio-window") return "object-event";
|
|
35840
|
-
if (kind === "timelapse" || kind === "ai-summary") return "system-event";
|
|
36648
|
+
if (kind === "timelapse" || kind === "ai-summary" || kind === "calendar-events") return "system-event";
|
|
35841
36649
|
return kind;
|
|
35842
36650
|
}
|
|
35843
36651
|
/**
|
|
@@ -35852,7 +36660,8 @@ function historyRecordKind(kind) {
|
|
|
35852
36660
|
function cooldownSubjectKind(kind) {
|
|
35853
36661
|
switch (kind) {
|
|
35854
36662
|
case "timelapse":
|
|
35855
|
-
case "ai-summary":
|
|
36663
|
+
case "ai-summary":
|
|
36664
|
+
case "calendar-events": return "device-event";
|
|
35856
36665
|
default: return kind;
|
|
35857
36666
|
}
|
|
35858
36667
|
}
|
|
@@ -35952,7 +36761,7 @@ function outboxEntryToHistory(texts, entry) {
|
|
|
35952
36761
|
id: entry.id,
|
|
35953
36762
|
ruleId: entry.ruleId,
|
|
35954
36763
|
ruleName: entry.payload.ruleName,
|
|
35955
|
-
delivery: entry.payload.delivery === "timelapse" || entry.payload.delivery === "ai-summary" ? "system-event" : entry.payload.delivery,
|
|
36764
|
+
delivery: entry.payload.delivery === "timelapse" || entry.payload.delivery === "ai-summary" || entry.payload.delivery === "calendar-events" ? "system-event" : entry.payload.delivery,
|
|
35956
36765
|
targetId: entry.targetId,
|
|
35957
36766
|
deviceId: entry.deviceId,
|
|
35958
36767
|
recordKind: historyRecordKind(entry.recordKind),
|
|
@@ -36162,6 +36971,9 @@ var NotificationCenter = class {
|
|
|
36162
36971
|
*/
|
|
36163
36972
|
summaryRules;
|
|
36164
36973
|
summaryProducer = null;
|
|
36974
|
+
/** Calendar-events digest rules + producer (hub-only producer). */
|
|
36975
|
+
calendarEventsRules;
|
|
36976
|
+
calendarEventsProducer = null;
|
|
36165
36977
|
/**
|
|
36166
36978
|
* What the deleted-device / deleted-target prune did, and why — its
|
|
36167
36979
|
* disable-once guard and the source of both UIs' banner. Loaded on EVERY
|
|
@@ -37152,6 +37964,11 @@ var NotificationCenter = class {
|
|
|
37152
37964
|
logger: this.logger.child("summary"),
|
|
37153
37965
|
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
37154
37966
|
});
|
|
37967
|
+
this.calendarEventsRules = new NcCalendarEventsStore({
|
|
37968
|
+
store: deps.store,
|
|
37969
|
+
logger: this.logger.child("calendar-events"),
|
|
37970
|
+
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
37971
|
+
});
|
|
37155
37972
|
this.pruneLedger = new NcPruneLedger({
|
|
37156
37973
|
store: deps.store,
|
|
37157
37974
|
logger: this.logger.child("device-prune")
|
|
@@ -37198,6 +38015,14 @@ var NotificationCenter = class {
|
|
|
37198
38015
|
zoneOwner: this.zoneOwners.lookup()
|
|
37199
38016
|
});
|
|
37200
38017
|
}
|
|
38018
|
+
const calendarsPort = deps.calendars;
|
|
38019
|
+
if (calendarsPort !== void 0) this.calendarEventsProducer = new NcCalendarEventsProducer({
|
|
38020
|
+
store: this.calendarEventsRules,
|
|
38021
|
+
outbox: this.outbox,
|
|
38022
|
+
calendars: calendarsPort,
|
|
38023
|
+
logger: this.logger.child("calendar-events"),
|
|
38024
|
+
now: this.now
|
|
38025
|
+
});
|
|
37201
38026
|
}
|
|
37202
38027
|
/**
|
|
37203
38028
|
* The durable timelapse rule set — exposed for the `nc.*Timelapse*` bridge
|
|
@@ -37211,6 +38036,10 @@ var NotificationCenter = class {
|
|
|
37211
38036
|
get summaryStore() {
|
|
37212
38037
|
return this.summaryRules;
|
|
37213
38038
|
}
|
|
38039
|
+
/** The durable calendar-events rule set — exposed for the `nc.*CalendarEvents*` bridge. */
|
|
38040
|
+
get calendarEventsStore() {
|
|
38041
|
+
return this.calendarEventsRules;
|
|
38042
|
+
}
|
|
37214
38043
|
/**
|
|
37215
38044
|
* Produce one digest window for one rule, right now.
|
|
37216
38045
|
*
|
|
@@ -37284,6 +38113,7 @@ var NotificationCenter = class {
|
|
|
37284
38113
|
await NcDeviceMuteStore.declare(store);
|
|
37285
38114
|
await TimelapseStore.declare(store);
|
|
37286
38115
|
await NcSummaryStore.declare(store);
|
|
38116
|
+
await NcCalendarEventsStore.declare(store);
|
|
37287
38117
|
await NcRuleAiStore.declare(store);
|
|
37288
38118
|
await NcTextCatalogStore.declare(store);
|
|
37289
38119
|
await NcArtifactIndex.declare(store);
|
|
@@ -38698,7 +39528,18 @@ var NotificationCenter = class {
|
|
|
38698
39528
|
},
|
|
38699
39529
|
getAlarmConfig: async () => this.alarmConfig(),
|
|
38700
39530
|
getAlarmSensors: async (input) => this.alarmSensors(input),
|
|
38701
|
-
setAlarmConfig: async ({ patch }) => this.setAlarmConfig(patch)
|
|
39531
|
+
setAlarmConfig: async ({ patch }) => this.setAlarmConfig(patch),
|
|
39532
|
+
createAlarmProfile: async ({ name, cloneFromId }) => this.mutateAlarmProfile((panel) => panel.createProfile({
|
|
39533
|
+
name,
|
|
39534
|
+
newId: randomUUID(),
|
|
39535
|
+
source: cloneFromId === void 0 ? { kind: "defaults" } : {
|
|
39536
|
+
kind: "clone",
|
|
39537
|
+
fromId: cloneFromId
|
|
39538
|
+
}
|
|
39539
|
+
})),
|
|
39540
|
+
renameAlarmProfile: async ({ id, name }) => this.mutateAlarmProfile((panel) => panel.renameProfile(id, name)),
|
|
39541
|
+
deleteAlarmProfile: async ({ id }) => this.mutateAlarmProfile((panel) => panel.deleteProfile(id)),
|
|
39542
|
+
activateAlarmProfile: async ({ id }) => this.mutateAlarmProfile((panel) => panel.activateProfile(id))
|
|
38702
39543
|
};
|
|
38703
39544
|
}
|
|
38704
39545
|
/**
|
|
@@ -38991,6 +39832,13 @@ var NotificationCenter = class {
|
|
|
38991
39832
|
});
|
|
38992
39833
|
return this.alarmConfig();
|
|
38993
39834
|
}
|
|
39835
|
+
/** Profile CRUD — each mutation returns the whole config (settings applied). */
|
|
39836
|
+
async mutateAlarmProfile(run) {
|
|
39837
|
+
const panel = this.alarmPanel;
|
|
39838
|
+
if (panel === null) throw new Error("this node has no alarm panel");
|
|
39839
|
+
await run(panel);
|
|
39840
|
+
return this.alarmConfig();
|
|
39841
|
+
}
|
|
38994
39842
|
/**
|
|
38995
39843
|
* The panel as it is NOW — see `alarm-live.ts`.
|
|
38996
39844
|
*
|
|
@@ -39087,6 +39935,7 @@ var NotificationCenter = class {
|
|
|
39087
39935
|
return {
|
|
39088
39936
|
deviceId: panel.deviceId,
|
|
39089
39937
|
settings: panel.settings(),
|
|
39938
|
+
profiles: panel.profilesView(),
|
|
39090
39939
|
live: await this.alarmLive(panel),
|
|
39091
39940
|
coverage: this.coverageFor(panel).map((c) => ({
|
|
39092
39941
|
...c,
|
|
@@ -40906,11 +41755,17 @@ var NotificationCenter = class {
|
|
|
40906
41755
|
await this.rules.load();
|
|
40907
41756
|
this.noteInertGroupedRules();
|
|
40908
41757
|
if (this.evaluationActive) this.deviceAlerts.tick();
|
|
40909
|
-
if (this.evaluationActive)
|
|
40910
|
-
this.
|
|
40911
|
-
|
|
41758
|
+
if (this.evaluationActive) {
|
|
41759
|
+
this.stateSummary.tick().catch((err) => {
|
|
41760
|
+
this.logger.warn("state summary tick failed", { meta: { error: String(err) } });
|
|
41761
|
+
});
|
|
41762
|
+
this.calendarEventsProducer?.tick().catch((err) => {
|
|
41763
|
+
this.logger.warn("calendar-events tick failed", { meta: { error: String(err) } });
|
|
41764
|
+
});
|
|
41765
|
+
}
|
|
40912
41766
|
await this.timelapseRules.load();
|
|
40913
41767
|
await this.summaryRules.load();
|
|
41768
|
+
await this.calendarEventsRules.load();
|
|
40914
41769
|
await this.ruleAi.load();
|
|
40915
41770
|
await this.reloadTexts();
|
|
40916
41771
|
this.refreshOccupancyWatch();
|
|
@@ -104721,6 +105576,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
104721
105576
|
store: center.summaryStore,
|
|
104722
105577
|
runNow: (input) => center.runSummaryNow(input)
|
|
104723
105578
|
},
|
|
105579
|
+
calendarEvents: { store: center.calendarEventsStore },
|
|
104724
105580
|
texts: center.textCatalogEditor,
|
|
104725
105581
|
previewTexts: center.textCatalog,
|
|
104726
105582
|
...this.ncArtifactIndex !== null ? { artifacts: this.ncArtifactIndex } : {},
|
|
@@ -105908,7 +106764,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
105908
106764
|
})).tracks;
|
|
105909
106765
|
},
|
|
105910
106766
|
...this.isPostProcessingNode ? { timelapse: this.buildTimelapsePorts(api, stores) } : {},
|
|
105911
|
-
...this.isPostProcessingNode ? { summary: this.buildSummaryPorts(api, stores) } : {}
|
|
106767
|
+
...this.isPostProcessingNode ? { summary: this.buildSummaryPorts(api, stores) } : {},
|
|
106768
|
+
...this.isPostProcessingNode ? { calendars: {
|
|
106769
|
+
listCalendars: async () => {
|
|
106770
|
+
return (await api.calendars.listCalendars.query()).map((c) => ({ id: c.id }));
|
|
106771
|
+
},
|
|
106772
|
+
listEvents: async (input) => api.calendars.listEvents.query({
|
|
106773
|
+
calendarIds: [...input.calendarIds],
|
|
106774
|
+
startMs: input.startMs,
|
|
106775
|
+
endMs: input.endMs
|
|
106776
|
+
})
|
|
106777
|
+
} } : {}
|
|
105912
106778
|
});
|
|
105913
106779
|
this.wireAlarmPanel(this.notificationCenter);
|
|
105914
106780
|
await this.serveNcActionPlane(this.notificationCenter);
|