@camstack/system 1.2.348 → 1.2.350

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.
Files changed (68) hide show
  1. package/dist/addon-runner.js +2 -2
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.d.ts +8 -0
  8. package/dist/builtins/alerts/alerts.addon.js +146 -31
  9. package/dist/builtins/alerts/alerts.addon.mjs +146 -31
  10. package/dist/builtins/alerts/device-label.d.ts +8 -0
  11. package/dist/builtins/autotrack/index.js +1 -1
  12. package/dist/builtins/autotrack/index.mjs +1 -1
  13. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  14. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  15. package/dist/builtins/calendars/calendars.addon.js +1 -1
  16. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  17. package/dist/builtins/camera-grid/index.js +1 -1
  18. package/dist/builtins/camera-grid/index.mjs +1 -1
  19. package/dist/builtins/composer/composer.addon.js +2 -2
  20. package/dist/builtins/composer/composer.addon.mjs +2 -2
  21. package/dist/builtins/console-logging/index.js +1 -1
  22. package/dist/builtins/console-logging/index.mjs +1 -1
  23. package/dist/builtins/core-blocks/block-type-defs.d.ts +20 -5
  24. package/dist/builtins/core-blocks/core-blocks.addon.js +171 -26
  25. package/dist/builtins/core-blocks/core-blocks.addon.mjs +171 -26
  26. package/dist/builtins/device-manager/device-bindings-store.d.ts +3 -1
  27. package/dist/builtins/device-manager/device-manager.addon.js +4 -56
  28. package/dist/builtins/device-manager/device-manager.addon.mjs +4 -56
  29. package/dist/builtins/hub-forwarder/index.js +1 -1
  30. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  31. package/dist/builtins/liveness-monitor/liveness-checks.d.ts +7 -0
  32. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +8 -4
  33. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +8 -4
  34. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  35. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  36. package/dist/builtins/local-network/local-network.addon.js +1 -1
  37. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  38. package/dist/builtins/loki-logging/index.js +1 -1
  39. package/dist/builtins/loki-logging/index.mjs +1 -1
  40. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  41. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  42. package/dist/builtins/platform-probe/index.js +1 -1
  43. package/dist/builtins/platform-probe/index.mjs +1 -1
  44. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  45. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  46. package/dist/builtins/snapshot/index.js +1 -1
  47. package/dist/builtins/snapshot/index.mjs +1 -1
  48. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  49. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  50. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  51. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  52. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  53. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  54. package/dist/builtins/system-config/system-config.addon.js +1 -1
  55. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  56. package/dist/builtins/winston-logging/index.js +1 -1
  57. package/dist/builtins/winston-logging/index.mjs +1 -1
  58. package/dist/{child-cap-dispatch-k3dEOCnu.js → child-cap-dispatch-CYgHiMdj.js} +1 -1
  59. package/dist/{child-cap-dispatch-B2N_gWV-.mjs → child-cap-dispatch-zEt3rqy-.mjs} +1 -1
  60. package/dist/{composition-sources-C42s3_1h.js → composition-sources-DyxH-DLr.js} +1 -1
  61. package/dist/{composition-sources-D7cxA4Yv.mjs → composition-sources-d7zXAC70.mjs} +1 -1
  62. package/dist/{dist-BgV_QADz.js → dist-DZKm2EOp.js} +103 -3
  63. package/dist/{dist-nUn3vto_.mjs → dist-Di0aX2zy.mjs} +103 -3
  64. package/dist/index.js +2 -2
  65. package/dist/index.mjs +2 -2
  66. package/dist/{retired-settings-keys-D980hwnw.js → retired-settings-keys-BEY9Wjf1.js} +1 -1
  67. package/dist/{retired-settings-keys-pKNWq-CL.mjs → retired-settings-keys-jzB4iJpq.mjs} +1 -1
  68. package/package.json +1 -1
@@ -1,12 +1,12 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_child_cap_dispatch = require("./child-cap-dispatch-k3dEOCnu.js");
2
+ const require_child_cap_dispatch = require("./child-cap-dispatch-CYgHiMdj.js");
3
3
  const require_custom_action_registry = require("./custom-action-registry-acBY3mHA.js");
4
4
  let node_path = require("node:path");
5
5
  node_path = require_chunk.__toESM(node_path);
6
+ let node_module = require("node:module");
6
7
  let node_url = require("node:url");
7
8
  let node_fs = require("node:fs");
8
9
  node_fs = require_chunk.__toESM(node_fs);
9
- let node_module = require("node:module");
10
10
  let _camstack_types_addon = require("@camstack/types/addon");
11
11
  //#region src/kernel/moleculer/child-addon-call-dispatch.ts
12
12
  function isRecord$1(value) {
@@ -1,4 +1,4 @@
1
- import { Ft as installManifestNativeDeps, H as createUdsLoggerWithControl, It as resolveAddonClass, St as validateProviderRegistrations, Tn as emitHeapDiagnosticReport, U as LocalChildClient, _t as getWorkerNativeCapProvider, bt as setWorkerNativeCapsChangeListener, en as RUNNER_RSS_BUDGET_ENV, f as installManifestSystemDeps, g as createUdsAddonContext, i as killOwnRunnerGroupAfterParentLoss, n as createChildCapDispatch, p as installManifestPythonDeps, pn as startRunnerHeapWatch, u as startRunnerParentDeathWatchdog, vt as getWorkerNativeCapSnapshot } from "./child-cap-dispatch-B2N_gWV-.mjs";
1
+ import { Ft as installManifestNativeDeps, H as createUdsLoggerWithControl, It as resolveAddonClass, St as validateProviderRegistrations, Tn as emitHeapDiagnosticReport, U as LocalChildClient, _t as getWorkerNativeCapProvider, bt as setWorkerNativeCapsChangeListener, en as RUNNER_RSS_BUDGET_ENV, f as installManifestSystemDeps, g as createUdsAddonContext, i as killOwnRunnerGroupAfterParentLoss, n as createChildCapDispatch, p as installManifestPythonDeps, pn as startRunnerHeapWatch, u as startRunnerParentDeathWatchdog, vt as getWorkerNativeCapSnapshot } from "./child-cap-dispatch-zEt3rqy-.mjs";
2
2
  import { t as CustomActionRegistry } from "./custom-action-registry-B4Cf4O_X.mjs";
3
3
  import { register } from "node:module";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BgV_QADz.js");
6
+ const require_dist = require("../../dist-DZKm2EOp.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { H as addonPagesCapability, Jn as errMsg, Yn as BaseAddon, br as EventCategory } from "../../dist-nUn3vto_.mjs";
1
+ import { H as addonPagesCapability, Jn as errMsg, Yn as BaseAddon, br as EventCategory } from "../../dist-Di0aX2zy.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BgV_QADz.js");
6
+ const require_dist = require("../../dist-DZKm2EOp.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_crypto = require("node:crypto");
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Jn as errMsg, W as addonWidgetsCapability, Yn as BaseAddon, br as EventCategory } from "../../dist-nUn3vto_.mjs";
1
+ import { Jn as errMsg, W as addonWidgetsCapability, Yn as BaseAddon, br as EventCategory } from "../../dist-Di0aX2zy.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as path$1 from "node:path";
@@ -67,6 +67,14 @@ export declare class AlertCenterAddon extends BaseAddon<AlertCenterConfig> {
67
67
  private handleLivenessFailed;
68
68
  /** SystemLivenessRecovered — the invariant holds again; drop the alert. */
69
69
  private handleLivenessRecovered;
70
+ /**
71
+ * Stream offline — ONLY `stale-timeout` is an outage. Suspend / destroy /
72
+ * idle dial are deliberate and must not ring the bell (same gate NC uses).
73
+ */
74
+ private handleStreamOffline;
75
+ /** Stream online after an outage — drop the matching offline card. */
76
+ private handleStreamOnline;
77
+ private resolveDeviceName;
70
78
  private buildMessage;
71
79
  private emitAlert;
72
80
  private updateAlert;
@@ -3,7 +3,27 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BgV_QADz.js");
6
+ const require_dist = require("../../dist-DZKm2EOp.js");
7
+ //#region src/builtins/alerts/device-label.ts
8
+ /**
9
+ * Operator-facing device label for alert messages.
10
+ *
11
+ * Prefer a real name; fall back to `Camera <id>` only when nothing else is
12
+ * known. Never prefer the bare id when a name is present — "Camera 618"
13
+ * next to a named "Cucina" is how the Recording-stopped alerts used to read.
14
+ */
15
+ function deviceLabelFromEventData(data) {
16
+ const name = stringNonEmpty(data["name"]) ?? stringNonEmpty(data["deviceName"]);
17
+ if (name !== void 0) return name;
18
+ const id = data["deviceId"];
19
+ if (typeof id === "number" && Number.isFinite(id)) return `Camera ${String(id)}`;
20
+ if (typeof id === "string" && id.length > 0) return `Camera ${id}`;
21
+ return "unknown device";
22
+ }
23
+ function stringNonEmpty(value) {
24
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
25
+ }
26
+ //#endregion
7
27
  //#region src/builtins/alerts/prune-policy.ts
8
28
  /**
9
29
  * Ids older than `cutoffMs`, oldest first.
@@ -33,6 +53,10 @@ var CATEGORY_MAPPINGS = {
33
53
  severity: "error",
34
54
  titleTemplate: "Addon crashed"
35
55
  },
56
+ "addon.runner-failed": {
57
+ severity: "error",
58
+ titleTemplate: "Addon crash loop"
59
+ },
36
60
  "addon.error": {
37
61
  severity: "error",
38
62
  titleTemplate: "Addon error"
@@ -53,30 +77,26 @@ var CATEGORY_MAPPINGS = {
53
77
  severity: "error",
54
78
  titleTemplate: "Notification failed"
55
79
  },
56
- "agent.online": {
57
- severity: "info",
58
- titleTemplate: "Agent connected"
59
- },
60
80
  "agent.offline": {
61
81
  severity: "warning",
62
82
  titleTemplate: "Agent disconnected"
63
83
  },
64
- "system.boot": {
65
- severity: "info",
66
- titleTemplate: "System boot"
67
- },
68
84
  "system.restarting": {
69
85
  severity: "warning",
70
86
  titleTemplate: "System restarting"
71
87
  },
88
+ "system.restart-completed": {
89
+ severity: "info",
90
+ titleTemplate: "Node updated"
91
+ },
92
+ "update.available": {
93
+ severity: "info",
94
+ titleTemplate: "Update available"
95
+ },
72
96
  "system.tls-cert-changed": {
73
97
  severity: "warning",
74
98
  titleTemplate: "TLS certificate changed"
75
99
  },
76
- "device.registered": {
77
- severity: "info",
78
- titleTemplate: "Device registered"
79
- },
80
100
  "device.unregistered": {
81
101
  severity: "warning",
82
102
  titleTemplate: "Device unregistered"
@@ -101,13 +121,9 @@ var CATEGORY_MAPPINGS = {
101
121
  severity: "error",
102
122
  titleTemplate: "Node liveness"
103
123
  },
104
- "addon.load-recovered": {
105
- severity: "info",
106
- titleTemplate: "Addon recovered"
107
- },
108
- "backup.completed": {
109
- severity: "info",
110
- titleTemplate: "Backup completed"
124
+ "stream.offline": {
125
+ severity: "warning",
126
+ titleTemplate: "Stream offline"
111
127
  },
112
128
  "backup.restored": {
113
129
  severity: "info",
@@ -118,7 +134,17 @@ var CATEGORY_MAPPINGS = {
118
134
  titleTemplate: "Model download"
119
135
  }
120
136
  };
121
- var TRACKED_CATEGORIES = Object.keys(CATEGORY_MAPPINGS);
137
+ /**
138
+ * Subscribed for dismiss / merge side-effects only — never mint a card.
139
+ * Must stay in the subscribe set even though they are absent from
140
+ * {@link CATEGORY_MAPPINGS}.
141
+ */
142
+ var SILENT_TRACKED_CATEGORIES = [
143
+ "addon.load-recovered",
144
+ "system.liveness-recovered",
145
+ "stream.online"
146
+ ];
147
+ var TRACKED_CATEGORIES = [...Object.keys(CATEGORY_MAPPINGS), ...SILENT_TRACKED_CATEGORIES];
122
148
  /**
123
149
  * `[ALERT-CENTER-EVENTS]` — rate limiting. Events that fire thousands of
124
150
  * times a minute (flaky cam reconnecting, chatty integration, …) would
@@ -305,12 +331,26 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
305
331
  await this.handleLivenessRecovered(event);
306
332
  return;
307
333
  }
334
+ if (category === require_dist.EventCategory.StreamOffline) {
335
+ await this.handleStreamOffline(event);
336
+ return;
337
+ }
338
+ if (category === require_dist.EventCategory.StreamOnline) {
339
+ await this.handleStreamOnline(event);
340
+ return;
341
+ }
308
342
  const mapping = CATEGORY_MAPPINGS[category];
309
343
  if (!mapping) return;
344
+ if (category === "addon.updated") {
345
+ const toVersion = event.data["toVersion"];
346
+ if (typeof toVersion !== "string" || toVersion.length === 0) return;
347
+ }
310
348
  if (this.shouldSuppressByRate(category, event)) return;
311
349
  const message = this.buildMessage(category, event.data, event.source ? String(event.source.id) : void 0);
350
+ const addonIdForKey = (typeof event.data["addonId"] === "string" ? event.data["addonId"] : void 0) ?? (event.source ? String(event.source.id) : void 0);
351
+ const toVersionForKey = typeof event.data["toVersion"] === "string" ? event.data["toVersion"] : void 0;
312
352
  const alert = {
313
- id: crypto.randomUUID(),
353
+ id: category === "addon.updated" && addonIdForKey !== void 0 && toVersionForKey !== void 0 ? `addon-updated:${addonIdForKey}:${toVersionForKey}` : crypto.randomUUID(),
314
354
  category,
315
355
  severity: mapping.severity,
316
356
  title: mapping.titleTemplate,
@@ -543,32 +583,107 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
543
583
  } });
544
584
  }
545
585
  }
586
+ /**
587
+ * Stream offline — ONLY `stale-timeout` is an outage. Suspend / destroy /
588
+ * idle dial are deliberate and must not ring the bell (same gate NC uses).
589
+ */
590
+ async handleStreamOffline(event) {
591
+ const reason = event.data["reason"];
592
+ if (reason !== "stale-timeout") return;
593
+ const deviceId = event.data["deviceId"];
594
+ const camStreamId = event.data["camStreamId"];
595
+ if (typeof deviceId !== "number" || typeof camStreamId !== "string") return;
596
+ const name = await this.resolveDeviceName(deviceId);
597
+ const label = name ?? `Camera ${String(deviceId)}`;
598
+ const alert = {
599
+ id: `stream-offline:${String(deviceId)}:${camStreamId}`,
600
+ category: require_dist.EventCategory.StreamOffline,
601
+ severity: "warning",
602
+ title: "Stream offline",
603
+ message: `${label} — ${camStreamId} stopped sending video (stale timeout).`,
604
+ status: "active",
605
+ read: false,
606
+ createdAt: Date.now(),
607
+ updatedAt: Date.now(),
608
+ source: {
609
+ type: "device",
610
+ id: String(deviceId)
611
+ },
612
+ metadata: {
613
+ deviceId,
614
+ camStreamId,
615
+ reason,
616
+ ...name !== void 0 ? { name } : {}
617
+ }
618
+ };
619
+ await this.emitAlert(alert);
620
+ }
621
+ /** Stream online after an outage — drop the matching offline card. */
622
+ async handleStreamOnline(event) {
623
+ const deviceId = event.data["deviceId"];
624
+ const camStreamId = event.data["camStreamId"];
625
+ if (typeof deviceId !== "number" || typeof camStreamId !== "string") return;
626
+ const stableId = `stream-offline:${String(deviceId)}:${camStreamId}`;
627
+ try {
628
+ await this.dismiss(stableId);
629
+ } catch (err) {
630
+ this.ctx.logger?.debug("StreamOnline: dismiss failed (likely already dismissed)", { meta: {
631
+ stableId,
632
+ error: require_dist.errMsg(err)
633
+ } });
634
+ }
635
+ }
636
+ async resolveDeviceName(deviceId) {
637
+ try {
638
+ const device = await this.ctx.api.deviceManager.getDevice.query({ deviceId });
639
+ if (device !== null && typeof device.name === "string" && device.name.trim().length > 0) return device.name.trim();
640
+ } catch (err) {
641
+ this.ctx.logger?.debug("resolveDeviceName failed", {
642
+ tags: { deviceId },
643
+ meta: { error: require_dist.errMsg(err) }
644
+ });
645
+ }
646
+ }
546
647
  buildMessage(category, data, sourceId) {
547
648
  const addonId = data.addonId ?? sourceId;
548
- const deviceId = data.deviceId;
649
+ const device = deviceLabelFromEventData(data);
549
650
  const error = data.error;
550
651
  const processId = data.processId;
551
652
  switch (category) {
552
653
  case "addon.crashed": return `Addon "${addonId ?? "unknown"}" crashed${error ? `: ${error}` : ""}`;
654
+ case "addon.runner-failed": {
655
+ const runnerId = typeof data.runnerId === "string" ? data.runnerId : "unknown";
656
+ const addonIds = Array.isArray(data.addonIds) ? data.addonIds.filter((x) => typeof x === "string").join(", ") : "";
657
+ return `Runner "${runnerId}" gave up${addonIds.length > 0 ? ` (addons: ${addonIds})` : ""}`;
658
+ }
553
659
  case "addon.error": return `Addon "${addonId ?? "unknown"}" error${error ? `: ${error}` : ""}`;
554
660
  case "addon.installed": return `Addon "${addonId ?? "unknown"}" installed`;
555
661
  case "addon.updated": {
556
662
  const from = data.fromVersion;
557
663
  const to = data.toVersion;
558
- return `Addon "${addonId ?? "unknown"}" updated${from && to ? ` from ${from} to ${to}` : ""}`;
664
+ return `Addon "${typeof data.packageName === "string" && data.packageName.length > 0 ? data.packageName : addonId ?? "unknown"}" updated${from && to ? ` from ${from} to ${to}` : to ? ` to ${to}` : ""}`;
559
665
  }
560
666
  case "addon.uninstalled": return `Addon "${addonId ?? "unknown"}" uninstalled`;
561
- case "device.registered": return `Device "${data.name ?? deviceId ?? "unknown"}" registered`;
562
- case "device.unregistered": return `Device "${deviceId ?? "unknown"}" unregistered`;
563
- case "agent.online": return `Agent "${data.agentId ?? data.nodeId ?? "unknown"}" connected`;
667
+ case "device.unregistered": return `Device "${device}" unregistered`;
564
668
  case "agent.offline": return `Agent "${data.agentId ?? data.nodeId ?? "unknown"}" disconnected${data.reason ? `: ${String(data.reason)}` : ""}`;
565
- case "backup.completed": return `Backup completed${data.targetId ? ` (${String(data.targetId)})` : ""}`;
566
669
  case "backup.restored": return `Backup restored${data.source ? ` from ${String(data.source)}` : ""}`;
567
670
  case "notification.failed": return `Notification failed${error ? `: ${error}` : ""}`;
568
671
  case "system.restarting": return `System restarting${data.reason ? ` — ${String(data.reason)}` : ""}`;
569
- case "recording.error": return `Recording error on device "${deviceId ?? "unknown"}"${error ? `: ${error}` : ""}`;
570
- case "recording.storage.critical": return `Storage critical for device "${deviceId ?? "unknown"}"`;
571
- case "system.boot": return `System booted in "${data.mode ?? "unknown"}" mode`;
672
+ case "system.restart-completed": {
673
+ const pkg = typeof data.packageName === "string" ? data.packageName : void 0;
674
+ const to = typeof data.toVersion === "string" ? data.toVersion : void 0;
675
+ const kind = typeof data.kind === "string" ? data.kind : "restart";
676
+ if (pkg !== void 0 && to !== void 0) return `${pkg} is now ${to}`;
677
+ return `Node restart completed (${kind})`;
678
+ }
679
+ case "update.available": {
680
+ const pkg = typeof data.packageName === "string" ? data.packageName : "package";
681
+ const latest = typeof data.latestVersion === "string" ? data.latestVersion : "?";
682
+ const node = typeof data.nodeId === "string" ? data.nodeId : void 0;
683
+ return `${pkg} ${latest} available${node !== void 0 ? ` on ${node}` : ""}`;
684
+ }
685
+ case "recording.error": return `Recording error on "${device}"${error ? `: ${error}` : ""}`;
686
+ case "recording.storage.critical": return `Storage critical for "${device}"`;
572
687
  case "system.tls-cert-changed": return data.caRotated === true ? `TLS certificate reissued (${String(data.reason)}) — the local CA also changed. Re-install ${String(data.caCertPath)} on any device that trusted the old one.` : `TLS certificate reissued (${String(data.reason)}) — the local CA is unchanged, previously trusted devices keep working.`;
573
688
  case "process.crashed": return `Process "${processId ?? "unknown"}" crashed`;
574
689
  default: return `Event: ${category}`;
@@ -1,4 +1,24 @@
1
- import { G as alertsCapability, Jn as errMsg, Yn as BaseAddon, ar as createEvent, br as EventCategory, n as AlertSchema } from "../../dist-nUn3vto_.mjs";
1
+ import { G as alertsCapability, Jn as errMsg, Yn as BaseAddon, ar as createEvent, br as EventCategory, n as AlertSchema } from "../../dist-Di0aX2zy.mjs";
2
+ //#region src/builtins/alerts/device-label.ts
3
+ /**
4
+ * Operator-facing device label for alert messages.
5
+ *
6
+ * Prefer a real name; fall back to `Camera <id>` only when nothing else is
7
+ * known. Never prefer the bare id when a name is present — "Camera 618"
8
+ * next to a named "Cucina" is how the Recording-stopped alerts used to read.
9
+ */
10
+ function deviceLabelFromEventData(data) {
11
+ const name = stringNonEmpty(data["name"]) ?? stringNonEmpty(data["deviceName"]);
12
+ if (name !== void 0) return name;
13
+ const id = data["deviceId"];
14
+ if (typeof id === "number" && Number.isFinite(id)) return `Camera ${String(id)}`;
15
+ if (typeof id === "string" && id.length > 0) return `Camera ${id}`;
16
+ return "unknown device";
17
+ }
18
+ function stringNonEmpty(value) {
19
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
20
+ }
21
+ //#endregion
2
22
  //#region src/builtins/alerts/prune-policy.ts
3
23
  /**
4
24
  * Ids older than `cutoffMs`, oldest first.
@@ -28,6 +48,10 @@ var CATEGORY_MAPPINGS = {
28
48
  severity: "error",
29
49
  titleTemplate: "Addon crashed"
30
50
  },
51
+ "addon.runner-failed": {
52
+ severity: "error",
53
+ titleTemplate: "Addon crash loop"
54
+ },
31
55
  "addon.error": {
32
56
  severity: "error",
33
57
  titleTemplate: "Addon error"
@@ -48,30 +72,26 @@ var CATEGORY_MAPPINGS = {
48
72
  severity: "error",
49
73
  titleTemplate: "Notification failed"
50
74
  },
51
- "agent.online": {
52
- severity: "info",
53
- titleTemplate: "Agent connected"
54
- },
55
75
  "agent.offline": {
56
76
  severity: "warning",
57
77
  titleTemplate: "Agent disconnected"
58
78
  },
59
- "system.boot": {
60
- severity: "info",
61
- titleTemplate: "System boot"
62
- },
63
79
  "system.restarting": {
64
80
  severity: "warning",
65
81
  titleTemplate: "System restarting"
66
82
  },
83
+ "system.restart-completed": {
84
+ severity: "info",
85
+ titleTemplate: "Node updated"
86
+ },
87
+ "update.available": {
88
+ severity: "info",
89
+ titleTemplate: "Update available"
90
+ },
67
91
  "system.tls-cert-changed": {
68
92
  severity: "warning",
69
93
  titleTemplate: "TLS certificate changed"
70
94
  },
71
- "device.registered": {
72
- severity: "info",
73
- titleTemplate: "Device registered"
74
- },
75
95
  "device.unregistered": {
76
96
  severity: "warning",
77
97
  titleTemplate: "Device unregistered"
@@ -96,13 +116,9 @@ var CATEGORY_MAPPINGS = {
96
116
  severity: "error",
97
117
  titleTemplate: "Node liveness"
98
118
  },
99
- "addon.load-recovered": {
100
- severity: "info",
101
- titleTemplate: "Addon recovered"
102
- },
103
- "backup.completed": {
104
- severity: "info",
105
- titleTemplate: "Backup completed"
119
+ "stream.offline": {
120
+ severity: "warning",
121
+ titleTemplate: "Stream offline"
106
122
  },
107
123
  "backup.restored": {
108
124
  severity: "info",
@@ -113,7 +129,17 @@ var CATEGORY_MAPPINGS = {
113
129
  titleTemplate: "Model download"
114
130
  }
115
131
  };
116
- var TRACKED_CATEGORIES = Object.keys(CATEGORY_MAPPINGS);
132
+ /**
133
+ * Subscribed for dismiss / merge side-effects only — never mint a card.
134
+ * Must stay in the subscribe set even though they are absent from
135
+ * {@link CATEGORY_MAPPINGS}.
136
+ */
137
+ var SILENT_TRACKED_CATEGORIES = [
138
+ "addon.load-recovered",
139
+ "system.liveness-recovered",
140
+ "stream.online"
141
+ ];
142
+ var TRACKED_CATEGORIES = [...Object.keys(CATEGORY_MAPPINGS), ...SILENT_TRACKED_CATEGORIES];
117
143
  /**
118
144
  * `[ALERT-CENTER-EVENTS]` — rate limiting. Events that fire thousands of
119
145
  * times a minute (flaky cam reconnecting, chatty integration, …) would
@@ -300,12 +326,26 @@ var AlertCenterAddon = class extends BaseAddon {
300
326
  await this.handleLivenessRecovered(event);
301
327
  return;
302
328
  }
329
+ if (category === EventCategory.StreamOffline) {
330
+ await this.handleStreamOffline(event);
331
+ return;
332
+ }
333
+ if (category === EventCategory.StreamOnline) {
334
+ await this.handleStreamOnline(event);
335
+ return;
336
+ }
303
337
  const mapping = CATEGORY_MAPPINGS[category];
304
338
  if (!mapping) return;
339
+ if (category === "addon.updated") {
340
+ const toVersion = event.data["toVersion"];
341
+ if (typeof toVersion !== "string" || toVersion.length === 0) return;
342
+ }
305
343
  if (this.shouldSuppressByRate(category, event)) return;
306
344
  const message = this.buildMessage(category, event.data, event.source ? String(event.source.id) : void 0);
345
+ const addonIdForKey = (typeof event.data["addonId"] === "string" ? event.data["addonId"] : void 0) ?? (event.source ? String(event.source.id) : void 0);
346
+ const toVersionForKey = typeof event.data["toVersion"] === "string" ? event.data["toVersion"] : void 0;
307
347
  const alert = {
308
- id: crypto.randomUUID(),
348
+ id: category === "addon.updated" && addonIdForKey !== void 0 && toVersionForKey !== void 0 ? `addon-updated:${addonIdForKey}:${toVersionForKey}` : crypto.randomUUID(),
309
349
  category,
310
350
  severity: mapping.severity,
311
351
  title: mapping.titleTemplate,
@@ -538,32 +578,107 @@ var AlertCenterAddon = class extends BaseAddon {
538
578
  } });
539
579
  }
540
580
  }
581
+ /**
582
+ * Stream offline — ONLY `stale-timeout` is an outage. Suspend / destroy /
583
+ * idle dial are deliberate and must not ring the bell (same gate NC uses).
584
+ */
585
+ async handleStreamOffline(event) {
586
+ const reason = event.data["reason"];
587
+ if (reason !== "stale-timeout") return;
588
+ const deviceId = event.data["deviceId"];
589
+ const camStreamId = event.data["camStreamId"];
590
+ if (typeof deviceId !== "number" || typeof camStreamId !== "string") return;
591
+ const name = await this.resolveDeviceName(deviceId);
592
+ const label = name ?? `Camera ${String(deviceId)}`;
593
+ const alert = {
594
+ id: `stream-offline:${String(deviceId)}:${camStreamId}`,
595
+ category: EventCategory.StreamOffline,
596
+ severity: "warning",
597
+ title: "Stream offline",
598
+ message: `${label} — ${camStreamId} stopped sending video (stale timeout).`,
599
+ status: "active",
600
+ read: false,
601
+ createdAt: Date.now(),
602
+ updatedAt: Date.now(),
603
+ source: {
604
+ type: "device",
605
+ id: String(deviceId)
606
+ },
607
+ metadata: {
608
+ deviceId,
609
+ camStreamId,
610
+ reason,
611
+ ...name !== void 0 ? { name } : {}
612
+ }
613
+ };
614
+ await this.emitAlert(alert);
615
+ }
616
+ /** Stream online after an outage — drop the matching offline card. */
617
+ async handleStreamOnline(event) {
618
+ const deviceId = event.data["deviceId"];
619
+ const camStreamId = event.data["camStreamId"];
620
+ if (typeof deviceId !== "number" || typeof camStreamId !== "string") return;
621
+ const stableId = `stream-offline:${String(deviceId)}:${camStreamId}`;
622
+ try {
623
+ await this.dismiss(stableId);
624
+ } catch (err) {
625
+ this.ctx.logger?.debug("StreamOnline: dismiss failed (likely already dismissed)", { meta: {
626
+ stableId,
627
+ error: errMsg(err)
628
+ } });
629
+ }
630
+ }
631
+ async resolveDeviceName(deviceId) {
632
+ try {
633
+ const device = await this.ctx.api.deviceManager.getDevice.query({ deviceId });
634
+ if (device !== null && typeof device.name === "string" && device.name.trim().length > 0) return device.name.trim();
635
+ } catch (err) {
636
+ this.ctx.logger?.debug("resolveDeviceName failed", {
637
+ tags: { deviceId },
638
+ meta: { error: errMsg(err) }
639
+ });
640
+ }
641
+ }
541
642
  buildMessage(category, data, sourceId) {
542
643
  const addonId = data.addonId ?? sourceId;
543
- const deviceId = data.deviceId;
644
+ const device = deviceLabelFromEventData(data);
544
645
  const error = data.error;
545
646
  const processId = data.processId;
546
647
  switch (category) {
547
648
  case "addon.crashed": return `Addon "${addonId ?? "unknown"}" crashed${error ? `: ${error}` : ""}`;
649
+ case "addon.runner-failed": {
650
+ const runnerId = typeof data.runnerId === "string" ? data.runnerId : "unknown";
651
+ const addonIds = Array.isArray(data.addonIds) ? data.addonIds.filter((x) => typeof x === "string").join(", ") : "";
652
+ return `Runner "${runnerId}" gave up${addonIds.length > 0 ? ` (addons: ${addonIds})` : ""}`;
653
+ }
548
654
  case "addon.error": return `Addon "${addonId ?? "unknown"}" error${error ? `: ${error}` : ""}`;
549
655
  case "addon.installed": return `Addon "${addonId ?? "unknown"}" installed`;
550
656
  case "addon.updated": {
551
657
  const from = data.fromVersion;
552
658
  const to = data.toVersion;
553
- return `Addon "${addonId ?? "unknown"}" updated${from && to ? ` from ${from} to ${to}` : ""}`;
659
+ return `Addon "${typeof data.packageName === "string" && data.packageName.length > 0 ? data.packageName : addonId ?? "unknown"}" updated${from && to ? ` from ${from} to ${to}` : to ? ` to ${to}` : ""}`;
554
660
  }
555
661
  case "addon.uninstalled": return `Addon "${addonId ?? "unknown"}" uninstalled`;
556
- case "device.registered": return `Device "${data.name ?? deviceId ?? "unknown"}" registered`;
557
- case "device.unregistered": return `Device "${deviceId ?? "unknown"}" unregistered`;
558
- case "agent.online": return `Agent "${data.agentId ?? data.nodeId ?? "unknown"}" connected`;
662
+ case "device.unregistered": return `Device "${device}" unregistered`;
559
663
  case "agent.offline": return `Agent "${data.agentId ?? data.nodeId ?? "unknown"}" disconnected${data.reason ? `: ${String(data.reason)}` : ""}`;
560
- case "backup.completed": return `Backup completed${data.targetId ? ` (${String(data.targetId)})` : ""}`;
561
664
  case "backup.restored": return `Backup restored${data.source ? ` from ${String(data.source)}` : ""}`;
562
665
  case "notification.failed": return `Notification failed${error ? `: ${error}` : ""}`;
563
666
  case "system.restarting": return `System restarting${data.reason ? ` — ${String(data.reason)}` : ""}`;
564
- case "recording.error": return `Recording error on device "${deviceId ?? "unknown"}"${error ? `: ${error}` : ""}`;
565
- case "recording.storage.critical": return `Storage critical for device "${deviceId ?? "unknown"}"`;
566
- case "system.boot": return `System booted in "${data.mode ?? "unknown"}" mode`;
667
+ case "system.restart-completed": {
668
+ const pkg = typeof data.packageName === "string" ? data.packageName : void 0;
669
+ const to = typeof data.toVersion === "string" ? data.toVersion : void 0;
670
+ const kind = typeof data.kind === "string" ? data.kind : "restart";
671
+ if (pkg !== void 0 && to !== void 0) return `${pkg} is now ${to}`;
672
+ return `Node restart completed (${kind})`;
673
+ }
674
+ case "update.available": {
675
+ const pkg = typeof data.packageName === "string" ? data.packageName : "package";
676
+ const latest = typeof data.latestVersion === "string" ? data.latestVersion : "?";
677
+ const node = typeof data.nodeId === "string" ? data.nodeId : void 0;
678
+ return `${pkg} ${latest} available${node !== void 0 ? ` on ${node}` : ""}`;
679
+ }
680
+ case "recording.error": return `Recording error on "${device}"${error ? `: ${error}` : ""}`;
681
+ case "recording.storage.critical": return `Storage critical for "${device}"`;
567
682
  case "system.tls-cert-changed": return data.caRotated === true ? `TLS certificate reissued (${String(data.reason)}) — the local CA also changed. Re-install ${String(data.caCertPath)} on any device that trusted the old one.` : `TLS certificate reissued (${String(data.reason)}) — the local CA is unchanged, previously trusted devices keep working.`;
568
683
  case "process.crashed": return `Process "${processId ?? "unknown"}" crashed`;
569
684
  default: return `Event: ${category}`;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Operator-facing device label for alert messages.
3
+ *
4
+ * Prefer a real name; fall back to `Camera <id>` only when nothing else is
5
+ * known. Never prefer the bare id when a name is present — "Camera 618"
6
+ * next to a named "Cucina" is how the Recording-stopped alerts used to read.
7
+ */
8
+ export declare function deviceLabelFromEventData(data: Readonly<Record<string, unknown>>): string;
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BgV_QADz.js");
6
+ const require_dist = require("../../dist-DZKm2EOp.js");
7
7
  //#region src/builtins/autotrack/autotrack-config.ts
8
8
  var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
9
9
  deadZoneFraction: .5,
@@ -1,4 +1,4 @@
1
- import { J as bareAddonId, Jn as errMsg, Yn as BaseAddon, br as EventCategory, dr as isEvent, on as ptzAutotrackCapability, x as FIRST_LEVEL_MACRO_CLASSES } from "../../dist-nUn3vto_.mjs";
1
+ import { J as bareAddonId, Jn as errMsg, Yn as BaseAddon, br as EventCategory, dr as isEvent, on as ptzAutotrackCapability, x as FIRST_LEVEL_MACRO_CLASSES } from "../../dist-Di0aX2zy.mjs";
2
2
  //#region src/builtins/autotrack/autotrack-config.ts
3
3
  var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
4
4
  deadZoneFraction: .5,
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BgV_QADz.js");
6
+ const require_dist = require("../../dist-DZKm2EOp.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let buffer = require("buffer");
9
9
  let node_fs_promises = require("node:fs/promises");
@@ -1,4 +1,4 @@
1
- import { Cn as storageOccupancyCapability, Yn as BaseAddon, br as EventCategory, fr as nodePin, h as DATABASE_SNAPSHOT_SUFFIX, q as backupCapability, qt as mayWriteToLocation, yr as toNodeId } from "../../dist-nUn3vto_.mjs";
1
+ import { Cn as storageOccupancyCapability, Yn as BaseAddon, br as EventCategory, fr as nodePin, h as DATABASE_SNAPSHOT_SUFFIX, q as backupCapability, qt as mayWriteToLocation, yr as toNodeId } from "../../dist-Di0aX2zy.mjs";
2
2
  import { randomBytes, randomUUID } from "node:crypto";
3
3
  import { Buffer as Buffer$1 } from "buffer";
4
4
  import * as fsp from "node:fs/promises";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BgV_QADz.js");
6
+ const require_dist = require("../../dist-DZKm2EOp.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let zod = require("zod");
9
9
  //#region src/builtins/calendars/calendar-color.ts
@@ -1,4 +1,4 @@
1
- import { Q as calendarsCapability, Yn as BaseAddon, f as CalendarPreferencesSchema } from "../../dist-nUn3vto_.mjs";
1
+ import { Q as calendarsCapability, Yn as BaseAddon, f as CalendarPreferencesSchema } from "../../dist-Di0aX2zy.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import { z } from "zod";
4
4
  //#region src/builtins/calendars/calendar-color.ts