@camstack/addon-post-analysis 1.2.320 → 1.2.321
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-BCx3qCL5.js} +89 -0
- package/dist/{clip-model-registry-C-IEAIQQ.mjs → clip-model-registry-Dnm9dfy0.mjs} +89 -0
- 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-CPcZN3mA.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CGUnHrR_.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-CVH5tT7I.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-ByDyL2zi.mjs → hostInit-Dt-jaGuG.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +648 -48
- package/dist/pipeline-analytics/index.mjs +648 -48
- 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 NC_ALARM_SYSTEM_EVENT_KINDS, $n as isVectorIndexDimConflict, $r as boolean, $t as SCENE_DEFAULT_UNCOVERED_POLICY, A as EMBEDDING_REQUIREMENTS_FAILED, An as describeSystemEventDeliveryProblem, Ar as templateCarriesSummary, At as NcScheduleSchema, B as ListTrackRelationsInputSchema, Bn as failureContributionCapability, Br as BaseAddon, Bt as OPS_LOG_DEFAULT_LIMIT, C as DEFAULT_EVENT_COLOR, Cn as assertTimelapseCadences, Cr as storageOccupancyCapability, Ct as NcConditionDescriptorSchema, D as DeclaredDevices, Dn as consumableLowPercentOf, Dr as systemEventFilterApplies, Dt as NcRuleSchema, E as DETECTION_PIPELINE_CAP_NAME, En as buildEventKindDescriptor, Er as systemEventFamilyProblems, Et as NcRulePatchSchema, F as EXPORT_DENSE_MAX_RANGES, Fr as canonicalHash, Ft as NcSystemEventKindSchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as isDetectionMacroClass, Gr as hydrateSchema, Gt as RebuildObjectEmbeddingsRefusalSchema, H as LockStateSchema, Hn as gifFromMp4Args, Hr as createEvent, I as FIRST_LEVEL_MACRO_CLASSES, Ir as evaluateSensorEdge, It as NcTaxonomySchema, J as MOTION_CLOSE_AFTER_MS, Jn as isMediaOwnerType, Jt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, K as MAX_TRACK_PURITIES, Kn as isEventOwnerType, Kr as isDeviceScopedCap, Kt as RetrainStatusSchema, L as FULL_IMAGE_BBOX, Ln as encodeVectorBase64, Lr as isSourceCap, Lt as NcTemplatePreviewInputSchema, M as EVENT_DENSITY_BATCH_MAX, Mr as vectorDimFromBase64, Mt as NcSnoozeSchema, N as EVENT_KIND_BY_CAP, Nr as zoneAnalyticsCapability, Nt as NcSnoozeSuppressedSchema, O as DetectionSourceSchema, On as cosineSimilarity$1, Or as systemEventSummaryScheduleOf, Ot as NcRuleTargetSchema, P as EVENT_OWNER_TYPES, Pn as embeddingRefusalOf, Pr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Pt as NcStateSummarySectionIdSchema, Q as NC_ALARM_DELAY_MAX_SEC, Qn as isSystemEventSummaryRule, Qr as array, Qt as SCENE_DEFAULT_QUIET_SECONDS, R as FailureCounters, Rn as evictionPolicyOfLocation, Rr as DeviceType, Rt as NcTemplatePreviewSchema, S as CoverStateSchema, Sn as applySecureOnArmEdit, Sr as servedTemplateVars, St as NcAlarmSettingsPatchSchema, T as DETECTION_MACRO_CLASSES, Tn as audioModeOf, Tr as systemEventDeliveryProblems, Tt as NcRuleInputSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Un as isAlarmControllableCap, Ur as customAction, Ut as RECORDING_EXPORT_MAX_READ_BYTES, V as ListTrackRelationsResultSchema, Vn as gifDeriveSpeed, Vr as CamProfileSchema, Vt as OpsLogEntrySchema, W as MAX_BIRTH_DECISION_RECORDS, Wn as isAudioClassName, Wr as defineCustomActions, Wt as RECORDING_SOURCE_CAMSTACK_ADDON, X as MotionTriggeredBySchema, Xn as isScheduleActive, Xr as ZodObject, Xt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, Y as MediaFileKindEnum, Yn as isRetiredClearKey, Yr as sleep$1, Yt as SCENE_DEFAULT_ANCHOR_THRESHOLD, Z as NC_ALARM_DELAY_FIELDS, Zn as isSystemDelivery, Zr as _enum, Zt as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, _ as BaseDevice, _n as alarmDeviceSecureOnArm, _r as ruleGroupingApplies, _t as NC_TAXONOMY, a as CLIP_EMBEDDING_STEP_ID, ai as partialRecord, an as SetTrackRelationInputSchema, at as NC_CLIP_RETENTION_MAX_SEC, b as ClearTrackRelationInputSchema, bn as applyModeDelaysEdits, br as secureActuationFor, bt as NcAlarmSensorsInputSchema, c as resolveClusterModelPin, ci as string, cn as TimelapseRuleInputSchema, cr as readDeviceStateFrom, ct as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, di as union, dn as TrackPuritySchema, dr as resolveClipSearchMinScore, dt as NC_GROUPING_MAX_SEC, ei as discriminatedUnion, en as SCENE_DIVERGED, er as kebabToCamel, et as NC_ALARM_TRIGGERED_MAX_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fi as unknown, fn as TrackRelationKindSchema, fr as resolveLocationMode, ft as NC_GROUPING_MIN_SEC, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as addonWidgetsSourceCapability, gr as ruleDeviceCompatCapQueries, gt as NC_TANK_SYSTEM_EVENT_KINDS, h as ArchivedDebugNoteSchema, hn as TrackSourceSchema, hr as ruleBelongsToAlarm$1, ht as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, ii as object, in as SetTrackPurityResultSchema, ir as occupancyScope, it as NC_CLIP_PRE_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as describeSystemEventFamilyProblem, jr as templateVarsFor, jt as NcSnoozeInputSchema, k as EMBEDDING_MODEL_FAILED, kn as deriveRecordingMode, kr as systemEventSummarySplitOf, kt as NcRuleUpdateInputSchema, l as APPEARANCE_BIN_COUNT, li as templateLiteral, ln as TimelapseRulePatchSchema, lr as readMotionTriggeredBy, lt as NC_DEFAULT_SNOOZE_MINUTES, m as AlarmArmModeSchema, mn as TrackSideSchema, mt as NC_SNOOZE_MAX_MINUTES, ni as looseObject, nn as SceneMonitorSchema, nr as memoizePinnedFfmpeg, nt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, o as FACE_EMBEDDING_STEP_ID, oi as record, on as TIMELAPSE_DENSE_FLOOR_SEC, or as pipelineAnalyticsCapability, ot as NC_CONDITION_CATALOG, p as AUDIO_MACRO_LABELS, pi as EventCategory, pn as TrackRelationSchema, pr as resolveModeDelays, pt as NC_SENSOR_TRANSITION_KINDS, q as MAX_TRACK_RELATIONS, qn as isKnownTemplateVar, qr as nodePin, qt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, ri as number, rn as SetTrackPurityInputSchema, rr as notificationRulesCapability, rt as NC_CLIP_POST_ROLL_DEFAULT_SEC, s as readClusterModelPinStrict, si as strictObject, sn as TRACK_RELATIONS_DEFAULT_LIMIT, sr as plateGalleryCapability, st as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as literal, tn as SCENE_RESET_RECAPTURES, tr as mayWriteToLocation, tt as NC_AUDIO_CONFIRM_HITS_DEFAULT, u as APPEARANCE_BLOCKS, ui as tuple, un as TimelapseRuleSchema, ur as readTimelapseGeneratedAt, ut as NC_GROUPING_DEFAULT_SEC, v as BirthDecisionRecordSchema, vn as alarmPanelCapability, vr as ruleKindOf, vt as NC_TEMPLATE_VARS, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as audioMetricsCapability, wr as subKindsOf, wt as NcConditionsSchema, x as ClearTrackRelationResultSchema, xn as applyNonBlockingEdit, xr as sensorRaisedOf, xt as NcAlarmSensorsSchema, y as COCO_TO_MACRO, yn as applyBlockingEdit, yr as sceneMonitorCapability, yt as NcAlarmConfigSchema, z as LabelAttributionSchema, zn as faceGalleryCapability, zr as errMsg, zt as NcTemplateVarDescriptorSchema } from "../clip-model-registry-
|
|
1
|
+
import { $ as NC_ALARM_SYSTEM_EVENT_KINDS, $n as isVectorIndexDimConflict, $r as boolean, $t as SCENE_DEFAULT_UNCOVERED_POLICY, A as EMBEDDING_REQUIREMENTS_FAILED, An as describeSystemEventDeliveryProblem, Ar as templateCarriesSummary, At as NcScheduleSchema, B as ListTrackRelationsInputSchema, Bn as failureContributionCapability, Br as BaseAddon, Bt as OPS_LOG_DEFAULT_LIMIT, C as DEFAULT_EVENT_COLOR, Cn as assertTimelapseCadences, Cr as storageOccupancyCapability, Ct as NcConditionDescriptorSchema, D as DeclaredDevices, Dn as consumableLowPercentOf, Dr as systemEventFilterApplies, Dt as NcRuleSchema, E as DETECTION_PIPELINE_CAP_NAME, En as buildEventKindDescriptor, Er as systemEventFamilyProblems, Et as NcRulePatchSchema, F as EXPORT_DENSE_MAX_RANGES, Fr as canonicalHash, Ft as NcSystemEventKindSchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as isDetectionMacroClass, Gr as hydrateSchema, Gt as RebuildObjectEmbeddingsRefusalSchema, H as LockStateSchema, Hn as gifFromMp4Args, Hr as createEvent, I as FIRST_LEVEL_MACRO_CLASSES, Ir as evaluateSensorEdge, It as NcTaxonomySchema, J as MOTION_CLOSE_AFTER_MS, Jn as isMediaOwnerType, Jt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, K as MAX_TRACK_PURITIES, Kn as isEventOwnerType, Kr as isDeviceScopedCap, Kt as RetrainStatusSchema, L as FULL_IMAGE_BBOX, Ln as encodeVectorBase64, Lr as isSourceCap, Lt as NcTemplatePreviewInputSchema, M as EVENT_DENSITY_BATCH_MAX, Mr as vectorDimFromBase64, Mt as NcSnoozeSchema, N as EVENT_KIND_BY_CAP, Nr as zoneAnalyticsCapability, Nt as NcSnoozeSuppressedSchema, O as DetectionSourceSchema, On as cosineSimilarity$1, Or as systemEventSummaryScheduleOf, Ot as NcRuleTargetSchema, P as EVENT_OWNER_TYPES, Pn as embeddingRefusalOf, Pr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Pt as NcStateSummarySectionIdSchema, Q as NC_ALARM_DELAY_MAX_SEC, Qn as isSystemEventSummaryRule, Qr as array, Qt as SCENE_DEFAULT_QUIET_SECONDS, R as FailureCounters, Rn as evictionPolicyOfLocation, Rr as DeviceType, Rt as NcTemplatePreviewSchema, S as CoverStateSchema, Sn as applySecureOnArmEdit, Sr as servedTemplateVars, St as NcAlarmSettingsPatchSchema, T as DETECTION_MACRO_CLASSES, Tn as audioModeOf, Tr as systemEventDeliveryProblems, Tt as NcRuleInputSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Un as isAlarmControllableCap, Ur as customAction, Ut as RECORDING_EXPORT_MAX_READ_BYTES, V as ListTrackRelationsResultSchema, Vn as gifDeriveSpeed, Vr as CamProfileSchema, Vt as OpsLogEntrySchema, W as MAX_BIRTH_DECISION_RECORDS, Wn as isAudioClassName, Wr as defineCustomActions, Wt as RECORDING_SOURCE_CAMSTACK_ADDON, X as MotionTriggeredBySchema, Xn as isScheduleActive, Xr as ZodObject, Xt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, Y as MediaFileKindEnum, Yn as isRetiredClearKey, Yr as sleep$1, Yt as SCENE_DEFAULT_ANCHOR_THRESHOLD, Z as NC_ALARM_DELAY_FIELDS, Zn as isSystemDelivery, Zr as _enum, Zt as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, _ as BaseDevice, _n as alarmDeviceSecureOnArm, _r as ruleGroupingApplies, _t as NC_TAXONOMY, a as CLIP_EMBEDDING_STEP_ID, ai as partialRecord, an as SetTrackRelationInputSchema, at as NC_CLIP_RETENTION_MAX_SEC, b as ClearTrackRelationInputSchema, bn as applyModeDelaysEdits, br as secureActuationFor, bt as NcAlarmSensorsInputSchema, c as resolveClusterModelPin, ci as string, cn as TimelapseRuleInputSchema, cr as readDeviceStateFrom, ct as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, di as union, dn as TrackPuritySchema, dr as resolveClipSearchMinScore, dt as NC_GROUPING_MAX_SEC, ei as discriminatedUnion, en as SCENE_DIVERGED, er as kebabToCamel, et as NC_ALARM_TRIGGERED_MAX_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fi as unknown, fn as TrackRelationKindSchema, fr as resolveLocationMode, ft as NC_GROUPING_MIN_SEC, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as addonWidgetsSourceCapability, gr as ruleDeviceCompatCapQueries, gt as NC_TANK_SYSTEM_EVENT_KINDS, h as ArchivedDebugNoteSchema, hn as TrackSourceSchema, hr as ruleBelongsToAlarm$1, ht as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, ii as object, in as SetTrackPurityResultSchema, ir as occupancyScope, it as NC_CLIP_PRE_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as describeSystemEventFamilyProblem, jr as templateVarsFor, jt as NcSnoozeInputSchema, k as EMBEDDING_MODEL_FAILED, kn as deriveRecordingMode, kr as systemEventSummarySplitOf, kt as NcRuleUpdateInputSchema, l as APPEARANCE_BIN_COUNT, li as templateLiteral, ln as TimelapseRulePatchSchema, lr as readMotionTriggeredBy, lt as NC_DEFAULT_SNOOZE_MINUTES, m as AlarmArmModeSchema, mn as TrackSideSchema, mt as NC_SNOOZE_MAX_MINUTES, ni as looseObject, nn as SceneMonitorSchema, nr as memoizePinnedFfmpeg, nt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, o as FACE_EMBEDDING_STEP_ID, oi as record, on as TIMELAPSE_DENSE_FLOOR_SEC, or as pipelineAnalyticsCapability, ot as NC_CONDITION_CATALOG, p as AUDIO_MACRO_LABELS, pi as EventCategory, pn as TrackRelationSchema, pr as resolveModeDelays, pt as NC_SENSOR_TRANSITION_KINDS, q as MAX_TRACK_RELATIONS, qn as isKnownTemplateVar, qr as nodePin, qt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, ri as number, rn as SetTrackPurityInputSchema, rr as notificationRulesCapability, rt as NC_CLIP_POST_ROLL_DEFAULT_SEC, s as readClusterModelPinStrict, si as strictObject, sn as TRACK_RELATIONS_DEFAULT_LIMIT, sr as plateGalleryCapability, st as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as literal, tn as SCENE_RESET_RECAPTURES, tr as mayWriteToLocation, tt as NC_AUDIO_CONFIRM_HITS_DEFAULT, u as APPEARANCE_BLOCKS, ui as tuple, un as TimelapseRuleSchema, ur as readTimelapseGeneratedAt, ut as NC_GROUPING_DEFAULT_SEC, v as BirthDecisionRecordSchema, vn as alarmPanelCapability, vr as ruleKindOf, vt as NC_TEMPLATE_VARS, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as audioMetricsCapability, wr as subKindsOf, wt as NcConditionsSchema, x as ClearTrackRelationResultSchema, xn as applyNonBlockingEdit, xr as sensorRaisedOf, xt as NcAlarmSensorsSchema, y as COCO_TO_MACRO, yn as applyBlockingEdit, yr as sceneMonitorCapability, yt as NcAlarmConfigSchema, z as LabelAttributionSchema, zn as faceGalleryCapability, zr as errMsg, zt as NcTemplateVarDescriptorSchema } from "../clip-model-registry-Dnm9dfy0.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";
|
|
@@ -5440,7 +5440,7 @@ function isAdoptable(record, nowMs) {
|
|
|
5440
5440
|
* what `localDayMinute` does for the same input — one behaviour for a bad
|
|
5441
5441
|
* zone, not two.
|
|
5442
5442
|
*/
|
|
5443
|
-
function clockOf$
|
|
5443
|
+
function clockOf$6(atMs, timeZone) {
|
|
5444
5444
|
if (timeZone !== void 0) try {
|
|
5445
5445
|
return new Intl.DateTimeFormat("en-GB", {
|
|
5446
5446
|
timeZone,
|
|
@@ -5576,7 +5576,7 @@ var TimelapseRenderer = class {
|
|
|
5576
5576
|
includeAudio: false,
|
|
5577
5577
|
maxLifeMs: await this.ports.exportLifeMs(),
|
|
5578
5578
|
deleteAfterDownload: false,
|
|
5579
|
-
title: `${request.ruleName} ${clockOf$
|
|
5579
|
+
title: `${request.ruleName} ${clockOf$6(window.startMs, request.timezone)}→${clockOf$6(window.endMs, request.timezone)}`,
|
|
5580
5580
|
timelapse: {
|
|
5581
5581
|
everyMs,
|
|
5582
5582
|
outputFps: request.framerate,
|
|
@@ -5907,7 +5907,7 @@ var TimelapseRenderer = class {
|
|
|
5907
5907
|
...meta,
|
|
5908
5908
|
unservedWindowCount: verdict.unservedWindows.length,
|
|
5909
5909
|
unservedWindows: verdict.unservedWindows.slice(0, 12).map((w) => ({
|
|
5910
|
-
at: clockOf$
|
|
5910
|
+
at: clockOf$6(request.window.startMs + w.fromSec * 1e3, request.timezone),
|
|
5911
5911
|
fromSec: Math.round(w.fromSec),
|
|
5912
5912
|
toSec: Math.round(w.toSec),
|
|
5913
5913
|
expectedDenseFrames: w.expectedDenseFrames,
|
|
@@ -13892,7 +13892,7 @@ var NcPruneRecordSchema = object({
|
|
|
13892
13892
|
source: _enum(["event", "reconcile"])
|
|
13893
13893
|
});
|
|
13894
13894
|
/** Rules × deleted subjects — small; a generous ceiling. */
|
|
13895
|
-
var LOAD_LIMIT$
|
|
13895
|
+
var LOAD_LIMIT$5 = 1e4;
|
|
13896
13896
|
function keyOf$1(row) {
|
|
13897
13897
|
return `${row.kind}:${row.ruleId}:${row.field}:${row.subject}`;
|
|
13898
13898
|
}
|
|
@@ -13911,7 +13911,7 @@ var NC_DEVICE_PRUNES_SPEC = {
|
|
|
13911
13911
|
return parsed.success ? parsed.data : null;
|
|
13912
13912
|
},
|
|
13913
13913
|
deviceIdOf: (row) => row.subjectKind === "device" ? Number(row.subject) : void 0,
|
|
13914
|
-
loadLimit: LOAD_LIMIT$
|
|
13914
|
+
loadLimit: LOAD_LIMIT$5
|
|
13915
13915
|
};
|
|
13916
13916
|
/** The outcomes that mean "this last reference was already dealt with". */
|
|
13917
13917
|
var KEPT_OUTCOMES = new Set(["kept-disabled", "kept"]);
|
|
@@ -13946,7 +13946,7 @@ var NcPruneLedger = class {
|
|
|
13946
13946
|
try {
|
|
13947
13947
|
const records = await this.store.query.query({
|
|
13948
13948
|
collection: NC_DEVICE_PRUNES_COLLECTION,
|
|
13949
|
-
filter: { limit: LOAD_LIMIT$
|
|
13949
|
+
filter: { limit: LOAD_LIMIT$5 }
|
|
13950
13950
|
});
|
|
13951
13951
|
const next = /* @__PURE__ */ new Map();
|
|
13952
13952
|
for (const record of records) {
|
|
@@ -17271,12 +17271,12 @@ var NcSummaryTemplateSchema = object({
|
|
|
17271
17271
|
title: string().max(500).optional(),
|
|
17272
17272
|
body: string().max(2e3).optional()
|
|
17273
17273
|
});
|
|
17274
|
-
var NameField = string().min(1).max(200);
|
|
17274
|
+
var NameField$1 = string().min(1).max(200);
|
|
17275
17275
|
/** MULTI-CAMERA is the point of the feature — a one-camera digest is a
|
|
17276
17276
|
* timelapse with extra steps, but it is allowed and it is cheap. */
|
|
17277
17277
|
var DeviceIdsField = array(number()).min(1).max(64);
|
|
17278
|
-
var TargetsField = array(NcRuleTargetSchema).min(1);
|
|
17279
|
-
var PriorityField = number().int().min(1).max(5);
|
|
17278
|
+
var TargetsField$1 = array(NcRuleTargetSchema).min(1);
|
|
17279
|
+
var PriorityField$1 = number().int().min(1).max(5);
|
|
17280
17280
|
/**
|
|
17281
17281
|
* The mosaic's tile ceiling. 9 (a 3×3 grid) is the shipped default: it is the
|
|
17282
17282
|
* most a phone lock-screen thumbnail can carry and still be read, and it is the
|
|
@@ -17441,7 +17441,7 @@ var NcSummaryAiSchema = object({
|
|
|
17441
17441
|
onFailure: _enum(["deliver-without-text", "skip"]).optional()
|
|
17442
17442
|
});
|
|
17443
17443
|
var NcSummaryRuleInputSchema = object({
|
|
17444
|
-
name: NameField,
|
|
17444
|
+
name: NameField$1,
|
|
17445
17445
|
enabled: boolean().default(true),
|
|
17446
17446
|
deviceIds: DeviceIdsField,
|
|
17447
17447
|
window: NcSummaryWindowSchema,
|
|
@@ -17450,7 +17450,7 @@ var NcSummaryRuleInputSchema = object({
|
|
|
17450
17450
|
maxAiImages: MaxAiImagesField.default(NC_SUMMARY_DEFAULT_AI_IMAGES),
|
|
17451
17451
|
rank: NcSummaryRankSchema.default("importance"),
|
|
17452
17452
|
barrierSeconds: BarrierSecondsField.default(120),
|
|
17453
|
-
targets: TargetsField,
|
|
17453
|
+
targets: TargetsField$1,
|
|
17454
17454
|
template: NcSummaryTemplateSchema.optional(),
|
|
17455
17455
|
/** Burned onto the mosaic's title band — and ONLY when `captions` is `all`. */
|
|
17456
17456
|
captions: NcSummaryCaptionsSchema.default(NC_SUMMARY_DEFAULT_CAPTIONS),
|
|
@@ -17460,7 +17460,7 @@ var NcSummaryRuleInputSchema = object({
|
|
|
17460
17460
|
/** Deliver a window that matched NOTHING? Default no: a nightly "0 events"
|
|
17461
17461
|
* is a line nobody reads, and it costs the lines beside it their reader. */
|
|
17462
17462
|
deliverEmpty: boolean().default(false),
|
|
17463
|
-
priority: PriorityField.default(3),
|
|
17463
|
+
priority: PriorityField$1.default(3),
|
|
17464
17464
|
ai: NcSummaryAiSchema.optional()
|
|
17465
17465
|
});
|
|
17466
17466
|
/**
|
|
@@ -17472,7 +17472,7 @@ var NcSummaryRuleInputSchema = object({
|
|
|
17472
17472
|
* plain `.optional()`.
|
|
17473
17473
|
*/
|
|
17474
17474
|
var NcSummaryRulePatchSchema = object({
|
|
17475
|
-
name: NameField.optional(),
|
|
17475
|
+
name: NameField$1.optional(),
|
|
17476
17476
|
enabled: boolean().optional(),
|
|
17477
17477
|
deviceIds: DeviceIdsField.optional(),
|
|
17478
17478
|
window: NcSummaryWindowSchema.optional(),
|
|
@@ -17481,12 +17481,12 @@ var NcSummaryRulePatchSchema = object({
|
|
|
17481
17481
|
maxAiImages: MaxAiImagesField.optional(),
|
|
17482
17482
|
rank: NcSummaryRankSchema.optional(),
|
|
17483
17483
|
barrierSeconds: BarrierSecondsField.optional(),
|
|
17484
|
-
targets: TargetsField.optional(),
|
|
17484
|
+
targets: TargetsField$1.optional(),
|
|
17485
17485
|
template: NcSummaryTemplateSchema.nullable().optional(),
|
|
17486
17486
|
captions: NcSummaryCaptionsSchema.optional(),
|
|
17487
17487
|
captionText: string().max(200).optional(),
|
|
17488
17488
|
deliverEmpty: boolean().optional(),
|
|
17489
|
-
priority: PriorityField.optional(),
|
|
17489
|
+
priority: PriorityField$1.optional(),
|
|
17490
17490
|
ai: NcSummaryAiSchema.nullable().optional()
|
|
17491
17491
|
});
|
|
17492
17492
|
/**
|
|
@@ -17519,6 +17519,89 @@ function assertSummaryBudget(rule) {
|
|
|
17519
17519
|
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
17520
|
}
|
|
17521
17521
|
//#endregion
|
|
17522
|
+
//#region src/notification-center/calendar-events/calendar-events-window.ts
|
|
17523
|
+
/** One day in milliseconds. */
|
|
17524
|
+
var CALENDAR_EVENTS_DAY_MS = 1440 * 60 * 1e3;
|
|
17525
|
+
/**
|
|
17526
|
+
* Floor / ceiling on the horizon days a rule may declare.
|
|
17527
|
+
* 1 day is the minimum useful digest; 30 keeps a single notification bounded.
|
|
17528
|
+
*/
|
|
17529
|
+
var CALENDAR_EVENTS_MIN_HORIZON_DAYS = 1;
|
|
17530
|
+
var CALENDAR_EVENTS_MAX_HORIZON_DAYS = 30;
|
|
17531
|
+
var CALENDAR_EVENTS_DEFAULT_HORIZON_DAYS = 7;
|
|
17532
|
+
/**
|
|
17533
|
+
* Build the producer window for one fire.
|
|
17534
|
+
*
|
|
17535
|
+
* `horizonDays` is clamped into the legal range rather than refused — a
|
|
17536
|
+
* stored rule that predates a tighter bound must still fire.
|
|
17537
|
+
*/
|
|
17538
|
+
function calendarEventsWindow(nowMs, horizonDays) {
|
|
17539
|
+
const days = Math.min(CALENDAR_EVENTS_MAX_HORIZON_DAYS, Math.max(CALENDAR_EVENTS_MIN_HORIZON_DAYS, Math.floor(horizonDays)));
|
|
17540
|
+
return {
|
|
17541
|
+
startMs: nowMs,
|
|
17542
|
+
endMs: nowMs + days * CALENDAR_EVENTS_DAY_MS,
|
|
17543
|
+
horizonDays: days
|
|
17544
|
+
};
|
|
17545
|
+
}
|
|
17546
|
+
//#endregion
|
|
17547
|
+
//#region src/notification-center/calendar-events/calendar-events-rule.ts
|
|
17548
|
+
/**
|
|
17549
|
+
* NcCalendarEventsRule — scheduled "upcoming calendar events" digest.
|
|
17550
|
+
*
|
|
17551
|
+
* Same ownership model as Summaries/Timelapses: lives in the addon (not
|
|
17552
|
+
* `@camstack/types`) so a field is live on the next post-analysis deploy.
|
|
17553
|
+
* Rides no `NcDelivery` member; reaches the outside world through
|
|
17554
|
+
* `nc.*CalendarEvents*` bridge actions.
|
|
17555
|
+
*
|
|
17556
|
+
* Calendar selection mirrors detection classes: `calendars` = Only,
|
|
17557
|
+
* `calendarsExclude` = Except, both empty = All.
|
|
17558
|
+
*/
|
|
17559
|
+
var NcCalendarEventsTemplateSchema = object({
|
|
17560
|
+
title: string().max(500).optional(),
|
|
17561
|
+
body: string().max(4e3).optional()
|
|
17562
|
+
});
|
|
17563
|
+
var NameField = string().min(1).max(200);
|
|
17564
|
+
var TargetsField = array(NcRuleTargetSchema).min(1);
|
|
17565
|
+
var PriorityField = number().int().min(1).max(5);
|
|
17566
|
+
var HorizonDaysField = number().int().min(CALENDAR_EVENTS_MIN_HORIZON_DAYS).max(CALENDAR_EVENTS_MAX_HORIZON_DAYS);
|
|
17567
|
+
var CalendarIdsField = array(string().min(1).max(200)).max(64);
|
|
17568
|
+
var NcCalendarEventsRuleInputSchema = object({
|
|
17569
|
+
name: NameField,
|
|
17570
|
+
enabled: boolean().default(true),
|
|
17571
|
+
schedule: NcScheduleSchema,
|
|
17572
|
+
/** Only these calendars. Empty/absent with no exclude = All. */
|
|
17573
|
+
calendars: CalendarIdsField.optional(),
|
|
17574
|
+
/** All except these. Ignored when `calendars` is non-empty. */
|
|
17575
|
+
calendarsExclude: CalendarIdsField.optional(),
|
|
17576
|
+
horizonDays: HorizonDaysField.default(CALENDAR_EVENTS_DEFAULT_HORIZON_DAYS),
|
|
17577
|
+
/** Deliver when the horizon holds no events? Default no. */
|
|
17578
|
+
deliverEmpty: boolean().default(false),
|
|
17579
|
+
targets: TargetsField,
|
|
17580
|
+
template: NcCalendarEventsTemplateSchema.optional(),
|
|
17581
|
+
priority: PriorityField.default(3)
|
|
17582
|
+
});
|
|
17583
|
+
var NcCalendarEventsRulePatchSchema = object({
|
|
17584
|
+
name: NameField.optional(),
|
|
17585
|
+
enabled: boolean().optional(),
|
|
17586
|
+
schedule: NcScheduleSchema.optional(),
|
|
17587
|
+
calendars: CalendarIdsField.nullable().optional(),
|
|
17588
|
+
calendarsExclude: CalendarIdsField.nullable().optional(),
|
|
17589
|
+
horizonDays: HorizonDaysField.optional(),
|
|
17590
|
+
deliverEmpty: boolean().optional(),
|
|
17591
|
+
targets: TargetsField.optional(),
|
|
17592
|
+
template: NcCalendarEventsTemplateSchema.nullable().optional(),
|
|
17593
|
+
priority: PriorityField.optional()
|
|
17594
|
+
});
|
|
17595
|
+
var NcCalendarEventsRuleSchema = NcCalendarEventsRuleInputSchema.extend({
|
|
17596
|
+
id: string(),
|
|
17597
|
+
ownerUserId: string().optional(),
|
|
17598
|
+
/** Epoch-ms of the schedule window end that was last delivered. */
|
|
17599
|
+
generatedAt: number().optional(),
|
|
17600
|
+
createdBy: string(),
|
|
17601
|
+
createdAt: number(),
|
|
17602
|
+
updatedAt: number()
|
|
17603
|
+
});
|
|
17604
|
+
//#endregion
|
|
17522
17605
|
//#region src/notification-center/unstored-media-id.ts
|
|
17523
17606
|
/**
|
|
17524
17607
|
* An id for media bytes that were NEVER STORED.
|
|
@@ -18664,6 +18747,22 @@ var ncActions = defineCustomActions({
|
|
|
18664
18747
|
kind: "mutation",
|
|
18665
18748
|
caller: "required"
|
|
18666
18749
|
}),
|
|
18750
|
+
"nc.listCalendarEventsRules": customAction(object({}), object({
|
|
18751
|
+
rules: array(NcCalendarEventsRuleSchema.extend({ readOnly: boolean() })),
|
|
18752
|
+
canAuthor: boolean()
|
|
18753
|
+
}), { caller: "required" }),
|
|
18754
|
+
"nc.upsertCalendarEventsRule": customAction(object({
|
|
18755
|
+
ruleId: string().optional(),
|
|
18756
|
+
rule: NcCalendarEventsRuleInputSchema.optional(),
|
|
18757
|
+
patch: NcCalendarEventsRulePatchSchema.optional()
|
|
18758
|
+
}), object({ rule: NcCalendarEventsRuleSchema }), {
|
|
18759
|
+
kind: "mutation",
|
|
18760
|
+
caller: "required"
|
|
18761
|
+
}),
|
|
18762
|
+
"nc.deleteCalendarEventsRule": customAction(object({ ruleId: string() }), object({ success: literal(true) }), {
|
|
18763
|
+
kind: "mutation",
|
|
18764
|
+
caller: "required"
|
|
18765
|
+
}),
|
|
18667
18766
|
/**
|
|
18668
18767
|
* Produce one digest window RIGHT NOW — the operator's Test button.
|
|
18669
18768
|
*
|
|
@@ -18950,6 +19049,11 @@ function makeNcActionHandlers(deps) {
|
|
|
18950
19049
|
if (sum === void 0) throw new Error("this node does not host the summary producer — use the hub");
|
|
18951
19050
|
return sum;
|
|
18952
19051
|
};
|
|
19052
|
+
const requireCalendarEvents = () => {
|
|
19053
|
+
const ce = deps.calendarEvents;
|
|
19054
|
+
if (ce === void 0) throw new Error("this node does not host calendar-events rules — use the hub");
|
|
19055
|
+
return ce;
|
|
19056
|
+
};
|
|
18953
19057
|
/** The artefact index, or an honest refusal. */
|
|
18954
19058
|
const requireArtifacts = () => {
|
|
18955
19059
|
const index = deps.artifacts;
|
|
@@ -18978,6 +19082,13 @@ function makeNcActionHandlers(deps) {
|
|
|
18978
19082
|
if (rule.ownerUserId !== caller.userId) throw new Error(`forbidden: summary rule not owned: ${ruleId}`);
|
|
18979
19083
|
return rule;
|
|
18980
19084
|
};
|
|
19085
|
+
const assertCalendarEventsEditable = (ruleId, caller, ce) => {
|
|
19086
|
+
const rule = ce.store.get(ruleId);
|
|
19087
|
+
if (rule === null) throw new Error(`forbidden: calendar-events rule not found: ${ruleId}`);
|
|
19088
|
+
if (caller.isAdmin) return rule;
|
|
19089
|
+
if (rule.ownerUserId !== caller.userId) throw new Error(`forbidden: calendar-events rule not owned: ${ruleId}`);
|
|
19090
|
+
return rule;
|
|
19091
|
+
};
|
|
18981
19092
|
/** A timelapse rule the caller may EDIT. Same admin bypass as `NcRule`. */
|
|
18982
19093
|
const assertTimelapseEditable = (ruleId, caller, tl) => {
|
|
18983
19094
|
const rule = tl.store.get(ruleId);
|
|
@@ -19519,6 +19630,53 @@ function makeNcActionHandlers(deps) {
|
|
|
19519
19630
|
...out.skippedByAi === true ? { skippedByAi: true } : {}
|
|
19520
19631
|
};
|
|
19521
19632
|
},
|
|
19633
|
+
"nc.listCalendarEventsRules": async (_input, caller) => {
|
|
19634
|
+
const c = requireCaller$1(caller);
|
|
19635
|
+
const ce = requireCalendarEvents();
|
|
19636
|
+
return {
|
|
19637
|
+
rules: (c.isAdmin ? ce.store.list() : ce.store.listForOwner(c.userId)).map((r) => ({
|
|
19638
|
+
...r,
|
|
19639
|
+
readOnly: !c.isAdmin && r.ownerUserId !== c.userId
|
|
19640
|
+
})),
|
|
19641
|
+
canAuthor: true
|
|
19642
|
+
};
|
|
19643
|
+
},
|
|
19644
|
+
"nc.upsertCalendarEventsRule": async (input, caller) => {
|
|
19645
|
+
const c = requireCaller$1(caller);
|
|
19646
|
+
const ce = requireCalendarEvents();
|
|
19647
|
+
if (input.ruleId === void 0) {
|
|
19648
|
+
if (input.rule === void 0) throw new Error("upsertCalendarEventsRule: rule or ruleId required");
|
|
19649
|
+
const parsed = NcCalendarEventsRuleInputSchema.parse(input.rule);
|
|
19650
|
+
await assertTargetsOwned(parsed.targets.map((t) => t.targetId), c);
|
|
19651
|
+
const rule = await ce.store.create(parsed, c.userId, c.isAdmin ? void 0 : c.userId);
|
|
19652
|
+
deps.logger.info("calendar-events rule created", { meta: {
|
|
19653
|
+
ruleId: rule.id,
|
|
19654
|
+
owner: c.userId
|
|
19655
|
+
} });
|
|
19656
|
+
return { rule };
|
|
19657
|
+
}
|
|
19658
|
+
assertCalendarEventsEditable(input.ruleId, c, ce);
|
|
19659
|
+
if (input.patch === void 0) throw new Error("upsertCalendarEventsRule: patch required");
|
|
19660
|
+
const patch = NcCalendarEventsRulePatchSchema.parse(input.patch);
|
|
19661
|
+
if (patch.targets !== void 0) await assertTargetsOwned(patch.targets.map((t) => t.targetId), c);
|
|
19662
|
+
const rule = await ce.store.update(input.ruleId, patch);
|
|
19663
|
+
deps.logger.info("calendar-events rule updated", { meta: {
|
|
19664
|
+
ruleId: rule.id,
|
|
19665
|
+
owner: c.userId
|
|
19666
|
+
} });
|
|
19667
|
+
return { rule };
|
|
19668
|
+
},
|
|
19669
|
+
"nc.deleteCalendarEventsRule": async (input, caller) => {
|
|
19670
|
+
const c = requireCaller$1(caller);
|
|
19671
|
+
const ce = requireCalendarEvents();
|
|
19672
|
+
assertCalendarEventsEditable(input.ruleId, c, ce);
|
|
19673
|
+
await ce.store.delete(input.ruleId);
|
|
19674
|
+
deps.logger.info("calendar-events rule deleted", { meta: {
|
|
19675
|
+
ruleId: input.ruleId,
|
|
19676
|
+
owner: c.userId
|
|
19677
|
+
} });
|
|
19678
|
+
return { success: true };
|
|
19679
|
+
},
|
|
19522
19680
|
"nc.getTexts": async (input) => {
|
|
19523
19681
|
const texts = requireTexts();
|
|
19524
19682
|
return input.language === void 0 ? texts.view() : texts.view(input.language);
|
|
@@ -28382,7 +28540,8 @@ var OUTBOX_RECORD_KINDS = new Set([
|
|
|
28382
28540
|
"audio-window",
|
|
28383
28541
|
"system-event",
|
|
28384
28542
|
"timelapse",
|
|
28385
|
-
"ai-summary"
|
|
28543
|
+
"ai-summary",
|
|
28544
|
+
"calendar-events"
|
|
28386
28545
|
]);
|
|
28387
28546
|
function isOutboxRecordKind(x) {
|
|
28388
28547
|
return typeof x === "string" && OUTBOX_RECORD_KINDS.has(x);
|
|
@@ -28735,7 +28894,7 @@ function buildRuleAiSystemPrompt(shape, language, maxWords, grouped = false) {
|
|
|
28735
28894
|
].join("");
|
|
28736
28895
|
}
|
|
28737
28896
|
/** `HH:MM` in the host timezone. */
|
|
28738
|
-
function clockOf$
|
|
28897
|
+
function clockOf$5(atMs) {
|
|
28739
28898
|
const d = new Date(atMs);
|
|
28740
28899
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
28741
28900
|
}
|
|
@@ -28773,7 +28932,7 @@ function buildRuleAiPrompt(input) {
|
|
|
28773
28932
|
if (shape === "detection-track") {
|
|
28774
28933
|
lines.push(`Camera: ${camera}.${located} Subject: ${subject}. The stills, in order:`);
|
|
28775
28934
|
for (const [i, frame] of input.frames.entries()) {
|
|
28776
|
-
const when = frame.atMs !== void 0 ? ` · ${clockOf$
|
|
28935
|
+
const when = frame.atMs !== void 0 ? ` · ${clockOf$5(frame.atMs)}` : "";
|
|
28777
28936
|
lines.push(`${String(i + 1)}.${when}`);
|
|
28778
28937
|
}
|
|
28779
28938
|
return lines.join("\n");
|
|
@@ -29070,7 +29229,7 @@ var NcRuleAiRowSchema = object({
|
|
|
29070
29229
|
updatedAt: number()
|
|
29071
29230
|
});
|
|
29072
29231
|
/** Operator-authored and tiny; a generous ceiling. */
|
|
29073
|
-
var LOAD_LIMIT$
|
|
29232
|
+
var LOAD_LIMIT$4 = 1e4;
|
|
29074
29233
|
var NC_RULE_AI_SPEC = {
|
|
29075
29234
|
collection: NC_RULE_AI_COLLECTION,
|
|
29076
29235
|
columns: [...NC_RULE_AI_COLUMNS],
|
|
@@ -29086,7 +29245,7 @@ var NC_RULE_AI_SPEC = {
|
|
|
29086
29245
|
const parsed = NcRuleAiRowSchema.safeParse(data["row"]);
|
|
29087
29246
|
return parsed.success ? parsed.data : null;
|
|
29088
29247
|
},
|
|
29089
|
-
loadLimit: LOAD_LIMIT$
|
|
29248
|
+
loadLimit: LOAD_LIMIT$4
|
|
29090
29249
|
};
|
|
29091
29250
|
var NcRuleAiStore = class {
|
|
29092
29251
|
ledger;
|
|
@@ -29423,7 +29582,7 @@ var NC_RULES_INDEXES = [{
|
|
|
29423
29582
|
columns: ["enabled"]
|
|
29424
29583
|
}];
|
|
29425
29584
|
/** Query cap — the rule set is operator-authored and tiny; a generous ceiling. */
|
|
29426
|
-
var LOAD_LIMIT$
|
|
29585
|
+
var LOAD_LIMIT$3 = 1e4;
|
|
29427
29586
|
function isRecord$3(value) {
|
|
29428
29587
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29429
29588
|
}
|
|
@@ -29472,7 +29631,7 @@ function makeRulesSpec(onProjection) {
|
|
|
29472
29631
|
if (projection.kind === "projected") onProjection?.(key, projection);
|
|
29473
29632
|
return parsed.data;
|
|
29474
29633
|
},
|
|
29475
|
-
loadLimit: LOAD_LIMIT$
|
|
29634
|
+
loadLimit: LOAD_LIMIT$3
|
|
29476
29635
|
};
|
|
29477
29636
|
}
|
|
29478
29637
|
/** The spec `declare` needs — no sink: declaring a collection projects nothing. */
|
|
@@ -30544,7 +30703,7 @@ var DEFAULT_PROMPT_EN = "What happened during this period? Summarise it for a ho
|
|
|
30544
30703
|
*/
|
|
30545
30704
|
var SummaryAnswerSchema = object({ summary: string() });
|
|
30546
30705
|
/** `HH:MM` in the host timezone — the two numbers a frame label needs. */
|
|
30547
|
-
function clockOf$
|
|
30706
|
+
function clockOf$4(atMs) {
|
|
30548
30707
|
const d = new Date(atMs);
|
|
30549
30708
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
30550
30709
|
}
|
|
@@ -30567,7 +30726,7 @@ function buildSummaryAiPrompt(input, frames) {
|
|
|
30567
30726
|
const roster = frames.map((f, i) => {
|
|
30568
30727
|
const location = plainLabel(f.location ?? "");
|
|
30569
30728
|
const where = location.length > 0 ? ` (${location})` : "";
|
|
30570
|
-
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$
|
|
30729
|
+
return `${String(i + 1)}. ${plainLabel(f.deviceName)}${where} · ${clockOf$4(f.atMs)} · ${plainVocabulary(f.className) || "subject"}`;
|
|
30571
30730
|
}).join("\n");
|
|
30572
30731
|
const described = /* @__PURE__ */ new Map();
|
|
30573
30732
|
for (const f of frames) {
|
|
@@ -31170,7 +31329,7 @@ var NO_DETECTIONS$1 = {
|
|
|
31170
31329
|
* has to check is a key somebody can get wrong. */
|
|
31171
31330
|
var BODY_SEPARATOR$1 = " · ";
|
|
31172
31331
|
/** `HH:MM` in the host timezone. */
|
|
31173
|
-
function clockOf$
|
|
31332
|
+
function clockOf$3(atMs) {
|
|
31174
31333
|
const d = new Date(atMs);
|
|
31175
31334
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
31176
31335
|
}
|
|
@@ -31184,8 +31343,8 @@ function clockOf$2(atMs) {
|
|
|
31184
31343
|
function summaryTemplateVars(input) {
|
|
31185
31344
|
return {
|
|
31186
31345
|
rule: input.ruleName,
|
|
31187
|
-
from: clockOf$
|
|
31188
|
-
to: clockOf$
|
|
31346
|
+
from: clockOf$3(input.window.startMs),
|
|
31347
|
+
to: clockOf$3(input.window.endMs),
|
|
31189
31348
|
time: ncClockText(input.texts.language, input.generatedAt),
|
|
31190
31349
|
events: String(input.tileCount),
|
|
31191
31350
|
cameras: String(input.cameraCount),
|
|
@@ -31229,7 +31388,7 @@ function composeSummaryBody(input) {
|
|
|
31229
31388
|
* string, for the reason the timelapse learned: a hand-rolled `n === 1` shipped
|
|
31230
31389
|
* "1 intervalli ripresi" to a live phone.
|
|
31231
31390
|
*/
|
|
31232
|
-
function derivedBody$
|
|
31391
|
+
function derivedBody$2(input, vars) {
|
|
31233
31392
|
const texts = input.texts;
|
|
31234
31393
|
const parts = [texts.text({
|
|
31235
31394
|
key: "summary.body.window",
|
|
@@ -31264,7 +31423,7 @@ function derivedBody$1(input, vars) {
|
|
|
31264
31423
|
}
|
|
31265
31424
|
/** Per-target params with the derived `clickUrl` filled in where absent. A
|
|
31266
31425
|
* value the operator set on the target WINS — he chose it. */
|
|
31267
|
-
function paramsFor$
|
|
31426
|
+
function paramsFor$2(target, clickUrl) {
|
|
31268
31427
|
if (clickUrl === void 0) return target.params;
|
|
31269
31428
|
if (target.params === void 0) return { clickUrl };
|
|
31270
31429
|
if (typeof target.params["clickUrl"] === "string") return target.params;
|
|
@@ -31280,11 +31439,11 @@ function summaryRecordId(ruleId, windowEndMs) {
|
|
|
31280
31439
|
/** The caption burned onto the mosaic's title band, with the same `{{var}}`
|
|
31281
31440
|
* vocabulary the title and body use. `''` is a real answer ("no caption"). */
|
|
31282
31441
|
function summaryCaptionText(input) {
|
|
31283
|
-
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$
|
|
31442
|
+
if (input.captionText === void 0) return `${input.ruleName} — ${clockOf$3(input.window.startMs)}→${clockOf$3(input.window.endMs)}`;
|
|
31284
31443
|
return renderTemplate(input.captionText, {
|
|
31285
31444
|
rule: input.ruleName,
|
|
31286
|
-
from: clockOf$
|
|
31287
|
-
to: clockOf$
|
|
31445
|
+
from: clockOf$3(input.window.startMs),
|
|
31446
|
+
to: clockOf$3(input.window.endMs)
|
|
31288
31447
|
}) ?? "";
|
|
31289
31448
|
}
|
|
31290
31449
|
/**
|
|
@@ -31302,7 +31461,7 @@ function buildSummaryOutboxInputs(input) {
|
|
|
31302
31461
|
const body = composeSummaryBody({
|
|
31303
31462
|
aiText: aiSentence(input),
|
|
31304
31463
|
renderedTemplateBody: renderTemplate(input.template?.body, vars),
|
|
31305
|
-
derivedBody: derivedBody$
|
|
31464
|
+
derivedBody: derivedBody$2(input, vars),
|
|
31306
31465
|
templateNamesAi: templatePlaceholders(input.template?.body ?? "").includes("ai")
|
|
31307
31466
|
});
|
|
31308
31467
|
const recordId = summaryRecordId(input.ruleId, input.window.endMs);
|
|
@@ -31321,7 +31480,7 @@ function buildSummaryOutboxInputs(input) {
|
|
|
31321
31480
|
},
|
|
31322
31481
|
media: "none",
|
|
31323
31482
|
...artifacts.length > 0 ? { artifacts } : {},
|
|
31324
|
-
...paramsFor$
|
|
31483
|
+
...paramsFor$2(target, input.clickUrl) !== void 0 ? { params: paramsFor$2(target, input.clickUrl) } : {},
|
|
31325
31484
|
subject: {
|
|
31326
31485
|
deviceId: input.primaryDeviceId,
|
|
31327
31486
|
className: "summary",
|
|
@@ -32216,7 +32375,7 @@ var NcSummaryProducer = class {
|
|
|
32216
32375
|
purpose: "summary",
|
|
32217
32376
|
tiles: tiles.map(({ candidate, jpeg }) => ({
|
|
32218
32377
|
jpeg,
|
|
32219
|
-
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$
|
|
32378
|
+
...plan.tileLabels ? { label: `${names.get(candidate.deviceId) ?? candidate.deviceId} · ${clockOf$2(candidate.firstSeen)} · ${candidate.className}` } : {},
|
|
32220
32379
|
deviceId: candidate.deviceId,
|
|
32221
32380
|
sourceId: candidate.trackId
|
|
32222
32381
|
})),
|
|
@@ -32439,7 +32598,7 @@ var NcSummaryProducer = class {
|
|
|
32439
32598
|
}
|
|
32440
32599
|
};
|
|
32441
32600
|
/** `HH:MM` in the host timezone — the two numbers a tile label needs. */
|
|
32442
|
-
function clockOf$
|
|
32601
|
+
function clockOf$2(atMs) {
|
|
32443
32602
|
const d = new Date(atMs);
|
|
32444
32603
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
32445
32604
|
}
|
|
@@ -32515,7 +32674,7 @@ var NC_SUMMARY_RULES_INDEXES = [{
|
|
|
32515
32674
|
columns: ["enabled"]
|
|
32516
32675
|
}];
|
|
32517
32676
|
/** The rule set is operator-authored and tiny; a generous ceiling. */
|
|
32518
|
-
var LOAD_LIMIT$
|
|
32677
|
+
var LOAD_LIMIT$2 = 1e4;
|
|
32519
32678
|
/**
|
|
32520
32679
|
* The ledger contract for the digest rule set.
|
|
32521
32680
|
*
|
|
@@ -32545,7 +32704,7 @@ var NC_SUMMARY_RULES_SPEC = {
|
|
|
32545
32704
|
const parsed = NcSummaryRuleSchema.safeParse(data["rule"]);
|
|
32546
32705
|
return parsed.success ? parsed.data : null;
|
|
32547
32706
|
},
|
|
32548
|
-
loadLimit: LOAD_LIMIT$
|
|
32707
|
+
loadLimit: LOAD_LIMIT$2
|
|
32549
32708
|
};
|
|
32550
32709
|
/**
|
|
32551
32710
|
* The three-way CLEARABLE patch signal, one function per field: `undefined`
|
|
@@ -32556,7 +32715,7 @@ var NC_SUMMARY_RULES_SPEC = {
|
|
|
32556
32715
|
* cast here would be a cast on the exact path that decides whether a persisted
|
|
32557
32716
|
* rule keeps a field. Three literal destructurings are three provable ones.
|
|
32558
32717
|
*/
|
|
32559
|
-
function applyTemplatePatch$
|
|
32718
|
+
function applyTemplatePatch$2(merged, template) {
|
|
32560
32719
|
if (template === void 0) return merged;
|
|
32561
32720
|
if (template !== null) return {
|
|
32562
32721
|
...merged,
|
|
@@ -32702,7 +32861,7 @@ var NcSummaryStore = class {
|
|
|
32702
32861
|
createdAt: existing.createdAt,
|
|
32703
32862
|
updatedAt: this.now()
|
|
32704
32863
|
};
|
|
32705
|
-
return this.write(applyAiPatch(applyFiltersPatch(applyTemplatePatch$
|
|
32864
|
+
return this.write(applyAiPatch(applyFiltersPatch(applyTemplatePatch$2(merged, template), filters), ai));
|
|
32706
32865
|
}
|
|
32707
32866
|
async setEnabled(ruleId, enabled) {
|
|
32708
32867
|
return this.update(ruleId, { enabled });
|
|
@@ -32754,6 +32913,408 @@ var NcSummaryStore = class {
|
|
|
32754
32913
|
}
|
|
32755
32914
|
};
|
|
32756
32915
|
//#endregion
|
|
32916
|
+
//#region src/notification-center/calendar-events/calendar-events-store.ts
|
|
32917
|
+
/**
|
|
32918
|
+
* Durable calendar-events rule set — same DurableLedger mechanics as
|
|
32919
|
+
* Summaries, with a per-rule `generatedAt` stamp (one fire per schedule window).
|
|
32920
|
+
*/
|
|
32921
|
+
/**
|
|
32922
|
+
* @durable class=config owner=notification-center
|
|
32923
|
+
* write="an operator creates or edits a calendar-events rule, or the producer stamps a delivered window via markGenerated"
|
|
32924
|
+
* retention="none — a row goes only when the operator deletes the rule."
|
|
32925
|
+
*/
|
|
32926
|
+
var NC_CALENDAR_EVENTS_RULES_COLLECTION = "notification-center:calendar-events-rules";
|
|
32927
|
+
var NC_CALENDAR_EVENTS_RULES_COLUMNS = [
|
|
32928
|
+
{
|
|
32929
|
+
name: "id",
|
|
32930
|
+
type: "TEXT",
|
|
32931
|
+
primaryKey: true,
|
|
32932
|
+
notNull: true
|
|
32933
|
+
},
|
|
32934
|
+
{
|
|
32935
|
+
name: "name",
|
|
32936
|
+
type: "TEXT",
|
|
32937
|
+
notNull: true
|
|
32938
|
+
},
|
|
32939
|
+
{
|
|
32940
|
+
name: "enabled",
|
|
32941
|
+
type: "BOOLEAN",
|
|
32942
|
+
notNull: true
|
|
32943
|
+
},
|
|
32944
|
+
{
|
|
32945
|
+
name: "updatedAt",
|
|
32946
|
+
type: "INTEGER",
|
|
32947
|
+
notNull: true
|
|
32948
|
+
},
|
|
32949
|
+
{
|
|
32950
|
+
name: "rule",
|
|
32951
|
+
type: "JSON",
|
|
32952
|
+
notNull: true
|
|
32953
|
+
}
|
|
32954
|
+
];
|
|
32955
|
+
var NC_CALENDAR_EVENTS_RULES_INDEXES = [{
|
|
32956
|
+
name: "idx_nc_calendar_events_rules_enabled",
|
|
32957
|
+
columns: ["enabled"]
|
|
32958
|
+
}];
|
|
32959
|
+
var LOAD_LIMIT$1 = 1e4;
|
|
32960
|
+
var NC_CALENDAR_EVENTS_RULES_SPEC = {
|
|
32961
|
+
collection: NC_CALENDAR_EVENTS_RULES_COLLECTION,
|
|
32962
|
+
columns: [...NC_CALENDAR_EVENTS_RULES_COLUMNS],
|
|
32963
|
+
indexes: [...NC_CALENDAR_EVENTS_RULES_INDEXES],
|
|
32964
|
+
writeMode: "write-through",
|
|
32965
|
+
keyOf: (rule) => rule.id,
|
|
32966
|
+
toValue: (rule) => ({
|
|
32967
|
+
name: rule.name,
|
|
32968
|
+
enabled: rule.enabled,
|
|
32969
|
+
updatedAt: rule.updatedAt,
|
|
32970
|
+
rule
|
|
32971
|
+
}),
|
|
32972
|
+
fromRecord: (_key, data) => {
|
|
32973
|
+
const parsed = NcCalendarEventsRuleSchema.safeParse(data["rule"]);
|
|
32974
|
+
return parsed.success ? parsed.data : null;
|
|
32975
|
+
},
|
|
32976
|
+
loadLimit: LOAD_LIMIT$1
|
|
32977
|
+
};
|
|
32978
|
+
function applyTemplatePatch$1(merged, template) {
|
|
32979
|
+
if (template === void 0) return merged;
|
|
32980
|
+
if (template !== null) return {
|
|
32981
|
+
...merged,
|
|
32982
|
+
template
|
|
32983
|
+
};
|
|
32984
|
+
const { template: _cleared, ...rest } = merged;
|
|
32985
|
+
return rest;
|
|
32986
|
+
}
|
|
32987
|
+
function applyOptionalStringList(merged, key, value) {
|
|
32988
|
+
if (value === void 0) return merged;
|
|
32989
|
+
if (value !== null) return {
|
|
32990
|
+
...merged,
|
|
32991
|
+
[key]: [...value]
|
|
32992
|
+
};
|
|
32993
|
+
const { [key]: _cleared, ...rest } = merged;
|
|
32994
|
+
return rest;
|
|
32995
|
+
}
|
|
32996
|
+
var NcCalendarEventsStore = class {
|
|
32997
|
+
ledger;
|
|
32998
|
+
store;
|
|
32999
|
+
logger;
|
|
33000
|
+
now;
|
|
33001
|
+
newId;
|
|
33002
|
+
constructor(deps) {
|
|
33003
|
+
this.store = deps.store;
|
|
33004
|
+
this.ledger = new DurableLedger({
|
|
33005
|
+
spec: NC_CALENDAR_EVENTS_RULES_SPEC,
|
|
33006
|
+
store: deps.store,
|
|
33007
|
+
logger: deps.logger
|
|
33008
|
+
});
|
|
33009
|
+
this.logger = deps.logger;
|
|
33010
|
+
this.now = deps.now ?? (() => Date.now());
|
|
33011
|
+
this.newId = deps.newId ?? (() => randomUUID());
|
|
33012
|
+
}
|
|
33013
|
+
static async declare(store) {
|
|
33014
|
+
await DurableLedger.declare(store, NC_CALENDAR_EVENTS_RULES_SPEC);
|
|
33015
|
+
}
|
|
33016
|
+
async load() {
|
|
33017
|
+
await this.ledger.load();
|
|
33018
|
+
this.logger.debug("calendar-events rules loaded", { meta: { rules: this.ledger.size } });
|
|
33019
|
+
}
|
|
33020
|
+
list() {
|
|
33021
|
+
return this.ledger.snapshot().toSorted((a, b) => b.updatedAt - a.updatedAt);
|
|
33022
|
+
}
|
|
33023
|
+
listEnabled() {
|
|
33024
|
+
return this.list().filter((r) => r.enabled);
|
|
33025
|
+
}
|
|
33026
|
+
get(ruleId) {
|
|
33027
|
+
return this.ledger.get(ruleId) ?? null;
|
|
33028
|
+
}
|
|
33029
|
+
listForOwner(userId) {
|
|
33030
|
+
return this.list().filter((r) => r.ownerUserId === void 0 || r.ownerUserId === userId);
|
|
33031
|
+
}
|
|
33032
|
+
async create(input, createdBy, ownerUserId) {
|
|
33033
|
+
const now = this.now();
|
|
33034
|
+
const rule = NcCalendarEventsRuleSchema.parse({
|
|
33035
|
+
...NcCalendarEventsRuleInputSchema.parse(input),
|
|
33036
|
+
id: this.newId(),
|
|
33037
|
+
...ownerUserId !== void 0 ? { ownerUserId } : {},
|
|
33038
|
+
createdBy,
|
|
33039
|
+
createdAt: now,
|
|
33040
|
+
updatedAt: now
|
|
33041
|
+
});
|
|
33042
|
+
await this.ledger.put(rule);
|
|
33043
|
+
return rule;
|
|
33044
|
+
}
|
|
33045
|
+
async update(ruleId, patch) {
|
|
33046
|
+
const existing = this.ledger.get(ruleId);
|
|
33047
|
+
if (!existing) throw new Error(`calendar-events rule not found: ${ruleId}`);
|
|
33048
|
+
const { template, calendars, calendarsExclude, ...rest } = patch;
|
|
33049
|
+
const merged = {
|
|
33050
|
+
...existing,
|
|
33051
|
+
...rest,
|
|
33052
|
+
id: existing.id,
|
|
33053
|
+
...existing.ownerUserId !== void 0 ? { ownerUserId: existing.ownerUserId } : {},
|
|
33054
|
+
...existing.generatedAt !== void 0 ? { generatedAt: existing.generatedAt } : {},
|
|
33055
|
+
createdBy: existing.createdBy,
|
|
33056
|
+
createdAt: existing.createdAt,
|
|
33057
|
+
updatedAt: this.now()
|
|
33058
|
+
};
|
|
33059
|
+
return this.write(applyOptionalStringList(applyOptionalStringList(applyTemplatePatch$1(merged, template), "calendars", calendars), "calendarsExclude", calendarsExclude));
|
|
33060
|
+
}
|
|
33061
|
+
async setEnabled(ruleId, enabled) {
|
|
33062
|
+
return this.update(ruleId, { enabled });
|
|
33063
|
+
}
|
|
33064
|
+
async markGenerated(ruleId, windowEndMs) {
|
|
33065
|
+
const existing = this.ledger.get(ruleId);
|
|
33066
|
+
if (!existing) throw new Error(`calendar-events rule not found: ${ruleId}`);
|
|
33067
|
+
return this.write({
|
|
33068
|
+
...existing,
|
|
33069
|
+
generatedAt: Math.max(existing.generatedAt ?? 0, windowEndMs)
|
|
33070
|
+
});
|
|
33071
|
+
}
|
|
33072
|
+
/**
|
|
33073
|
+
* Idempotent delete — unknown ids are a no-op in RAM, still attempted on disk.
|
|
33074
|
+
*
|
|
33075
|
+
* Deliberately NOT {@link DurableLedger.forget}: that swallows a failed
|
|
33076
|
+
* durable delete. Mirror eviction first; durable half re-throws on failure
|
|
33077
|
+
* (same shape as Summaries).
|
|
33078
|
+
*/
|
|
33079
|
+
async delete(ruleId) {
|
|
33080
|
+
this.ledger.evict(ruleId);
|
|
33081
|
+
try {
|
|
33082
|
+
await this.store.delete.mutate({
|
|
33083
|
+
collection: NC_CALENDAR_EVENTS_RULES_COLLECTION,
|
|
33084
|
+
key: ruleId
|
|
33085
|
+
});
|
|
33086
|
+
} catch (err) {
|
|
33087
|
+
this.logger.warn("calendar-events rule durable delete failed", { meta: {
|
|
33088
|
+
ruleId,
|
|
33089
|
+
error: err instanceof Error ? err.message : String(err)
|
|
33090
|
+
} });
|
|
33091
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
33092
|
+
}
|
|
33093
|
+
}
|
|
33094
|
+
async write(rule) {
|
|
33095
|
+
const validated = NcCalendarEventsRuleSchema.parse(rule);
|
|
33096
|
+
await this.ledger.put(validated);
|
|
33097
|
+
return validated;
|
|
33098
|
+
}
|
|
33099
|
+
};
|
|
33100
|
+
//#endregion
|
|
33101
|
+
//#region src/notification-center/calendar-events/calendar-events-delivery.ts
|
|
33102
|
+
function calendarEventsRecordId(ruleId, windowEndMs) {
|
|
33103
|
+
return `calendar-events:${ruleId}:${windowEndMs}`;
|
|
33104
|
+
}
|
|
33105
|
+
/** `YYYY-MM-DD HH:MM` in the host timezone. */
|
|
33106
|
+
function clockOf$1(atMs) {
|
|
33107
|
+
const d = new Date(atMs);
|
|
33108
|
+
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")}`}`;
|
|
33109
|
+
}
|
|
33110
|
+
function formatEventLine(event) {
|
|
33111
|
+
const when = event.allDay ? clockOf$1(event.startMs).slice(0, 10) : `${clockOf$1(event.startMs)}–${clockOf$1(event.endMs)}`;
|
|
33112
|
+
const where = event.location !== void 0 && event.location.length > 0 ? ` @ ${event.location}` : "";
|
|
33113
|
+
return `• ${when} ${event.title}${where}`;
|
|
33114
|
+
}
|
|
33115
|
+
function derivedBody$1(input) {
|
|
33116
|
+
if (input.events.length === 0) return `No events in the next ${String(input.horizonDays)} day(s).`;
|
|
33117
|
+
const lines = input.events.slice(0, 40).map(formatEventLine);
|
|
33118
|
+
const extra = input.events.length > 40 ? `\n…and ${String(input.events.length - 40)} more` : "";
|
|
33119
|
+
return lines.join("\n") + extra;
|
|
33120
|
+
}
|
|
33121
|
+
function templateVars(input) {
|
|
33122
|
+
return {
|
|
33123
|
+
rule: input.ruleName,
|
|
33124
|
+
count: String(input.events.length),
|
|
33125
|
+
horizonDays: String(input.horizonDays),
|
|
33126
|
+
from: clockOf$1(input.eventWindow.startMs),
|
|
33127
|
+
to: clockOf$1(input.eventWindow.endMs),
|
|
33128
|
+
events: derivedBody$1(input),
|
|
33129
|
+
calendars: String(input.calendarIds.length)
|
|
33130
|
+
};
|
|
33131
|
+
}
|
|
33132
|
+
function paramsFor$1(target) {
|
|
33133
|
+
const params = target.params;
|
|
33134
|
+
if (params === void 0) return void 0;
|
|
33135
|
+
const out = {};
|
|
33136
|
+
for (const [k, v] of Object.entries(params)) if (typeof v === "string") out[k] = v;
|
|
33137
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
33138
|
+
}
|
|
33139
|
+
/**
|
|
33140
|
+
* One outbox row per enabled target. Ready for `NcOutbox.enqueue`.
|
|
33141
|
+
*/
|
|
33142
|
+
function buildCalendarEventsOutboxInputs(input) {
|
|
33143
|
+
const vars = templateVars(input);
|
|
33144
|
+
const optedOut = new Set(input.disabledTargetIds ?? []);
|
|
33145
|
+
const title = renderTemplate(input.template?.title, vars) ?? `${input.ruleName}: ${String(input.events.length)} upcoming`;
|
|
33146
|
+
const body = renderTemplate(input.template?.body, vars) ?? derivedBody$1(input);
|
|
33147
|
+
const recordId = calendarEventsRecordId(input.ruleId, input.scheduleWindow.endMs);
|
|
33148
|
+
return input.targets.filter((t) => !optedOut.has(t.targetId)).map((target) => {
|
|
33149
|
+
const payload = {
|
|
33150
|
+
ruleName: input.ruleName,
|
|
33151
|
+
delivery: "calendar-events",
|
|
33152
|
+
priority: input.priority,
|
|
33153
|
+
template: {
|
|
33154
|
+
title,
|
|
33155
|
+
body
|
|
33156
|
+
},
|
|
33157
|
+
media: "none",
|
|
33158
|
+
...paramsFor$1(target) !== void 0 ? { params: paramsFor$1(target) } : {},
|
|
33159
|
+
subject: {
|
|
33160
|
+
deviceId: NC_SYSTEM_DEVICE_ID$1,
|
|
33161
|
+
className: "calendar-events",
|
|
33162
|
+
zones: [],
|
|
33163
|
+
timestamp: input.scheduleWindow.endMs
|
|
33164
|
+
}
|
|
33165
|
+
};
|
|
33166
|
+
return {
|
|
33167
|
+
ruleId: input.ruleId,
|
|
33168
|
+
targetId: target.targetId,
|
|
33169
|
+
deviceId: NC_SYSTEM_DEVICE_ID$1,
|
|
33170
|
+
recordKind: "calendar-events",
|
|
33171
|
+
recordId,
|
|
33172
|
+
payload
|
|
33173
|
+
};
|
|
33174
|
+
});
|
|
33175
|
+
}
|
|
33176
|
+
//#endregion
|
|
33177
|
+
//#region src/notification-center/calendar-events/calendar-events-selection.ts
|
|
33178
|
+
/**
|
|
33179
|
+
* Expand a rule's calendar scope against the known calendar ids.
|
|
33180
|
+
*
|
|
33181
|
+
* - All (both lists empty/absent) → every known id
|
|
33182
|
+
* - Only → the include list, intersected with known (unknown ids dropped)
|
|
33183
|
+
* - Except → known minus the exclude list
|
|
33184
|
+
*
|
|
33185
|
+
* An empty known set always yields empty — there is nothing to notify about.
|
|
33186
|
+
*/
|
|
33187
|
+
function expandCalendarScope(scope, knownIds) {
|
|
33188
|
+
const known = [...knownIds];
|
|
33189
|
+
if (known.length === 0) return [];
|
|
33190
|
+
const include = scope?.calendars ?? [];
|
|
33191
|
+
const exclude = scope?.calendarsExclude ?? [];
|
|
33192
|
+
if (include.length > 0) {
|
|
33193
|
+
const wanted = new Set(include);
|
|
33194
|
+
return known.filter((id) => wanted.has(id));
|
|
33195
|
+
}
|
|
33196
|
+
if (exclude.length > 0) {
|
|
33197
|
+
const banned = new Set(exclude);
|
|
33198
|
+
return known.filter((id) => !banned.has(id));
|
|
33199
|
+
}
|
|
33200
|
+
return known;
|
|
33201
|
+
}
|
|
33202
|
+
//#endregion
|
|
33203
|
+
//#region src/notification-center/calendar-events/calendar-events-producer.ts
|
|
33204
|
+
/** How far back a closed schedule window may still be owed. */
|
|
33205
|
+
var CALENDAR_EVENTS_LOOKBACK_MS = 2160 * 60 * 1e3;
|
|
33206
|
+
var NcCalendarEventsProducer = class {
|
|
33207
|
+
deps;
|
|
33208
|
+
inFlight = false;
|
|
33209
|
+
now;
|
|
33210
|
+
constructor(deps) {
|
|
33211
|
+
this.deps = deps;
|
|
33212
|
+
this.now = deps.now ?? (() => Date.now());
|
|
33213
|
+
}
|
|
33214
|
+
async tick() {
|
|
33215
|
+
if (this.inFlight) return {
|
|
33216
|
+
examined: 0,
|
|
33217
|
+
sent: 0,
|
|
33218
|
+
empty: 0,
|
|
33219
|
+
skipped: 0,
|
|
33220
|
+
failed: 0
|
|
33221
|
+
};
|
|
33222
|
+
this.inFlight = true;
|
|
33223
|
+
const report = {
|
|
33224
|
+
examined: 0,
|
|
33225
|
+
sent: 0,
|
|
33226
|
+
empty: 0,
|
|
33227
|
+
skipped: 0,
|
|
33228
|
+
failed: 0
|
|
33229
|
+
};
|
|
33230
|
+
try {
|
|
33231
|
+
const rules = this.deps.store.listEnabled();
|
|
33232
|
+
for (const rule of rules) {
|
|
33233
|
+
report.examined += 1;
|
|
33234
|
+
try {
|
|
33235
|
+
const outcome = await this.runRule(rule);
|
|
33236
|
+
if (outcome === "sent") report.sent += 1;
|
|
33237
|
+
else if (outcome === "empty") report.empty += 1;
|
|
33238
|
+
else report.skipped += 1;
|
|
33239
|
+
} catch (err) {
|
|
33240
|
+
report.failed += 1;
|
|
33241
|
+
this.deps.logger.warn("calendar-events rule failed", { meta: {
|
|
33242
|
+
ruleId: rule.id,
|
|
33243
|
+
error: err instanceof Error ? err.message : String(err)
|
|
33244
|
+
} });
|
|
33245
|
+
}
|
|
33246
|
+
}
|
|
33247
|
+
return report;
|
|
33248
|
+
} finally {
|
|
33249
|
+
this.inFlight = false;
|
|
33250
|
+
}
|
|
33251
|
+
}
|
|
33252
|
+
async runRule(rule) {
|
|
33253
|
+
const nowMs = this.now();
|
|
33254
|
+
const scheduleWindow = findLastClosedWindow({
|
|
33255
|
+
schedule: rule.schedule,
|
|
33256
|
+
nowMs,
|
|
33257
|
+
lookbackMs: CALENDAR_EVENTS_LOOKBACK_MS
|
|
33258
|
+
});
|
|
33259
|
+
if (scheduleWindow === null) return "skipped";
|
|
33260
|
+
if ((rule.generatedAt ?? 0) >= scheduleWindow.endMs) return "skipped";
|
|
33261
|
+
const known = await this.deps.calendars.listCalendars();
|
|
33262
|
+
const calendarIds = expandCalendarScope({
|
|
33263
|
+
calendars: rule.calendars,
|
|
33264
|
+
calendarsExclude: rule.calendarsExclude
|
|
33265
|
+
}, known.map((c) => c.id));
|
|
33266
|
+
if (calendarIds.length === 0) {
|
|
33267
|
+
this.deps.logger.info("calendar-events: no calendars in scope", { meta: { ruleId: rule.id } });
|
|
33268
|
+
if (!rule.deliverEmpty) {
|
|
33269
|
+
await this.deps.store.markGenerated(rule.id, scheduleWindow.endMs);
|
|
33270
|
+
return "empty";
|
|
33271
|
+
}
|
|
33272
|
+
}
|
|
33273
|
+
const eventWindow = calendarEventsWindow(nowMs, rule.horizonDays);
|
|
33274
|
+
const events = calendarIds.length === 0 ? [] : await this.deps.calendars.listEvents({
|
|
33275
|
+
calendarIds,
|
|
33276
|
+
startMs: eventWindow.startMs,
|
|
33277
|
+
endMs: eventWindow.endMs
|
|
33278
|
+
});
|
|
33279
|
+
if (events.length === 0 && !rule.deliverEmpty) {
|
|
33280
|
+
this.deps.logger.info("calendar-events: empty horizon, not delivering", { meta: {
|
|
33281
|
+
ruleId: rule.id,
|
|
33282
|
+
horizonDays: eventWindow.horizonDays
|
|
33283
|
+
} });
|
|
33284
|
+
await this.deps.store.markGenerated(rule.id, scheduleWindow.endMs);
|
|
33285
|
+
return "empty";
|
|
33286
|
+
}
|
|
33287
|
+
const sorted = [...events].toSorted((a, b) => a.startMs - b.startMs);
|
|
33288
|
+
await this.enqueue(rule, scheduleWindow, eventWindow, calendarIds, sorted);
|
|
33289
|
+
await this.deps.store.markGenerated(rule.id, scheduleWindow.endMs);
|
|
33290
|
+
return "sent";
|
|
33291
|
+
}
|
|
33292
|
+
async enqueue(rule, scheduleWindow, eventWindow, calendarIds, events) {
|
|
33293
|
+
const inputs = buildCalendarEventsOutboxInputs({
|
|
33294
|
+
ruleId: rule.id,
|
|
33295
|
+
ruleName: rule.name,
|
|
33296
|
+
scheduleWindow,
|
|
33297
|
+
eventWindow: {
|
|
33298
|
+
startMs: eventWindow.startMs,
|
|
33299
|
+
endMs: eventWindow.endMs
|
|
33300
|
+
},
|
|
33301
|
+
horizonDays: eventWindow.horizonDays,
|
|
33302
|
+
events,
|
|
33303
|
+
calendarIds,
|
|
33304
|
+
targets: rule.targets,
|
|
33305
|
+
priority: rule.priority,
|
|
33306
|
+
...rule.template !== void 0 ? { template: rule.template } : {}
|
|
33307
|
+
});
|
|
33308
|
+
for (const input of inputs) await this.deps.outbox.enqueue(input);
|
|
33309
|
+
this.deps.logger.info("calendar-events delivered", { meta: {
|
|
33310
|
+
ruleId: rule.id,
|
|
33311
|
+
events: events.length,
|
|
33312
|
+
targets: inputs.length,
|
|
33313
|
+
windowEndMs: scheduleWindow.endMs
|
|
33314
|
+
} });
|
|
33315
|
+
}
|
|
33316
|
+
};
|
|
33317
|
+
//#endregion
|
|
32757
33318
|
//#region src/notification-center/text-catalog-editor.ts
|
|
32758
33319
|
var NcTextCatalogEditor = class {
|
|
32759
33320
|
store;
|
|
@@ -35837,7 +36398,7 @@ function packagePhaseOf(ev) {
|
|
|
35837
36398
|
*/
|
|
35838
36399
|
function historyRecordKind(kind) {
|
|
35839
36400
|
if (kind === "audio-event" || kind === "audio-window") return "object-event";
|
|
35840
|
-
if (kind === "timelapse" || kind === "ai-summary") return "system-event";
|
|
36401
|
+
if (kind === "timelapse" || kind === "ai-summary" || kind === "calendar-events") return "system-event";
|
|
35841
36402
|
return kind;
|
|
35842
36403
|
}
|
|
35843
36404
|
/**
|
|
@@ -35852,7 +36413,8 @@ function historyRecordKind(kind) {
|
|
|
35852
36413
|
function cooldownSubjectKind(kind) {
|
|
35853
36414
|
switch (kind) {
|
|
35854
36415
|
case "timelapse":
|
|
35855
|
-
case "ai-summary":
|
|
36416
|
+
case "ai-summary":
|
|
36417
|
+
case "calendar-events": return "device-event";
|
|
35856
36418
|
default: return kind;
|
|
35857
36419
|
}
|
|
35858
36420
|
}
|
|
@@ -35952,7 +36514,7 @@ function outboxEntryToHistory(texts, entry) {
|
|
|
35952
36514
|
id: entry.id,
|
|
35953
36515
|
ruleId: entry.ruleId,
|
|
35954
36516
|
ruleName: entry.payload.ruleName,
|
|
35955
|
-
delivery: entry.payload.delivery === "timelapse" || entry.payload.delivery === "ai-summary" ? "system-event" : entry.payload.delivery,
|
|
36517
|
+
delivery: entry.payload.delivery === "timelapse" || entry.payload.delivery === "ai-summary" || entry.payload.delivery === "calendar-events" ? "system-event" : entry.payload.delivery,
|
|
35956
36518
|
targetId: entry.targetId,
|
|
35957
36519
|
deviceId: entry.deviceId,
|
|
35958
36520
|
recordKind: historyRecordKind(entry.recordKind),
|
|
@@ -36162,6 +36724,9 @@ var NotificationCenter = class {
|
|
|
36162
36724
|
*/
|
|
36163
36725
|
summaryRules;
|
|
36164
36726
|
summaryProducer = null;
|
|
36727
|
+
/** Calendar-events digest rules + producer (hub-only producer). */
|
|
36728
|
+
calendarEventsRules;
|
|
36729
|
+
calendarEventsProducer = null;
|
|
36165
36730
|
/**
|
|
36166
36731
|
* What the deleted-device / deleted-target prune did, and why — its
|
|
36167
36732
|
* disable-once guard and the source of both UIs' banner. Loaded on EVERY
|
|
@@ -37152,6 +37717,11 @@ var NotificationCenter = class {
|
|
|
37152
37717
|
logger: this.logger.child("summary"),
|
|
37153
37718
|
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
37154
37719
|
});
|
|
37720
|
+
this.calendarEventsRules = new NcCalendarEventsStore({
|
|
37721
|
+
store: deps.store,
|
|
37722
|
+
logger: this.logger.child("calendar-events"),
|
|
37723
|
+
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
37724
|
+
});
|
|
37155
37725
|
this.pruneLedger = new NcPruneLedger({
|
|
37156
37726
|
store: deps.store,
|
|
37157
37727
|
logger: this.logger.child("device-prune")
|
|
@@ -37198,6 +37768,14 @@ var NotificationCenter = class {
|
|
|
37198
37768
|
zoneOwner: this.zoneOwners.lookup()
|
|
37199
37769
|
});
|
|
37200
37770
|
}
|
|
37771
|
+
const calendarsPort = deps.calendars;
|
|
37772
|
+
if (calendarsPort !== void 0) this.calendarEventsProducer = new NcCalendarEventsProducer({
|
|
37773
|
+
store: this.calendarEventsRules,
|
|
37774
|
+
outbox: this.outbox,
|
|
37775
|
+
calendars: calendarsPort,
|
|
37776
|
+
logger: this.logger.child("calendar-events"),
|
|
37777
|
+
now: this.now
|
|
37778
|
+
});
|
|
37201
37779
|
}
|
|
37202
37780
|
/**
|
|
37203
37781
|
* The durable timelapse rule set — exposed for the `nc.*Timelapse*` bridge
|
|
@@ -37211,6 +37789,10 @@ var NotificationCenter = class {
|
|
|
37211
37789
|
get summaryStore() {
|
|
37212
37790
|
return this.summaryRules;
|
|
37213
37791
|
}
|
|
37792
|
+
/** The durable calendar-events rule set — exposed for the `nc.*CalendarEvents*` bridge. */
|
|
37793
|
+
get calendarEventsStore() {
|
|
37794
|
+
return this.calendarEventsRules;
|
|
37795
|
+
}
|
|
37214
37796
|
/**
|
|
37215
37797
|
* Produce one digest window for one rule, right now.
|
|
37216
37798
|
*
|
|
@@ -37284,6 +37866,7 @@ var NotificationCenter = class {
|
|
|
37284
37866
|
await NcDeviceMuteStore.declare(store);
|
|
37285
37867
|
await TimelapseStore.declare(store);
|
|
37286
37868
|
await NcSummaryStore.declare(store);
|
|
37869
|
+
await NcCalendarEventsStore.declare(store);
|
|
37287
37870
|
await NcRuleAiStore.declare(store);
|
|
37288
37871
|
await NcTextCatalogStore.declare(store);
|
|
37289
37872
|
await NcArtifactIndex.declare(store);
|
|
@@ -40906,11 +41489,17 @@ var NotificationCenter = class {
|
|
|
40906
41489
|
await this.rules.load();
|
|
40907
41490
|
this.noteInertGroupedRules();
|
|
40908
41491
|
if (this.evaluationActive) this.deviceAlerts.tick();
|
|
40909
|
-
if (this.evaluationActive)
|
|
40910
|
-
this.
|
|
40911
|
-
|
|
41492
|
+
if (this.evaluationActive) {
|
|
41493
|
+
this.stateSummary.tick().catch((err) => {
|
|
41494
|
+
this.logger.warn("state summary tick failed", { meta: { error: String(err) } });
|
|
41495
|
+
});
|
|
41496
|
+
this.calendarEventsProducer?.tick().catch((err) => {
|
|
41497
|
+
this.logger.warn("calendar-events tick failed", { meta: { error: String(err) } });
|
|
41498
|
+
});
|
|
41499
|
+
}
|
|
40912
41500
|
await this.timelapseRules.load();
|
|
40913
41501
|
await this.summaryRules.load();
|
|
41502
|
+
await this.calendarEventsRules.load();
|
|
40914
41503
|
await this.ruleAi.load();
|
|
40915
41504
|
await this.reloadTexts();
|
|
40916
41505
|
this.refreshOccupancyWatch();
|
|
@@ -104721,6 +105310,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
104721
105310
|
store: center.summaryStore,
|
|
104722
105311
|
runNow: (input) => center.runSummaryNow(input)
|
|
104723
105312
|
},
|
|
105313
|
+
calendarEvents: { store: center.calendarEventsStore },
|
|
104724
105314
|
texts: center.textCatalogEditor,
|
|
104725
105315
|
previewTexts: center.textCatalog,
|
|
104726
105316
|
...this.ncArtifactIndex !== null ? { artifacts: this.ncArtifactIndex } : {},
|
|
@@ -105908,7 +106498,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
105908
106498
|
})).tracks;
|
|
105909
106499
|
},
|
|
105910
106500
|
...this.isPostProcessingNode ? { timelapse: this.buildTimelapsePorts(api, stores) } : {},
|
|
105911
|
-
...this.isPostProcessingNode ? { summary: this.buildSummaryPorts(api, stores) } : {}
|
|
106501
|
+
...this.isPostProcessingNode ? { summary: this.buildSummaryPorts(api, stores) } : {},
|
|
106502
|
+
...this.isPostProcessingNode ? { calendars: {
|
|
106503
|
+
listCalendars: async () => {
|
|
106504
|
+
return (await api.calendars.listCalendars.query()).map((c) => ({ id: c.id }));
|
|
106505
|
+
},
|
|
106506
|
+
listEvents: async (input) => api.calendars.listEvents.query({
|
|
106507
|
+
calendarIds: [...input.calendarIds],
|
|
106508
|
+
startMs: input.startMs,
|
|
106509
|
+
endMs: input.endMs
|
|
106510
|
+
})
|
|
106511
|
+
} } : {}
|
|
105912
106512
|
});
|
|
105913
106513
|
this.wireAlarmPanel(this.notificationCenter);
|
|
105914
106514
|
await this.serveNcActionPlane(this.notificationCenter);
|