@agent-native/core 0.84.67 → 0.85.0
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 +14 -0
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +55 -0
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +199 -38
- package/corpus/core/src/agent/run-manager.ts +79 -0
- package/corpus/core/src/agent/run-store.ts +117 -5
- package/corpus/core/src/agent/types.ts +13 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +14 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +475 -0
- package/corpus/core/src/client/analytics.ts +2 -0
- package/corpus/core/src/client/error-format.ts +4 -0
- package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +1 -1
- package/corpus/core/src/client/session-replay.ts +774 -2
- package/corpus/core/src/client/sse-event-processor.ts +65 -9
- package/corpus/core/src/client/use-run-stuck-detection.ts +31 -7
- package/corpus/core/src/db/runtime-diagnostics.ts +331 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +115 -2
- package/corpus/core/src/server/core-routes-plugin.ts +91 -4
- package/corpus/core/src/server/http-response-telemetry.ts +189 -0
- package/corpus/core/src/server/self-dispatch.ts +29 -0
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +53 -1
- package/corpus/templates/analytics/AGENTS.md +13 -0
- package/corpus/templates/analytics/actions/view-screen.ts +25 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +20 -0
- package/corpus/templates/analytics/app/global.css +55 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +100 -1
- package/corpus/templates/analytics/app/i18n-data.ts +1226 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +3 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +182 -144
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +11 -18
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +11 -18
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +49 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +557 -0
- package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +274 -0
- package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +974 -0
- package/corpus/templates/analytics/changelog/2026-07-02-alert-rules-can-now-be-viewed-and-managed-from-settings.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-recent-ask-chats-show-a-loading-placeholder-while-your-chat-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-session-replays-now-capture-console-logs-and-network-request.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-sharing-status-labels-use-neutral-icons-instead-of-colored-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-the-session-replay-viewer-has-a-dev-tools-panel-with-console.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +3 -0
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +4 -0
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +142 -1
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +369 -8
- package/corpus/templates/analytics/server/lib/session-replay.ts +76 -2
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +101 -0
- package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +78 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +24 -12
- package/corpus/templates/design/changelog/2026-07-02-the-temporary-code-tab-is-hidden-from-the-design-editor-side.md +6 -0
- package/corpus/templates/plan/actions/create-visual-recap.ts +1 -1
- package/corpus/templates/plan/actions/update-local-plan-folder.ts +3 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
- package/corpus/templates/plan/changelog/2026-07-02-trying-to-edit-a-recap-or-plan-you-can-only-view-now-explain.md +6 -0
- package/corpus/templates/plan/server/plans.ts +26 -9
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +155 -34
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +29 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +73 -0
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +41 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +100 -4
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +13 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +13 -0
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +410 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +2 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/session-replay.d.ts +36 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +579 -1
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +20 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +42 -11
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +14 -4
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +18 -4
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/runtime-diagnostics.d.ts +52 -0
- package/dist/db/runtime-diagnostics.d.ts.map +1 -0
- package/dist/db/runtime-diagnostics.js +250 -0
- package/dist/db/runtime-diagnostics.js.map +1 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +101 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +17 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +65 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts +4 -0
- package/dist/server/http-response-telemetry.d.ts.map +1 -0
- package/dist/server/http-response-telemetry.js +174 -0
- package/dist/server/http-response-telemetry.js.map +1 -0
- package/dist/server/self-dispatch.d.ts +17 -0
- package/dist/server/self-dispatch.d.ts.map +1 -1
- package/dist/server/self-dispatch.js +11 -0
- package/dist/server/self-dispatch.js.map +1 -1
- package/docs/content/locales/ar-SA/tracking.mdx +55 -0
- package/docs/content/locales/de-DE/tracking.mdx +55 -0
- package/docs/content/locales/es-ES/tracking.mdx +55 -0
- package/docs/content/locales/fr-FR/tracking.mdx +55 -0
- package/docs/content/locales/hi-IN/tracking.mdx +55 -0
- package/docs/content/locales/ja-JP/tracking.mdx +55 -0
- package/docs/content/locales/ko-KR/tracking.mdx +55 -0
- package/docs/content/locales/pt-BR/tracking.mdx +55 -0
- package/docs/content/locales/zh-CN/tracking.mdx +55 -0
- package/docs/content/locales/zh-TW/tracking.mdx +55 -0
- package/docs/content/tracking.mdx +2 -0
- package/package.json +1 -1
|
@@ -8088,6 +8088,9 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
8088
8088
|
stages: m.RUN_DIAG_STAGE,
|
|
8089
8089
|
}))
|
|
8090
8090
|
.catch(() => null);
|
|
8091
|
+
const runtimeDetail = await import("../db/runtime-diagnostics.js")
|
|
8092
|
+
.then((m) => m.formatRuntimeDebugFingerprint())
|
|
8093
|
+
.catch(() => "");
|
|
8091
8094
|
|
|
8092
8095
|
// Record "the route handler was entered" against the run after auth
|
|
8093
8096
|
// succeeds. This is the proof the bg-fn invocation actually reached
|
|
@@ -8097,14 +8100,74 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
8097
8100
|
.record(prepared.runId, diag.stages.routeEntered)
|
|
8098
8101
|
.catch(() => {});
|
|
8099
8102
|
await diag
|
|
8100
|
-
.record(prepared.runId, diag.stages.authPassed)
|
|
8103
|
+
.record(prepared.runId, diag.stages.authPassed, runtimeDetail)
|
|
8101
8104
|
.catch(() => {});
|
|
8102
8105
|
}
|
|
8103
8106
|
|
|
8107
|
+
// PAYLOAD REHYDRATION: a `payloadRef` marker means the dispatch
|
|
8108
|
+
// carried ONLY the marker (Netlify caps background-function request
|
|
8109
|
+
// bodies at 256KB — a large chat history silently exceeded it) and
|
|
8110
|
+
// the full request body is persisted on the run row. Rehydrate it
|
|
8111
|
+
// here. The payload is NOT cleared on read: a Netlify at-least-once
|
|
8112
|
+
// retry of a failed invocation must be able to rehydrate again (the
|
|
8113
|
+
// claim CAS still dedupes execution); terminal status writes clear it.
|
|
8114
|
+
let workerBody: Record<string, unknown> = prepared.body;
|
|
8115
|
+
const preparedMarkerRecord =
|
|
8116
|
+
preparedMarker && typeof preparedMarker === "object"
|
|
8117
|
+
? (preparedMarker as Record<string, unknown>)
|
|
8118
|
+
: null;
|
|
8119
|
+
if (preparedMarkerRecord?.payloadRef === true) {
|
|
8120
|
+
const runStore = await import("../agent/run-store.js");
|
|
8121
|
+
const rawPayload = await runStore
|
|
8122
|
+
.readRunDispatchPayload(prepared.runId)
|
|
8123
|
+
.catch(() => null);
|
|
8124
|
+
let parsedPayload: Record<string, unknown> | null = null;
|
|
8125
|
+
if (rawPayload) {
|
|
8126
|
+
try {
|
|
8127
|
+
const candidate = JSON.parse(rawPayload);
|
|
8128
|
+
if (candidate && typeof candidate === "object") {
|
|
8129
|
+
parsedPayload = candidate as Record<string, unknown>;
|
|
8130
|
+
}
|
|
8131
|
+
} catch {
|
|
8132
|
+
// Corrupt payload — treated as missing below.
|
|
8133
|
+
}
|
|
8134
|
+
}
|
|
8135
|
+
if (!parsedPayload) {
|
|
8136
|
+
// Row missing / reaped / already terminal — there is nothing to
|
|
8137
|
+
// run. Fail the run loudly (if it is still running) and ack the
|
|
8138
|
+
// dispatch with a 200 so Netlify does not retry a dead handoff.
|
|
8139
|
+
if (diag) {
|
|
8140
|
+
await diag
|
|
8141
|
+
.record(
|
|
8142
|
+
prepared.runId,
|
|
8143
|
+
diag.stages.workerThrew,
|
|
8144
|
+
"dispatch payload missing — cannot rehydrate background run body",
|
|
8145
|
+
)
|
|
8146
|
+
.catch(() => {});
|
|
8147
|
+
}
|
|
8148
|
+
const statusUpdated = await runStore
|
|
8149
|
+
.updateRunStatusIfRunning(prepared.runId, "errored")
|
|
8150
|
+
.catch(() => false);
|
|
8151
|
+
if (statusUpdated) {
|
|
8152
|
+
await runStore
|
|
8153
|
+
.setRunTerminalReason(
|
|
8154
|
+
prepared.runId,
|
|
8155
|
+
"dispatch_payload_missing",
|
|
8156
|
+
)
|
|
8157
|
+
.catch(() => {});
|
|
8158
|
+
}
|
|
8159
|
+
return { ok: false, skipped: "dispatch-payload-missing" };
|
|
8160
|
+
}
|
|
8161
|
+
workerBody = {
|
|
8162
|
+
...parsedPayload,
|
|
8163
|
+
[AGENT_CHAT_BACKGROUND_RUN_FIELD]: preparedMarker,
|
|
8164
|
+
};
|
|
8165
|
+
}
|
|
8166
|
+
|
|
8104
8167
|
// Stash the verified+augmented body for the handler — the body stream
|
|
8105
8168
|
// is already consumed, so the handler reads this instead.
|
|
8106
8169
|
(event as any).context = (event as any).context ?? {};
|
|
8107
|
-
(event as any).context.__agentChatBackgroundBody =
|
|
8170
|
+
(event as any).context.__agentChatBackgroundBody = workerBody;
|
|
8108
8171
|
|
|
8109
8172
|
// Durable owner context: this self-dispatch is cookieless (HMAC-only).
|
|
8110
8173
|
// Resolve the owner from the persisted run row, never the request
|
|
@@ -8287,6 +8350,56 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
8287
8350
|
}, 15_000); // Start 15s after init (after the scheduler)
|
|
8288
8351
|
})();
|
|
8289
8352
|
|
|
8353
|
+
// ─── Unclaimed background-run sweep ────────────────────────────────
|
|
8354
|
+
// Backstop for LOST background handoffs. The foreground circuit-breaker
|
|
8355
|
+
// covers the initial dispatch (a connected client is polling the claim),
|
|
8356
|
+
// but a server-chained CONTINUATION handoff has no foreground watching
|
|
8357
|
+
// it: if the dispatch is lost after the successor row was inserted, the
|
|
8358
|
+
// row would sit at dispatch_mode='background' forever and the turn hangs
|
|
8359
|
+
// silently. This sweep reaps such rows into a loud, attributable error
|
|
8360
|
+
// (`background_worker_never_started`) that the client renders, instead
|
|
8361
|
+
// of an idle spinner. Cheap: one indexed-ish query per 2-min window.
|
|
8362
|
+
(() => {
|
|
8363
|
+
let lastSweep = 0;
|
|
8364
|
+
const SWEEP_INTERVAL_MS = 2 * 60 * 1000;
|
|
8365
|
+
|
|
8366
|
+
setTimeout(() => {
|
|
8367
|
+
setInterval(() => {
|
|
8368
|
+
const now = Date.now();
|
|
8369
|
+
if (now - lastSweep < SWEEP_INTERVAL_MS) return;
|
|
8370
|
+
lastSweep = now;
|
|
8371
|
+
|
|
8372
|
+
(async () => {
|
|
8373
|
+
const {
|
|
8374
|
+
listUnclaimedBackgroundRunIds,
|
|
8375
|
+
reapUnclaimedBackgroundRun,
|
|
8376
|
+
} = await import("../agent/run-store.js");
|
|
8377
|
+
let runIds: string[];
|
|
8378
|
+
try {
|
|
8379
|
+
runIds = await listUnclaimedBackgroundRunIds();
|
|
8380
|
+
} catch {
|
|
8381
|
+
return; // Table may not exist yet on first boot
|
|
8382
|
+
}
|
|
8383
|
+
for (const staleRunId of runIds) {
|
|
8384
|
+
try {
|
|
8385
|
+
const reaped = await reapUnclaimedBackgroundRun(staleRunId);
|
|
8386
|
+
if (reaped) {
|
|
8387
|
+
console.error(
|
|
8388
|
+
"[agent-chat] swept unclaimed background run (handoff lost):",
|
|
8389
|
+
staleRunId,
|
|
8390
|
+
);
|
|
8391
|
+
}
|
|
8392
|
+
} catch {
|
|
8393
|
+
// best-effort per run
|
|
8394
|
+
}
|
|
8395
|
+
}
|
|
8396
|
+
})().catch(() => {
|
|
8397
|
+
// best-effort — never break the server
|
|
8398
|
+
});
|
|
8399
|
+
}, 30_000); // Check every 30s but only sweep once per 2min
|
|
8400
|
+
}, 20_000); // Start 20s after init (after the agent-teams sweep)
|
|
8401
|
+
})();
|
|
8402
|
+
|
|
8290
8403
|
// ─── Trigger Dispatcher (event-based automations) ─────────────────
|
|
8291
8404
|
if (disableRecurringJobsRuntime) {
|
|
8292
8405
|
if (process.env.DEBUG) {
|
|
@@ -45,6 +45,12 @@ import {
|
|
|
45
45
|
import { mountBrowserSessionRoutes } from "../browser-sessions/routes.js";
|
|
46
46
|
import { mountDbAdminRoutes } from "../db-admin/routes.js";
|
|
47
47
|
import { getDbExec } from "../db/client.js";
|
|
48
|
+
import {
|
|
49
|
+
getDatabaseRuntimeFingerprint,
|
|
50
|
+
getRuntimeDebugFingerprint,
|
|
51
|
+
runDatabaseSchemaHealthCheck,
|
|
52
|
+
type DatabaseSchemaHealthResult,
|
|
53
|
+
} from "../db/runtime-diagnostics.js";
|
|
48
54
|
import {
|
|
49
55
|
uploadFile,
|
|
50
56
|
getActiveFileUploadProviderForRequest,
|
|
@@ -143,6 +149,7 @@ import {
|
|
|
143
149
|
DEFAULT_UPLOAD_MAX_FILE_BYTES,
|
|
144
150
|
isAllowedUploadMimeType,
|
|
145
151
|
} from "./h3-helpers.js";
|
|
152
|
+
import { createHttpResponseTelemetryMiddleware } from "./http-response-telemetry.js";
|
|
146
153
|
import { isIdentitySsoEnabled } from "./identity-sso-store.js";
|
|
147
154
|
import { handleIdentitySso } from "./identity-sso.js";
|
|
148
155
|
import { createOpenRouteHandler } from "./open-route.js";
|
|
@@ -204,10 +211,24 @@ export function getFrameworkEnvKeys(): EnvKeyConfig[] {
|
|
|
204
211
|
export interface DbHealthProbeResult {
|
|
205
212
|
/** The serverless function is live and served the request. */
|
|
206
213
|
ok: true;
|
|
214
|
+
/** Database + optional schema readiness for stricter production monitors. */
|
|
215
|
+
ready: boolean;
|
|
207
216
|
/** A trivial `SELECT 1` reached the database (false = no DB or unreachable). */
|
|
208
217
|
db: boolean;
|
|
209
218
|
/** Round-trip time of the probe in milliseconds. */
|
|
210
219
|
ms: number;
|
|
220
|
+
/** Redacted database routing details useful for deploy/runtime checks. */
|
|
221
|
+
database: {
|
|
222
|
+
configured: boolean;
|
|
223
|
+
source: string;
|
|
224
|
+
dialect: string;
|
|
225
|
+
urlHash?: string;
|
|
226
|
+
appName?: string;
|
|
227
|
+
authTokenConfigured: boolean;
|
|
228
|
+
netlifyDatabaseUrlConfigured: boolean;
|
|
229
|
+
};
|
|
230
|
+
/** Optional metadata-only schema compatibility check. */
|
|
231
|
+
schema?: DatabaseSchemaHealthResult;
|
|
211
232
|
}
|
|
212
233
|
|
|
213
234
|
/**
|
|
@@ -222,16 +243,40 @@ export interface DbHealthProbeResult {
|
|
|
222
243
|
*/
|
|
223
244
|
export async function runDbHealthProbe(
|
|
224
245
|
exec: () => { execute: (sql: string) => Promise<unknown> } = getDbExec,
|
|
246
|
+
options: { schema?: boolean } = {},
|
|
225
247
|
): Promise<DbHealthProbeResult> {
|
|
226
248
|
const startedAt = Date.now();
|
|
227
249
|
let db = false;
|
|
250
|
+
let schema: DatabaseSchemaHealthResult | undefined;
|
|
251
|
+
const dbExec = exec();
|
|
228
252
|
try {
|
|
229
|
-
await
|
|
253
|
+
await dbExec.execute("SELECT 1");
|
|
230
254
|
db = true;
|
|
231
255
|
} catch {
|
|
232
256
|
// Live even when the DB is unreachable or the app has no database.
|
|
233
257
|
}
|
|
234
|
-
|
|
258
|
+
if (db && options.schema) {
|
|
259
|
+
schema = await runDatabaseSchemaHealthCheck({
|
|
260
|
+
exec: dbExec as ReturnType<typeof getDbExec>,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const database = getDatabaseRuntimeFingerprint();
|
|
264
|
+
return {
|
|
265
|
+
ok: true,
|
|
266
|
+
ready: db && (!schema || schema.ok),
|
|
267
|
+
db,
|
|
268
|
+
ms: Date.now() - startedAt,
|
|
269
|
+
database: {
|
|
270
|
+
configured: database.configured,
|
|
271
|
+
source: database.source,
|
|
272
|
+
dialect: database.dialect,
|
|
273
|
+
urlHash: database.urlHash,
|
|
274
|
+
appName: database.appName,
|
|
275
|
+
authTokenConfigured: database.authTokenConfigured,
|
|
276
|
+
netlifyDatabaseUrlConfigured: database.netlifyDatabaseUrlConfigured,
|
|
277
|
+
},
|
|
278
|
+
...(schema ? { schema } : {}),
|
|
279
|
+
};
|
|
235
280
|
}
|
|
236
281
|
const DEFAULT_BUILDER_WAITLIST_FORM_ID = "DYTHuM0jlV";
|
|
237
282
|
const DEFAULT_BUILDER_WAITLIST_FORMS_ORIGIN = "https://forms.agent-native.com";
|
|
@@ -863,6 +908,8 @@ export function createCoreRoutesPlugin(
|
|
|
863
908
|
|
|
864
909
|
const P = FRAMEWORK_ROUTE_PREFIX;
|
|
865
910
|
|
|
911
|
+
getH3App(nitroApp).use(createHttpResponseTelemetryMiddleware());
|
|
912
|
+
|
|
866
913
|
// Security response headers — emitted on every framework response.
|
|
867
914
|
// Mounted before route handlers so 4xx/5xx error pages also carry the
|
|
868
915
|
// headers. Routes that need to tighten a specific header override via
|
|
@@ -1071,17 +1118,57 @@ export function createCoreRoutesPlugin(
|
|
|
1071
1118
|
// Health + DB warmup — liveness probe that touches the database so
|
|
1072
1119
|
// uptime monitors and the keep-warm cron prevent a scale-to-zero
|
|
1073
1120
|
// serverless DB (e.g. Neon) from cold-starting on the next real
|
|
1074
|
-
// request. Public, side-effect free, and never cached.
|
|
1121
|
+
// request. Public, side-effect free, and never cached. Add ?schema=1
|
|
1122
|
+
// for metadata-only schema checks, and ?strict=1 to turn a not-ready
|
|
1123
|
+
// DB/schema probe into a failing HTTP status for monitors.
|
|
1075
1124
|
if (!options.disableHealth) {
|
|
1076
1125
|
getH3App(nitroApp).use(
|
|
1077
1126
|
`${P}/health`,
|
|
1078
1127
|
defineEventHandler(async (event) => {
|
|
1079
1128
|
setResponseHeader(event, "cache-control", "no-store");
|
|
1080
|
-
|
|
1129
|
+
const schema =
|
|
1130
|
+
event.url?.searchParams.get("schema") === "1" ||
|
|
1131
|
+
event.url?.searchParams.get("schema") === "true";
|
|
1132
|
+
const strict =
|
|
1133
|
+
event.url?.searchParams.get("strict") === "1" ||
|
|
1134
|
+
event.url?.searchParams.get("strict") === "true" ||
|
|
1135
|
+
process.env.AGENT_NATIVE_HEALTH_STRICT_SCHEMA === "true";
|
|
1136
|
+
const result = await runDbHealthProbe(getDbExec, { schema });
|
|
1137
|
+
if (strict && !result.ready) setResponseStatus(event, 503);
|
|
1138
|
+
return result;
|
|
1081
1139
|
}),
|
|
1082
1140
|
);
|
|
1083
1141
|
}
|
|
1084
1142
|
|
|
1143
|
+
getH3App(nitroApp).use(
|
|
1144
|
+
`${P}/debug/runtime`,
|
|
1145
|
+
defineEventHandler(async (event) => {
|
|
1146
|
+
setResponseHeader(event, "cache-control", "no-store");
|
|
1147
|
+
const session = await getSession(event).catch(() => null);
|
|
1148
|
+
const productionLike =
|
|
1149
|
+
process.env.NODE_ENV === "production" ||
|
|
1150
|
+
process.env.NETLIFY === "true" ||
|
|
1151
|
+
process.env.VERCEL === "1";
|
|
1152
|
+
if (!session?.email && productionLike) {
|
|
1153
|
+
setResponseStatus(event, 401);
|
|
1154
|
+
return { error: "Authentication required" };
|
|
1155
|
+
}
|
|
1156
|
+
const schema = await runDatabaseSchemaHealthCheck().catch((err) => ({
|
|
1157
|
+
ok: false,
|
|
1158
|
+
checked: false,
|
|
1159
|
+
dialect: getDatabaseRuntimeFingerprint().dialect,
|
|
1160
|
+
missingTables: [],
|
|
1161
|
+
missingColumns: [],
|
|
1162
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1163
|
+
}));
|
|
1164
|
+
return {
|
|
1165
|
+
ok: true,
|
|
1166
|
+
runtime: getRuntimeDebugFingerprint(),
|
|
1167
|
+
schema,
|
|
1168
|
+
};
|
|
1169
|
+
}),
|
|
1170
|
+
);
|
|
1171
|
+
|
|
1085
1172
|
getH3App(nitroApp).use(
|
|
1086
1173
|
`${P}/speculation-rules.json`,
|
|
1087
1174
|
defineEventHandler((event) => {
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { defineEventHandler, getHeader, getMethod } from "h3";
|
|
2
|
+
import type { EventHandler, H3Event } from "h3";
|
|
3
|
+
|
|
4
|
+
import { getDatabaseRuntimeFingerprint } from "../db/runtime-diagnostics.js";
|
|
5
|
+
import { track } from "../tracking/index.js";
|
|
6
|
+
import { getAppName } from "./app-name.js";
|
|
7
|
+
|
|
8
|
+
const TELEMETRY_EVENT_NAME = "http.response";
|
|
9
|
+
const TRACKING_INGEST_PATHS = new Set([
|
|
10
|
+
"/track",
|
|
11
|
+
"/api/analytics/track",
|
|
12
|
+
"/api/events/track",
|
|
13
|
+
"/_agent-native/track",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
function envValue(key: string): string | undefined {
|
|
17
|
+
const value = process.env[key]?.trim();
|
|
18
|
+
return value || undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function boolEnv(key: string): boolean {
|
|
22
|
+
return ["1", "true", "yes", "on"].includes(
|
|
23
|
+
(process.env[key] ?? "").trim().toLowerCase(),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sampleRate(): number {
|
|
28
|
+
const raw = envValue("AGENT_NATIVE_HTTP_TELEMETRY_SAMPLE_RATE");
|
|
29
|
+
if (!raw) return 1;
|
|
30
|
+
const parsed = Number.parseFloat(raw);
|
|
31
|
+
if (!Number.isFinite(parsed)) return 1;
|
|
32
|
+
return Math.max(0, Math.min(1, parsed));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function shouldDisableTelemetry(): boolean {
|
|
36
|
+
return boolEnv("AGENT_NATIVE_HTTP_TELEMETRY_DISABLED");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function requestPath(event: H3Event): string {
|
|
40
|
+
const raw =
|
|
41
|
+
event.url?.pathname ??
|
|
42
|
+
String(event.node?.req?.url ?? event.path ?? "/").split("?")[0] ??
|
|
43
|
+
"/";
|
|
44
|
+
return raw || "/";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeSegment(segment: string): string {
|
|
48
|
+
if (!segment) return segment;
|
|
49
|
+
if (/^[0-9]+$/.test(segment)) return ":id";
|
|
50
|
+
if (/^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(segment)) return ":id";
|
|
51
|
+
if (
|
|
52
|
+
/^(run|turn|thread|design|screen|file|msg|key|tok)_[a-z0-9_-]+$/i.test(
|
|
53
|
+
segment,
|
|
54
|
+
)
|
|
55
|
+
) {
|
|
56
|
+
return ":id";
|
|
57
|
+
}
|
|
58
|
+
if (/^(run|turn)-[0-9]{10,}-[a-z0-9]+$/i.test(segment)) return ":id";
|
|
59
|
+
if (segment.length > 36 && /^[a-z0-9_-]+$/i.test(segment)) return ":id";
|
|
60
|
+
return segment;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function normalizeHttpTelemetryPath(pathname: string): string {
|
|
64
|
+
const normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
65
|
+
return normalized
|
|
66
|
+
.split("/")
|
|
67
|
+
.map((segment, index) => (index === 0 ? "" : normalizeSegment(segment)))
|
|
68
|
+
.join("/");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function statusClass(statusCode: number): string {
|
|
72
|
+
if (!Number.isFinite(statusCode) || statusCode < 100) return "unknown";
|
|
73
|
+
return `${Math.floor(statusCode / 100)}xx`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function routeKind(pathname: string): string {
|
|
77
|
+
if (pathname === "/_agent-native" || pathname.startsWith("/_agent-native/")) {
|
|
78
|
+
return "framework";
|
|
79
|
+
}
|
|
80
|
+
if (pathname === "/api" || pathname.startsWith("/api/")) return "api";
|
|
81
|
+
if (pathname.startsWith("/.well-known/")) return "well-known";
|
|
82
|
+
return "app";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function hostForEvent(event: H3Event): string | undefined {
|
|
86
|
+
return (
|
|
87
|
+
getHeader(event, "x-forwarded-host") ??
|
|
88
|
+
getHeader(event, "host") ??
|
|
89
|
+
undefined
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function organizationForHost(host: string | undefined): string | undefined {
|
|
94
|
+
const configured =
|
|
95
|
+
envValue("AGENT_NATIVE_ANALYTICS_ORG_NAME") ??
|
|
96
|
+
envValue("AGENT_NATIVE_ORG_NAME");
|
|
97
|
+
if (configured) return configured;
|
|
98
|
+
const normalized = host?.split(":")[0]?.toLowerCase();
|
|
99
|
+
return normalized?.endsWith(".agent-native.com") ||
|
|
100
|
+
normalized === "agent-native.com"
|
|
101
|
+
? "Builder.io"
|
|
102
|
+
: undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function shouldTrack(pathname: string, statusCode: number): boolean {
|
|
106
|
+
if (shouldDisableTelemetry()) return false;
|
|
107
|
+
if (TRACKING_INGEST_PATHS.has(pathname)) return false;
|
|
108
|
+
if (pathname.startsWith("/api/analytics/replay")) return false;
|
|
109
|
+
if (statusCode >= 500) return true;
|
|
110
|
+
const rate = sampleRate();
|
|
111
|
+
if (rate <= 0) return false;
|
|
112
|
+
if (rate >= 1) return true;
|
|
113
|
+
return Math.random() < rate;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function responseStatusCode(event: H3Event): number {
|
|
117
|
+
const raw =
|
|
118
|
+
(event.node?.res as any)?.statusCode ??
|
|
119
|
+
(event.node?.res as any)?.status ??
|
|
120
|
+
(event as any).res?.status ??
|
|
121
|
+
200;
|
|
122
|
+
const parsed = Number(raw);
|
|
123
|
+
return Number.isFinite(parsed) ? parsed : 200;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function emitTelemetry(event: H3Event, startedAt: number): void {
|
|
127
|
+
const statusCode = responseStatusCode(event);
|
|
128
|
+
const pathname = requestPath(event);
|
|
129
|
+
if (!shouldTrack(pathname, statusCode)) return;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const host = hostForEvent(event);
|
|
133
|
+
const db = getDatabaseRuntimeFingerprint();
|
|
134
|
+
track(TELEMETRY_EVENT_NAME, {
|
|
135
|
+
source: "server",
|
|
136
|
+
app: getAppName(),
|
|
137
|
+
template: envValue("AGENT_NATIVE_TEMPLATE") ?? getAppName(),
|
|
138
|
+
organization: organizationForHost(host),
|
|
139
|
+
method: getMethod(event),
|
|
140
|
+
path: normalizeHttpTelemetryPath(pathname),
|
|
141
|
+
route_kind: routeKind(pathname),
|
|
142
|
+
status_code: statusCode,
|
|
143
|
+
status_class: statusClass(statusCode),
|
|
144
|
+
duration_ms: Math.max(0, Date.now() - startedAt),
|
|
145
|
+
host,
|
|
146
|
+
environment: envValue("NODE_ENV"),
|
|
147
|
+
deploy_context: envValue("CONTEXT") ?? envValue("VERCEL_ENV"),
|
|
148
|
+
deploy_id: envValue("DEPLOY_ID") ?? envValue("VERCEL_DEPLOYMENT_ID"),
|
|
149
|
+
commit_ref:
|
|
150
|
+
envValue("COMMIT_REF") ??
|
|
151
|
+
envValue("NETLIFY_COMMIT_REF") ??
|
|
152
|
+
envValue("VERCEL_GIT_COMMIT_SHA") ??
|
|
153
|
+
envValue("GIT_COMMIT_SHA"),
|
|
154
|
+
db_source: db.source,
|
|
155
|
+
db_dialect: db.dialect,
|
|
156
|
+
db_url_hash: db.urlHash,
|
|
157
|
+
db_neon_endpoint: db.neon?.endpointId,
|
|
158
|
+
db_neon_pooled: db.neon?.pooled,
|
|
159
|
+
});
|
|
160
|
+
} catch {
|
|
161
|
+
// Response telemetry is best-effort. Never perturb request handling.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function createHttpResponseTelemetryMiddleware(): EventHandler {
|
|
166
|
+
return defineEventHandler((event) => {
|
|
167
|
+
const startedAt = Date.now();
|
|
168
|
+
const res = event.node?.res as
|
|
169
|
+
| {
|
|
170
|
+
once?: (event: "finish" | "close", cb: () => void) => void;
|
|
171
|
+
on?: (event: "finish" | "close", cb: () => void) => void;
|
|
172
|
+
}
|
|
173
|
+
| undefined;
|
|
174
|
+
let emitted = false;
|
|
175
|
+
const emitOnce = () => {
|
|
176
|
+
if (emitted) return;
|
|
177
|
+
emitted = true;
|
|
178
|
+
emitTelemetry(event, startedAt);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (typeof res?.once === "function") {
|
|
182
|
+
res.once("finish", emitOnce);
|
|
183
|
+
res.once("close", emitOnce);
|
|
184
|
+
} else if (typeof res?.on === "function") {
|
|
185
|
+
res.on("finish", emitOnce);
|
|
186
|
+
res.on("close", emitOnce);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
@@ -94,6 +94,23 @@ export interface FireInternalDispatchOptions {
|
|
|
94
94
|
body?: Record<string, unknown>;
|
|
95
95
|
/** Max ms to wait for the outbound request to leave the box. Default 250ms. */
|
|
96
96
|
settleMs?: number;
|
|
97
|
+
/**
|
|
98
|
+
* Await the dispatch response fully instead of racing the settle timer.
|
|
99
|
+
*
|
|
100
|
+
* The 250ms settle race is correct for a synchronous handler that must
|
|
101
|
+
* respond to its own caller quickly — but it is WRONG for a handoff fired
|
|
102
|
+
* from a function that is about to finish (e.g. a background worker chaining
|
|
103
|
+
* its continuation chunk): once the handler's promise resolves, the Lambda
|
|
104
|
+
* freezes and a still-in-flight dispatch fetch is killed WITHOUT rejecting,
|
|
105
|
+
* so the handoff is lost silently — the error path never fires. With
|
|
106
|
+
* `awaitResponse: true` the call resolves only after the target confirmed
|
|
107
|
+
* receipt (Netlify background functions 202 on enqueue, normally well under
|
|
108
|
+
* a second) and throws on any network error or non-2xx, bounded by
|
|
109
|
+
* `responseTimeoutMs`.
|
|
110
|
+
*/
|
|
111
|
+
awaitResponse?: boolean;
|
|
112
|
+
/** Max ms to await the dispatch response when `awaitResponse` is set. Default 15s. */
|
|
113
|
+
responseTimeoutMs?: number;
|
|
97
114
|
}
|
|
98
115
|
|
|
99
116
|
async function dispatchResponseError(
|
|
@@ -167,10 +184,14 @@ export async function fireInternalDispatch(
|
|
|
167
184
|
}
|
|
168
185
|
}
|
|
169
186
|
|
|
187
|
+
const awaitResponse = options.awaitResponse === true;
|
|
170
188
|
const dispatchPromise = fetch(url, {
|
|
171
189
|
method: "POST",
|
|
172
190
|
headers,
|
|
173
191
|
body: JSON.stringify({ taskId: options.taskId, ...(options.body ?? {}) }),
|
|
192
|
+
...(awaitResponse
|
|
193
|
+
? { signal: AbortSignal.timeout(options.responseTimeoutMs ?? 15_000) }
|
|
194
|
+
: {}),
|
|
174
195
|
}).then(async (res) => {
|
|
175
196
|
if (!res.ok) {
|
|
176
197
|
throw await dispatchResponseError(options.path, res);
|
|
@@ -187,6 +208,14 @@ export async function fireInternalDispatch(
|
|
|
187
208
|
);
|
|
188
209
|
});
|
|
189
210
|
|
|
211
|
+
if (awaitResponse) {
|
|
212
|
+
// Confirmed handoff: resolve only once the target acknowledged the
|
|
213
|
+
// dispatch (throws on network error / timeout / non-2xx). Used by callers
|
|
214
|
+
// whose own invocation is about to end — see the option doc above.
|
|
215
|
+
await dispatchPromise;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
190
219
|
const settleMs = options.settleMs ?? DEFAULT_DISPATCH_SETTLE_MS;
|
|
191
220
|
await Promise.race([
|
|
192
221
|
dispatchPromise,
|
|
@@ -33,10 +33,62 @@ agent answers about browser recordings in the Analytics template.
|
|
|
33
33
|
scoped to the recording, embeds a small SSR discovery payload on
|
|
34
34
|
`/sessions/:recordingId`, and advertises
|
|
35
35
|
`/api/session-replay/agent-context.json` plus bounded
|
|
36
|
-
`/api/session-replay/agent-events.json`
|
|
36
|
+
`/api/session-replay/agent-events.json` and
|
|
37
|
+
`/api/session-replay/agent-diagnostics.json` reads.
|
|
37
38
|
- Do not make session recordings public just so an agent can inspect them.
|
|
38
39
|
Tokenized agent links are the intended handoff path.
|
|
39
40
|
|
|
41
|
+
## Console And Network Capture
|
|
42
|
+
|
|
43
|
+
- While recording, the core client (`session-replay.ts` in
|
|
44
|
+
`@agent-native/core`) patches console (`log`/`info`/`warn`/`error`/`debug`),
|
|
45
|
+
window `error` / `unhandledrejection`, `fetch`, and XHR, and emits rrweb
|
|
46
|
+
custom events tagged `agent-native.console` and `agent-native.network`.
|
|
47
|
+
- Capture is on by default whenever session replay is enabled. Tune or disable
|
|
48
|
+
it with the `console` / `network` options on the session replay config; each
|
|
49
|
+
accepts a boolean or an options object.
|
|
50
|
+
- Privacy bounds: request/response bodies and headers are never captured, URLs
|
|
51
|
+
are scrubbed, messages are truncated, and recorder self-traffic (the replay
|
|
52
|
+
ingest and tracking endpoints) is excluded.
|
|
53
|
+
- Per-session budgets: 1000 console events and 2000 network events, with a
|
|
54
|
+
truncation notice event once a budget is hit.
|
|
55
|
+
- On ingest, `deriveReplaySignals` computes the real `errorCount` from tagged
|
|
56
|
+
console events plus the additive `networkErrorCount` column on
|
|
57
|
+
`session_recordings`. Keep new columns additive.
|
|
58
|
+
|
|
59
|
+
## Agent Diagnostics Surface
|
|
60
|
+
|
|
61
|
+
- `buildSessionReplayAgentContext` includes a `diagnostics` section: up to 50
|
|
62
|
+
console entries and 50 network entries, errors/failures first, with totals
|
|
63
|
+
and truncated flags. Agent-context instructions steer agents to diagnostics
|
|
64
|
+
as the primary debugging signal.
|
|
65
|
+
- The agent timeline includes `console-error` / `network-error` markers; error
|
|
66
|
+
markers are kept preferentially under the 200-marker cap.
|
|
67
|
+
- `apis.diagnostics` advertises the fuller bounded list:
|
|
68
|
+
`GET /api/session-replay/agent-diagnostics.json?id=<recordingId>&agent_access=<token>&kind=console|network|all&level=<level>&limit=<n>`
|
|
69
|
+
(limit defaults to 200, max 500). It uses the same recording-scoped
|
|
70
|
+
`agent_access` token as the other agent JSON APIs.
|
|
71
|
+
|
|
72
|
+
## Dev Tools Panel
|
|
73
|
+
|
|
74
|
+
- The `/sessions/:recordingId` replay player has a Dev Tools toggle that opens
|
|
75
|
+
a panel with Console and Network tabs: filter chips, search, an error-count
|
|
76
|
+
badge, and playback-time highlighting.
|
|
77
|
+
- Rows are Jump-to-seek: clicking one seeks the player to that moment. Extend
|
|
78
|
+
this panel instead of adding a separate debugging surface.
|
|
79
|
+
|
|
80
|
+
## Debugging A User-Reported Bug
|
|
81
|
+
|
|
82
|
+
1. Search the reporting user's email on `/sessions` to find their recordings.
|
|
83
|
+
2. Open the relevant session at `/sessions/:recordingId` and click
|
|
84
|
+
**Copy for agent** to mint the two-hour tokenized link.
|
|
85
|
+
3. Paste the link to an agent. The agent fetches
|
|
86
|
+
`/api/session-replay/agent-context.json`, reads the `diagnostics` section
|
|
87
|
+
and timeline markers first, then drills into `apis.diagnostics` (filtered
|
|
88
|
+
by `kind`/`level`) and `apis.events` for the fuller bounded lists as needed.
|
|
89
|
+
4. For human verification, open the Dev Tools panel in the replay player and
|
|
90
|
+
jump-to-seek from the failing console or network row.
|
|
91
|
+
|
|
40
92
|
## Capture Defaults
|
|
41
93
|
|
|
42
94
|
- Replay is off unless enabled by config/env.
|
|
@@ -128,6 +128,8 @@ details live in `.agents/skills/`.
|
|
|
128
128
|
runtimes use the in-process scheduler unless `ANALYTICS_ALERT_JOBS=0` is set.
|
|
129
129
|
External cron callers can POST `/api/analytics-alerts/run` with
|
|
130
130
|
`Authorization: Bearer $ANALYTICS_ALERTS_CRON_SECRET`.
|
|
131
|
+
Users can view and manage these rules in Settings under the Alerts card, which
|
|
132
|
+
uses the same action surface as the agent.
|
|
131
133
|
|
|
132
134
|
## Application State
|
|
133
135
|
|
|
@@ -153,6 +155,17 @@ details live in `.agents/skills/`.
|
|
|
153
155
|
scoped to one recording for two hours; SSR embeds an agent discovery payload
|
|
154
156
|
and the JSON APIs expose only summary/timeline metadata plus bounded event
|
|
155
157
|
reads.
|
|
158
|
+
- Recordings also capture console logs and network request metadata (no
|
|
159
|
+
bodies/headers, scrubbed URLs, truncated messages, per-session budgets);
|
|
160
|
+
ingest derives `errorCount`/`networkErrorCount` and adds
|
|
161
|
+
`console-error`/`network-error` timeline markers.
|
|
162
|
+
- The agent context includes a bounded `diagnostics` section (errors first) and
|
|
163
|
+
advertises `GET /api/session-replay/agent-diagnostics.json` with
|
|
164
|
+
`kind`/`level`/`limit` params (limit max 500) under the same `agent_access`
|
|
165
|
+
token — the primary signal when debugging a user-reported issue.
|
|
166
|
+
- The replay player has a Dev Tools panel with Console and Network tabs
|
|
167
|
+
(filters, search, jump-to-seek, error badge); extend it rather than adding a
|
|
168
|
+
separate replay debugging surface. See the `session-replay` skill.
|
|
156
169
|
- Dashboard rows that include `recording_id` should link to
|
|
157
170
|
`/sessions/:recordingId`; rows that only include `session_id` can link to a
|
|
158
171
|
filtered `/sessions` search.
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "@agent-native/core/server";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
+
import { listAnalyticsAlertRules } from "../server/lib/analytics-alerts";
|
|
12
13
|
import { listDashboardCatalog } from "../server/lib/dashboard-catalog";
|
|
13
14
|
import { getAnalysis, getDashboard } from "../server/lib/dashboards-store";
|
|
14
15
|
import { listAnalyticsPublicKeys } from "../server/lib/first-party-analytics.js";
|
|
@@ -195,6 +196,30 @@ export default defineAction({
|
|
|
195
196
|
}
|
|
196
197
|
} else if (nav?.view === "settings") {
|
|
197
198
|
screen.page = "settings";
|
|
199
|
+
const email = getRequestUserEmail();
|
|
200
|
+
if (email) {
|
|
201
|
+
const orgId = getRequestOrgId() || null;
|
|
202
|
+
const alertRules = await listAnalyticsAlertRules({ email, orgId });
|
|
203
|
+
screen.analyticsAlerts = alertRules.map((rule) => ({
|
|
204
|
+
id: rule.id,
|
|
205
|
+
name: rule.name,
|
|
206
|
+
enabled: rule.enabled,
|
|
207
|
+
severity: rule.severity,
|
|
208
|
+
eventName: rule.eventName,
|
|
209
|
+
filters: rule.filters,
|
|
210
|
+
thresholdMode: rule.thresholdMode,
|
|
211
|
+
distinctBy: rule.distinctBy,
|
|
212
|
+
threshold: rule.threshold,
|
|
213
|
+
windowMinutes: rule.windowMinutes,
|
|
214
|
+
cooldownMinutes: rule.cooldownMinutes,
|
|
215
|
+
channels: rule.channels,
|
|
216
|
+
emailRecipients: rule.emailRecipients,
|
|
217
|
+
lastStatus: rule.lastStatus,
|
|
218
|
+
lastEvaluatedAt: rule.lastEvaluatedAt,
|
|
219
|
+
lastTriggeredAt: rule.lastTriggeredAt,
|
|
220
|
+
lastError: rule.lastError,
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
198
223
|
}
|
|
199
224
|
|
|
200
225
|
if (Object.keys(screen).length === 0) {
|