@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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +26 -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/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/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-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 +29 -16
- 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 +305 -31
- 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/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/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.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/agent-engine-api-key-route.d.ts +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
|
@@ -44,6 +44,10 @@ import {
|
|
|
44
44
|
|
|
45
45
|
import { getDb, schema } from "../db/index.js";
|
|
46
46
|
|
|
47
|
+
declare global {
|
|
48
|
+
var __AGENT_NATIVE_UPTIME_MONITOR_SCHEDULED_RUNTIME__: boolean | undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
47
51
|
// ---------------------------------------------------------------------------
|
|
48
52
|
// Types
|
|
49
53
|
// ---------------------------------------------------------------------------
|
|
@@ -158,6 +162,52 @@ export interface MonitorCheckResult {
|
|
|
158
162
|
latencyMs: number | null;
|
|
159
163
|
error: string | null;
|
|
160
164
|
failedAssertions: string[];
|
|
165
|
+
diagnostics: MonitorCheckDiagnostics;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export type MonitorCheckSource =
|
|
169
|
+
| "netlify-scheduled"
|
|
170
|
+
| "netlify-runtime"
|
|
171
|
+
| "in-process"
|
|
172
|
+
| "manual"
|
|
173
|
+
| "unknown";
|
|
174
|
+
|
|
175
|
+
export interface MonitorCheckDiagnostics {
|
|
176
|
+
source: MonitorCheckSource;
|
|
177
|
+
runtime: {
|
|
178
|
+
nodeEnv?: string;
|
|
179
|
+
netlify?: boolean;
|
|
180
|
+
deployId?: string;
|
|
181
|
+
deployContext?: string;
|
|
182
|
+
commitRef?: string;
|
|
183
|
+
functionName?: string;
|
|
184
|
+
region?: string;
|
|
185
|
+
};
|
|
186
|
+
request: {
|
|
187
|
+
method: MonitorMethod;
|
|
188
|
+
timeoutMs: number;
|
|
189
|
+
followRedirects: boolean;
|
|
190
|
+
assertionTypes: AssertionType[];
|
|
191
|
+
bodyReadRequired: boolean;
|
|
192
|
+
allowPrivateHosts: boolean;
|
|
193
|
+
};
|
|
194
|
+
timings: {
|
|
195
|
+
totalMs?: number;
|
|
196
|
+
ssrfSetupMs?: number;
|
|
197
|
+
requestMs?: number;
|
|
198
|
+
bodyReadMs?: number;
|
|
199
|
+
};
|
|
200
|
+
response?: {
|
|
201
|
+
finalUrl?: string;
|
|
202
|
+
finalHost?: string;
|
|
203
|
+
statusCode?: number;
|
|
204
|
+
headers?: Record<string, string>;
|
|
205
|
+
};
|
|
206
|
+
error?: {
|
|
207
|
+
kind: "config" | "timeout" | "network" | "body-timeout";
|
|
208
|
+
name?: string;
|
|
209
|
+
message: string;
|
|
210
|
+
};
|
|
161
211
|
}
|
|
162
212
|
|
|
163
213
|
export interface MonitorIncident {
|
|
@@ -170,6 +220,7 @@ export interface MonitorIncident {
|
|
|
170
220
|
cause: string;
|
|
171
221
|
lastError: string | null;
|
|
172
222
|
notificationId: string | null;
|
|
223
|
+
notificationDelivered: boolean;
|
|
173
224
|
checksFailed: number;
|
|
174
225
|
createdAt: string;
|
|
175
226
|
}
|
|
@@ -183,6 +234,7 @@ export interface CheckOutcome {
|
|
|
183
234
|
latencyMs: number | null;
|
|
184
235
|
error: string | null;
|
|
185
236
|
failedAssertions: string[];
|
|
237
|
+
diagnostics: MonitorCheckDiagnostics;
|
|
186
238
|
}
|
|
187
239
|
|
|
188
240
|
export interface AssertionFailure {
|
|
@@ -212,6 +264,7 @@ const MAX_REQUEST_BODY_BYTES = 16 * 1024;
|
|
|
212
264
|
const MAX_ASSERTIONS_PER_MONITOR = 20;
|
|
213
265
|
const MAX_ASSERTION_VALUE_BYTES = 2048;
|
|
214
266
|
const MAX_ASSERTION_HEADER_LENGTH = 128;
|
|
267
|
+
const MAX_MONITOR_DIAGNOSTICS_BYTES = 4096;
|
|
215
268
|
|
|
216
269
|
const MIN_INTERVAL_SECONDS = 30;
|
|
217
270
|
const MAX_INTERVAL_SECONDS = 24 * 60 * 60;
|
|
@@ -235,6 +288,152 @@ function nowIso(): string {
|
|
|
235
288
|
return new Date().toISOString();
|
|
236
289
|
}
|
|
237
290
|
|
|
291
|
+
function detectMonitorCheckSource(): MonitorCheckSource {
|
|
292
|
+
if (globalThis.__AGENT_NATIVE_UPTIME_MONITOR_SCHEDULED_RUNTIME__ === true) {
|
|
293
|
+
return "netlify-scheduled";
|
|
294
|
+
}
|
|
295
|
+
if (process.env.NETLIFY === "true") return "netlify-runtime";
|
|
296
|
+
if (process.env.NODE_ENV === "production") return "in-process";
|
|
297
|
+
return "unknown";
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function monitorCheckRuntimeDiagnostics(): MonitorCheckDiagnostics["runtime"] {
|
|
301
|
+
return {
|
|
302
|
+
nodeEnv: process.env.NODE_ENV,
|
|
303
|
+
netlify: process.env.NETLIFY === "true" || undefined,
|
|
304
|
+
deployId: process.env.DEPLOY_ID,
|
|
305
|
+
deployContext: process.env.CONTEXT,
|
|
306
|
+
commitRef: process.env.COMMIT_REF,
|
|
307
|
+
functionName:
|
|
308
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.NETLIFY_FUNCTION_NAME,
|
|
309
|
+
region: process.env.AWS_REGION,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function safeResponseHeaders(
|
|
314
|
+
headers: Record<string, string>,
|
|
315
|
+
): Record<string, string> | undefined {
|
|
316
|
+
const picked: Record<string, string> = {};
|
|
317
|
+
for (const key of [
|
|
318
|
+
"server",
|
|
319
|
+
"x-nf-request-id",
|
|
320
|
+
"cache-status",
|
|
321
|
+
"cdn-cache-control",
|
|
322
|
+
"content-type",
|
|
323
|
+
]) {
|
|
324
|
+
const value = headers[key];
|
|
325
|
+
if (value) picked[key] = value.slice(0, 300);
|
|
326
|
+
}
|
|
327
|
+
return Object.keys(picked).length ? picked : undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function finalUrlDiagnostics(url: string | undefined): {
|
|
331
|
+
finalUrl?: string;
|
|
332
|
+
finalHost?: string;
|
|
333
|
+
} {
|
|
334
|
+
if (!url) return {};
|
|
335
|
+
try {
|
|
336
|
+
const parsed = new URL(url);
|
|
337
|
+
return {
|
|
338
|
+
finalHost: parsed.host,
|
|
339
|
+
};
|
|
340
|
+
} catch {
|
|
341
|
+
return {};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function defaultMonitorCheckDiagnostics(): MonitorCheckDiagnostics {
|
|
346
|
+
return {
|
|
347
|
+
source: "unknown",
|
|
348
|
+
runtime: {},
|
|
349
|
+
request: {
|
|
350
|
+
method: "GET",
|
|
351
|
+
timeoutMs: DEFAULT_MONITOR_TIMEOUT_MS,
|
|
352
|
+
followRedirects: true,
|
|
353
|
+
assertionTypes: [],
|
|
354
|
+
bodyReadRequired: false,
|
|
355
|
+
allowPrivateHosts: false,
|
|
356
|
+
},
|
|
357
|
+
timings: {},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function normalizeMonitorCheckDiagnostics(
|
|
362
|
+
raw: unknown,
|
|
363
|
+
): MonitorCheckDiagnostics {
|
|
364
|
+
const fallback = defaultMonitorCheckDiagnostics();
|
|
365
|
+
if (!raw || typeof raw !== "object") return fallback;
|
|
366
|
+
const parsed = raw as Partial<MonitorCheckDiagnostics>;
|
|
367
|
+
return {
|
|
368
|
+
...fallback,
|
|
369
|
+
...parsed,
|
|
370
|
+
runtime:
|
|
371
|
+
parsed.runtime && typeof parsed.runtime === "object"
|
|
372
|
+
? parsed.runtime
|
|
373
|
+
: fallback.runtime,
|
|
374
|
+
request:
|
|
375
|
+
parsed.request && typeof parsed.request === "object"
|
|
376
|
+
? { ...fallback.request, ...parsed.request }
|
|
377
|
+
: fallback.request,
|
|
378
|
+
timings:
|
|
379
|
+
parsed.timings && typeof parsed.timings === "object"
|
|
380
|
+
? parsed.timings
|
|
381
|
+
: fallback.timings,
|
|
382
|
+
response:
|
|
383
|
+
parsed.response && typeof parsed.response === "object"
|
|
384
|
+
? {
|
|
385
|
+
finalHost: parsed.response.finalHost,
|
|
386
|
+
statusCode: parsed.response.statusCode,
|
|
387
|
+
headers: parsed.response.headers,
|
|
388
|
+
}
|
|
389
|
+
: undefined,
|
|
390
|
+
error:
|
|
391
|
+
parsed.error && typeof parsed.error === "object"
|
|
392
|
+
? parsed.error
|
|
393
|
+
: undefined,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function compactDiagnostics(
|
|
398
|
+
diagnostics: MonitorCheckDiagnostics,
|
|
399
|
+
): MonitorCheckDiagnostics {
|
|
400
|
+
return {
|
|
401
|
+
...diagnostics,
|
|
402
|
+
runtime: Object.fromEntries(
|
|
403
|
+
Object.entries(diagnostics.runtime).filter(([, value]) => value != null),
|
|
404
|
+
) as MonitorCheckDiagnostics["runtime"],
|
|
405
|
+
response: diagnostics.response
|
|
406
|
+
? {
|
|
407
|
+
...diagnostics.response,
|
|
408
|
+
headers: diagnostics.response.headers,
|
|
409
|
+
}
|
|
410
|
+
: undefined,
|
|
411
|
+
error: diagnostics.error
|
|
412
|
+
? {
|
|
413
|
+
...diagnostics.error,
|
|
414
|
+
message: diagnostics.error.message.slice(0, 500),
|
|
415
|
+
}
|
|
416
|
+
: undefined,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function serializeMonitorDiagnostics(
|
|
421
|
+
diagnostics: MonitorCheckDiagnostics,
|
|
422
|
+
): string {
|
|
423
|
+
const compact = compactDiagnostics(diagnostics);
|
|
424
|
+
const serialized = JSON.stringify(compact);
|
|
425
|
+
if (byteLength(serialized) <= MAX_MONITOR_DIAGNOSTICS_BYTES) {
|
|
426
|
+
return serialized;
|
|
427
|
+
}
|
|
428
|
+
return JSON.stringify({
|
|
429
|
+
...compact,
|
|
430
|
+
response: compact.response
|
|
431
|
+
? { ...compact.response, headers: undefined }
|
|
432
|
+
: undefined,
|
|
433
|
+
truncated: true,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
238
437
|
function safeJsonParse<T>(raw: unknown, fallback: T): T {
|
|
239
438
|
if (typeof raw !== "string" || !raw.trim()) return fallback;
|
|
240
439
|
try {
|
|
@@ -893,17 +1092,48 @@ export async function runMonitorCheck(
|
|
|
893
1092
|
| "assertions"
|
|
894
1093
|
| "followRedirects"
|
|
895
1094
|
>,
|
|
896
|
-
opts: { allowPrivateHosts?: boolean } = {},
|
|
1095
|
+
opts: { allowPrivateHosts?: boolean; source?: MonitorCheckSource } = {},
|
|
897
1096
|
): Promise<CheckOutcome> {
|
|
898
1097
|
const checkedAt = nowIso();
|
|
899
1098
|
const matcher = monitor.expectedStatus;
|
|
900
1099
|
const assertions = monitor.assertions;
|
|
901
1100
|
const allowPrivateHosts =
|
|
902
1101
|
opts.allowPrivateHosts ?? monitorAllowPrivateHosts();
|
|
1102
|
+
const timeoutMs = clampInt(
|
|
1103
|
+
monitor.timeoutMs,
|
|
1104
|
+
MIN_TIMEOUT_MS,
|
|
1105
|
+
MAX_TIMEOUT_MS,
|
|
1106
|
+
DEFAULT_MONITOR_TIMEOUT_MS,
|
|
1107
|
+
);
|
|
1108
|
+
const method = normalizeMethod(monitor.method);
|
|
1109
|
+
const diagnostics: MonitorCheckDiagnostics = {
|
|
1110
|
+
source: opts.source ?? detectMonitorCheckSource(),
|
|
1111
|
+
runtime: monitorCheckRuntimeDiagnostics(),
|
|
1112
|
+
request: {
|
|
1113
|
+
method,
|
|
1114
|
+
timeoutMs,
|
|
1115
|
+
followRedirects: monitor.followRedirects,
|
|
1116
|
+
assertionTypes: assertions.map((assertion) => assertion.type),
|
|
1117
|
+
bodyReadRequired: method !== "HEAD" && needsResponseBody(assertions),
|
|
1118
|
+
allowPrivateHosts,
|
|
1119
|
+
},
|
|
1120
|
+
timings: {},
|
|
1121
|
+
};
|
|
1122
|
+
const totalStart = Date.now();
|
|
1123
|
+
const finish = <T extends Omit<CheckOutcome, "diagnostics">>(
|
|
1124
|
+
outcome: T,
|
|
1125
|
+
): T & { diagnostics: MonitorCheckDiagnostics } => {
|
|
1126
|
+
diagnostics.timings.totalMs = Date.now() - totalStart;
|
|
1127
|
+
return { ...outcome, diagnostics };
|
|
1128
|
+
};
|
|
903
1129
|
|
|
904
1130
|
// Fast, deterministic pre-flight guard (scheme + literal private hosts).
|
|
905
1131
|
if (!allowPrivateHosts && isBlockedExtensionUrl(monitor.url)) {
|
|
906
|
-
|
|
1132
|
+
diagnostics.error = {
|
|
1133
|
+
kind: "config",
|
|
1134
|
+
message: "SSRF blocked: private/internal or non-http(s) address",
|
|
1135
|
+
};
|
|
1136
|
+
return finish({
|
|
907
1137
|
checkedAt,
|
|
908
1138
|
statusCode: null,
|
|
909
1139
|
latencyMs: null,
|
|
@@ -918,26 +1148,27 @@ export async function runMonitorCheck(
|
|
|
918
1148
|
errorKind: "config",
|
|
919
1149
|
}),
|
|
920
1150
|
error: "SSRF blocked: private/internal or non-http(s) address",
|
|
921
|
-
};
|
|
1151
|
+
});
|
|
922
1152
|
}
|
|
923
1153
|
|
|
924
|
-
const timeoutMs = clampInt(
|
|
925
|
-
monitor.timeoutMs,
|
|
926
|
-
MIN_TIMEOUT_MS,
|
|
927
|
-
MAX_TIMEOUT_MS,
|
|
928
|
-
DEFAULT_MONITOR_TIMEOUT_MS,
|
|
929
|
-
);
|
|
930
|
-
|
|
931
1154
|
let dispatcher: unknown | undefined;
|
|
932
1155
|
try {
|
|
1156
|
+
const ssrfStart = Date.now();
|
|
933
1157
|
({ dispatcher } = await prepareMonitorFetch(monitor.url, {
|
|
934
1158
|
allowPrivateHosts,
|
|
935
1159
|
}));
|
|
1160
|
+
diagnostics.timings.ssrfSetupMs = Date.now() - ssrfStart;
|
|
936
1161
|
} catch (err) {
|
|
937
1162
|
const message =
|
|
938
1163
|
err instanceof Error ? err.message : String(err ?? "check failed");
|
|
939
1164
|
const isConfig = message.startsWith("SSRF blocked");
|
|
940
1165
|
const errorText = message.slice(0, 500);
|
|
1166
|
+
diagnostics.timings.ssrfSetupMs = Date.now() - totalStart;
|
|
1167
|
+
diagnostics.error = {
|
|
1168
|
+
kind: isConfig ? "config" : "network",
|
|
1169
|
+
name: err instanceof Error ? err.name : undefined,
|
|
1170
|
+
message: errorText,
|
|
1171
|
+
};
|
|
941
1172
|
const outcome = evaluateCheck({
|
|
942
1173
|
statusCode: null,
|
|
943
1174
|
latencyMs: null,
|
|
@@ -948,7 +1179,7 @@ export async function runMonitorCheck(
|
|
|
948
1179
|
fetchError: errorText,
|
|
949
1180
|
errorKind: isConfig ? "config" : "network",
|
|
950
1181
|
});
|
|
951
|
-
return {
|
|
1182
|
+
return finish({
|
|
952
1183
|
checkedAt,
|
|
953
1184
|
statusCode: null,
|
|
954
1185
|
latencyMs: null,
|
|
@@ -956,7 +1187,7 @@ export async function runMonitorCheck(
|
|
|
956
1187
|
ok: outcome.ok,
|
|
957
1188
|
failedAssertions: outcome.failedAssertions,
|
|
958
1189
|
error: errorText,
|
|
959
|
-
};
|
|
1190
|
+
});
|
|
960
1191
|
}
|
|
961
1192
|
|
|
962
1193
|
const controller = new AbortController();
|
|
@@ -965,10 +1196,9 @@ export async function runMonitorCheck(
|
|
|
965
1196
|
timedOut = true;
|
|
966
1197
|
controller.abort();
|
|
967
1198
|
}, timeoutMs);
|
|
968
|
-
const
|
|
1199
|
+
const requestStart = Date.now();
|
|
969
1200
|
|
|
970
1201
|
try {
|
|
971
|
-
const method = normalizeMethod(monitor.method);
|
|
972
1202
|
const hasBody =
|
|
973
1203
|
method !== "GET" &&
|
|
974
1204
|
method !== "HEAD" &&
|
|
@@ -988,25 +1218,37 @@ export async function runMonitorCheck(
|
|
|
988
1218
|
dispatcher,
|
|
989
1219
|
initialDnsChecked: Boolean(dispatcher),
|
|
990
1220
|
});
|
|
1221
|
+
diagnostics.timings.requestMs = Date.now() - requestStart;
|
|
991
1222
|
|
|
992
1223
|
// Stop the abort timer as soon as headers arrive so a slow/optional body
|
|
993
1224
|
// read cannot be mislabeled as a request timeout with a null status code.
|
|
994
1225
|
clearTimeout(timer);
|
|
995
1226
|
|
|
996
|
-
const latencyMs =
|
|
1227
|
+
const latencyMs = diagnostics.timings.requestMs;
|
|
997
1228
|
const statusCode = response.status;
|
|
998
1229
|
const headers = headersToObject(response.headers);
|
|
1230
|
+
diagnostics.response = {
|
|
1231
|
+
...finalUrlDiagnostics(response.url),
|
|
1232
|
+
statusCode,
|
|
1233
|
+
headers: safeResponseHeaders(headers),
|
|
1234
|
+
};
|
|
999
1235
|
let bodyText = "";
|
|
1000
1236
|
let bodyReadError: string | null = null;
|
|
1001
1237
|
if (method !== "HEAD" && needsResponseBody(assertions)) {
|
|
1238
|
+
const bodyReadStart = Date.now();
|
|
1002
1239
|
const body = await readCappedText(
|
|
1003
1240
|
response,
|
|
1004
1241
|
MAX_RESPONSE_BODY_BYTES,
|
|
1005
1242
|
timeoutMs,
|
|
1006
1243
|
);
|
|
1244
|
+
diagnostics.timings.bodyReadMs = Date.now() - bodyReadStart;
|
|
1007
1245
|
bodyText = body.text;
|
|
1008
1246
|
if (body.timedOut) {
|
|
1009
1247
|
bodyReadError = `Response body read timed out after ${timeoutMs}ms`;
|
|
1248
|
+
diagnostics.error = {
|
|
1249
|
+
kind: "body-timeout",
|
|
1250
|
+
message: bodyReadError,
|
|
1251
|
+
};
|
|
1010
1252
|
}
|
|
1011
1253
|
} else {
|
|
1012
1254
|
await cancelResponseBody(response);
|
|
@@ -1025,7 +1267,7 @@ export async function runMonitorCheck(
|
|
|
1025
1267
|
: outcome.failedAssertions;
|
|
1026
1268
|
const status = bodyReadError ? "down" : outcome.status;
|
|
1027
1269
|
|
|
1028
|
-
return {
|
|
1270
|
+
return finish({
|
|
1029
1271
|
checkedAt,
|
|
1030
1272
|
statusCode,
|
|
1031
1273
|
latencyMs,
|
|
@@ -1033,7 +1275,7 @@ export async function runMonitorCheck(
|
|
|
1033
1275
|
ok: status === "up" && outcome.ok,
|
|
1034
1276
|
failedAssertions,
|
|
1035
1277
|
error: failedAssertions.length ? failedAssertions.join("; ") : null,
|
|
1036
|
-
};
|
|
1278
|
+
});
|
|
1037
1279
|
} catch (err) {
|
|
1038
1280
|
const message =
|
|
1039
1281
|
err instanceof Error ? err.message : String(err ?? "check failed");
|
|
@@ -1044,6 +1286,12 @@ export async function runMonitorCheck(
|
|
|
1044
1286
|
const errorText = isTimeout
|
|
1045
1287
|
? `Timed out after ${timeoutMs}ms`
|
|
1046
1288
|
: message.slice(0, 500);
|
|
1289
|
+
diagnostics.timings.requestMs = Date.now() - requestStart;
|
|
1290
|
+
diagnostics.error = {
|
|
1291
|
+
kind: isConfig ? "config" : isTimeout ? "timeout" : "network",
|
|
1292
|
+
name: err instanceof Error ? err.name : undefined,
|
|
1293
|
+
message: errorText,
|
|
1294
|
+
};
|
|
1047
1295
|
const outcome = evaluateCheck({
|
|
1048
1296
|
statusCode: null,
|
|
1049
1297
|
latencyMs: isTimeout ? timeoutMs : null,
|
|
@@ -1054,7 +1302,7 @@ export async function runMonitorCheck(
|
|
|
1054
1302
|
fetchError: errorText,
|
|
1055
1303
|
errorKind: isConfig ? "config" : "network",
|
|
1056
1304
|
});
|
|
1057
|
-
return {
|
|
1305
|
+
return finish({
|
|
1058
1306
|
checkedAt,
|
|
1059
1307
|
statusCode: null,
|
|
1060
1308
|
latencyMs: isTimeout ? timeoutMs : null,
|
|
@@ -1062,7 +1310,7 @@ export async function runMonitorCheck(
|
|
|
1062
1310
|
ok: outcome.ok,
|
|
1063
1311
|
failedAssertions: outcome.failedAssertions,
|
|
1064
1312
|
error: errorText,
|
|
1065
|
-
};
|
|
1313
|
+
});
|
|
1066
1314
|
} finally {
|
|
1067
1315
|
clearTimeout(timer);
|
|
1068
1316
|
}
|
|
@@ -1122,6 +1370,9 @@ function rowToResult(row: any): MonitorCheckResult {
|
|
|
1122
1370
|
latencyMs: row.latencyMs ?? null,
|
|
1123
1371
|
error: row.error ?? null,
|
|
1124
1372
|
failedAssertions: safeJsonParse<string[]>(row.failedAssertions, []),
|
|
1373
|
+
diagnostics: normalizeMonitorCheckDiagnostics(
|
|
1374
|
+
safeJsonParse<unknown>(row.diagnostics, {}),
|
|
1375
|
+
),
|
|
1125
1376
|
};
|
|
1126
1377
|
}
|
|
1127
1378
|
|
|
@@ -1136,6 +1387,10 @@ function rowToIncident(row: any): MonitorIncident {
|
|
|
1136
1387
|
cause: row.cause ?? "",
|
|
1137
1388
|
lastError: row.lastError ?? null,
|
|
1138
1389
|
notificationId: row.notificationId ?? null,
|
|
1390
|
+
notificationDelivered:
|
|
1391
|
+
row.notificationDelivered === true ||
|
|
1392
|
+
row.notificationDelivered === 1 ||
|
|
1393
|
+
Boolean(row.notificationId),
|
|
1139
1394
|
checksFailed: Number(row.checksFailed ?? 1),
|
|
1140
1395
|
createdAt: row.createdAt,
|
|
1141
1396
|
};
|
|
@@ -1548,6 +1803,7 @@ export async function recordMonitorResult(
|
|
|
1548
1803
|
latencyMs: outcome.latencyMs,
|
|
1549
1804
|
error: outcome.error,
|
|
1550
1805
|
failedAssertions: JSON.stringify(outcome.failedAssertions),
|
|
1806
|
+
diagnostics: serializeMonitorDiagnostics(outcome.diagnostics),
|
|
1551
1807
|
createdAt: outcome.checkedAt,
|
|
1552
1808
|
ownerEmail: monitor.ownerEmail,
|
|
1553
1809
|
orgId: monitor.orgId,
|
|
@@ -1600,12 +1856,24 @@ function isTransientNoResponseFailure(outcome: CheckOutcome): boolean {
|
|
|
1600
1856
|
);
|
|
1601
1857
|
}
|
|
1602
1858
|
|
|
1859
|
+
function isTransientDegradedFailure(outcome: CheckOutcome): boolean {
|
|
1860
|
+
return (
|
|
1861
|
+
outcome.status === "degraded" &&
|
|
1862
|
+
outcome.failedAssertions.some((assertion) =>
|
|
1863
|
+
assertion.startsWith("Response took "),
|
|
1864
|
+
)
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1603
1868
|
export function shouldOpenMonitorIncident(
|
|
1604
1869
|
outcome: CheckOutcome,
|
|
1605
1870
|
priorConsecutiveFailures: number,
|
|
1606
1871
|
confirmationChecks = transientFailureConfirmationChecks(),
|
|
1607
1872
|
): boolean {
|
|
1608
|
-
const
|
|
1873
|
+
const needsConfirmation =
|
|
1874
|
+
isTransientNoResponseFailure(outcome) ||
|
|
1875
|
+
isTransientDegradedFailure(outcome);
|
|
1876
|
+
const needed = needsConfirmation
|
|
1609
1877
|
? Math.max(1, Math.min(10, Math.floor(confirmationChecks)))
|
|
1610
1878
|
: 1;
|
|
1611
1879
|
return Math.max(0, Math.floor(priorConsecutiveFailures)) + 1 >= needed;
|
|
@@ -1799,10 +2067,13 @@ export async function evaluateAndNotifyMonitor(
|
|
|
1799
2067
|
|
|
1800
2068
|
const suppressed = await recentlyResolvedWithinCooldown(monitor, ctx, now);
|
|
1801
2069
|
let notificationId: string | undefined;
|
|
2070
|
+
let notificationDelivered = false;
|
|
1802
2071
|
if (!suppressed) {
|
|
1803
2072
|
try {
|
|
1804
2073
|
const delivery = await notifyMonitorDown(monitor, outcome);
|
|
1805
2074
|
notificationId = delivery.notification?.id;
|
|
2075
|
+
notificationDelivered =
|
|
2076
|
+
Boolean(notificationId) || delivery.deliveredChannels.length > 0;
|
|
1806
2077
|
} catch (err) {
|
|
1807
2078
|
console.error(
|
|
1808
2079
|
`[uptime-monitors] notify failed for ${monitor.id}:`,
|
|
@@ -1825,6 +2096,7 @@ export async function evaluateAndNotifyMonitor(
|
|
|
1825
2096
|
cause,
|
|
1826
2097
|
lastError: outcome.error,
|
|
1827
2098
|
notificationId: notificationId ?? null,
|
|
2099
|
+
notificationDelivered,
|
|
1828
2100
|
checksFailed: nextChecksFailed,
|
|
1829
2101
|
createdAt: outcome.checkedAt,
|
|
1830
2102
|
ownerEmail: monitor.ownerEmail,
|
|
@@ -1833,7 +2105,7 @@ export async function evaluateAndNotifyMonitor(
|
|
|
1833
2105
|
return {
|
|
1834
2106
|
status: outcome.status,
|
|
1835
2107
|
incidentId,
|
|
1836
|
-
notified:
|
|
2108
|
+
notified: notificationDelivered,
|
|
1837
2109
|
};
|
|
1838
2110
|
}
|
|
1839
2111
|
|
|
@@ -1844,14 +2116,16 @@ export async function evaluateAndNotifyMonitor(
|
|
|
1844
2116
|
.set({ resolvedAt: outcome.checkedAt })
|
|
1845
2117
|
.where(eq(schema.monitorIncidents.id, open.id));
|
|
1846
2118
|
let notified = false;
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
2119
|
+
if (open.notificationDelivered) {
|
|
2120
|
+
try {
|
|
2121
|
+
await notifyMonitorRecovered(monitor, outcome, open);
|
|
2122
|
+
notified = true;
|
|
2123
|
+
} catch (err) {
|
|
2124
|
+
console.error(
|
|
2125
|
+
`[uptime-monitors] recovery notify failed for ${monitor.id}:`,
|
|
2126
|
+
err,
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
1855
2129
|
}
|
|
1856
2130
|
return { status: "up", incidentId: open.id, notified, recovered: true };
|
|
1857
2131
|
}
|
|
@@ -1866,7 +2140,7 @@ export async function evaluateAndNotifyMonitor(
|
|
|
1866
2140
|
export async function runAndProcessMonitor(
|
|
1867
2141
|
monitor: Monitor,
|
|
1868
2142
|
ctx: AccessCtx,
|
|
1869
|
-
opts: { allowPrivateHosts?: boolean } = {},
|
|
2143
|
+
opts: { allowPrivateHosts?: boolean; source?: MonitorCheckSource } = {},
|
|
1870
2144
|
): Promise<CheckOutcome> {
|
|
1871
2145
|
const outcome = await runMonitorCheck(monitor, opts);
|
|
1872
2146
|
// recordMonitorResult() emits the "monitors" change for the UI.
|
|
@@ -1897,7 +2171,7 @@ export async function runMonitorNow(
|
|
|
1897
2171
|
statusCode: 409,
|
|
1898
2172
|
});
|
|
1899
2173
|
}
|
|
1900
|
-
return runAndProcessMonitor(monitor, ctx);
|
|
2174
|
+
return runAndProcessMonitor(monitor, ctx, { source: "manual" });
|
|
1901
2175
|
}
|
|
1902
2176
|
|
|
1903
2177
|
// ---------------------------------------------------------------------------
|
|
@@ -999,6 +999,7 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
999
999
|
cause TEXT NOT NULL DEFAULT '',
|
|
1000
1000
|
last_error TEXT,
|
|
1001
1001
|
notification_id TEXT,
|
|
1002
|
+
notification_delivered BOOLEAN NOT NULL DEFAULT false,
|
|
1002
1003
|
checks_failed INTEGER NOT NULL DEFAULT 1,
|
|
1003
1004
|
created_at TEXT NOT NULL DEFAULT (now()::text),
|
|
1004
1005
|
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
@@ -1015,6 +1016,7 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
1015
1016
|
cause TEXT NOT NULL DEFAULT '',
|
|
1016
1017
|
last_error TEXT,
|
|
1017
1018
|
notification_id TEXT,
|
|
1019
|
+
notification_delivered INTEGER NOT NULL DEFAULT 0,
|
|
1018
1020
|
checks_failed INTEGER NOT NULL DEFAULT 1,
|
|
1019
1021
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1020
1022
|
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
@@ -1234,6 +1236,19 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
1234
1236
|
`,
|
|
1235
1237
|
},
|
|
1236
1238
|
},
|
|
1239
|
+
{
|
|
1240
|
+
version: 116,
|
|
1241
|
+
name: "uptime-monitor-check-diagnostics",
|
|
1242
|
+
sql: `ALTER TABLE monitor_check_results ADD COLUMN IF NOT EXISTS diagnostics TEXT NOT NULL DEFAULT '{}'`,
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
version: 117,
|
|
1246
|
+
name: "uptime-monitor-incident-notification-delivered",
|
|
1247
|
+
sql: {
|
|
1248
|
+
postgres: `ALTER TABLE monitor_incidents ADD COLUMN IF NOT EXISTS notification_delivered BOOLEAN NOT NULL DEFAULT false`,
|
|
1249
|
+
sqlite: `ALTER TABLE monitor_incidents ADD COLUMN IF NOT EXISTS notification_delivered INTEGER NOT NULL DEFAULT 0`,
|
|
1250
|
+
},
|
|
1251
|
+
},
|
|
1237
1252
|
],
|
|
1238
1253
|
{ table: "analytics_migrations" },
|
|
1239
1254
|
);
|
|
@@ -54,7 +54,7 @@ export default function registerUptimeMonitorJobs(): void {
|
|
|
54
54
|
|
|
55
55
|
const ms = intervalMs();
|
|
56
56
|
setInterval(() => {
|
|
57
|
-
runDueMonitorsOnce().catch((err) =>
|
|
57
|
+
runDueMonitorsOnce({ source: "in-process" }).catch((err) =>
|
|
58
58
|
console.error("[uptime-monitors] interval failed:", err),
|
|
59
59
|
);
|
|
60
60
|
}, ms);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { createError, defineEventHandler, getHeader } from "h3";
|
|
4
|
+
|
|
5
|
+
import { runDueMonitorsOnce } from "../../../jobs/uptime-monitors";
|
|
6
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
var __AGENT_NATIVE_UPTIME_MONITOR_SCHEDULED_RUNTIME__: boolean | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function productionLike(): boolean {
|
|
12
|
+
return (
|
|
13
|
+
process.env.NODE_ENV === "production" ||
|
|
14
|
+
process.env.NETLIFY === "true" ||
|
|
15
|
+
process.env.VERCEL === "1"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function scheduledFunctionRuntime(): boolean {
|
|
20
|
+
return globalThis.__AGENT_NATIVE_UPTIME_MONITOR_SCHEDULED_RUNTIME__ === true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cronSecret(): string | null {
|
|
24
|
+
const secret = process.env.UPTIME_MONITORS_CRON_SECRET?.trim();
|
|
25
|
+
return secret ? secret : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function headerMatchesSecret(
|
|
29
|
+
header: string | undefined,
|
|
30
|
+
secret: string,
|
|
31
|
+
): boolean {
|
|
32
|
+
const expected = `Bearer ${secret}`;
|
|
33
|
+
const value = header?.trim() ?? "";
|
|
34
|
+
if (value.length !== expected.length) return false;
|
|
35
|
+
return timingSafeEqual(Buffer.from(value), Buffer.from(expected));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default defineEventHandler(async (event) => {
|
|
39
|
+
const secret = cronSecret();
|
|
40
|
+
const scheduledRuntime = scheduledFunctionRuntime();
|
|
41
|
+
if (!secret && productionLike() && !scheduledRuntime) {
|
|
42
|
+
throw createError({
|
|
43
|
+
statusCode: 503,
|
|
44
|
+
statusMessage: "UPTIME_MONITORS_CRON_SECRET is required",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
secret &&
|
|
50
|
+
!scheduledRuntime &&
|
|
51
|
+
!headerMatchesSecret(getHeader(event, "authorization"), secret)
|
|
52
|
+
) {
|
|
53
|
+
throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return runDueMonitorsOnce();
|
|
57
|
+
});
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import { z } from "zod";
|
|
32
32
|
|
|
33
33
|
import { getDb, schema } from "../server/db/index.js";
|
|
34
|
+
import { isPersonalSoloCalendarEvent } from "../server/lib/calendar-event-classification.js";
|
|
34
35
|
import {
|
|
35
36
|
calendarEventToMeetingView,
|
|
36
37
|
eventEndIso,
|
|
@@ -82,6 +83,11 @@ export default defineAction({
|
|
|
82
83
|
.describe(
|
|
83
84
|
"Also include meetings that started within this many minutes (desktop reminder hold window). Default 0.",
|
|
84
85
|
),
|
|
86
|
+
excludePersonalSoloEvents: booleanParam
|
|
87
|
+
.default(false)
|
|
88
|
+
.describe(
|
|
89
|
+
"Exclude obvious solo personal calendar blocks such as Gym or Dinner. Used by desktop meeting reminders.",
|
|
90
|
+
),
|
|
85
91
|
}),
|
|
86
92
|
http: { method: "GET" },
|
|
87
93
|
run: async (args) => {
|
|
@@ -263,6 +269,12 @@ export default defineAction({
|
|
|
263
269
|
for (const event of items) {
|
|
264
270
|
if (!event.id || event.status === "cancelled") continue;
|
|
265
271
|
if (!isTimedCalendarEvent(event)) continue;
|
|
272
|
+
if (
|
|
273
|
+
args.excludePersonalSoloEvents &&
|
|
274
|
+
isPersonalSoloCalendarEvent({ account, event })
|
|
275
|
+
) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
266
278
|
const startIso = eventStartIso(event);
|
|
267
279
|
const endIso = eventEndIso(event);
|
|
268
280
|
if (!startIso || !endIso) continue;
|