@agent-native/core 0.92.1 → 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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +32 -0
- package/corpus/core/docs/content/locales/ar-SA/server.mdx +35 -0
- package/corpus/core/docs/content/locales/de-DE/server.mdx +35 -0
- package/corpus/core/docs/content/locales/es-ES/server.mdx +35 -0
- package/corpus/core/docs/content/locales/fr-FR/server.mdx +35 -0
- package/corpus/core/docs/content/locales/hi-IN/server.mdx +35 -0
- package/corpus/core/docs/content/locales/ja-JP/server.mdx +35 -0
- package/corpus/core/docs/content/locales/ko-KR/server.mdx +35 -0
- package/corpus/core/docs/content/locales/pt-BR/server.mdx +35 -0
- package/corpus/core/docs/content/locales/zh-CN/server.mdx +35 -0
- package/corpus/core/docs/content/locales/zh-TW/server.mdx +35 -0
- package/corpus/core/docs/content/server.mdx +35 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +14 -4
- package/corpus/core/src/agent/engine/anthropic-engine.ts +15 -3
- package/corpus/core/src/agent/engine/builder-engine.ts +30 -4
- package/corpus/core/src/agent/engine/registry.ts +63 -2
- package/corpus/core/src/agent/model-config.ts +0 -2
- package/corpus/core/src/agent/production-agent.ts +12 -1
- package/corpus/core/src/agent/run-manager.ts +57 -36
- package/corpus/core/src/agent/run-store.ts +81 -2
- package/corpus/core/src/agent/thread-data-builder.ts +33 -2
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +29 -1
- package/corpus/core/src/client/AssistantChat.tsx +314 -96
- package/corpus/core/src/client/agent-chat-adapter.ts +17 -1
- package/corpus/core/src/client/chat/repo-helpers.ts +45 -0
- package/corpus/core/src/client/chat/tool-call-display.tsx +36 -27
- package/corpus/core/src/client/chat-model-groups.ts +1 -5
- package/corpus/core/src/client/composer/TiptapComposer.tsx +0 -2
- package/corpus/core/src/client/embed-auth.ts +20 -0
- package/corpus/core/src/client/settings/SettingsSection.tsx +66 -5
- package/corpus/core/src/client/sse-event-processor.ts +49 -17
- package/corpus/core/src/client/use-db-sync.ts +31 -9
- package/corpus/core/src/collab/agent-presence.ts +6 -1
- package/corpus/core/src/collab/awareness-store.ts +185 -0
- package/corpus/core/src/collab/awareness.ts +58 -2
- package/corpus/core/src/notifications/channels.ts +1 -22
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +7 -8
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +7 -18
- package/corpus/core/src/server/core-routes-plugin.ts +21 -2
- package/corpus/core/src/server/embed-session.ts +20 -4
- package/corpus/core/src/styles/agent-native.css +51 -0
- package/corpus/core/src/tracking/providers.ts +33 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +8 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +9 -1
- package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorDetail.tsx +41 -11
- package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +44 -0
- package/corpus/templates/analytics/changelog/2026-07-08-alert-emails-now-show-the-message-without-raw-metadata-json.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-checks-no-longer-count-ssrf-setup-time-against-the-re.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-checks-reuse-one-keep-alive-connection-so-recorded-la.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-monitors-now-confirm-transient-timeout-failures-befor.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-action-menus-now-close-cleanly-before-opening-hist.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-email-reports-still-send-when-the-screenshot-captu.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-uptime-alerts-now-suppress-recovery-noise-during-flapping-an.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-uptime-check-history-now-shows-timing-diagnostics-so-slow-or.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-uptime-recovery-alerts-now-send-when-the-original-outage-not.md +6 -0
- package/corpus/templates/analytics/docs/uptime-monitoring.md +47 -22
- package/corpus/templates/analytics/netlify.toml +3 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +126 -0
- package/corpus/templates/analytics/server/db/schema-monitoring.ts +5 -0
- package/corpus/templates/analytics/server/jobs/uptime-monitors.ts +5 -1
- package/corpus/templates/analytics/server/lib/uptime-monitors.ts +538 -54
- package/corpus/templates/analytics/server/plugins/db.ts +15 -0
- package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +1 -1
- package/corpus/templates/analytics/server/routes/api/uptime-monitors/run.post.ts +57 -0
- package/corpus/templates/clips/actions/list-meetings.ts +12 -0
- package/corpus/templates/clips/changelog/2026-07-09-meeting-reminders-now-ignore-obvious-solo-personal-calendar-.md +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +1 -0
- package/corpus/templates/clips/server/lib/calendar-event-classification.ts +79 -0
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +13 -4
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +14 -3
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +26 -4
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +47 -2
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/model-config.d.ts +2 -2
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +0 -2
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +12 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +52 -33
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +24 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +64 -2
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +28 -2
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.d.ts.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.js +24 -1
- package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +243 -36
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +15 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/repo-helpers.d.ts.map +1 -1
- package/dist/client/chat/repo-helpers.js +43 -0
- package/dist/client/chat/repo-helpers.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +4 -4
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +1 -3
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +0 -2
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/embed-auth.d.ts.map +1 -1
- package/dist/client/embed-auth.js +14 -0
- package/dist/client/embed-auth.js.map +1 -1
- package/dist/client/settings/SettingsSection.d.ts.map +1 -1
- package/dist/client/settings/SettingsSection.js +32 -3
- package/dist/client/settings/SettingsSection.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +44 -17
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +29 -9
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/agent-presence.d.ts.map +1 -1
- package/dist/collab/agent-presence.js +5 -2
- package/dist/collab/agent-presence.js.map +1 -1
- package/dist/collab/awareness-store.d.ts +1 -1
- package/dist/collab/awareness-store.d.ts.map +1 -1
- package/dist/collab/awareness-store.js +0 -0
- package/dist/collab/awareness-store.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -0
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/awareness.js +46 -2
- package/dist/collab/awareness.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/notifications/channels.js +1 -17
- package/dist/notifications/channels.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js +6 -6
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +5 -13
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +5 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +10 -3
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/embed-session.d.ts.map +1 -1
- package/dist/server/embed-session.js +17 -4
- package/dist/server/embed-session.js.map +1 -1
- package/dist/styles/agent-native.css +51 -0
- package/dist/tracking/providers.d.ts.map +1 -1
- package/dist/tracking/providers.js +23 -5
- package/dist/tracking/providers.js.map +1 -1
- package/docs/content/locales/ar-SA/server.mdx +35 -0
- package/docs/content/locales/de-DE/server.mdx +35 -0
- package/docs/content/locales/es-ES/server.mdx +35 -0
- package/docs/content/locales/fr-FR/server.mdx +35 -0
- package/docs/content/locales/hi-IN/server.mdx +35 -0
- package/docs/content/locales/ja-JP/server.mdx +35 -0
- package/docs/content/locales/ko-KR/server.mdx +35 -0
- package/docs/content/locales/pt-BR/server.mdx +35 -0
- package/docs/content/locales/zh-CN/server.mdx +35 -0
- package/docs/content/locales/zh-TW/server.mdx +35 -0
- package/docs/content/server.mdx +35 -0
- package/package.json +1 -1
|
@@ -464,6 +464,7 @@ export default function SqlDashboardPage() {
|
|
|
464
464
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
|
465
465
|
const [emailReportOpen, setEmailReportOpen] = useState(false);
|
|
466
466
|
const [historyOpen, setHistoryOpen] = useState(false);
|
|
467
|
+
const [dashboardActionsOpen, setDashboardActionsOpen] = useState(false);
|
|
467
468
|
const [activeDropSlot, setActiveDropSlot] =
|
|
468
469
|
useState<DashboardDropSlot | null>(null);
|
|
469
470
|
const [activeDragPanelId, setActiveDragPanelId] = useState<string | null>(
|
|
@@ -1395,7 +1396,10 @@ export default function SqlDashboardPage() {
|
|
|
1395
1396
|
</Button>
|
|
1396
1397
|
</AddPanelPopover>
|
|
1397
1398
|
) : null}
|
|
1398
|
-
<DropdownMenu
|
|
1399
|
+
<DropdownMenu
|
|
1400
|
+
open={dashboardActionsOpen}
|
|
1401
|
+
onOpenChange={setDashboardActionsOpen}
|
|
1402
|
+
>
|
|
1399
1403
|
<Tooltip>
|
|
1400
1404
|
<TooltipTrigger asChild>
|
|
1401
1405
|
<DropdownMenuTrigger asChild>
|
|
@@ -1468,6 +1472,7 @@ export default function SqlDashboardPage() {
|
|
|
1468
1472
|
<DropdownMenuItem
|
|
1469
1473
|
onSelect={(event) => {
|
|
1470
1474
|
event.preventDefault();
|
|
1475
|
+
setDashboardActionsOpen(false);
|
|
1471
1476
|
setEmailReportOpen(true);
|
|
1472
1477
|
}}
|
|
1473
1478
|
>
|
|
@@ -1477,6 +1482,7 @@ export default function SqlDashboardPage() {
|
|
|
1477
1482
|
<DropdownMenuItem
|
|
1478
1483
|
onSelect={(event) => {
|
|
1479
1484
|
event.preventDefault();
|
|
1485
|
+
setDashboardActionsOpen(false);
|
|
1480
1486
|
setHistoryOpen(true);
|
|
1481
1487
|
}}
|
|
1482
1488
|
>
|
|
@@ -1489,6 +1495,7 @@ export default function SqlDashboardPage() {
|
|
|
1489
1495
|
<DropdownMenuItem
|
|
1490
1496
|
onSelect={(event) => {
|
|
1491
1497
|
event.preventDefault();
|
|
1498
|
+
setDashboardActionsOpen(false);
|
|
1492
1499
|
void handleArchive();
|
|
1493
1500
|
}}
|
|
1494
1501
|
>
|
|
@@ -1503,6 +1510,7 @@ export default function SqlDashboardPage() {
|
|
|
1503
1510
|
<DropdownMenuItem
|
|
1504
1511
|
onSelect={(event) => {
|
|
1505
1512
|
event.preventDefault();
|
|
1513
|
+
setDashboardActionsOpen(false);
|
|
1506
1514
|
setConfirmDeleteOpen(true);
|
|
1507
1515
|
}}
|
|
1508
1516
|
className="text-destructive focus:text-destructive"
|
|
@@ -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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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 {
|
|
@@ -19,18 +19,29 @@ are shareable and the agent can deep-link a monitor.
|
|
|
19
19
|
|
|
20
20
|
Each check runs on the server (`server/lib/uptime-monitors.ts`):
|
|
21
21
|
|
|
22
|
-
1.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
1. Run SSRF setup (dispatcher + DNS private-address check) **outside** the
|
|
23
|
+
request timeout budget. The connect-time dispatcher is built once and reused
|
|
24
|
+
across checks (HTTP keep-alive), so latency reflects the real request round
|
|
25
|
+
trip instead of a fresh DNS + TCP + TLS handshake on every probe.
|
|
26
|
+
2. Fetch the monitor's `url` with the configured `method`, headers, and body,
|
|
27
|
+
through that SSRF-safe path, with an `AbortController` timeout that covers
|
|
28
|
+
only the HTTP request (headers / redirect chain).
|
|
29
|
+
3. Measure latency from the HTTP response headers. If a body assertion is
|
|
30
|
+
configured, read up to **512 KB** of the response body (skipped for `HEAD`)
|
|
31
|
+
with a bounded body-read timer after clearing the request abort timer, so a
|
|
32
|
+
slow body cannot be mislabeled as a request timeout or hang indefinitely.
|
|
33
|
+
4. Evaluate the **status matcher** and every **assertion**.
|
|
34
|
+
5. Classify the result:
|
|
28
35
|
- `up` — status matches and all assertions pass.
|
|
29
36
|
- `degraded` — status matches but only a `max_latency_ms` assertion fails
|
|
30
37
|
(the endpoint responded, just too slowly). Alerts at `warning` severity.
|
|
31
38
|
- `down` — the status does not match, a content/header assertion fails, or
|
|
32
39
|
the request errored/timed out. Alerts at the monitor's configured severity.
|
|
33
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.
|
|
34
45
|
|
|
35
46
|
### Status matcher
|
|
36
47
|
|
|
@@ -58,19 +69,27 @@ Each check runs on the server (`server/lib/uptime-monitors.ts`):
|
|
|
58
69
|
|
|
59
70
|
`evaluateAndNotifyMonitor` manages incidents like the analytics alert engine:
|
|
60
71
|
|
|
61
|
-
- On a transition **into failure**, it opens a `monitor_incidents` row
|
|
62
|
-
continuous failure streak) and calls `notifyWithDelivery` with a
|
|
63
|
-
and body plus safe inbox metadata
|
|
72
|
+
- On a transition **into confirmed failure**, it opens a `monitor_incidents` row
|
|
73
|
+
(one per continuous failure streak) and calls `notifyWithDelivery` with a
|
|
74
|
+
clear title and body plus safe inbox metadata
|
|
64
75
|
`{ kind: "uptime_monitor", monitorId, url, statusCode, latencyMs, failedAssertions, emailRecipients }`.
|
|
65
76
|
The `inbox` channel is the in-app bell; `email` uses the monitor's
|
|
66
77
|
`emailRecipients`; `slack` / `webhook` use the monitor's optional
|
|
67
78
|
delivery-only `slackWebhookUrl` / `webhookUrl` (falling back to workspace
|
|
68
79
|
`NOTIFICATIONS_SLACK_WEBHOOK_URL` / `NOTIFICATIONS_WEBHOOK_URL` when unset).
|
|
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
|
|
83
|
+
`UPTIME_MONITOR_TRANSIENT_FAILURE_CONFIRMATION_CHECKS=1` to alert on the first
|
|
84
|
+
transient failure, or a higher value (max 10) for stricter confirmation.
|
|
69
85
|
- While an incident is open it will not re-alert. After an incident resolves,
|
|
70
86
|
`cooldownMinutes` suppresses a fresh "down" alert for that window to prevent
|
|
71
|
-
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.
|
|
72
90
|
- On **recovery**, it resolves the open incident and sends an informational
|
|
73
|
-
"recovered" notification (with downtime duration)
|
|
91
|
+
"recovered" notification (with downtime duration) only when the incident sent
|
|
92
|
+
an initial down/degraded notification.
|
|
74
93
|
|
|
75
94
|
A `lastStatus === "running"` claim guard (with a staleness timeout) ensures that
|
|
76
95
|
concurrent sweeps never double-run the same monitor.
|
|
@@ -94,22 +113,28 @@ set `UPTIME_MONITOR_ALLOW_PRIVATE_HOSTS=1` (off by default).
|
|
|
94
113
|
|
|
95
114
|
## Background job
|
|
96
115
|
|
|
97
|
-
The sweep is registered by `server/plugins/uptime-monitor-jobs.ts`
|
|
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
|
|
98
121
|
`runDueMonitorsOnce()` (`server/jobs/uptime-monitors.ts`), which selects enabled
|
|
99
122
|
monitors whose interval has elapsed, checks them, records results, opens/resolves
|
|
100
123
|
incidents, and prunes old check results.
|
|
101
124
|
|
|
102
125
|
Environment flags (mirrors the analytics alert job):
|
|
103
126
|
|
|
104
|
-
| flag
|
|
105
|
-
|
|
|
106
|
-
| `UPTIME_MONITOR_JOBS`
|
|
107
|
-
| `RUN_BACKGROUND_JOBS`
|
|
108
|
-
| (production)
|
|
109
|
-
| `UPTIME_MONITOR_INTERVAL_MS`
|
|
110
|
-
| `UPTIME_MONITOR_SWEEP_LIMIT`
|
|
111
|
-
| `UPTIME_MONITOR_RESULT_RETENTION_DAYS`
|
|
112
|
-
| `UPTIME_MONITOR_ALLOW_PRIVATE_HOSTS`
|
|
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`. |
|
|
113
138
|
|
|
114
139
|
The monitor tables (`monitors`, `monitor_check_results`, `monitor_incidents`)
|
|
115
140
|
are created by the app migration list in `server/plugins/db.ts`, which runs at
|
|
@@ -145,7 +170,7 @@ All three tables carry `ownableColumns()` (`owner_email` / `org_id` /
|
|
|
145
170
|
`last_checked_at`, `last_success_at`, `last_error`, `last_latency_ms`,
|
|
146
171
|
`last_status_code`, `consecutive_failures`).
|
|
147
172
|
- **`monitor_check_results`** — one row per probe (`ok`, `status`, `status_code`,
|
|
148
|
-
`latency_ms`, `error`, `failed_assertions`), indexed on
|
|
173
|
+
`latency_ms`, `error`, `failed_assertions`, `diagnostics`), indexed on
|
|
149
174
|
`(monitor_id, checked_at)`; pruned by the sweep so it stays bounded.
|
|
150
175
|
- **`monitor_incidents`** — one row per failure streak (`started_at`,
|
|
151
176
|
`resolved_at` null = ongoing, `cause`, `severity`, `notification_id`,
|
|
@@ -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, {
|
|
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,
|