@camstack/addon-post-analysis 1.2.58 → 1.2.59

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.
@@ -1,4 +1,4 @@
1
- //#region ../types/dist/event-category-41fKf-q9.mjs
1
+ //#region ../types/dist/event-category-Cv9dO26A.mjs
2
2
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
3
3
  EventCategory["SystemBoot"] = "system.boot";
4
4
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -14,6 +14,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
14
14
  */
15
15
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
16
16
  /**
17
+ * A newer addon or server-root package version was found by the
18
+ * authoritative registry check. Emitted once per
19
+ * `(target, packageName, currentVersion, latestVersion)` transition; repeated
20
+ * polling of the same result is deduplicated by the checker.
21
+ */
22
+ EventCategory["UpdateAvailable"] = "update.available";
23
+ /**
17
24
  * Readiness transition for a capability provider. Every producer emits
18
25
  * this event on `onInitialize` completion, `onDestroy`, and
19
26
  * `$node.reconnect`; every consumer that needs to gate on a cross-process
@@ -14256,8 +14263,35 @@ var NcDeliverySchema = _enum([
14256
14263
  "immediate",
14257
14264
  "track-end",
14258
14265
  "device-event",
14259
- "package-event"
14266
+ "package-event",
14267
+ "system-event"
14260
14268
  ]);
14269
+ /**
14270
+ * Stable Notification Center vocabulary over infrastructure/liveness events.
14271
+ * Bus categories are normalized into these intent-level kinds so rules do not
14272
+ * depend on a provider's raw event name or payload shape.
14273
+ */
14274
+ var NcSystemEventKindSchema = _enum([
14275
+ "camera-online",
14276
+ "camera-offline",
14277
+ "stream-online",
14278
+ "stream-offline",
14279
+ "node-online",
14280
+ "node-offline",
14281
+ "addon-update-available",
14282
+ "server-update-available"
14283
+ ]);
14284
+ /**
14285
+ * One coherent system-event condition. `kinds` is the required opt-in safety
14286
+ * gate; the remaining lists are optional narrowing filters relevant to the
14287
+ * selected kinds.
14288
+ */
14289
+ var NcSystemEventConditionSchema = object({
14290
+ kinds: array(NcSystemEventKindSchema).min(1),
14291
+ deviceIds: array(number().int()).min(1).optional(),
14292
+ nodeIds: array(string().min(1)).min(1).optional(),
14293
+ packageNames: array(string().min(1)).min(1).optional()
14294
+ });
14261
14295
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
14262
14296
  var NcScheduleSchema = object({
14263
14297
  windows: array(object({
@@ -14562,6 +14596,8 @@ var NcConditionsSchema = object({
14562
14596
  "picked-up",
14563
14597
  "both"
14564
14598
  ]).optional(),
14599
+ /** Infrastructure/liveness/update event matcher (`system-event` delivery). */
14600
+ systemEvent: NcSystemEventConditionSchema.optional(),
14565
14601
  /**
14566
14602
  * PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
14567
14603
  * (MaskShape vocabulary). A record passes when its bbox overlaps ANY
@@ -14781,7 +14817,8 @@ var NcTestResultSchema = object({
14781
14817
  "object-event",
14782
14818
  "track",
14783
14819
  "device-event",
14784
- "package-event"
14820
+ "package-event",
14821
+ "system-event"
14785
14822
  ]),
14786
14823
  deviceId: number(),
14787
14824
  timestamp: number(),
@@ -14803,7 +14840,8 @@ var NcConditionDescriptorSchema = object({
14803
14840
  "schedule",
14804
14841
  "device",
14805
14842
  "package",
14806
- "occupancy"
14843
+ "occupancy",
14844
+ "system"
14807
14845
  ]),
14808
14846
  label: string(),
14809
14847
  /** Editor widget the UI renders — never hardcode per-condition forms. */
@@ -14821,7 +14859,8 @@ var NcConditionDescriptorSchema = object({
14821
14859
  "crossingSelect",
14822
14860
  "polygonDraw",
14823
14861
  "occupancy",
14824
- "deviceState"
14862
+ "deviceState",
14863
+ "systemEvent"
14825
14864
  ]),
14826
14865
  operator: _enum([
14827
14866
  "in",
@@ -14863,6 +14902,50 @@ var NcConditionDescriptorSchema = object({
14863
14902
  * rule editors render from the catalog, not hardcoded forms (spec §4.2).
14864
14903
  */
14865
14904
  var NC_CONDITION_CATALOG = [
14905
+ {
14906
+ id: "systemEvent",
14907
+ group: "system",
14908
+ label: "System event",
14909
+ valueType: "systemEvent",
14910
+ options: [
14911
+ {
14912
+ value: "camera-online",
14913
+ label: "Camera online"
14914
+ },
14915
+ {
14916
+ value: "camera-offline",
14917
+ label: "Camera offline"
14918
+ },
14919
+ {
14920
+ value: "stream-online",
14921
+ label: "Stream online"
14922
+ },
14923
+ {
14924
+ value: "stream-offline",
14925
+ label: "Stream offline"
14926
+ },
14927
+ {
14928
+ value: "node-online",
14929
+ label: "Node online"
14930
+ },
14931
+ {
14932
+ value: "node-offline",
14933
+ label: "Node offline"
14934
+ },
14935
+ {
14936
+ value: "addon-update-available",
14937
+ label: "Addon update available"
14938
+ },
14939
+ {
14940
+ value: "server-update-available",
14941
+ label: "Server update available"
14942
+ }
14943
+ ],
14944
+ operator: "in",
14945
+ appliesTo: ["system-event"],
14946
+ phase: "P1",
14947
+ description: "Infrastructure and update events. Optionally narrow camera/stream events by device, node events by node id, and addon updates by package name."
14948
+ },
14866
14949
  {
14867
14950
  id: "devices",
14868
14951
  group: "scope",
@@ -15177,7 +15260,8 @@ var NC_CONDITION_CATALOG = [
15177
15260
  "immediate",
15178
15261
  "track-end",
15179
15262
  "device-event",
15180
- "package-event"
15263
+ "package-event",
15264
+ "system-event"
15181
15265
  ],
15182
15266
  phase: "P1",
15183
15267
  description: "Weekly activation windows (invertible); absent = always active."
@@ -15205,7 +15289,8 @@ var NcHistoryRecordKindSchema = _enum([
15205
15289
  "object-event",
15206
15290
  "track-end",
15207
15291
  "device-event",
15208
- "package-event"
15292
+ "package-event",
15293
+ "system-event"
15209
15294
  ]);
15210
15295
  /** Subject summary frozen on the row at fire time (survives rule/record edits). */
15211
15296
  var NcHistorySubjectSchema = object({
@@ -15213,7 +15298,14 @@ var NcHistorySubjectSchema = object({
15213
15298
  label: string().optional(),
15214
15299
  confidence: number().optional(),
15215
15300
  zones: array(string()),
15216
- timestamp: number()
15301
+ timestamp: number(),
15302
+ systemEvent: object({
15303
+ kind: NcSystemEventKindSchema,
15304
+ subject: string(),
15305
+ title: string(),
15306
+ body: string(),
15307
+ data: record(string(), unknown())
15308
+ }).optional()
15217
15309
  });
15218
15310
  /**
15219
15311
  * One delivery-history row. This is a read-only VIEW over the durable
@@ -29,7 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  enumerable: true
30
30
  }) : target, mod));
31
31
  //#endregion
32
- //#region ../types/dist/event-category-41fKf-q9.mjs
32
+ //#region ../types/dist/event-category-Cv9dO26A.mjs
33
33
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
34
34
  EventCategory["SystemBoot"] = "system.boot";
35
35
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -45,6 +45,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
45
45
  */
46
46
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
47
47
  /**
48
+ * A newer addon or server-root package version was found by the
49
+ * authoritative registry check. Emitted once per
50
+ * `(target, packageName, currentVersion, latestVersion)` transition; repeated
51
+ * polling of the same result is deduplicated by the checker.
52
+ */
53
+ EventCategory["UpdateAvailable"] = "update.available";
54
+ /**
48
55
  * Readiness transition for a capability provider. Every producer emits
49
56
  * this event on `onInitialize` completion, `onDestroy`, and
50
57
  * `$node.reconnect`; every consumer that needs to gate on a cross-process
@@ -14287,8 +14294,35 @@ var NcDeliverySchema = _enum([
14287
14294
  "immediate",
14288
14295
  "track-end",
14289
14296
  "device-event",
14290
- "package-event"
14297
+ "package-event",
14298
+ "system-event"
14291
14299
  ]);
14300
+ /**
14301
+ * Stable Notification Center vocabulary over infrastructure/liveness events.
14302
+ * Bus categories are normalized into these intent-level kinds so rules do not
14303
+ * depend on a provider's raw event name or payload shape.
14304
+ */
14305
+ var NcSystemEventKindSchema = _enum([
14306
+ "camera-online",
14307
+ "camera-offline",
14308
+ "stream-online",
14309
+ "stream-offline",
14310
+ "node-online",
14311
+ "node-offline",
14312
+ "addon-update-available",
14313
+ "server-update-available"
14314
+ ]);
14315
+ /**
14316
+ * One coherent system-event condition. `kinds` is the required opt-in safety
14317
+ * gate; the remaining lists are optional narrowing filters relevant to the
14318
+ * selected kinds.
14319
+ */
14320
+ var NcSystemEventConditionSchema = object({
14321
+ kinds: array(NcSystemEventKindSchema).min(1),
14322
+ deviceIds: array(number().int()).min(1).optional(),
14323
+ nodeIds: array(string().min(1)).min(1).optional(),
14324
+ packageNames: array(string().min(1)).min(1).optional()
14325
+ });
14292
14326
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
14293
14327
  var NcScheduleSchema = object({
14294
14328
  windows: array(object({
@@ -14593,6 +14627,8 @@ var NcConditionsSchema = object({
14593
14627
  "picked-up",
14594
14628
  "both"
14595
14629
  ]).optional(),
14630
+ /** Infrastructure/liveness/update event matcher (`system-event` delivery). */
14631
+ systemEvent: NcSystemEventConditionSchema.optional(),
14596
14632
  /**
14597
14633
  * PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
14598
14634
  * (MaskShape vocabulary). A record passes when its bbox overlaps ANY
@@ -14812,7 +14848,8 @@ var NcTestResultSchema = object({
14812
14848
  "object-event",
14813
14849
  "track",
14814
14850
  "device-event",
14815
- "package-event"
14851
+ "package-event",
14852
+ "system-event"
14816
14853
  ]),
14817
14854
  deviceId: number(),
14818
14855
  timestamp: number(),
@@ -14834,7 +14871,8 @@ var NcConditionDescriptorSchema = object({
14834
14871
  "schedule",
14835
14872
  "device",
14836
14873
  "package",
14837
- "occupancy"
14874
+ "occupancy",
14875
+ "system"
14838
14876
  ]),
14839
14877
  label: string(),
14840
14878
  /** Editor widget the UI renders — never hardcode per-condition forms. */
@@ -14852,7 +14890,8 @@ var NcConditionDescriptorSchema = object({
14852
14890
  "crossingSelect",
14853
14891
  "polygonDraw",
14854
14892
  "occupancy",
14855
- "deviceState"
14893
+ "deviceState",
14894
+ "systemEvent"
14856
14895
  ]),
14857
14896
  operator: _enum([
14858
14897
  "in",
@@ -14894,6 +14933,50 @@ var NcConditionDescriptorSchema = object({
14894
14933
  * rule editors render from the catalog, not hardcoded forms (spec §4.2).
14895
14934
  */
14896
14935
  var NC_CONDITION_CATALOG = [
14936
+ {
14937
+ id: "systemEvent",
14938
+ group: "system",
14939
+ label: "System event",
14940
+ valueType: "systemEvent",
14941
+ options: [
14942
+ {
14943
+ value: "camera-online",
14944
+ label: "Camera online"
14945
+ },
14946
+ {
14947
+ value: "camera-offline",
14948
+ label: "Camera offline"
14949
+ },
14950
+ {
14951
+ value: "stream-online",
14952
+ label: "Stream online"
14953
+ },
14954
+ {
14955
+ value: "stream-offline",
14956
+ label: "Stream offline"
14957
+ },
14958
+ {
14959
+ value: "node-online",
14960
+ label: "Node online"
14961
+ },
14962
+ {
14963
+ value: "node-offline",
14964
+ label: "Node offline"
14965
+ },
14966
+ {
14967
+ value: "addon-update-available",
14968
+ label: "Addon update available"
14969
+ },
14970
+ {
14971
+ value: "server-update-available",
14972
+ label: "Server update available"
14973
+ }
14974
+ ],
14975
+ operator: "in",
14976
+ appliesTo: ["system-event"],
14977
+ phase: "P1",
14978
+ description: "Infrastructure and update events. Optionally narrow camera/stream events by device, node events by node id, and addon updates by package name."
14979
+ },
14897
14980
  {
14898
14981
  id: "devices",
14899
14982
  group: "scope",
@@ -15208,7 +15291,8 @@ var NC_CONDITION_CATALOG = [
15208
15291
  "immediate",
15209
15292
  "track-end",
15210
15293
  "device-event",
15211
- "package-event"
15294
+ "package-event",
15295
+ "system-event"
15212
15296
  ],
15213
15297
  phase: "P1",
15214
15298
  description: "Weekly activation windows (invertible); absent = always active."
@@ -15236,7 +15320,8 @@ var NcHistoryRecordKindSchema = _enum([
15236
15320
  "object-event",
15237
15321
  "track-end",
15238
15322
  "device-event",
15239
- "package-event"
15323
+ "package-event",
15324
+ "system-event"
15240
15325
  ]);
15241
15326
  /** Subject summary frozen on the row at fire time (survives rule/record edits). */
15242
15327
  var NcHistorySubjectSchema = object({
@@ -15244,7 +15329,14 @@ var NcHistorySubjectSchema = object({
15244
15329
  label: string().optional(),
15245
15330
  confidence: number().optional(),
15246
15331
  zones: array(string()),
15247
- timestamp: number()
15332
+ timestamp: number(),
15333
+ systemEvent: object({
15334
+ kind: NcSystemEventKindSchema,
15335
+ subject: string(),
15336
+ title: string(),
15337
+ body: string(),
15338
+ data: record(string(), unknown())
15339
+ }).optional()
15248
15340
  });
15249
15341
  /**
15250
15342
  * One delivery-history row. This is a read-only VIEW over the durable
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-Ba_nAILh.js");
5
+ const require_dist = require("../dist-VrW5no7b.js");
6
6
  let node_fs = require("node:fs");
7
7
  let node_fs$1 = require_dist.__toESM(node_fs, 1);
8
8
  node_fs = require_dist.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { A as embeddingEncoderCapability, G as BaseAddon, N as hfModelUrl } from "../dist-CI2VspUN.mjs";
1
+ import { A as embeddingEncoderCapability, G as BaseAddon, N as hfModelUrl } from "../dist-DqEv8qMO.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
4
  import "node:fs";
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.2.11",
6
+ version: "1.2.12",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_pipeline_analytics_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.55",
21
+ version: "1.2.56",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_pipeline_analytics_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.38",
36
+ version: "1.2.39",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_pipeline_analytics_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.55",
39
+ version: "1.2.56",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -45,7 +45,7 @@ async function r() {
45
45
  }
46
46
  },
47
47
  "@camstack/sdk": {
48
- version: "1.2.11",
48
+ version: "1.2.12",
49
49
  scope: "default",
50
50
  shareConfig: {
51
51
  singleton: !0,
@@ -81,7 +81,7 @@ async function r() {
81
81
  }
82
82
  },
83
83
  "@camstack/ui-library": {
84
- version: "1.2.38",
84
+ version: "1.2.39",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-Ba_nAILh.js");
5
+ const require_dist = require("../dist-VrW5no7b.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_dist.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -2047,6 +2047,7 @@ function fail(condition) {
2047
2047
  */
2048
2048
  function presentConditionIds(c) {
2049
2049
  const ids = [];
2050
+ if (c.systemEvent !== void 0) ids.push("systemEvent");
2050
2051
  if (c.devices !== void 0 && c.devices.length > 0) ids.push("devices");
2051
2052
  if (c.source !== void 0 && c.source !== "any") ids.push("source");
2052
2053
  if (c.classes !== void 0 && c.classes.length > 0) ids.push("classes");
@@ -2140,19 +2141,34 @@ function referencesAudioClass(classes) {
2140
2141
  if (classes === void 0) return false;
2141
2142
  return classes.some((c) => c.trim().toLowerCase().startsWith("audio-"));
2142
2143
  }
2144
+ function matchesSystemEvent(condition, subject) {
2145
+ if (!condition.kinds.includes(subject.kind)) return false;
2146
+ if (condition.deviceIds !== void 0 && (subject.deviceId === void 0 || !condition.deviceIds.includes(subject.deviceId))) return false;
2147
+ if (condition.nodeIds !== void 0 && (subject.nodeId === void 0 || !condition.nodeIds.includes(subject.nodeId))) return false;
2148
+ if (condition.packageNames !== void 0 && (subject.packageName === void 0 || !condition.packageNames.includes(subject.packageName))) return false;
2149
+ return true;
2150
+ }
2143
2151
  function evaluateRule(rule, subject, deviceState) {
2144
- if (subject.kind === "occupancy-event") {
2152
+ if (subject.kind === "system-event") {
2153
+ if (rule.delivery !== "system-event") return fail("delivery");
2154
+ const condition = rule.conditions.systemEvent;
2155
+ if (condition === void 0 || subject.systemEvent === void 0 || !matchesSystemEvent(condition, subject.systemEvent)) return fail("systemEvent");
2156
+ if (rule.conditions.occupancy !== void 0) return fail("occupancy");
2157
+ } else if (subject.kind === "occupancy-event") {
2145
2158
  if (rule.delivery !== "device-event") return fail("delivery");
2146
2159
  const occ = rule.conditions.occupancy;
2147
2160
  if (occ === void 0) return fail("occupancy");
2148
2161
  if (subject.occupancy === void 0 || !matchesOccupancy(occ, subject.occupancy)) return fail("occupancy");
2162
+ if (rule.conditions.systemEvent !== void 0) return fail("systemEvent");
2149
2163
  } else if (subject.kind === "audio-event") {
2150
2164
  if (rule.delivery !== "immediate") return fail("delivery");
2151
2165
  if (!referencesAudioClass(rule.conditions.classes)) return fail("classes");
2152
2166
  if (rule.conditions.occupancy !== void 0) return fail("occupancy");
2167
+ if (rule.conditions.systemEvent !== void 0) return fail("systemEvent");
2153
2168
  } else {
2154
2169
  if ((rule.delivery === "immediate" ? "object-event" : rule.delivery) !== subject.kind) return fail("delivery");
2155
2170
  if (rule.conditions.occupancy !== void 0) return fail("occupancy");
2171
+ if (rule.conditions.systemEvent !== void 0) return fail("systemEvent");
2156
2172
  }
2157
2173
  const c = rule.conditions;
2158
2174
  if (c.deviceState !== void 0) {
@@ -2312,7 +2328,8 @@ function matchesDeviceScope(scope, subject) {
2312
2328
  function cooldownKey(rule, subject) {
2313
2329
  const first = subject.classNames[0];
2314
2330
  const classKey = keysPerClass(rule, subject) && first !== void 0 ? `:c:${first}` : "";
2315
- return rule.throttle.scope === "rule" ? `r:${rule.id}${classKey}` : `r:${rule.id}:d:${subject.deviceId}${classKey}`;
2331
+ const scopeRef = subject.systemEvent !== void 0 ? `s:${subject.systemEvent.subject}` : `d:${subject.deviceId}`;
2332
+ return rule.throttle.scope === "rule" ? `r:${rule.id}${classKey}` : `r:${rule.id}:${scopeRef}${classKey}`;
2316
2333
  }
2317
2334
  /** True when the rule fired within its cooldown window before `now`. */
2318
2335
  function isCoolingDown(rule, lastFiredAt, now) {
@@ -2709,10 +2726,12 @@ var NcDispatcher = class {
2709
2726
  }
2710
2727
  async buildNotification(entry, target) {
2711
2728
  const subject = entry.payload.subject;
2712
- const deviceName = await this.deps.getDeviceName(subject.deviceId).catch(() => null) ?? `camera ${subject.deviceId}`;
2713
- const zoneLabels = await resolveZoneLabels(this.deps.getZoneNames, subject.deviceId, subject.zones);
2729
+ const systemEvent = subject.systemEvent;
2730
+ const displayDeviceId = systemEvent?.deviceId ?? subject.deviceId;
2731
+ const deviceName = systemEvent !== void 0 && systemEvent.deviceId === void 0 ? "system" : await this.deps.getDeviceName(displayDeviceId).catch(() => null) ?? `camera ${displayDeviceId}`;
2732
+ const zoneLabels = systemEvent !== void 0 ? [] : await resolveZoneLabels(this.deps.getZoneNames, subject.deviceId, subject.zones);
2714
2733
  const vars = buildTemplateVars(entry, deviceName, zoneLabels);
2715
- const title = renderTemplate(entry.payload.template?.title, vars) ?? entry.payload.ruleName;
2734
+ const title = renderTemplate(entry.payload.template?.title, vars) ?? systemEvent?.title ?? entry.payload.ruleName;
2716
2735
  const body = renderTemplate(entry.payload.template?.body, vars) ?? defaultBody(entry, deviceName, zoneLabels);
2717
2736
  const attachments = await this.withArtifactUrls(await this.resolveAttachments(entry));
2718
2737
  const params = pickParams(entry.payload.params);
@@ -2723,8 +2742,13 @@ var NcDispatcher = class {
2723
2742
  priority: clampPriority(paramNumber(entry.payload.params, "priority") ?? entry.payload.priority),
2724
2743
  ...params,
2725
2744
  tag: entry.ruleId,
2726
- deviceId: subject.deviceId,
2745
+ ...systemEvent !== void 0 ? systemEvent.deviceId !== void 0 ? { deviceId: systemEvent.deviceId } : {} : { deviceId: subject.deviceId },
2727
2746
  ...subject.eventId !== void 0 ? { eventId: subject.eventId } : {},
2747
+ ...systemEvent !== void 0 ? { metadata: {
2748
+ systemEventKind: systemEvent.kind,
2749
+ subject: systemEvent.subject,
2750
+ ...systemEvent.data
2751
+ } } : {},
2728
2752
  ...attachments.length > 0 ? { attachments } : {},
2729
2753
  ...actions.length > 0 ? { actions } : {}
2730
2754
  };
@@ -2951,6 +2975,7 @@ async function resolveZoneLabels(getZoneNames, deviceId, zoneIds) {
2951
2975
  function buildTemplateVars(entry, deviceName, zoneLabels) {
2952
2976
  const subject = entry.payload.subject;
2953
2977
  const occupancy = subject.occupancy;
2978
+ const systemEvent = subject.systemEvent;
2954
2979
  return {
2955
2980
  camera: deviceName,
2956
2981
  class: subject.className,
@@ -2962,7 +2987,13 @@ function buildTemplateVars(entry, deviceName, zoneLabels) {
2962
2987
  rule: entry.payload.ruleName,
2963
2988
  count: occupancy !== void 0 ? `${occupancy.count}` : "",
2964
2989
  capacity: occupancy !== void 0 ? `${occupancy.capacity}` : "",
2965
- op: occupancy !== void 0 ? occupancyOpWord(occupancy.occupied) : ""
2990
+ op: occupancy !== void 0 ? occupancyOpWord(occupancy.occupied) : "",
2991
+ eventKind: systemEvent?.kind ?? "",
2992
+ subject: systemEvent?.subject ?? "",
2993
+ node: systemEvent?.nodeId ?? "",
2994
+ package: systemEvent?.packageName ?? "",
2995
+ currentVersion: typeof systemEvent?.data["currentVersion"] === "string" ? systemEvent.data["currentVersion"] : "",
2996
+ latestVersion: typeof systemEvent?.data["latestVersion"] === "string" ? systemEvent.data["latestVersion"] : ""
2966
2997
  };
2967
2998
  }
2968
2999
  /** The human-readable edge polarity used by `{{op}}` and the default body. */
@@ -2976,6 +3007,7 @@ function renderTemplate(template, vars) {
2976
3007
  }
2977
3008
  function defaultBody(entry, deviceName, zoneLabels) {
2978
3009
  const subject = entry.payload.subject;
3010
+ if (subject.systemEvent !== void 0) return subject.systemEvent.body;
2979
3011
  const occupancy = subject.occupancy;
2980
3012
  if (occupancy !== void 0) return `${occupancy.zone ?? deviceName} ${occupancyOpWord(occupancy.occupied)} (${occupancy.count}/${occupancy.capacity})`;
2981
3013
  const label = subject.label !== void 0 ? ` (${subject.label})` : "";
@@ -3107,6 +3139,146 @@ function incomingFromOccupancyEdge(edge) {
3107
3139
  })
3108
3140
  };
3109
3141
  }
3142
+ function stringField(data, key) {
3143
+ const value = data[key];
3144
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3145
+ }
3146
+ function numberField(data, key) {
3147
+ const value = data[key];
3148
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3149
+ }
3150
+ function recordField(data, key) {
3151
+ const value = data[key];
3152
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3153
+ }
3154
+ function systemSubject(input) {
3155
+ const data = {
3156
+ ...input.event.data,
3157
+ category: input.event.category
3158
+ };
3159
+ const deviceId = input.deviceId ?? 0;
3160
+ const tags = input.deviceId !== void 0 ? { deviceId: input.deviceId } : {};
3161
+ return {
3162
+ kind: "system-event",
3163
+ origin: "system",
3164
+ subject: {
3165
+ kind: "system-event",
3166
+ recordId: input.event.id,
3167
+ deviceId,
3168
+ timestamp: input.event.timestamp.getTime(),
3169
+ classNames: [],
3170
+ zones: [],
3171
+ systemEvent: {
3172
+ kind: input.kind,
3173
+ subject: input.subject,
3174
+ title: input.title,
3175
+ body: input.body,
3176
+ ...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
3177
+ ...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {},
3178
+ ...input.packageName !== void 0 ? { packageName: input.packageName } : {},
3179
+ data
3180
+ }
3181
+ },
3182
+ log: () => ({
3183
+ tags,
3184
+ meta: {
3185
+ eventId: input.event.id,
3186
+ category: input.event.category,
3187
+ kind: input.kind,
3188
+ subject: input.subject
3189
+ }
3190
+ })
3191
+ };
3192
+ }
3193
+ /**
3194
+ * Normalize the infrastructure categories consumed by Notification Center.
3195
+ * Malformed payloads are rejected instead of guessing an identity.
3196
+ */
3197
+ function incomingFromSystemEvent(event) {
3198
+ const data = event.data;
3199
+ switch (event.category) {
3200
+ case "device.state-changed": {
3201
+ const deviceId = numberField(data, "deviceId");
3202
+ const capName = stringField(data, "capName");
3203
+ const online = recordField(data, "slice")?.["online"];
3204
+ if (deviceId === void 0 || capName !== "device-status" || typeof online !== "boolean") return null;
3205
+ return systemSubject({
3206
+ event,
3207
+ kind: online ? "camera-online" : "camera-offline",
3208
+ subject: `camera:${deviceId}`,
3209
+ title: online ? "Camera online" : "Camera offline",
3210
+ body: `Camera ${deviceId} is ${online ? "online" : "offline"}`,
3211
+ deviceId
3212
+ });
3213
+ }
3214
+ case "device.online":
3215
+ case "device.offline": {
3216
+ const deviceId = numberField(data, "deviceId");
3217
+ if (deviceId === void 0) return null;
3218
+ const online = event.category === "device.online";
3219
+ return systemSubject({
3220
+ event,
3221
+ kind: online ? "camera-online" : "camera-offline",
3222
+ subject: `camera:${deviceId}`,
3223
+ title: online ? "Camera online" : "Camera offline",
3224
+ body: `Camera ${deviceId} is ${online ? "online" : "offline"}`,
3225
+ deviceId
3226
+ });
3227
+ }
3228
+ case "stream.online":
3229
+ case "stream.offline": {
3230
+ const deviceId = numberField(data, "deviceId");
3231
+ const camStreamId = stringField(data, "camStreamId");
3232
+ if (deviceId === void 0 || camStreamId === void 0) return null;
3233
+ const online = event.category === "stream.online";
3234
+ const profile = stringField(data, "profile");
3235
+ return systemSubject({
3236
+ event,
3237
+ kind: online ? "stream-online" : "stream-offline",
3238
+ subject: `stream:${camStreamId}`,
3239
+ title: online ? "Stream online" : "Stream offline",
3240
+ body: `${profile !== void 0 ? `${profile} stream` : "Stream"} for camera ${deviceId} is ${online ? "online" : "offline"}`,
3241
+ deviceId
3242
+ });
3243
+ }
3244
+ case "agent.online":
3245
+ case "agent.offline":
3246
+ case "worker.online":
3247
+ case "worker.offline": {
3248
+ const agent = event.category.startsWith("agent.");
3249
+ const nodeId = stringField(data, agent ? "agentId" : "workerId");
3250
+ if (nodeId === void 0) return null;
3251
+ const online = event.category.endsWith(".online");
3252
+ return systemSubject({
3253
+ event,
3254
+ kind: online ? "node-online" : "node-offline",
3255
+ subject: `node:${nodeId}`,
3256
+ title: online ? "Node online" : "Node offline",
3257
+ body: `${agent ? "Agent" : "Worker"} ${nodeId} is ${online ? "online" : "offline"}`,
3258
+ nodeId
3259
+ });
3260
+ }
3261
+ case "update.available": {
3262
+ const target = stringField(data, "target");
3263
+ const packageName = stringField(data, "packageName");
3264
+ const currentVersion = stringField(data, "currentVersion");
3265
+ const latestVersion = stringField(data, "latestVersion");
3266
+ if (target !== "addon" && target !== "server" || packageName === void 0 || currentVersion === void 0 || latestVersion === void 0) return null;
3267
+ const addon = target === "addon";
3268
+ const nodeId = stringField(data, "nodeId");
3269
+ return systemSubject({
3270
+ event,
3271
+ kind: addon ? "addon-update-available" : "server-update-available",
3272
+ subject: addon ? `package:${nodeId ?? "hub"}:${packageName}` : `package:${packageName}`,
3273
+ title: addon ? "Addon update available" : "Server update available",
3274
+ body: `${packageName} ${latestVersion} is available (current ${currentVersion})${nodeId !== void 0 ? ` on node ${nodeId}` : ""}`,
3275
+ packageName,
3276
+ ...nodeId !== void 0 ? { nodeId } : {}
3277
+ });
3278
+ }
3279
+ default: return null;
3280
+ }
3281
+ }
3110
3282
  /** Re-stamp a live-producer envelope as a boot-reconcile replay of the same
3111
3283
  * record — same subject, honest origin in the log. */
3112
3284
  function asReconcile(event) {
@@ -3950,7 +4122,8 @@ var OUTBOX_RECORD_KINDS = new Set([
3950
4122
  "track-end",
3951
4123
  "device-event",
3952
4124
  "package-event",
3953
- "audio-event"
4125
+ "audio-event",
4126
+ "system-event"
3954
4127
  ]);
3955
4128
  function isOutboxRecordKind(x) {
3956
4129
  return typeof x === "string" && OUTBOX_RECORD_KINDS.has(x);
@@ -5466,7 +5639,8 @@ function outboxEntryToHistory(entry) {
5466
5639
  ...s.label !== void 0 ? { label: s.label } : {},
5467
5640
  ...s.confidence !== void 0 ? { confidence: s.confidence } : {},
5468
5641
  zones: [...s.zones],
5469
- timestamp: s.timestamp
5642
+ timestamp: s.timestamp,
5643
+ ...s.systemEvent !== void 0 ? { systemEvent: s.systemEvent } : {}
5470
5644
  }
5471
5645
  };
5472
5646
  }
@@ -5515,6 +5689,9 @@ var NotificationCenter = class NotificationCenter {
5515
5689
  /** Same rate-limit pair for the per-camera mute drop — see `reportMutedDrop`. */
5516
5690
  lastMutedReportAt = /* @__PURE__ */ new Map();
5517
5691
  mutedSuppressed = /* @__PURE__ */ new Map();
5692
+ /** Dedupes the explicit liveness category and device-status fallback when
5693
+ * both describe the same edge. */
5694
+ lastSystemLiveness = /* @__PURE__ */ new Map();
5518
5695
  /**
5519
5696
  * Serialized evaluation chain. The persist hooks are fire-and-forget for
5520
5697
  * the frame path, but two concurrent evaluations of the same device
@@ -5992,6 +6169,17 @@ var NotificationCenter = class NotificationCenter {
5992
6169
  onOccupancyEdge(edge) {
5993
6170
  this.consumeEvent(incomingFromOccupancyEdge(edge));
5994
6171
  }
6172
+ /** Event-bus infrastructure/update intake. Invalid payloads are ignored. */
6173
+ onSystemEvent(event) {
6174
+ const incoming = incomingFromSystemEvent(event);
6175
+ if (incoming === null) return;
6176
+ const system = incoming.subject.systemEvent;
6177
+ if (system !== void 0 && (system.kind.startsWith("camera-") || system.kind.startsWith("stream-") || system.kind.startsWith("node-"))) {
6178
+ if (this.lastSystemLiveness.get(system.subject) === system.kind) return;
6179
+ this.lastSystemLiveness.set(system.subject, system.kind);
6180
+ }
6181
+ this.consumeEvent(incoming);
6182
+ }
5995
6183
  buildProvider() {
5996
6184
  return {
5997
6185
  listRules: async () => ({ rules: [...this.rules.list()] }),
@@ -6384,6 +6572,7 @@ var NotificationCenter = class NotificationCenter {
6384
6572
  buildEntries(rule, subject, kind, matchedOn, userTargets = [], origin = "pipeline") {
6385
6573
  const hasEventMedia = kind === "object-event" || kind === "package-event";
6386
6574
  const isTrackScoped = kind === "object-event" || kind === "track-end";
6575
+ const isSystemEvent = kind === "system-event";
6387
6576
  const direct = new Set(rule.targets.map((t) => t.targetId));
6388
6577
  return [...rule.targets, ...userTargets.filter((t) => !direct.has(t.targetId))].filter((target) => !rule.disabledTargetIds.includes(target.targetId)).map((target) => {
6389
6578
  const payload = {
@@ -6392,15 +6581,15 @@ var NotificationCenter = class NotificationCenter {
6392
6581
  ...origin === "synthetic" ? { synthetic: true } : {},
6393
6582
  priority: rule.priority,
6394
6583
  ...rule.template !== void 0 ? { template: rule.template } : {},
6395
- media: rule.media.attach,
6396
- ...rule.media.zoneCrop === true && rule.conditions.zones !== void 0 ? { mediaZoneIds: [...rule.conditions.zones.ids] } : {},
6397
- ...rule.media.frame !== void 0 ? { mediaFrame: rule.media.frame } : {},
6398
- ...rule.media.profile !== void 0 ? { mediaProfile: rule.media.profile } : {},
6399
- ...rule.media.gif === true ? { mediaGif: true } : {},
6400
- ...rule.media.clip === true ? { mediaClip: true } : {},
6401
- ...rule.media.clipPreRollSec !== void 0 ? { mediaClipPreRollSec: rule.media.clipPreRollSec } : {},
6402
- ...rule.media?.clipSpeed !== void 0 ? { mediaClipSpeed: rule.media.clipSpeed } : {},
6403
- ...rule.media.clipPostRollSec !== void 0 ? { mediaClipPostRollSec: rule.media.clipPostRollSec } : {},
6584
+ media: isSystemEvent ? "none" : rule.media.attach,
6585
+ ...!isSystemEvent && rule.media.zoneCrop === true && rule.conditions.zones !== void 0 ? { mediaZoneIds: [...rule.conditions.zones.ids] } : {},
6586
+ ...!isSystemEvent && rule.media.frame !== void 0 ? { mediaFrame: rule.media.frame } : {},
6587
+ ...!isSystemEvent && rule.media.profile !== void 0 ? { mediaProfile: rule.media.profile } : {},
6588
+ ...!isSystemEvent && rule.media.gif === true ? { mediaGif: true } : {},
6589
+ ...!isSystemEvent && rule.media.clip === true ? { mediaClip: true } : {},
6590
+ ...!isSystemEvent && rule.media.clipPreRollSec !== void 0 ? { mediaClipPreRollSec: rule.media.clipPreRollSec } : {},
6591
+ ...!isSystemEvent && rule.media?.clipSpeed !== void 0 ? { mediaClipSpeed: rule.media.clipSpeed } : {},
6592
+ ...!isSystemEvent && rule.media.clipPostRollSec !== void 0 ? { mediaClipPostRollSec: rule.media.clipPostRollSec } : {},
6404
6593
  ...matchedOn !== void 0 && matchedOn.length > 0 ? { matchedOn } : {},
6405
6594
  ...target.params !== void 0 ? { params: target.params } : {},
6406
6595
  subject: {
@@ -6412,7 +6601,8 @@ var NotificationCenter = class NotificationCenter {
6412
6601
  ...subject.confidence !== void 0 ? { confidence: subject.confidence } : {},
6413
6602
  zones: subject.zones,
6414
6603
  timestamp: subject.timestamp,
6415
- ...subject.occupancy !== void 0 ? { occupancy: frozenOccupancy(rule, subject.occupancy) } : {}
6604
+ ...subject.occupancy !== void 0 ? { occupancy: frozenOccupancy(rule, subject.occupancy) } : {},
6605
+ ...subject.systemEvent !== void 0 ? { systemEvent: subject.systemEvent } : {}
6416
6606
  }
6417
6607
  };
6418
6608
  return {
@@ -6489,7 +6679,7 @@ var NotificationCenter = class NotificationCenter {
6489
6679
  for (const entry of entries) {
6490
6680
  const rule = this.rules.get(entry.ruleId);
6491
6681
  if (rule === null) continue;
6492
- const key = rule.throttle.scope === "rule" ? `r:${rule.id}` : `r:${rule.id}:d:${entry.deviceId}`;
6682
+ const key = rule.throttle.scope === "rule" ? `r:${rule.id}` : entry.payload.subject.systemEvent !== void 0 ? `r:${rule.id}:s:${entry.payload.subject.systemEvent.subject}` : `r:${rule.id}:d:${entry.deviceId}`;
6493
6683
  const prev = this.lastFiredAt.get(key) ?? 0;
6494
6684
  if (entry.createdAt > prev) this.lastFiredAt.set(key, entry.createdAt);
6495
6685
  }
@@ -26517,6 +26707,18 @@ function wireSubscriptions(deps) {
26517
26707
  if (nodes === null) return;
26518
26708
  deps.onClusterTopology(nodes);
26519
26709
  }));
26710
+ unsubs.push(deps.eventBus.subscribe({ category: [
26711
+ require_dist.EventCategory.DeviceOnline,
26712
+ require_dist.EventCategory.DeviceOffline,
26713
+ require_dist.EventCategory.DeviceStateChanged,
26714
+ require_dist.EventCategory.StreamOnline,
26715
+ require_dist.EventCategory.StreamOffline,
26716
+ require_dist.EventCategory.AgentOnline,
26717
+ require_dist.EventCategory.AgentOffline,
26718
+ require_dist.EventCategory.WorkerOnline,
26719
+ require_dist.EventCategory.WorkerOffline,
26720
+ require_dist.EventCategory.UpdateAvailable
26721
+ ] }, (ev) => deps.onSystemEvent(ev)));
26520
26722
  }
26521
26723
  unsubs.push(deps.eventBus.subscribe({ category: require_dist.EventCategory.DeviceBindingsChanged }, (ev) => {
26522
26724
  deps.onBindingsChanged(ev.data);
@@ -31570,7 +31772,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
31570
31772
  onSensorStateChanged: (data, timestampMs) => this.handleSensorStateChanged(data, timestampMs),
31571
31773
  onBindingsChanged: (data) => this.handleBindingsChangedEvent(data),
31572
31774
  onDeviceUnregistered: (deviceId) => this.handleDeviceUnregisteredEvent(deviceId),
31573
- onClusterTopology: (nodes) => this.nodeLiveness.applySnapshot(nodes, Date.now())
31775
+ onClusterTopology: (nodes) => this.nodeLiveness.applySnapshot(nodes, Date.now()),
31776
+ onSystemEvent: (event) => this.notificationCenter?.onSystemEvent(event)
31574
31777
  });
31575
31778
  if (this.isPostProcessingNode) await this.startEmbeddingDispatcher(logger, transport);
31576
31779
  this.startSweepTimers();
@@ -1,4 +1,4 @@
1
- import { $ as array, B as subKindsOf, C as addonWidgetsSourceCapability, D as cosineSimilarity, E as buildEventKindDescriptor, F as kebabToCamel, G as BaseAddon, H as videoclipsCapability, I as notificationRulesCapability, J as hydrateSchema, K as DeviceType, L as pipelineAnalyticsCapability, M as faceGalleryCapability, O as customAction, P as isScheduleActive, Q as _enum, R as plateGalleryCapability, S as TrackSourceSchema, T as audioMetricsCapability, U as zoneAnalyticsCapability, V as vectorDimFromBase64, W as errMsg, X as nodePin, Y as isDeviceScopedCap, Z as sleep, _ as NcTaxonomySchema, a as EVENT_PAD_MS, at as string, b as TimelapseRuleInputSchema, c as NC_CONDITION_CATALOG, d as NcRuleInputSchema, et as boolean, f as NcRulePatchSchema, g as NcSnoozeSuppressedSchema, h as NcSnoozeSchema, i as EVENT_KIND_BY_CAP, it as record, j as encodeVectorBase64, k as defineCustomActions, l as NC_TAXONOMY, m as NcSnoozeInputSchema, n as DEFAULT_EVENT_COLOR, nt as number, o as LabelAttributionSchema, ot as unknown, p as NcRuleSchema, q as createEvent, r as DeclaredDevices, rt as object, s as MACRO_LABELS, st as EventCategory, t as BaseDevice, tt as literal, u as NcConditionDescriptorSchema, v as OpsLogEntrySchema, w as alarmPanelCapability, x as TimelapseRuleSchema, y as RetrainStatusSchema, z as readDeviceStateFrom } from "../dist-CI2VspUN.mjs";
1
+ import { $ as array, B as subKindsOf, C as addonWidgetsSourceCapability, D as cosineSimilarity, E as buildEventKindDescriptor, F as kebabToCamel, G as BaseAddon, H as videoclipsCapability, I as notificationRulesCapability, J as hydrateSchema, K as DeviceType, L as pipelineAnalyticsCapability, M as faceGalleryCapability, O as customAction, P as isScheduleActive, Q as _enum, R as plateGalleryCapability, S as TrackSourceSchema, T as audioMetricsCapability, U as zoneAnalyticsCapability, V as vectorDimFromBase64, W as errMsg, X as nodePin, Y as isDeviceScopedCap, Z as sleep, _ as NcTaxonomySchema, a as EVENT_PAD_MS, at as string, b as TimelapseRuleInputSchema, c as NC_CONDITION_CATALOG, d as NcRuleInputSchema, et as boolean, f as NcRulePatchSchema, g as NcSnoozeSuppressedSchema, h as NcSnoozeSchema, i as EVENT_KIND_BY_CAP, it as record, j as encodeVectorBase64, k as defineCustomActions, l as NC_TAXONOMY, m as NcSnoozeInputSchema, n as DEFAULT_EVENT_COLOR, nt as number, o as LabelAttributionSchema, ot as unknown, p as NcRuleSchema, q as createEvent, r as DeclaredDevices, rt as object, s as MACRO_LABELS, st as EventCategory, t as BaseDevice, tt as literal, u as NcConditionDescriptorSchema, v as OpsLogEntrySchema, w as alarmPanelCapability, x as TimelapseRuleSchema, y as RetrainStatusSchema, z as readDeviceStateFrom } from "../dist-DqEv8qMO.mjs";
2
2
  import { t as __exportAll } from "../embedding-encoder/index.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import { promises } from "node:fs";
@@ -2042,6 +2042,7 @@ function fail(condition) {
2042
2042
  */
2043
2043
  function presentConditionIds(c) {
2044
2044
  const ids = [];
2045
+ if (c.systemEvent !== void 0) ids.push("systemEvent");
2045
2046
  if (c.devices !== void 0 && c.devices.length > 0) ids.push("devices");
2046
2047
  if (c.source !== void 0 && c.source !== "any") ids.push("source");
2047
2048
  if (c.classes !== void 0 && c.classes.length > 0) ids.push("classes");
@@ -2135,19 +2136,34 @@ function referencesAudioClass(classes) {
2135
2136
  if (classes === void 0) return false;
2136
2137
  return classes.some((c) => c.trim().toLowerCase().startsWith("audio-"));
2137
2138
  }
2139
+ function matchesSystemEvent(condition, subject) {
2140
+ if (!condition.kinds.includes(subject.kind)) return false;
2141
+ if (condition.deviceIds !== void 0 && (subject.deviceId === void 0 || !condition.deviceIds.includes(subject.deviceId))) return false;
2142
+ if (condition.nodeIds !== void 0 && (subject.nodeId === void 0 || !condition.nodeIds.includes(subject.nodeId))) return false;
2143
+ if (condition.packageNames !== void 0 && (subject.packageName === void 0 || !condition.packageNames.includes(subject.packageName))) return false;
2144
+ return true;
2145
+ }
2138
2146
  function evaluateRule(rule, subject, deviceState) {
2139
- if (subject.kind === "occupancy-event") {
2147
+ if (subject.kind === "system-event") {
2148
+ if (rule.delivery !== "system-event") return fail("delivery");
2149
+ const condition = rule.conditions.systemEvent;
2150
+ if (condition === void 0 || subject.systemEvent === void 0 || !matchesSystemEvent(condition, subject.systemEvent)) return fail("systemEvent");
2151
+ if (rule.conditions.occupancy !== void 0) return fail("occupancy");
2152
+ } else if (subject.kind === "occupancy-event") {
2140
2153
  if (rule.delivery !== "device-event") return fail("delivery");
2141
2154
  const occ = rule.conditions.occupancy;
2142
2155
  if (occ === void 0) return fail("occupancy");
2143
2156
  if (subject.occupancy === void 0 || !matchesOccupancy(occ, subject.occupancy)) return fail("occupancy");
2157
+ if (rule.conditions.systemEvent !== void 0) return fail("systemEvent");
2144
2158
  } else if (subject.kind === "audio-event") {
2145
2159
  if (rule.delivery !== "immediate") return fail("delivery");
2146
2160
  if (!referencesAudioClass(rule.conditions.classes)) return fail("classes");
2147
2161
  if (rule.conditions.occupancy !== void 0) return fail("occupancy");
2162
+ if (rule.conditions.systemEvent !== void 0) return fail("systemEvent");
2148
2163
  } else {
2149
2164
  if ((rule.delivery === "immediate" ? "object-event" : rule.delivery) !== subject.kind) return fail("delivery");
2150
2165
  if (rule.conditions.occupancy !== void 0) return fail("occupancy");
2166
+ if (rule.conditions.systemEvent !== void 0) return fail("systemEvent");
2151
2167
  }
2152
2168
  const c = rule.conditions;
2153
2169
  if (c.deviceState !== void 0) {
@@ -2307,7 +2323,8 @@ function matchesDeviceScope(scope, subject) {
2307
2323
  function cooldownKey(rule, subject) {
2308
2324
  const first = subject.classNames[0];
2309
2325
  const classKey = keysPerClass(rule, subject) && first !== void 0 ? `:c:${first}` : "";
2310
- return rule.throttle.scope === "rule" ? `r:${rule.id}${classKey}` : `r:${rule.id}:d:${subject.deviceId}${classKey}`;
2326
+ const scopeRef = subject.systemEvent !== void 0 ? `s:${subject.systemEvent.subject}` : `d:${subject.deviceId}`;
2327
+ return rule.throttle.scope === "rule" ? `r:${rule.id}${classKey}` : `r:${rule.id}:${scopeRef}${classKey}`;
2311
2328
  }
2312
2329
  /** True when the rule fired within its cooldown window before `now`. */
2313
2330
  function isCoolingDown(rule, lastFiredAt, now) {
@@ -2704,10 +2721,12 @@ var NcDispatcher = class {
2704
2721
  }
2705
2722
  async buildNotification(entry, target) {
2706
2723
  const subject = entry.payload.subject;
2707
- const deviceName = await this.deps.getDeviceName(subject.deviceId).catch(() => null) ?? `camera ${subject.deviceId}`;
2708
- const zoneLabels = await resolveZoneLabels(this.deps.getZoneNames, subject.deviceId, subject.zones);
2724
+ const systemEvent = subject.systemEvent;
2725
+ const displayDeviceId = systemEvent?.deviceId ?? subject.deviceId;
2726
+ const deviceName = systemEvent !== void 0 && systemEvent.deviceId === void 0 ? "system" : await this.deps.getDeviceName(displayDeviceId).catch(() => null) ?? `camera ${displayDeviceId}`;
2727
+ const zoneLabels = systemEvent !== void 0 ? [] : await resolveZoneLabels(this.deps.getZoneNames, subject.deviceId, subject.zones);
2709
2728
  const vars = buildTemplateVars(entry, deviceName, zoneLabels);
2710
- const title = renderTemplate(entry.payload.template?.title, vars) ?? entry.payload.ruleName;
2729
+ const title = renderTemplate(entry.payload.template?.title, vars) ?? systemEvent?.title ?? entry.payload.ruleName;
2711
2730
  const body = renderTemplate(entry.payload.template?.body, vars) ?? defaultBody(entry, deviceName, zoneLabels);
2712
2731
  const attachments = await this.withArtifactUrls(await this.resolveAttachments(entry));
2713
2732
  const params = pickParams(entry.payload.params);
@@ -2718,8 +2737,13 @@ var NcDispatcher = class {
2718
2737
  priority: clampPriority(paramNumber(entry.payload.params, "priority") ?? entry.payload.priority),
2719
2738
  ...params,
2720
2739
  tag: entry.ruleId,
2721
- deviceId: subject.deviceId,
2740
+ ...systemEvent !== void 0 ? systemEvent.deviceId !== void 0 ? { deviceId: systemEvent.deviceId } : {} : { deviceId: subject.deviceId },
2722
2741
  ...subject.eventId !== void 0 ? { eventId: subject.eventId } : {},
2742
+ ...systemEvent !== void 0 ? { metadata: {
2743
+ systemEventKind: systemEvent.kind,
2744
+ subject: systemEvent.subject,
2745
+ ...systemEvent.data
2746
+ } } : {},
2723
2747
  ...attachments.length > 0 ? { attachments } : {},
2724
2748
  ...actions.length > 0 ? { actions } : {}
2725
2749
  };
@@ -2946,6 +2970,7 @@ async function resolveZoneLabels(getZoneNames, deviceId, zoneIds) {
2946
2970
  function buildTemplateVars(entry, deviceName, zoneLabels) {
2947
2971
  const subject = entry.payload.subject;
2948
2972
  const occupancy = subject.occupancy;
2973
+ const systemEvent = subject.systemEvent;
2949
2974
  return {
2950
2975
  camera: deviceName,
2951
2976
  class: subject.className,
@@ -2957,7 +2982,13 @@ function buildTemplateVars(entry, deviceName, zoneLabels) {
2957
2982
  rule: entry.payload.ruleName,
2958
2983
  count: occupancy !== void 0 ? `${occupancy.count}` : "",
2959
2984
  capacity: occupancy !== void 0 ? `${occupancy.capacity}` : "",
2960
- op: occupancy !== void 0 ? occupancyOpWord(occupancy.occupied) : ""
2985
+ op: occupancy !== void 0 ? occupancyOpWord(occupancy.occupied) : "",
2986
+ eventKind: systemEvent?.kind ?? "",
2987
+ subject: systemEvent?.subject ?? "",
2988
+ node: systemEvent?.nodeId ?? "",
2989
+ package: systemEvent?.packageName ?? "",
2990
+ currentVersion: typeof systemEvent?.data["currentVersion"] === "string" ? systemEvent.data["currentVersion"] : "",
2991
+ latestVersion: typeof systemEvent?.data["latestVersion"] === "string" ? systemEvent.data["latestVersion"] : ""
2961
2992
  };
2962
2993
  }
2963
2994
  /** The human-readable edge polarity used by `{{op}}` and the default body. */
@@ -2971,6 +3002,7 @@ function renderTemplate(template, vars) {
2971
3002
  }
2972
3003
  function defaultBody(entry, deviceName, zoneLabels) {
2973
3004
  const subject = entry.payload.subject;
3005
+ if (subject.systemEvent !== void 0) return subject.systemEvent.body;
2974
3006
  const occupancy = subject.occupancy;
2975
3007
  if (occupancy !== void 0) return `${occupancy.zone ?? deviceName} ${occupancyOpWord(occupancy.occupied)} (${occupancy.count}/${occupancy.capacity})`;
2976
3008
  const label = subject.label !== void 0 ? ` (${subject.label})` : "";
@@ -3102,6 +3134,146 @@ function incomingFromOccupancyEdge(edge) {
3102
3134
  })
3103
3135
  };
3104
3136
  }
3137
+ function stringField(data, key) {
3138
+ const value = data[key];
3139
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3140
+ }
3141
+ function numberField(data, key) {
3142
+ const value = data[key];
3143
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3144
+ }
3145
+ function recordField(data, key) {
3146
+ const value = data[key];
3147
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3148
+ }
3149
+ function systemSubject(input) {
3150
+ const data = {
3151
+ ...input.event.data,
3152
+ category: input.event.category
3153
+ };
3154
+ const deviceId = input.deviceId ?? 0;
3155
+ const tags = input.deviceId !== void 0 ? { deviceId: input.deviceId } : {};
3156
+ return {
3157
+ kind: "system-event",
3158
+ origin: "system",
3159
+ subject: {
3160
+ kind: "system-event",
3161
+ recordId: input.event.id,
3162
+ deviceId,
3163
+ timestamp: input.event.timestamp.getTime(),
3164
+ classNames: [],
3165
+ zones: [],
3166
+ systemEvent: {
3167
+ kind: input.kind,
3168
+ subject: input.subject,
3169
+ title: input.title,
3170
+ body: input.body,
3171
+ ...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
3172
+ ...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {},
3173
+ ...input.packageName !== void 0 ? { packageName: input.packageName } : {},
3174
+ data
3175
+ }
3176
+ },
3177
+ log: () => ({
3178
+ tags,
3179
+ meta: {
3180
+ eventId: input.event.id,
3181
+ category: input.event.category,
3182
+ kind: input.kind,
3183
+ subject: input.subject
3184
+ }
3185
+ })
3186
+ };
3187
+ }
3188
+ /**
3189
+ * Normalize the infrastructure categories consumed by Notification Center.
3190
+ * Malformed payloads are rejected instead of guessing an identity.
3191
+ */
3192
+ function incomingFromSystemEvent(event) {
3193
+ const data = event.data;
3194
+ switch (event.category) {
3195
+ case "device.state-changed": {
3196
+ const deviceId = numberField(data, "deviceId");
3197
+ const capName = stringField(data, "capName");
3198
+ const online = recordField(data, "slice")?.["online"];
3199
+ if (deviceId === void 0 || capName !== "device-status" || typeof online !== "boolean") return null;
3200
+ return systemSubject({
3201
+ event,
3202
+ kind: online ? "camera-online" : "camera-offline",
3203
+ subject: `camera:${deviceId}`,
3204
+ title: online ? "Camera online" : "Camera offline",
3205
+ body: `Camera ${deviceId} is ${online ? "online" : "offline"}`,
3206
+ deviceId
3207
+ });
3208
+ }
3209
+ case "device.online":
3210
+ case "device.offline": {
3211
+ const deviceId = numberField(data, "deviceId");
3212
+ if (deviceId === void 0) return null;
3213
+ const online = event.category === "device.online";
3214
+ return systemSubject({
3215
+ event,
3216
+ kind: online ? "camera-online" : "camera-offline",
3217
+ subject: `camera:${deviceId}`,
3218
+ title: online ? "Camera online" : "Camera offline",
3219
+ body: `Camera ${deviceId} is ${online ? "online" : "offline"}`,
3220
+ deviceId
3221
+ });
3222
+ }
3223
+ case "stream.online":
3224
+ case "stream.offline": {
3225
+ const deviceId = numberField(data, "deviceId");
3226
+ const camStreamId = stringField(data, "camStreamId");
3227
+ if (deviceId === void 0 || camStreamId === void 0) return null;
3228
+ const online = event.category === "stream.online";
3229
+ const profile = stringField(data, "profile");
3230
+ return systemSubject({
3231
+ event,
3232
+ kind: online ? "stream-online" : "stream-offline",
3233
+ subject: `stream:${camStreamId}`,
3234
+ title: online ? "Stream online" : "Stream offline",
3235
+ body: `${profile !== void 0 ? `${profile} stream` : "Stream"} for camera ${deviceId} is ${online ? "online" : "offline"}`,
3236
+ deviceId
3237
+ });
3238
+ }
3239
+ case "agent.online":
3240
+ case "agent.offline":
3241
+ case "worker.online":
3242
+ case "worker.offline": {
3243
+ const agent = event.category.startsWith("agent.");
3244
+ const nodeId = stringField(data, agent ? "agentId" : "workerId");
3245
+ if (nodeId === void 0) return null;
3246
+ const online = event.category.endsWith(".online");
3247
+ return systemSubject({
3248
+ event,
3249
+ kind: online ? "node-online" : "node-offline",
3250
+ subject: `node:${nodeId}`,
3251
+ title: online ? "Node online" : "Node offline",
3252
+ body: `${agent ? "Agent" : "Worker"} ${nodeId} is ${online ? "online" : "offline"}`,
3253
+ nodeId
3254
+ });
3255
+ }
3256
+ case "update.available": {
3257
+ const target = stringField(data, "target");
3258
+ const packageName = stringField(data, "packageName");
3259
+ const currentVersion = stringField(data, "currentVersion");
3260
+ const latestVersion = stringField(data, "latestVersion");
3261
+ if (target !== "addon" && target !== "server" || packageName === void 0 || currentVersion === void 0 || latestVersion === void 0) return null;
3262
+ const addon = target === "addon";
3263
+ const nodeId = stringField(data, "nodeId");
3264
+ return systemSubject({
3265
+ event,
3266
+ kind: addon ? "addon-update-available" : "server-update-available",
3267
+ subject: addon ? `package:${nodeId ?? "hub"}:${packageName}` : `package:${packageName}`,
3268
+ title: addon ? "Addon update available" : "Server update available",
3269
+ body: `${packageName} ${latestVersion} is available (current ${currentVersion})${nodeId !== void 0 ? ` on node ${nodeId}` : ""}`,
3270
+ packageName,
3271
+ ...nodeId !== void 0 ? { nodeId } : {}
3272
+ });
3273
+ }
3274
+ default: return null;
3275
+ }
3276
+ }
3105
3277
  /** Re-stamp a live-producer envelope as a boot-reconcile replay of the same
3106
3278
  * record — same subject, honest origin in the log. */
3107
3279
  function asReconcile(event) {
@@ -3945,7 +4117,8 @@ var OUTBOX_RECORD_KINDS = new Set([
3945
4117
  "track-end",
3946
4118
  "device-event",
3947
4119
  "package-event",
3948
- "audio-event"
4120
+ "audio-event",
4121
+ "system-event"
3949
4122
  ]);
3950
4123
  function isOutboxRecordKind(x) {
3951
4124
  return typeof x === "string" && OUTBOX_RECORD_KINDS.has(x);
@@ -5453,7 +5626,8 @@ function outboxEntryToHistory(entry) {
5453
5626
  ...s.label !== void 0 ? { label: s.label } : {},
5454
5627
  ...s.confidence !== void 0 ? { confidence: s.confidence } : {},
5455
5628
  zones: [...s.zones],
5456
- timestamp: s.timestamp
5629
+ timestamp: s.timestamp,
5630
+ ...s.systemEvent !== void 0 ? { systemEvent: s.systemEvent } : {}
5457
5631
  }
5458
5632
  };
5459
5633
  }
@@ -5502,6 +5676,9 @@ var NotificationCenter = class NotificationCenter {
5502
5676
  /** Same rate-limit pair for the per-camera mute drop — see `reportMutedDrop`. */
5503
5677
  lastMutedReportAt = /* @__PURE__ */ new Map();
5504
5678
  mutedSuppressed = /* @__PURE__ */ new Map();
5679
+ /** Dedupes the explicit liveness category and device-status fallback when
5680
+ * both describe the same edge. */
5681
+ lastSystemLiveness = /* @__PURE__ */ new Map();
5505
5682
  /**
5506
5683
  * Serialized evaluation chain. The persist hooks are fire-and-forget for
5507
5684
  * the frame path, but two concurrent evaluations of the same device
@@ -5979,6 +6156,17 @@ var NotificationCenter = class NotificationCenter {
5979
6156
  onOccupancyEdge(edge) {
5980
6157
  this.consumeEvent(incomingFromOccupancyEdge(edge));
5981
6158
  }
6159
+ /** Event-bus infrastructure/update intake. Invalid payloads are ignored. */
6160
+ onSystemEvent(event) {
6161
+ const incoming = incomingFromSystemEvent(event);
6162
+ if (incoming === null) return;
6163
+ const system = incoming.subject.systemEvent;
6164
+ if (system !== void 0 && (system.kind.startsWith("camera-") || system.kind.startsWith("stream-") || system.kind.startsWith("node-"))) {
6165
+ if (this.lastSystemLiveness.get(system.subject) === system.kind) return;
6166
+ this.lastSystemLiveness.set(system.subject, system.kind);
6167
+ }
6168
+ this.consumeEvent(incoming);
6169
+ }
5982
6170
  buildProvider() {
5983
6171
  return {
5984
6172
  listRules: async () => ({ rules: [...this.rules.list()] }),
@@ -6371,6 +6559,7 @@ var NotificationCenter = class NotificationCenter {
6371
6559
  buildEntries(rule, subject, kind, matchedOn, userTargets = [], origin = "pipeline") {
6372
6560
  const hasEventMedia = kind === "object-event" || kind === "package-event";
6373
6561
  const isTrackScoped = kind === "object-event" || kind === "track-end";
6562
+ const isSystemEvent = kind === "system-event";
6374
6563
  const direct = new Set(rule.targets.map((t) => t.targetId));
6375
6564
  return [...rule.targets, ...userTargets.filter((t) => !direct.has(t.targetId))].filter((target) => !rule.disabledTargetIds.includes(target.targetId)).map((target) => {
6376
6565
  const payload = {
@@ -6379,15 +6568,15 @@ var NotificationCenter = class NotificationCenter {
6379
6568
  ...origin === "synthetic" ? { synthetic: true } : {},
6380
6569
  priority: rule.priority,
6381
6570
  ...rule.template !== void 0 ? { template: rule.template } : {},
6382
- media: rule.media.attach,
6383
- ...rule.media.zoneCrop === true && rule.conditions.zones !== void 0 ? { mediaZoneIds: [...rule.conditions.zones.ids] } : {},
6384
- ...rule.media.frame !== void 0 ? { mediaFrame: rule.media.frame } : {},
6385
- ...rule.media.profile !== void 0 ? { mediaProfile: rule.media.profile } : {},
6386
- ...rule.media.gif === true ? { mediaGif: true } : {},
6387
- ...rule.media.clip === true ? { mediaClip: true } : {},
6388
- ...rule.media.clipPreRollSec !== void 0 ? { mediaClipPreRollSec: rule.media.clipPreRollSec } : {},
6389
- ...rule.media?.clipSpeed !== void 0 ? { mediaClipSpeed: rule.media.clipSpeed } : {},
6390
- ...rule.media.clipPostRollSec !== void 0 ? { mediaClipPostRollSec: rule.media.clipPostRollSec } : {},
6571
+ media: isSystemEvent ? "none" : rule.media.attach,
6572
+ ...!isSystemEvent && rule.media.zoneCrop === true && rule.conditions.zones !== void 0 ? { mediaZoneIds: [...rule.conditions.zones.ids] } : {},
6573
+ ...!isSystemEvent && rule.media.frame !== void 0 ? { mediaFrame: rule.media.frame } : {},
6574
+ ...!isSystemEvent && rule.media.profile !== void 0 ? { mediaProfile: rule.media.profile } : {},
6575
+ ...!isSystemEvent && rule.media.gif === true ? { mediaGif: true } : {},
6576
+ ...!isSystemEvent && rule.media.clip === true ? { mediaClip: true } : {},
6577
+ ...!isSystemEvent && rule.media.clipPreRollSec !== void 0 ? { mediaClipPreRollSec: rule.media.clipPreRollSec } : {},
6578
+ ...!isSystemEvent && rule.media?.clipSpeed !== void 0 ? { mediaClipSpeed: rule.media.clipSpeed } : {},
6579
+ ...!isSystemEvent && rule.media.clipPostRollSec !== void 0 ? { mediaClipPostRollSec: rule.media.clipPostRollSec } : {},
6391
6580
  ...matchedOn !== void 0 && matchedOn.length > 0 ? { matchedOn } : {},
6392
6581
  ...target.params !== void 0 ? { params: target.params } : {},
6393
6582
  subject: {
@@ -6399,7 +6588,8 @@ var NotificationCenter = class NotificationCenter {
6399
6588
  ...subject.confidence !== void 0 ? { confidence: subject.confidence } : {},
6400
6589
  zones: subject.zones,
6401
6590
  timestamp: subject.timestamp,
6402
- ...subject.occupancy !== void 0 ? { occupancy: frozenOccupancy(rule, subject.occupancy) } : {}
6591
+ ...subject.occupancy !== void 0 ? { occupancy: frozenOccupancy(rule, subject.occupancy) } : {},
6592
+ ...subject.systemEvent !== void 0 ? { systemEvent: subject.systemEvent } : {}
6403
6593
  }
6404
6594
  };
6405
6595
  return {
@@ -6476,7 +6666,7 @@ var NotificationCenter = class NotificationCenter {
6476
6666
  for (const entry of entries) {
6477
6667
  const rule = this.rules.get(entry.ruleId);
6478
6668
  if (rule === null) continue;
6479
- const key = rule.throttle.scope === "rule" ? `r:${rule.id}` : `r:${rule.id}:d:${entry.deviceId}`;
6669
+ const key = rule.throttle.scope === "rule" ? `r:${rule.id}` : entry.payload.subject.systemEvent !== void 0 ? `r:${rule.id}:s:${entry.payload.subject.systemEvent.subject}` : `r:${rule.id}:d:${entry.deviceId}`;
6480
6670
  const prev = this.lastFiredAt.get(key) ?? 0;
6481
6671
  if (entry.createdAt > prev) this.lastFiredAt.set(key, entry.createdAt);
6482
6672
  }
@@ -26504,6 +26694,18 @@ function wireSubscriptions(deps) {
26504
26694
  if (nodes === null) return;
26505
26695
  deps.onClusterTopology(nodes);
26506
26696
  }));
26697
+ unsubs.push(deps.eventBus.subscribe({ category: [
26698
+ EventCategory.DeviceOnline,
26699
+ EventCategory.DeviceOffline,
26700
+ EventCategory.DeviceStateChanged,
26701
+ EventCategory.StreamOnline,
26702
+ EventCategory.StreamOffline,
26703
+ EventCategory.AgentOnline,
26704
+ EventCategory.AgentOffline,
26705
+ EventCategory.WorkerOnline,
26706
+ EventCategory.WorkerOffline,
26707
+ EventCategory.UpdateAvailable
26708
+ ] }, (ev) => deps.onSystemEvent(ev)));
26507
26709
  }
26508
26710
  unsubs.push(deps.eventBus.subscribe({ category: EventCategory.DeviceBindingsChanged }, (ev) => {
26509
26711
  deps.onBindingsChanged(ev.data);
@@ -31557,7 +31759,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
31557
31759
  onSensorStateChanged: (data, timestampMs) => this.handleSensorStateChanged(data, timestampMs),
31558
31760
  onBindingsChanged: (data) => this.handleBindingsChangedEvent(data),
31559
31761
  onDeviceUnregistered: (deviceId) => this.handleDeviceUnregisteredEvent(deviceId),
31560
- onClusterTopology: (nodes) => this.nodeLiveness.applySnapshot(nodes, Date.now())
31762
+ onClusterTopology: (nodes) => this.nodeLiveness.applySnapshot(nodes, Date.now()),
31763
+ onSystemEvent: (event) => this.notificationCenter?.onSystemEvent(event)
31561
31764
  });
31562
31765
  if (this.isPostProcessingNode) await this.startEmbeddingDispatcher(logger, transport);
31563
31766
  this.startSweepTimers();
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CUxRQmzz.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-ChhzqbFf.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "1.2.58",
3
+ "version": "1.2.59",
4
4
  "description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
5
5
  "keywords": [
6
6
  "camstack",