@agent-native/core 0.92.2 → 0.92.3

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 (170) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +26 -0
  3. package/corpus/core/docs/content/locales/ar-SA/server.mdx +35 -0
  4. package/corpus/core/docs/content/locales/de-DE/server.mdx +35 -0
  5. package/corpus/core/docs/content/locales/es-ES/server.mdx +35 -0
  6. package/corpus/core/docs/content/locales/fr-FR/server.mdx +35 -0
  7. package/corpus/core/docs/content/locales/hi-IN/server.mdx +35 -0
  8. package/corpus/core/docs/content/locales/ja-JP/server.mdx +35 -0
  9. package/corpus/core/docs/content/locales/ko-KR/server.mdx +35 -0
  10. package/corpus/core/docs/content/locales/pt-BR/server.mdx +35 -0
  11. package/corpus/core/docs/content/locales/zh-CN/server.mdx +35 -0
  12. package/corpus/core/docs/content/locales/zh-TW/server.mdx +35 -0
  13. package/corpus/core/docs/content/server.mdx +35 -0
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +14 -4
  16. package/corpus/core/src/agent/engine/anthropic-engine.ts +15 -3
  17. package/corpus/core/src/agent/engine/builder-engine.ts +30 -4
  18. package/corpus/core/src/agent/engine/registry.ts +63 -2
  19. package/corpus/core/src/agent/model-config.ts +0 -2
  20. package/corpus/core/src/agent/production-agent.ts +12 -1
  21. package/corpus/core/src/agent/run-manager.ts +57 -36
  22. package/corpus/core/src/agent/run-store.ts +81 -2
  23. package/corpus/core/src/agent/thread-data-builder.ts +33 -2
  24. package/corpus/core/src/client/AssistantChat.tsx +314 -96
  25. package/corpus/core/src/client/agent-chat-adapter.ts +17 -1
  26. package/corpus/core/src/client/chat/repo-helpers.ts +45 -0
  27. package/corpus/core/src/client/chat/tool-call-display.tsx +36 -27
  28. package/corpus/core/src/client/chat-model-groups.ts +1 -5
  29. package/corpus/core/src/client/composer/TiptapComposer.tsx +0 -2
  30. package/corpus/core/src/client/embed-auth.ts +20 -0
  31. package/corpus/core/src/client/settings/SettingsSection.tsx +66 -5
  32. package/corpus/core/src/client/sse-event-processor.ts +49 -17
  33. package/corpus/core/src/client/use-db-sync.ts +31 -9
  34. package/corpus/core/src/collab/agent-presence.ts +6 -1
  35. package/corpus/core/src/collab/awareness-store.ts +185 -0
  36. package/corpus/core/src/collab/awareness.ts +58 -2
  37. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +7 -8
  38. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +7 -18
  39. package/corpus/core/src/server/core-routes-plugin.ts +21 -2
  40. package/corpus/core/src/server/embed-session.ts +20 -4
  41. package/corpus/core/src/styles/agent-native.css +51 -0
  42. package/corpus/core/src/tracking/providers.ts +33 -2
  43. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +8 -1
  44. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +9 -1
  45. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorDetail.tsx +41 -11
  46. package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +44 -0
  47. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-action-menus-now-close-cleanly-before-opening-hist.md +6 -0
  48. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-email-reports-still-send-when-the-screenshot-captu.md +6 -0
  49. package/corpus/templates/analytics/changelog/2026-07-09-uptime-alerts-now-suppress-recovery-noise-during-flapping-an.md +6 -0
  50. package/corpus/templates/analytics/changelog/2026-07-09-uptime-check-history-now-shows-timing-diagnostics-so-slow-or.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-07-09-uptime-recovery-alerts-now-send-when-the-original-outage-not.md +6 -0
  52. package/corpus/templates/analytics/docs/uptime-monitoring.md +29 -16
  53. package/corpus/templates/analytics/netlify.toml +3 -0
  54. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +126 -0
  55. package/corpus/templates/analytics/server/db/schema-monitoring.ts +5 -0
  56. package/corpus/templates/analytics/server/jobs/uptime-monitors.ts +5 -1
  57. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +305 -31
  58. package/corpus/templates/analytics/server/plugins/db.ts +15 -0
  59. package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +1 -1
  60. package/corpus/templates/analytics/server/routes/api/uptime-monitors/run.post.ts +57 -0
  61. package/corpus/templates/clips/actions/list-meetings.ts +12 -0
  62. package/corpus/templates/clips/changelog/2026-07-09-meeting-reminders-now-ignore-obvious-solo-personal-calendar-.md +6 -0
  63. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +1 -0
  64. package/corpus/templates/clips/server/lib/calendar-event-classification.ts +79 -0
  65. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  66. package/dist/agent/engine/ai-sdk-engine.js +13 -4
  67. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  68. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  69. package/dist/agent/engine/anthropic-engine.js +14 -3
  70. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  71. package/dist/agent/engine/builder-engine.d.ts +1 -1
  72. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  73. package/dist/agent/engine/builder-engine.js +26 -4
  74. package/dist/agent/engine/builder-engine.js.map +1 -1
  75. package/dist/agent/engine/registry.d.ts.map +1 -1
  76. package/dist/agent/engine/registry.js +47 -2
  77. package/dist/agent/engine/registry.js.map +1 -1
  78. package/dist/agent/model-config.d.ts +2 -2
  79. package/dist/agent/model-config.d.ts.map +1 -1
  80. package/dist/agent/model-config.js +0 -2
  81. package/dist/agent/model-config.js.map +1 -1
  82. package/dist/agent/production-agent.d.ts.map +1 -1
  83. package/dist/agent/production-agent.js +12 -1
  84. package/dist/agent/production-agent.js.map +1 -1
  85. package/dist/agent/run-manager.d.ts.map +1 -1
  86. package/dist/agent/run-manager.js +52 -33
  87. package/dist/agent/run-manager.js.map +1 -1
  88. package/dist/agent/run-store.d.ts +24 -0
  89. package/dist/agent/run-store.d.ts.map +1 -1
  90. package/dist/agent/run-store.js +64 -2
  91. package/dist/agent/run-store.js.map +1 -1
  92. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  93. package/dist/agent/thread-data-builder.js +28 -2
  94. package/dist/agent/thread-data-builder.js.map +1 -1
  95. package/dist/client/AssistantChat.d.ts +6 -0
  96. package/dist/client/AssistantChat.d.ts.map +1 -1
  97. package/dist/client/AssistantChat.js +243 -36
  98. package/dist/client/AssistantChat.js.map +1 -1
  99. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  100. package/dist/client/agent-chat-adapter.js +15 -1
  101. package/dist/client/agent-chat-adapter.js.map +1 -1
  102. package/dist/client/chat/repo-helpers.d.ts.map +1 -1
  103. package/dist/client/chat/repo-helpers.js +43 -0
  104. package/dist/client/chat/repo-helpers.js.map +1 -1
  105. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  106. package/dist/client/chat/tool-call-display.js +4 -4
  107. package/dist/client/chat/tool-call-display.js.map +1 -1
  108. package/dist/client/chat-model-groups.d.ts.map +1 -1
  109. package/dist/client/chat-model-groups.js +1 -3
  110. package/dist/client/chat-model-groups.js.map +1 -1
  111. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  112. package/dist/client/composer/TiptapComposer.js +0 -2
  113. package/dist/client/composer/TiptapComposer.js.map +1 -1
  114. package/dist/client/embed-auth.d.ts.map +1 -1
  115. package/dist/client/embed-auth.js +14 -0
  116. package/dist/client/embed-auth.js.map +1 -1
  117. package/dist/client/settings/SettingsSection.d.ts.map +1 -1
  118. package/dist/client/settings/SettingsSection.js +32 -3
  119. package/dist/client/settings/SettingsSection.js.map +1 -1
  120. package/dist/client/sse-event-processor.d.ts.map +1 -1
  121. package/dist/client/sse-event-processor.js +44 -17
  122. package/dist/client/sse-event-processor.js.map +1 -1
  123. package/dist/client/use-db-sync.d.ts.map +1 -1
  124. package/dist/client/use-db-sync.js +29 -9
  125. package/dist/client/use-db-sync.js.map +1 -1
  126. package/dist/collab/agent-presence.d.ts.map +1 -1
  127. package/dist/collab/agent-presence.js +5 -2
  128. package/dist/collab/agent-presence.js.map +1 -1
  129. package/dist/collab/awareness-store.d.ts +1 -1
  130. package/dist/collab/awareness-store.d.ts.map +1 -1
  131. package/dist/collab/awareness-store.js +0 -0
  132. package/dist/collab/awareness-store.js.map +1 -1
  133. package/dist/collab/awareness.d.ts +2 -0
  134. package/dist/collab/awareness.d.ts.map +1 -1
  135. package/dist/collab/awareness.js +46 -2
  136. package/dist/collab/awareness.js.map +1 -1
  137. package/dist/collab/routes.d.ts +1 -1
  138. package/dist/notifications/routes.d.ts +1 -1
  139. package/dist/observability/routes.d.ts +5 -5
  140. package/dist/progress/routes.d.ts +1 -1
  141. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  142. package/dist/scripts/agent-engines/manage-agent-engine.js +6 -6
  143. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  144. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  145. package/dist/scripts/agent-engines/set-agent-engine.js +5 -13
  146. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  147. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  148. package/dist/server/core-routes-plugin.d.ts +5 -0
  149. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  150. package/dist/server/core-routes-plugin.js +10 -3
  151. package/dist/server/core-routes-plugin.js.map +1 -1
  152. package/dist/server/embed-session.d.ts.map +1 -1
  153. package/dist/server/embed-session.js +17 -4
  154. package/dist/server/embed-session.js.map +1 -1
  155. package/dist/styles/agent-native.css +51 -0
  156. package/dist/tracking/providers.d.ts.map +1 -1
  157. package/dist/tracking/providers.js +23 -5
  158. package/dist/tracking/providers.js.map +1 -1
  159. package/docs/content/locales/ar-SA/server.mdx +35 -0
  160. package/docs/content/locales/de-DE/server.mdx +35 -0
  161. package/docs/content/locales/es-ES/server.mdx +35 -0
  162. package/docs/content/locales/fr-FR/server.mdx +35 -0
  163. package/docs/content/locales/hi-IN/server.mdx +35 -0
  164. package/docs/content/locales/ja-JP/server.mdx +35 -0
  165. package/docs/content/locales/ko-KR/server.mdx +35 -0
  166. package/docs/content/locales/pt-BR/server.mdx +35 -0
  167. package/docs/content/locales/zh-CN/server.mdx +35 -0
  168. package/docs/content/locales/zh-TW/server.mdx +35 -0
  169. package/docs/content/server.mdx +35 -0
  170. package/package.json +1 -1
@@ -36,6 +36,7 @@ import { cn } from "@/lib/utils";
36
36
 
37
37
  import { fmt, useUptimeT } from "./i18n";
38
38
  import type {
39
+ MonitorCheckDiagnostics,
39
40
  MonitorDetail as MonitorDetailData,
40
41
  MonitorStats,
41
42
  MonitorSummary,
@@ -420,17 +421,22 @@ export function MonitorDetail({
420
421
  {formatLatency(result.latencyMs)}
421
422
  </TableCell>
422
423
  <TableCell className="max-w-[220px] text-xs text-muted-foreground">
423
- {result.failedAssertions.length > 0 ? (
424
- <span className="text-amber-500">
425
- {result.failedAssertions.join("; ")}
426
- </span>
427
- ) : result.error ? (
428
- <span className="text-destructive">
429
- {result.error}
430
- </span>
431
- ) : (
432
- <span className="text-emerald-500">{t.ok}</span>
433
- )}
424
+ <div className="space-y-1">
425
+ {result.failedAssertions.length > 0 ? (
426
+ <span className="text-amber-500">
427
+ {result.failedAssertions.join("; ")}
428
+ </span>
429
+ ) : result.error ? (
430
+ <span className="text-destructive">
431
+ {result.error}
432
+ </span>
433
+ ) : (
434
+ <span className="text-emerald-500">{t.ok}</span>
435
+ )}
436
+ <div className="text-[11px] leading-snug text-muted-foreground/80">
437
+ {formatDiagnostics(result.diagnostics)}
438
+ </div>
439
+ </div>
434
440
  </TableCell>
435
441
  </TableRow>
436
442
  );
@@ -508,6 +514,30 @@ export function MonitorDetail({
508
514
  );
509
515
  }
510
516
 
517
+ function formatDiagnostics(diagnostics: MonitorCheckDiagnostics): string {
518
+ const timings = diagnostics.timings ?? {};
519
+ const parts = [
520
+ diagnostics.source,
521
+ timings.requestMs != null
522
+ ? `headers ${formatLatency(timings.requestMs)}`
523
+ : null,
524
+ timings.ssrfSetupMs != null
525
+ ? `ssrf ${formatLatency(timings.ssrfSetupMs)}`
526
+ : null,
527
+ timings.bodyReadMs != null
528
+ ? `body ${formatLatency(timings.bodyReadMs)}`
529
+ : null,
530
+ diagnostics.response?.finalHost
531
+ ? `host ${diagnostics.response.finalHost}`
532
+ : null,
533
+ diagnostics.error?.kind ? `error ${diagnostics.error.kind}` : null,
534
+ diagnostics.runtime?.commitRef
535
+ ? `sha ${diagnostics.runtime.commitRef.slice(0, 7)}`
536
+ : null,
537
+ ].filter(Boolean);
538
+ return parts.join(" · ");
539
+ }
540
+
511
541
  function StatCard({
512
542
  label,
513
543
  children,
@@ -91,6 +91,50 @@ export interface MonitorCheckResult {
91
91
  latencyMs: number | null;
92
92
  error: string | null;
93
93
  failedAssertions: string[];
94
+ diagnostics: MonitorCheckDiagnostics;
95
+ }
96
+
97
+ export interface MonitorCheckDiagnostics {
98
+ source:
99
+ | "netlify-scheduled"
100
+ | "netlify-runtime"
101
+ | "in-process"
102
+ | "manual"
103
+ | "unknown";
104
+ runtime: {
105
+ nodeEnv?: string;
106
+ netlify?: boolean;
107
+ deployId?: string;
108
+ deployContext?: string;
109
+ commitRef?: string;
110
+ functionName?: string;
111
+ region?: string;
112
+ };
113
+ request: {
114
+ method: MonitorMethod;
115
+ timeoutMs: number;
116
+ followRedirects: boolean;
117
+ assertionTypes: AssertionType[];
118
+ bodyReadRequired: boolean;
119
+ allowPrivateHosts: boolean;
120
+ };
121
+ timings: {
122
+ totalMs?: number;
123
+ ssrfSetupMs?: number;
124
+ requestMs?: number;
125
+ bodyReadMs?: number;
126
+ };
127
+ response?: {
128
+ finalUrl?: string;
129
+ finalHost?: string;
130
+ statusCode?: number;
131
+ headers?: Record<string, string>;
132
+ };
133
+ error?: {
134
+ kind: "config" | "timeout" | "network" | "body-timeout";
135
+ name?: string;
136
+ message: string;
137
+ };
94
138
  }
95
139
 
96
140
  export interface MonitorIncident {
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-09
4
+ ---
5
+
6
+ Dashboard action menus now close cleanly before opening history, email, archive, or delete dialogs.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-09
4
+ ---
5
+
6
+ Dashboard email report screenshots load reliably after dashboard URL redirects.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-09
4
+ ---
5
+
6
+ Uptime alerts now suppress recovery noise during flapping and confirm slow-response spikes before emailing.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-09
4
+ ---
5
+
6
+ Uptime check history now shows timing diagnostics so slow or timed-out probes are easier to debug.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-09
4
+ ---
5
+
6
+ Uptime recovery alerts now send when the original outage notification reached email, Slack, or webhook channels.
@@ -38,6 +38,10 @@ Each check runs on the server (`server/lib/uptime-monitors.ts`):
38
38
  - `down` — the status does not match, a content/header assertion fails, or
39
39
  the request errored/timed out. Alerts at the monitor's configured severity.
40
40
  - `error` — a configuration/SSRF problem prevented the check from running.
41
+ 6. Persist a compact diagnostics payload on the check row with safe runtime
42
+ context, phase timings, response headers such as `x-nf-request-id`, and the
43
+ error kind. Diagnostics intentionally exclude request headers, request bodies,
44
+ response bodies, secrets, and large payloads.
41
45
 
42
46
  ### Status matcher
43
47
 
@@ -73,15 +77,19 @@ Each check runs on the server (`server/lib/uptime-monitors.ts`):
73
77
  `emailRecipients`; `slack` / `webhook` use the monitor's optional
74
78
  delivery-only `slackWebhookUrl` / `webhookUrl` (falling back to workspace
75
79
  `NOTIFICATIONS_SLACK_WEBHOOK_URL` / `NOTIFICATIONS_WEBHOOK_URL` when unset).
76
- - Transient no-response failures, such as network errors or timeouts, must fail
77
- twice in a row before opening an incident or alerting. Set
80
+ - Transient no-response failures, such as network errors or timeouts, and
81
+ transient latency degradations must fail twice in a row before opening an
82
+ incident or alerting. Set
78
83
  `UPTIME_MONITOR_TRANSIENT_FAILURE_CONFIRMATION_CHECKS=1` to alert on the first
79
84
  transient failure, or a higher value (max 10) for stricter confirmation.
80
85
  - While an incident is open it will not re-alert. After an incident resolves,
81
86
  `cooldownMinutes` suppresses a fresh "down" alert for that window to prevent
82
- flapping.
87
+ flapping. Incidents opened during that quiet window do not send recovery
88
+ emails either, so one flaky target cannot produce "recovered" noise for an
89
+ alert that was intentionally suppressed.
83
90
  - On **recovery**, it resolves the open incident and sends an informational
84
- "recovered" notification (with downtime duration).
91
+ "recovered" notification (with downtime duration) only when the incident sent
92
+ an initial down/degraded notification.
85
93
 
86
94
  A `lastStatus === "running"` claim guard (with a staleness timeout) ensures that
87
95
  concurrent sweeps never double-run the same monitor.
@@ -105,23 +113,28 @@ set `UPTIME_MONITOR_ALLOW_PRIVATE_HOSTS=1` (off by default).
105
113
 
106
114
  ## Background job
107
115
 
108
- The sweep is registered by `server/plugins/uptime-monitor-jobs.ts` and runs
116
+ The sweep is registered by `server/plugins/uptime-monitor-jobs.ts` for
117
+ long-lived runtimes. Netlify builds emit a scheduled trigger plus background
118
+ worker from `scripts/emit-netlify-dashboard-report-cron.ts`; the route
119
+ `/api/uptime-monitors/run` is callable by that generated worker or by an
120
+ external cron with `UPTIME_MONITORS_CRON_SECRET`. Both paths run
109
121
  `runDueMonitorsOnce()` (`server/jobs/uptime-monitors.ts`), which selects enabled
110
122
  monitors whose interval has elapsed, checks them, records results, opens/resolves
111
123
  incidents, and prunes old check results.
112
124
 
113
125
  Environment flags (mirrors the analytics alert job):
114
126
 
115
- | flag | effect |
116
- | ------------------------------------------------------ | ---------------------------------------------------------------------------------- |
117
- | `UPTIME_MONITOR_JOBS` | `1` enables the in-process cron; `0` disables it. |
118
- | `RUN_BACKGROUND_JOBS` | fallback flag when `UPTIME_MONITOR_JOBS` is unset. |
119
- | (production) | on by default unless a flag is `0` or a platform scheduler owns it. |
120
- | `UPTIME_MONITOR_INTERVAL_MS` | wake interval for the sweep (default 30s, min 10s). |
121
- | `UPTIME_MONITOR_SWEEP_LIMIT` | max monitors processed per sweep (default 100). |
122
- | `UPTIME_MONITOR_RESULT_RETENTION_DAYS` | how long check results are kept (default 30). |
123
- | `UPTIME_MONITOR_ALLOW_PRIVATE_HOSTS` | allow probing private/internal hosts. |
124
- | `UPTIME_MONITOR_TRANSIENT_FAILURE_CONFIRMATION_CHECKS` | consecutive timeout/network failures required before alerting (default 2, max 10). |
127
+ | flag | effect |
128
+ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
129
+ | `UPTIME_MONITOR_JOBS` | `1` enables the in-process cron; `0` disables it. |
130
+ | `RUN_BACKGROUND_JOBS` | fallback flag when `UPTIME_MONITOR_JOBS` is unset. |
131
+ | (production) | on by default unless a flag is `0` or a platform scheduler owns it. |
132
+ | `UPTIME_MONITOR_INTERVAL_MS` | wake interval for the sweep (default 30s, min 10s). |
133
+ | `UPTIME_MONITOR_SWEEP_LIMIT` | max monitors processed per sweep (default 100). |
134
+ | `UPTIME_MONITOR_RESULT_RETENTION_DAYS` | how long check results are kept (default 30). |
135
+ | `UPTIME_MONITOR_ALLOW_PRIVATE_HOSTS` | allow probing private/internal hosts. |
136
+ | `UPTIME_MONITOR_TRANSIENT_FAILURE_CONFIRMATION_CHECKS` | consecutive timeout/network/slow-response failures required before alerting (default 2, max 10). |
137
+ | `UPTIME_MONITORS_CRON_SECRET` | bearer token for external cron callers of `/api/uptime-monitors/run`. |
125
138
 
126
139
  The monitor tables (`monitors`, `monitor_check_results`, `monitor_incidents`)
127
140
  are created by the app migration list in `server/plugins/db.ts`, which runs at
@@ -157,7 +170,7 @@ All three tables carry `ownableColumns()` (`owner_email` / `org_id` /
157
170
  `last_checked_at`, `last_success_at`, `last_error`, `last_latency_ms`,
158
171
  `last_status_code`, `consecutive_failures`).
159
172
  - **`monitor_check_results`** — one row per probe (`ok`, `status`, `status_code`,
160
- `latency_ms`, `error`, `failed_assertions`), indexed on
173
+ `latency_ms`, `error`, `failed_assertions`, `diagnostics`), indexed on
161
174
  `(monitor_id, checked_at)`; pruned by the sweep so it stays bounded.
162
175
  - **`monitor_incidents`** — one row per failure streak (`started_at`,
163
176
  `resolved_at` null = ongoing, `cause`, `severity`, `notification_id`,
@@ -26,3 +26,6 @@ memory = "2gb"
26
26
 
27
27
  [functions.analytics-alert-sweep-background]
28
28
  timeout = 120
29
+
30
+ [functions.uptime-monitor-sweep-background]
31
+ timeout = 120
@@ -16,6 +16,10 @@ const ALERT_SCHEDULED_NAME = "analytics-alert-cron";
16
16
  const ALERT_WORKER_NAME = "analytics-alert-sweep-background";
17
17
  const ALERT_ROUTE_PATH = "/api/analytics-alerts/run";
18
18
  const ALERT_SCHEDULE = "*/5 * * * *";
19
+ const UPTIME_SCHEDULED_NAME = "uptime-monitor-cron";
20
+ const UPTIME_WORKER_NAME = "uptime-monitor-sweep-background";
21
+ const UPTIME_ROUTE_PATH = "/api/uptime-monitors/run";
22
+ const UPTIME_SCHEDULE = "* * * * *";
19
23
 
20
24
  function ensureDir(dir: string) {
21
25
  mkdirSync(dir, { recursive: true });
@@ -236,6 +240,122 @@ export const config = {
236
240
  writeFileSync(path.join(dest, `${ALERT_WORKER_NAME}.mjs`), source);
237
241
  }
238
242
 
243
+ function emitUptimeScheduledTrigger(token: string) {
244
+ const dest = path.join(FUNCTIONS_DIR, UPTIME_SCHEDULED_NAME);
245
+ rmSync(dest, { recursive: true, force: true });
246
+ ensureDir(dest);
247
+
248
+ const source = `const WORKER_PATH = "/.netlify/functions/${UPTIME_WORKER_NAME}";
249
+ const CRON_TOKEN = ${JSON.stringify(token)};
250
+
251
+ function siteOrigin(request) {
252
+ const configured = process.env.URL || process.env.DEPLOY_URL;
253
+ if (configured) return configured;
254
+ const url = new URL(request.url);
255
+ return url.origin;
256
+ }
257
+
258
+ async function readScheduledInvocation(request) {
259
+ if (request.method !== "POST") return null;
260
+ let body;
261
+ try {
262
+ body = await request.json();
263
+ } catch {
264
+ return null;
265
+ }
266
+ const nextRun = typeof body?.next_run === "string" ? body.next_run : "";
267
+ return nextRun && Number.isFinite(Date.parse(nextRun))
268
+ ? { nextRun }
269
+ : null;
270
+ }
271
+
272
+ export default async function handler(request) {
273
+ const scheduled = await readScheduledInvocation(request);
274
+ if (!scheduled) return new Response("Not Found", { status: 404 });
275
+
276
+ const url = new URL(WORKER_PATH, siteOrigin(request));
277
+ const response = await fetch(url.toString(), {
278
+ method: "POST",
279
+ headers: {
280
+ "content-type": "application/json",
281
+ "x-agent-native-uptime-monitor-cron": CRON_TOKEN,
282
+ },
283
+ body: JSON.stringify({ scheduled: true, next_run: scheduled.nextRun }),
284
+ });
285
+
286
+ if (!response.ok && response.status !== 202) {
287
+ console.error(
288
+ "[uptime-monitor-cron] Background sweep trigger failed:",
289
+ response.status,
290
+ await response.text().catch(() => ""),
291
+ );
292
+ }
293
+ }
294
+
295
+ export const config = {
296
+ name: "uptime monitor cron trigger",
297
+ generator: "agent-native analytics build",
298
+ schedule: ${JSON.stringify(UPTIME_SCHEDULE)},
299
+ };
300
+ `;
301
+
302
+ writeFileSync(path.join(dest, `${UPTIME_SCHEDULED_NAME}.mjs`), source);
303
+ }
304
+
305
+ function emitUptimeBackgroundWorker(token: string) {
306
+ const dest = path.join(FUNCTIONS_DIR, UPTIME_WORKER_NAME);
307
+ rmSync(dest, { recursive: true, force: true });
308
+ cpSync(SERVER_DIR, dest, { recursive: true });
309
+ rmSync(path.join(dest, "server.mjs"), { force: true });
310
+
311
+ const source = `globalThis.__AGENT_NATIVE_UPTIME_MONITOR_SCHEDULED_RUNTIME__ = true;
312
+
313
+ const CRON_TOKEN = ${JSON.stringify(token)};
314
+ const ROUTE_PATH = ${JSON.stringify(UPTIME_ROUTE_PATH)};
315
+ let cachedHandler;
316
+
317
+ function timingSafeEquals(a, b) {
318
+ if (a.length !== b.length) return false;
319
+ let diff = 0;
320
+ for (let i = 0; i < a.length; i += 1) {
321
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
322
+ }
323
+ return diff === 0;
324
+ }
325
+
326
+ export default async function handler(request, context) {
327
+ const token = request.headers.get("x-agent-native-uptime-monitor-cron") || "";
328
+ if (!timingSafeEquals(token, CRON_TOKEN)) {
329
+ return new Response("Unauthorized", { status: 401 });
330
+ }
331
+
332
+ cachedHandler ??= (await import("./main.mjs")).default;
333
+ const url = new URL(request.url);
334
+ url.pathname = ROUTE_PATH;
335
+ url.search = "";
336
+
337
+ const rewritten = new Request(url.toString(), {
338
+ method: "POST",
339
+ headers: new Headers({ "content-type": "application/json" }),
340
+ body: JSON.stringify({ scheduled: true }),
341
+ });
342
+
343
+ return await cachedHandler(rewritten, context);
344
+ }
345
+
346
+ export const config = {
347
+ name: "uptime monitor background sweep",
348
+ generator: "agent-native analytics build",
349
+ background: true,
350
+ nodeBundler: "none",
351
+ includedFiles: ["**"],
352
+ preferStatic: false,
353
+ };
354
+ `;
355
+
356
+ writeFileSync(path.join(dest, `${UPTIME_WORKER_NAME}.mjs`), source);
357
+ }
358
+
239
359
  function isDirectRun(): boolean {
240
360
  const entrypoint = process.argv[1];
241
361
  return Boolean(
@@ -254,16 +374,22 @@ function main(): void {
254
374
 
255
375
  const token = randomBytes(32).toString("hex");
256
376
  const alertToken = randomBytes(32).toString("hex");
377
+ const uptimeToken = randomBytes(32).toString("hex");
257
378
  emitScheduledTrigger(token);
258
379
  emitBackgroundWorker(token);
259
380
  emitAlertScheduledTrigger(alertToken);
260
381
  emitAlertBackgroundWorker(alertToken);
382
+ emitUptimeScheduledTrigger(uptimeToken);
383
+ emitUptimeBackgroundWorker(uptimeToken);
261
384
  console.log(
262
385
  `[dashboard-report-cron] Emitted Netlify scheduled trigger "${SCHEDULED_NAME}" (${SCHEDULE}) and background worker "${WORKER_NAME}".`,
263
386
  );
264
387
  console.log(
265
388
  `[analytics-alert-cron] Emitted Netlify scheduled trigger "${ALERT_SCHEDULED_NAME}" (${ALERT_SCHEDULE}) and background worker "${ALERT_WORKER_NAME}".`,
266
389
  );
390
+ console.log(
391
+ `[uptime-monitor-cron] Emitted Netlify scheduled trigger "${UPTIME_SCHEDULED_NAME}" (${UPTIME_SCHEDULE}) and background worker "${UPTIME_WORKER_NAME}".`,
392
+ );
267
393
  }
268
394
 
269
395
  if (isDirectRun()) {
@@ -109,6 +109,8 @@ export const monitorCheckResults = table("monitor_check_results", {
109
109
  error: text("error"),
110
110
  /** JSON array of human-readable assertion failures. */
111
111
  failedAssertions: text("failed_assertions").notNull().default("[]"),
112
+ /** Compact JSON with phase timings and safe runtime/response metadata. */
113
+ diagnostics: text("diagnostics").notNull().default("{}"),
112
114
  createdAt: text("created_at").notNull().default(now()),
113
115
  ...ownableColumns(),
114
116
  });
@@ -129,6 +131,9 @@ export const monitorIncidents = table("monitor_incidents", {
129
131
  cause: text("cause").notNull().default(""),
130
132
  lastError: text("last_error"),
131
133
  notificationId: text("notification_id"),
134
+ notificationDelivered: integer("notification_delivered", { mode: "boolean" })
135
+ .notNull()
136
+ .default(false),
132
137
  checksFailed: integer("checks_failed").notNull().default(1),
133
138
  createdAt: text("created_at").notNull().default(now()),
134
139
  ...ownableColumns(),
@@ -51,6 +51,7 @@ export async function runDueMonitorsOnce(
51
51
  ownerEmail?: string;
52
52
  orgId?: string | null;
53
53
  limit?: number;
54
+ source?: "netlify-scheduled" | "in-process";
54
55
  } = {},
55
56
  ): Promise<UptimeSweepResult> {
56
57
  if (running) {
@@ -94,7 +95,10 @@ export async function runDueMonitorsOnce(
94
95
  const claimed = await claimMonitorRun(monitor);
95
96
  if (!claimed) continue;
96
97
  processed++;
97
- const outcome = await runMonitorCheck(monitor, { allowPrivateHosts });
98
+ const outcome = await runMonitorCheck(monitor, {
99
+ allowPrivateHosts,
100
+ source: options.source,
101
+ });
98
102
  await recordMonitorResult(monitor, outcome);
99
103
  await evaluateAndNotifyMonitor(monitor, outcome, {
100
104
  email: monitor.ownerEmail,