@agent-native/core 0.84.66 → 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 +20 -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/analytics.ts +1 -34
- 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/core/src/server/ssr-handler.ts +18 -377
- 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/clips/server/routes/api/video/[recordingId].get.ts +0 -2
- 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/forms/server/lib/public-form-ssr.ts +0 -1
- 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 +1 -1
- 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/observability/routes.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +2 -2
- 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/analytics.d.ts +1 -19
- package/dist/server/analytics.d.ts.map +1 -1
- package/dist/server/analytics.js +1 -31
- package/dist/server/analytics.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/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +18 -271
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +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
|
@@ -29,17 +29,27 @@ export interface UseRunStuckDetectionOptions {
|
|
|
29
29
|
/** The thread to monitor. Pass null/undefined to disable polling. */
|
|
30
30
|
threadId: string | null | undefined;
|
|
31
31
|
/**
|
|
32
|
-
* Threshold above which an in-flight run is considered stuck.
|
|
33
|
-
* sits comfortably above the adapter's 75s no-progress
|
|
34
|
-
* automatic recovery has already had its chance.
|
|
32
|
+
* Threshold above which an in-flight FOREGROUND run is considered stuck.
|
|
33
|
+
* The default sits comfortably above the adapter's 75s no-progress
|
|
34
|
+
* reconnect — by then automatic recovery has already had its chance.
|
|
35
35
|
*/
|
|
36
36
|
stuckThresholdMs?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Threshold for BACKGROUND-dispatched runs (dispatchMode starts with
|
|
39
|
+
* "background"). The server owns recovery for these — its run-manager
|
|
40
|
+
* no-progress backstop (150s) and unclaimed-run sweep act first — so the
|
|
41
|
+
* user-facing "stuck" affordance is a late fallback, not a race against
|
|
42
|
+
* them. Selected inside the hook because the dispatch mode is only known
|
|
43
|
+
* from the same poll response that computes the elapsed time.
|
|
44
|
+
*/
|
|
45
|
+
backgroundStuckThresholdMs?: number;
|
|
37
46
|
/** Poll interval. Default 5_000ms. */
|
|
38
47
|
pollIntervalMs?: number;
|
|
39
48
|
/** API base path. Default `/_agent-native/agent-chat`. */
|
|
40
49
|
apiUrl?: string;
|
|
41
50
|
}
|
|
42
|
-
export declare
|
|
51
|
+
export declare const DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS = 180000;
|
|
52
|
+
export declare function useRunStuckDetection({ threadId, stuckThresholdMs, backgroundStuckThresholdMs, pollIntervalMs, apiUrl, }: UseRunStuckDetectionOptions): RunStuckState;
|
|
43
53
|
/**
|
|
44
54
|
* POST `/runs/:id/abort` so the server flips the run to "aborted" and the
|
|
45
55
|
* adapter's reconnect loop exits cleanly. Returns the run id that was
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-run-stuck-detection.d.ts","sourceRoot":"","sources":["../../src/client/use-run-stuck-detection.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,4EAA4E;IAC5E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,oDAAoD;IACpD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iEAAiE;IACjE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2EAA2E;IAC3E,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,4EAA4E;IAC5E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,2BAA2B;IAC1C,qEAAqE;IACrE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sCAAsC;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"use-run-stuck-detection.d.ts","sourceRoot":"","sources":["../../src/client/use-run-stuck-detection.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,4EAA4E;IAC5E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,oDAAoD;IACpD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iEAAiE;IACjE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2EAA2E;IAC3E,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,4EAA4E;IAC5E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,2BAA2B;IAC1C,qEAAqE;IACrE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,sCAAsC;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAGD,eAAO,MAAM,qCAAqC,SAAU,CAAC;AA0B7D,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,gBAA6C,EAC7C,0BAAkE,EAClE,cAAyC,EACzC,MAAM,GACP,EAAE,2BAA2B,GAAG,aAAa,CA6F7C;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,WAEzB,MAAM,WAAU,MAAM,KAAY,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBzE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState, useCallback } from "react";
|
|
2
2
|
import { agentNativePath } from "./api-path.js";
|
|
3
3
|
const DEFAULT_STUCK_THRESHOLD_MS = 90_000;
|
|
4
|
+
export const DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS = 180_000;
|
|
4
5
|
const DEFAULT_POLL_INTERVAL_MS = 5_000;
|
|
5
6
|
const IDLE_BACKOFF_INTERVAL_MS = 15_000;
|
|
6
7
|
const EMPTY_STATE = {
|
|
@@ -13,7 +14,7 @@ const EMPTY_STATE = {
|
|
|
13
14
|
heartbeatSinceMs: null,
|
|
14
15
|
dispatchMode: null,
|
|
15
16
|
};
|
|
16
|
-
export function useRunStuckDetection({ threadId, stuckThresholdMs = DEFAULT_STUCK_THRESHOLD_MS, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, apiUrl, }) {
|
|
17
|
+
export function useRunStuckDetection({ threadId, stuckThresholdMs = DEFAULT_STUCK_THRESHOLD_MS, backgroundStuckThresholdMs = DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, apiUrl, }) {
|
|
17
18
|
const [state, setState] = useState(EMPTY_STATE);
|
|
18
19
|
useEffect(() => {
|
|
19
20
|
// Reset on every thread change so the previous thread's stuck banner
|
|
@@ -44,10 +45,17 @@ export function useRunStuckDetection({ threadId, stuckThresholdMs = DEFAULT_STUC
|
|
|
44
45
|
const stuckSinceMs = lastProgressAt != null ? nowMs - lastProgressAt : null;
|
|
45
46
|
const heartbeatAt = data.heartbeatAt ?? null;
|
|
46
47
|
const heartbeatSinceMs = heartbeatAt != null ? nowMs - heartbeatAt : null;
|
|
48
|
+
const dispatchMode = typeof data.dispatchMode === "string" ? data.dispatchMode : null;
|
|
49
|
+
// Background-dispatched runs get the wider threshold: the server's
|
|
50
|
+
// own recovery (150s no-progress backstop + chained continuations)
|
|
51
|
+
// must get its chance before the user sees a "stuck" affordance.
|
|
52
|
+
const effectiveThresholdMs = dispatchMode?.startsWith("background")
|
|
53
|
+
? backgroundStuckThresholdMs
|
|
54
|
+
: stuckThresholdMs;
|
|
47
55
|
const isStuck = Boolean(data.active &&
|
|
48
56
|
data.status === "running" &&
|
|
49
57
|
stuckSinceMs != null &&
|
|
50
|
-
stuckSinceMs >
|
|
58
|
+
stuckSinceMs > effectiveThresholdMs);
|
|
51
59
|
setState({
|
|
52
60
|
isStuck,
|
|
53
61
|
runId: data.runId ?? null,
|
|
@@ -56,7 +64,7 @@ export function useRunStuckDetection({ threadId, stuckThresholdMs = DEFAULT_STUC
|
|
|
56
64
|
stuckSinceMs,
|
|
57
65
|
heartbeatAt,
|
|
58
66
|
heartbeatSinceMs,
|
|
59
|
-
dispatchMode
|
|
67
|
+
dispatchMode,
|
|
60
68
|
});
|
|
61
69
|
// Back off polling when nothing is in flight — there's no point
|
|
62
70
|
// hammering the endpoint while the chat is idle. We still poll
|
|
@@ -82,7 +90,13 @@ export function useRunStuckDetection({ threadId, stuckThresholdMs = DEFAULT_STUC
|
|
|
82
90
|
if (timer)
|
|
83
91
|
clearTimeout(timer);
|
|
84
92
|
};
|
|
85
|
-
}, [
|
|
93
|
+
}, [
|
|
94
|
+
threadId,
|
|
95
|
+
stuckThresholdMs,
|
|
96
|
+
backgroundStuckThresholdMs,
|
|
97
|
+
pollIntervalMs,
|
|
98
|
+
apiUrl,
|
|
99
|
+
]);
|
|
86
100
|
return state;
|
|
87
101
|
}
|
|
88
102
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-run-stuck-detection.js","sourceRoot":"","sources":["../../src/client/use-run-stuck-detection.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState, useCallback } from \"react\";\n\nimport { agentNativePath } from \"./api-path.js\";\n\n/**\n * Per-thread chat run health, derived from the durable `last_progress_at`\n * timestamp on the server. Drives the user-visible \"this chat looks stuck\"\n * affordance — distinct from the silent reconnect logic in\n * `agent-chat-adapter.ts`, which keeps trying in the background. When\n * automatic recovery isn't making progress (for whatever reason), this\n * hook surfaces a Retry / Cancel button to the user instead of leaving\n * them staring at a frozen spinner.\n */\nexport interface RunStuckState {\n /** True when an active run hasn't emitted an event for `stuckThresholdMs`. */\n isStuck: boolean;\n /** ID of the active run, or null when nothing is in flight. */\n runId: string | null;\n /** Server-side run status (\"running\" / \"completed\" / \"errored\" / etc.). */\n status: string | null;\n /** Server timestamp (ms) of the last emitted event, or null if none yet. */\n lastProgressAt: number | null;\n /** Milliseconds since `lastProgressAt`, or null. */\n stuckSinceMs: number | null;\n /** Server timestamp (ms) of the last process-alive heartbeat. */\n heartbeatAt: number | null;\n /** Milliseconds since `heartbeatAt`, computed against the server clock. */\n heartbeatSinceMs: number | null;\n /** How the run was dispatched, e.g. foreground or background-processing. */\n dispatchMode: string | null;\n}\n\nexport interface UseRunStuckDetectionOptions {\n /** The thread to monitor. Pass null/undefined to disable polling. */\n threadId: string | null | undefined;\n /**\n * Threshold above which an in-flight run is considered stuck. The default\n * sits comfortably above the adapter's 75s no-progress reconnect — by then\n * automatic recovery has already had its chance.\n */\n stuckThresholdMs?: number;\n /** Poll interval. Default 5_000ms. */\n pollIntervalMs?: number;\n /** API base path. Default `/_agent-native/agent-chat`. */\n apiUrl?: string;\n}\n\nconst DEFAULT_STUCK_THRESHOLD_MS = 90_000;\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst IDLE_BACKOFF_INTERVAL_MS = 15_000;\n\ninterface ActiveRunResponse {\n active: boolean;\n runId?: string;\n status?: string;\n heartbeatAt: number | null;\n lastProgressAt?: number | null;\n dispatchMode?: string | null;\n /** Server clock at response time, used to compute elapsed server-relative. */\n serverNow?: number;\n}\n\nconst EMPTY_STATE: RunStuckState = {\n isStuck: false,\n runId: null,\n status: null,\n lastProgressAt: null,\n stuckSinceMs: null,\n heartbeatAt: null,\n heartbeatSinceMs: null,\n dispatchMode: null,\n};\n\nexport function useRunStuckDetection({\n threadId,\n stuckThresholdMs = DEFAULT_STUCK_THRESHOLD_MS,\n pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,\n apiUrl,\n}: UseRunStuckDetectionOptions): RunStuckState {\n const [state, setState] = useState<RunStuckState>(EMPTY_STATE);\n\n useEffect(() => {\n // Reset on every thread change so the previous thread's stuck banner\n // doesn't bleed onto the new one before the first poll completes.\n setState(EMPTY_STATE);\n if (!threadId) return;\n\n const base = apiUrl ?? agentNativePath(\"/_agent-native/agent-chat\");\n let cancelled = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const poll = async () => {\n if (cancelled) return;\n let nextDelay = pollIntervalMs;\n try {\n const res = await fetch(\n `${base}/runs/active?threadId=${encodeURIComponent(threadId)}`,\n { credentials: \"same-origin\" },\n );\n if (cancelled) return;\n if (res.ok) {\n const data = (await res.json()) as ActiveRunResponse;\n const lastProgressAt = data.lastProgressAt ?? null;\n // Measure elapsed against the SERVER clock (serverNow) rather than the\n // client's Date.now(). lastProgressAt is a server timestamp, so client\n // clock skew of more than stuckThresholdMs would otherwise mark every\n // run stuck (clock ahead) or never stuck (clock behind). Fall back to\n // the client clock for older bundles that don't send serverNow.\n const nowMs = data.serverNow ?? Date.now();\n const stuckSinceMs =\n lastProgressAt != null ? nowMs - lastProgressAt : null;\n const heartbeatAt = data.heartbeatAt ?? null;\n const heartbeatSinceMs =\n heartbeatAt != null ? nowMs - heartbeatAt : null;\n const isStuck = Boolean(\n data.active &&\n data.status === \"running\" &&\n stuckSinceMs != null &&\n stuckSinceMs > stuckThresholdMs,\n );\n setState({\n isStuck,\n runId: data.runId ?? null,\n status: data.status ?? null,\n lastProgressAt,\n stuckSinceMs,\n heartbeatAt,\n heartbeatSinceMs,\n dispatchMode:\n typeof data.dispatchMode === \"string\" ? data.dispatchMode : null,\n });\n // Back off polling when nothing is in flight — there's no point\n // hammering the endpoint while the chat is idle. We still poll\n // occasionally so a fresh run started in another tab is picked up.\n if (!data.active || data.status !== \"running\") {\n nextDelay = IDLE_BACKOFF_INTERVAL_MS;\n }\n }\n } catch {\n // Network blip — leave previous state. Next tick will retry.\n }\n if (!cancelled) {\n timer = setTimeout(poll, nextDelay);\n }\n };\n\n // Stagger the first poll so a freshly-started run isn't immediately\n // classified as stuck before the server has had a chance to record\n // any progress events.\n timer = setTimeout(poll, 2_000);\n\n return () => {\n cancelled = true;\n if (timer) clearTimeout(timer);\n };\n }, [threadId, stuckThresholdMs, pollIntervalMs, apiUrl]);\n\n return state;\n}\n\n/**\n * POST `/runs/:id/abort` so the server flips the run to \"aborted\" and the\n * adapter's reconnect loop exits cleanly. Returns the run id that was\n * aborted (or null on failure) so callers can correlate observability\n * events. Best-effort — failures are swallowed, since the user's intent\n * is already captured locally.\n */\nexport function useAbortRun(apiUrl?: string) {\n return useCallback(\n async (runId: string, reason: string = \"user\"): Promise<string | null> => {\n const base = apiUrl ?? agentNativePath(\"/_agent-native/agent-chat\");\n try {\n const res = await fetch(\n `${base}/runs/${encodeURIComponent(runId)}/abort`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"same-origin\",\n body: JSON.stringify({ reason }),\n },\n );\n if (!res.ok) return null;\n return runId;\n } catch {\n return null;\n }\n },\n [apiUrl],\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-run-stuck-detection.js","sourceRoot":"","sources":["../../src/client/use-run-stuck-detection.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState, useCallback } from \"react\";\n\nimport { agentNativePath } from \"./api-path.js\";\n\n/**\n * Per-thread chat run health, derived from the durable `last_progress_at`\n * timestamp on the server. Drives the user-visible \"this chat looks stuck\"\n * affordance — distinct from the silent reconnect logic in\n * `agent-chat-adapter.ts`, which keeps trying in the background. When\n * automatic recovery isn't making progress (for whatever reason), this\n * hook surfaces a Retry / Cancel button to the user instead of leaving\n * them staring at a frozen spinner.\n */\nexport interface RunStuckState {\n /** True when an active run hasn't emitted an event for `stuckThresholdMs`. */\n isStuck: boolean;\n /** ID of the active run, or null when nothing is in flight. */\n runId: string | null;\n /** Server-side run status (\"running\" / \"completed\" / \"errored\" / etc.). */\n status: string | null;\n /** Server timestamp (ms) of the last emitted event, or null if none yet. */\n lastProgressAt: number | null;\n /** Milliseconds since `lastProgressAt`, or null. */\n stuckSinceMs: number | null;\n /** Server timestamp (ms) of the last process-alive heartbeat. */\n heartbeatAt: number | null;\n /** Milliseconds since `heartbeatAt`, computed against the server clock. */\n heartbeatSinceMs: number | null;\n /** How the run was dispatched, e.g. foreground or background-processing. */\n dispatchMode: string | null;\n}\n\nexport interface UseRunStuckDetectionOptions {\n /** The thread to monitor. Pass null/undefined to disable polling. */\n threadId: string | null | undefined;\n /**\n * Threshold above which an in-flight FOREGROUND run is considered stuck.\n * The default sits comfortably above the adapter's 75s no-progress\n * reconnect — by then automatic recovery has already had its chance.\n */\n stuckThresholdMs?: number;\n /**\n * Threshold for BACKGROUND-dispatched runs (dispatchMode starts with\n * \"background\"). The server owns recovery for these — its run-manager\n * no-progress backstop (150s) and unclaimed-run sweep act first — so the\n * user-facing \"stuck\" affordance is a late fallback, not a race against\n * them. Selected inside the hook because the dispatch mode is only known\n * from the same poll response that computes the elapsed time.\n */\n backgroundStuckThresholdMs?: number;\n /** Poll interval. Default 5_000ms. */\n pollIntervalMs?: number;\n /** API base path. Default `/_agent-native/agent-chat`. */\n apiUrl?: string;\n}\n\nconst DEFAULT_STUCK_THRESHOLD_MS = 90_000;\nexport const DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS = 180_000;\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst IDLE_BACKOFF_INTERVAL_MS = 15_000;\n\ninterface ActiveRunResponse {\n active: boolean;\n runId?: string;\n status?: string;\n heartbeatAt: number | null;\n lastProgressAt?: number | null;\n dispatchMode?: string | null;\n /** Server clock at response time, used to compute elapsed server-relative. */\n serverNow?: number;\n}\n\nconst EMPTY_STATE: RunStuckState = {\n isStuck: false,\n runId: null,\n status: null,\n lastProgressAt: null,\n stuckSinceMs: null,\n heartbeatAt: null,\n heartbeatSinceMs: null,\n dispatchMode: null,\n};\n\nexport function useRunStuckDetection({\n threadId,\n stuckThresholdMs = DEFAULT_STUCK_THRESHOLD_MS,\n backgroundStuckThresholdMs = DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS,\n pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,\n apiUrl,\n}: UseRunStuckDetectionOptions): RunStuckState {\n const [state, setState] = useState<RunStuckState>(EMPTY_STATE);\n\n useEffect(() => {\n // Reset on every thread change so the previous thread's stuck banner\n // doesn't bleed onto the new one before the first poll completes.\n setState(EMPTY_STATE);\n if (!threadId) return;\n\n const base = apiUrl ?? agentNativePath(\"/_agent-native/agent-chat\");\n let cancelled = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const poll = async () => {\n if (cancelled) return;\n let nextDelay = pollIntervalMs;\n try {\n const res = await fetch(\n `${base}/runs/active?threadId=${encodeURIComponent(threadId)}`,\n { credentials: \"same-origin\" },\n );\n if (cancelled) return;\n if (res.ok) {\n const data = (await res.json()) as ActiveRunResponse;\n const lastProgressAt = data.lastProgressAt ?? null;\n // Measure elapsed against the SERVER clock (serverNow) rather than the\n // client's Date.now(). lastProgressAt is a server timestamp, so client\n // clock skew of more than stuckThresholdMs would otherwise mark every\n // run stuck (clock ahead) or never stuck (clock behind). Fall back to\n // the client clock for older bundles that don't send serverNow.\n const nowMs = data.serverNow ?? Date.now();\n const stuckSinceMs =\n lastProgressAt != null ? nowMs - lastProgressAt : null;\n const heartbeatAt = data.heartbeatAt ?? null;\n const heartbeatSinceMs =\n heartbeatAt != null ? nowMs - heartbeatAt : null;\n const dispatchMode =\n typeof data.dispatchMode === \"string\" ? data.dispatchMode : null;\n // Background-dispatched runs get the wider threshold: the server's\n // own recovery (150s no-progress backstop + chained continuations)\n // must get its chance before the user sees a \"stuck\" affordance.\n const effectiveThresholdMs = dispatchMode?.startsWith(\"background\")\n ? backgroundStuckThresholdMs\n : stuckThresholdMs;\n const isStuck = Boolean(\n data.active &&\n data.status === \"running\" &&\n stuckSinceMs != null &&\n stuckSinceMs > effectiveThresholdMs,\n );\n setState({\n isStuck,\n runId: data.runId ?? null,\n status: data.status ?? null,\n lastProgressAt,\n stuckSinceMs,\n heartbeatAt,\n heartbeatSinceMs,\n dispatchMode,\n });\n // Back off polling when nothing is in flight — there's no point\n // hammering the endpoint while the chat is idle. We still poll\n // occasionally so a fresh run started in another tab is picked up.\n if (!data.active || data.status !== \"running\") {\n nextDelay = IDLE_BACKOFF_INTERVAL_MS;\n }\n }\n } catch {\n // Network blip — leave previous state. Next tick will retry.\n }\n if (!cancelled) {\n timer = setTimeout(poll, nextDelay);\n }\n };\n\n // Stagger the first poll so a freshly-started run isn't immediately\n // classified as stuck before the server has had a chance to record\n // any progress events.\n timer = setTimeout(poll, 2_000);\n\n return () => {\n cancelled = true;\n if (timer) clearTimeout(timer);\n };\n }, [\n threadId,\n stuckThresholdMs,\n backgroundStuckThresholdMs,\n pollIntervalMs,\n apiUrl,\n ]);\n\n return state;\n}\n\n/**\n * POST `/runs/:id/abort` so the server flips the run to \"aborted\" and the\n * adapter's reconnect loop exits cleanly. Returns the run id that was\n * aborted (or null on failure) so callers can correlate observability\n * events. Best-effort — failures are swallowed, since the user's intent\n * is already captured locally.\n */\nexport function useAbortRun(apiUrl?: string) {\n return useCallback(\n async (runId: string, reason: string = \"user\"): Promise<string | null> => {\n const base = apiUrl ?? agentNativePath(\"/_agent-native/agent-chat\");\n try {\n const res = await fetch(\n `${base}/runs/${encodeURIComponent(runId)}/abort`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"same-origin\",\n body: JSON.stringify({ reason }),\n },\n );\n if (!res.ok) return null;\n return runId;\n } catch {\n return null;\n }\n },\n [apiUrl],\n );\n}\n"]}
|
|
@@ -49,11 +49,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
49
49
|
error: string;
|
|
50
50
|
states?: undefined;
|
|
51
51
|
} | {
|
|
52
|
+
error?: undefined;
|
|
52
53
|
states: {
|
|
53
54
|
clientId: number;
|
|
54
55
|
state: string;
|
|
55
56
|
}[];
|
|
56
|
-
error?: undefined;
|
|
57
57
|
}>>;
|
|
58
58
|
/**
|
|
59
59
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -64,10 +64,10 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
64
64
|
error: string;
|
|
65
65
|
users?: undefined;
|
|
66
66
|
} | {
|
|
67
|
+
error?: undefined;
|
|
67
68
|
users: {
|
|
68
69
|
clientId: number;
|
|
69
70
|
lastSeen: number;
|
|
70
71
|
}[];
|
|
71
|
-
error?: undefined;
|
|
72
72
|
}>>;
|
|
73
73
|
//# sourceMappingURL=awareness.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAS3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAKD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,IAAI,CAUN;AAKD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAUD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa
|
|
1
|
+
{"version":3,"file":"awareness.d.ts","sourceRoot":"","sources":["../../src/collab/awareness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAS3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,sBAAsB,EAAG,kBAA2B,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAKD,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,KAAK,CAAC,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,IAAI,CAUN;AAKD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAO1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI,CAOnE;AAUD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;;;kBAuCa,MAAM;eAAS,MAAM;;GAa1D,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;kBAWM,MAAM;kBAAY,MAAM;;GAMvD,CAAC"}
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -41,9 +41,9 @@ export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import
|
|
|
41
41
|
* Body: { text: string, fieldName?: string, requestSource?: string }
|
|
42
42
|
*/
|
|
43
43
|
export declare const postCollabText: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
44
|
+
text?: undefined;
|
|
44
45
|
ok?: undefined;
|
|
45
46
|
error: string;
|
|
46
|
-
text?: undefined;
|
|
47
47
|
} | {
|
|
48
48
|
error?: undefined;
|
|
49
49
|
ok: boolean;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type DbExec, type Dialect } from "./client.js";
|
|
2
|
+
export interface DatabaseRuntimeFingerprint {
|
|
3
|
+
configured: boolean;
|
|
4
|
+
source: string;
|
|
5
|
+
dialect: Dialect;
|
|
6
|
+
urlHash?: string;
|
|
7
|
+
protocol?: string;
|
|
8
|
+
host?: string;
|
|
9
|
+
database?: string;
|
|
10
|
+
appName?: string;
|
|
11
|
+
authTokenConfigured: boolean;
|
|
12
|
+
netlifyDatabaseUrlConfigured: boolean;
|
|
13
|
+
neon?: {
|
|
14
|
+
endpointId?: string;
|
|
15
|
+
pooled: boolean;
|
|
16
|
+
projectHost?: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface RuntimeDebugFingerprint {
|
|
20
|
+
app: string;
|
|
21
|
+
environment: string;
|
|
22
|
+
deployContext?: string;
|
|
23
|
+
deployId?: string;
|
|
24
|
+
commitRef?: string;
|
|
25
|
+
branch?: string;
|
|
26
|
+
siteName?: string;
|
|
27
|
+
database: DatabaseRuntimeFingerprint;
|
|
28
|
+
}
|
|
29
|
+
export interface RequiredSchemaTable {
|
|
30
|
+
table: string;
|
|
31
|
+
columns: string[];
|
|
32
|
+
}
|
|
33
|
+
export interface DatabaseSchemaHealthResult {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
checked: boolean;
|
|
36
|
+
dialect: Dialect;
|
|
37
|
+
missingTables: string[];
|
|
38
|
+
missingColumns: Array<{
|
|
39
|
+
table: string;
|
|
40
|
+
column: string;
|
|
41
|
+
}>;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
export declare function getDatabaseRuntimeFingerprint(): DatabaseRuntimeFingerprint;
|
|
45
|
+
export declare function getRuntimeDebugFingerprint(): RuntimeDebugFingerprint;
|
|
46
|
+
export declare function runDatabaseSchemaHealthCheck(options?: {
|
|
47
|
+
exec?: DbExec;
|
|
48
|
+
dialect?: Dialect;
|
|
49
|
+
required?: RequiredSchemaTable[];
|
|
50
|
+
}): Promise<DatabaseSchemaHealthResult>;
|
|
51
|
+
export declare function formatRuntimeDebugFingerprint(fingerprint?: RuntimeDebugFingerprint): string;
|
|
52
|
+
//# sourceMappingURL=runtime-diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-diagnostics.d.ts","sourceRoot":"","sources":["../../src/db/runtime-diagnostics.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,MAAM,EACX,KAAK,OAAO,EACb,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,4BAA4B,EAAE,OAAO,CAAC;IACtC,IAAI,CAAC,EAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,OAAO,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,0BAA0B,CAAC;CACtC;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+HD,wBAAgB,6BAA6B,IAAI,0BAA0B,CAa1E;AAED,wBAAgB,0BAA0B,IAAI,uBAAuB,CAkBpE;AA4CD,wBAAsB,4BAA4B,CAChD,OAAO,GAAE;IACP,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC7B,GACL,OAAO,CAAC,0BAA0B,CAAC,CAsCrC;AAED,wBAAgB,6BAA6B,CAC3C,WAAW,GAAE,uBAAsD,GAClE,MAAM,CA2BR"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { getDatabaseUrl, getDialect, getDbExec, } from "./client.js";
|
|
3
|
+
const DEFAULT_REQUIRED_SCHEMA = [
|
|
4
|
+
{
|
|
5
|
+
table: "agent_runs",
|
|
6
|
+
columns: [
|
|
7
|
+
"id",
|
|
8
|
+
"thread_id",
|
|
9
|
+
"status",
|
|
10
|
+
"started_at",
|
|
11
|
+
"completed_at",
|
|
12
|
+
"heartbeat_at",
|
|
13
|
+
"last_progress_at",
|
|
14
|
+
"turn_id",
|
|
15
|
+
"error_code",
|
|
16
|
+
"error_detail",
|
|
17
|
+
"terminal_reason",
|
|
18
|
+
"dispatch_mode",
|
|
19
|
+
"diag_stage",
|
|
20
|
+
"worker_stage",
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
table: "agent_run_events",
|
|
25
|
+
columns: ["run_id", "seq", "event_at", "event_data"],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
table: "agent_tool_ledger",
|
|
29
|
+
columns: ["thread_id", "tool_key", "result_summary", "completed_at"],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
table: "chat_threads",
|
|
33
|
+
columns: [
|
|
34
|
+
"id",
|
|
35
|
+
"owner_email",
|
|
36
|
+
"title",
|
|
37
|
+
"preview",
|
|
38
|
+
"thread_data",
|
|
39
|
+
"message_count",
|
|
40
|
+
"created_at",
|
|
41
|
+
"updated_at",
|
|
42
|
+
"scope_type",
|
|
43
|
+
"scope_id",
|
|
44
|
+
"scope_label",
|
|
45
|
+
"pinned_at",
|
|
46
|
+
"archived_at",
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
table: "settings",
|
|
51
|
+
columns: ["key", "value", "updated_at"],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
table: "application_state",
|
|
55
|
+
columns: ["session_id", "key", "value", "updated_at"],
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
function envValue(key) {
|
|
59
|
+
const value = process.env[key]?.trim();
|
|
60
|
+
return value || undefined;
|
|
61
|
+
}
|
|
62
|
+
function appEnvPrefix() {
|
|
63
|
+
return envValue("APP_NAME")?.toUpperCase().replace(/-/g, "_");
|
|
64
|
+
}
|
|
65
|
+
function databaseUrlSource() {
|
|
66
|
+
const appName = appEnvPrefix();
|
|
67
|
+
if (appName && envValue(`${appName}_DATABASE_URL`)) {
|
|
68
|
+
return `${appName}_DATABASE_URL`;
|
|
69
|
+
}
|
|
70
|
+
if (envValue("DATABASE_URL"))
|
|
71
|
+
return "DATABASE_URL";
|
|
72
|
+
if (envValue("NETLIFY_DATABASE_URL"))
|
|
73
|
+
return "NETLIFY_DATABASE_URL";
|
|
74
|
+
return "default";
|
|
75
|
+
}
|
|
76
|
+
function databaseAuthTokenConfigured() {
|
|
77
|
+
const appName = appEnvPrefix();
|
|
78
|
+
return Boolean((appName && envValue(`${appName}_DATABASE_AUTH_TOKEN`)) ||
|
|
79
|
+
envValue("DATABASE_AUTH_TOKEN") ||
|
|
80
|
+
envValue("NETLIFY_DATABASE_AUTH_TOKEN"));
|
|
81
|
+
}
|
|
82
|
+
function shortHash(value) {
|
|
83
|
+
if (!value)
|
|
84
|
+
return undefined;
|
|
85
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
86
|
+
}
|
|
87
|
+
function parseDatabaseUrl(url) {
|
|
88
|
+
if (!url)
|
|
89
|
+
return {};
|
|
90
|
+
if (url.startsWith("pglite:")) {
|
|
91
|
+
return { protocol: "pglite", database: url.slice("pglite:".length) };
|
|
92
|
+
}
|
|
93
|
+
if (url.startsWith("file:")) {
|
|
94
|
+
return { protocol: "file", database: url.slice("file:".length) };
|
|
95
|
+
}
|
|
96
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) {
|
|
97
|
+
return { protocol: "sqlite", database: url };
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const parsed = new URL(url);
|
|
101
|
+
const host = parsed.hostname || undefined;
|
|
102
|
+
const neonMatch = host?.match(/^(ep-[a-z0-9-]+?)(-pooler)?\.(.+)$/i);
|
|
103
|
+
const isNeon = Boolean(host?.endsWith(".neon.tech") && neonMatch);
|
|
104
|
+
return {
|
|
105
|
+
protocol: parsed.protocol.replace(/:$/, ""),
|
|
106
|
+
host,
|
|
107
|
+
database: parsed.pathname.replace(/^\//, "") || undefined,
|
|
108
|
+
...(isNeon
|
|
109
|
+
? {
|
|
110
|
+
neon: {
|
|
111
|
+
endpointId: neonMatch?.[1],
|
|
112
|
+
pooled: Boolean(neonMatch?.[2]),
|
|
113
|
+
projectHost: neonMatch?.[3],
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
: {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export function getDatabaseRuntimeFingerprint() {
|
|
124
|
+
const url = getDatabaseUrl();
|
|
125
|
+
const parsed = parseDatabaseUrl(url);
|
|
126
|
+
return {
|
|
127
|
+
configured: Boolean(url),
|
|
128
|
+
source: databaseUrlSource(),
|
|
129
|
+
dialect: getDialect(),
|
|
130
|
+
urlHash: shortHash(url),
|
|
131
|
+
appName: envValue("APP_NAME"),
|
|
132
|
+
authTokenConfigured: databaseAuthTokenConfigured(),
|
|
133
|
+
netlifyDatabaseUrlConfigured: Boolean(envValue("NETLIFY_DATABASE_URL")),
|
|
134
|
+
...parsed,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function getRuntimeDebugFingerprint() {
|
|
138
|
+
return {
|
|
139
|
+
app: envValue("APP_NAME") ?? "unknown",
|
|
140
|
+
environment: envValue("NODE_ENV") ?? "unknown",
|
|
141
|
+
deployContext: envValue("CONTEXT") ?? envValue("VERCEL_ENV"),
|
|
142
|
+
deployId: envValue("DEPLOY_ID") ?? envValue("VERCEL_DEPLOYMENT_ID"),
|
|
143
|
+
commitRef: envValue("COMMIT_REF") ??
|
|
144
|
+
envValue("NETLIFY_COMMIT_REF") ??
|
|
145
|
+
envValue("VERCEL_GIT_COMMIT_SHA") ??
|
|
146
|
+
envValue("GIT_COMMIT_SHA"),
|
|
147
|
+
branch: envValue("BRANCH") ??
|
|
148
|
+
envValue("HEAD") ??
|
|
149
|
+
envValue("VERCEL_GIT_COMMIT_REF"),
|
|
150
|
+
siteName: envValue("SITE_NAME") ?? envValue("NETLIFY_SITE_NAME"),
|
|
151
|
+
database: getDatabaseRuntimeFingerprint(),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function assertSafeIdentifier(value) {
|
|
155
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
|
|
156
|
+
throw new Error(`Unsafe schema identifier: ${value}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function postgresTableColumns(exec, table) {
|
|
160
|
+
const result = await exec.execute({
|
|
161
|
+
sql: `SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ?`,
|
|
162
|
+
args: [table],
|
|
163
|
+
});
|
|
164
|
+
if (!result.rows.length)
|
|
165
|
+
return null;
|
|
166
|
+
return new Set(result.rows.map((row) => String(row.column_name)));
|
|
167
|
+
}
|
|
168
|
+
async function sqliteTableColumns(exec, table) {
|
|
169
|
+
assertSafeIdentifier(table);
|
|
170
|
+
const exists = await exec.execute({
|
|
171
|
+
sql: `SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1`,
|
|
172
|
+
args: [table],
|
|
173
|
+
});
|
|
174
|
+
if (!exists.rows.length)
|
|
175
|
+
return null;
|
|
176
|
+
const result = await exec.execute(`PRAGMA table_info(${table})`);
|
|
177
|
+
return new Set(result.rows.map((row) => String(row.name)));
|
|
178
|
+
}
|
|
179
|
+
async function tableColumns(exec, dialect, table) {
|
|
180
|
+
return dialect === "postgres"
|
|
181
|
+
? postgresTableColumns(exec, table)
|
|
182
|
+
: sqliteTableColumns(exec, table);
|
|
183
|
+
}
|
|
184
|
+
export async function runDatabaseSchemaHealthCheck(options = {}) {
|
|
185
|
+
const dialect = options.dialect ?? getDialect();
|
|
186
|
+
const exec = options.exec ?? getDbExec();
|
|
187
|
+
const required = options.required ?? DEFAULT_REQUIRED_SCHEMA;
|
|
188
|
+
const missingTables = [];
|
|
189
|
+
const missingColumns = [];
|
|
190
|
+
try {
|
|
191
|
+
for (const requirement of required) {
|
|
192
|
+
const columns = await tableColumns(exec, dialect, requirement.table);
|
|
193
|
+
if (!columns) {
|
|
194
|
+
missingTables.push(requirement.table);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
for (const column of requirement.columns) {
|
|
198
|
+
if (!columns.has(column)) {
|
|
199
|
+
missingColumns.push({ table: requirement.table, column });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
return {
|
|
206
|
+
ok: false,
|
|
207
|
+
checked: false,
|
|
208
|
+
dialect,
|
|
209
|
+
missingTables,
|
|
210
|
+
missingColumns,
|
|
211
|
+
error: err instanceof Error ? err.message : String(err),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
ok: missingTables.length === 0 && missingColumns.length === 0,
|
|
216
|
+
checked: true,
|
|
217
|
+
dialect,
|
|
218
|
+
missingTables,
|
|
219
|
+
missingColumns,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
export function formatRuntimeDebugFingerprint(fingerprint = getRuntimeDebugFingerprint()) {
|
|
223
|
+
const db = fingerprint.database;
|
|
224
|
+
return [
|
|
225
|
+
`app: ${fingerprint.app}`,
|
|
226
|
+
`environment: ${fingerprint.environment}`,
|
|
227
|
+
fingerprint.deployContext
|
|
228
|
+
? `deploy_context: ${fingerprint.deployContext}`
|
|
229
|
+
: "",
|
|
230
|
+
fingerprint.deployId ? `deploy_id: ${fingerprint.deployId}` : "",
|
|
231
|
+
fingerprint.commitRef ? `commit_ref: ${fingerprint.commitRef}` : "",
|
|
232
|
+
fingerprint.branch ? `branch: ${fingerprint.branch}` : "",
|
|
233
|
+
fingerprint.siteName ? `site_name: ${fingerprint.siteName}` : "",
|
|
234
|
+
`db_configured: ${db.configured}`,
|
|
235
|
+
`db_source: ${db.source}`,
|
|
236
|
+
`db_dialect: ${db.dialect}`,
|
|
237
|
+
db.protocol ? `db_protocol: ${db.protocol}` : "",
|
|
238
|
+
db.host ? `db_host: ${db.host}` : "",
|
|
239
|
+
db.database ? `db_database: ${db.database}` : "",
|
|
240
|
+
db.urlHash ? `db_url_hash: ${db.urlHash}` : "",
|
|
241
|
+
db.neon?.endpointId ? `db_neon_endpoint: ${db.neon.endpointId}` : "",
|
|
242
|
+
db.neon ? `db_neon_pooled: ${db.neon.pooled}` : "",
|
|
243
|
+
db.neon?.projectHost ? `db_neon_project_host: ${db.neon.projectHost}` : "",
|
|
244
|
+
`db_auth_token_configured: ${db.authTokenConfigured}`,
|
|
245
|
+
`netlify_database_url_configured: ${db.netlifyDatabaseUrlConfigured}`,
|
|
246
|
+
]
|
|
247
|
+
.filter(Boolean)
|
|
248
|
+
.join("\n");
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=runtime-diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-diagnostics.js","sourceRoot":"","sources":["../../src/db/runtime-diagnostics.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\n\nimport {\n getDatabaseUrl,\n getDialect,\n getDbExec,\n type DbExec,\n type Dialect,\n} from \"./client.js\";\n\nexport interface DatabaseRuntimeFingerprint {\n configured: boolean;\n source: string;\n dialect: Dialect;\n urlHash?: string;\n protocol?: string;\n host?: string;\n database?: string;\n appName?: string;\n authTokenConfigured: boolean;\n netlifyDatabaseUrlConfigured: boolean;\n neon?: {\n endpointId?: string;\n pooled: boolean;\n projectHost?: string;\n };\n}\n\nexport interface RuntimeDebugFingerprint {\n app: string;\n environment: string;\n deployContext?: string;\n deployId?: string;\n commitRef?: string;\n branch?: string;\n siteName?: string;\n database: DatabaseRuntimeFingerprint;\n}\n\nexport interface RequiredSchemaTable {\n table: string;\n columns: string[];\n}\n\nexport interface DatabaseSchemaHealthResult {\n ok: boolean;\n checked: boolean;\n dialect: Dialect;\n missingTables: string[];\n missingColumns: Array<{ table: string; column: string }>;\n error?: string;\n}\n\nconst DEFAULT_REQUIRED_SCHEMA: RequiredSchemaTable[] = [\n {\n table: \"agent_runs\",\n columns: [\n \"id\",\n \"thread_id\",\n \"status\",\n \"started_at\",\n \"completed_at\",\n \"heartbeat_at\",\n \"last_progress_at\",\n \"turn_id\",\n \"error_code\",\n \"error_detail\",\n \"terminal_reason\",\n \"dispatch_mode\",\n \"diag_stage\",\n \"worker_stage\",\n ],\n },\n {\n table: \"agent_run_events\",\n columns: [\"run_id\", \"seq\", \"event_at\", \"event_data\"],\n },\n {\n table: \"agent_tool_ledger\",\n columns: [\"thread_id\", \"tool_key\", \"result_summary\", \"completed_at\"],\n },\n {\n table: \"chat_threads\",\n columns: [\n \"id\",\n \"owner_email\",\n \"title\",\n \"preview\",\n \"thread_data\",\n \"message_count\",\n \"created_at\",\n \"updated_at\",\n \"scope_type\",\n \"scope_id\",\n \"scope_label\",\n \"pinned_at\",\n \"archived_at\",\n ],\n },\n {\n table: \"settings\",\n columns: [\"key\", \"value\", \"updated_at\"],\n },\n {\n table: \"application_state\",\n columns: [\"session_id\", \"key\", \"value\", \"updated_at\"],\n },\n];\n\nfunction envValue(key: string): string | undefined {\n const value = process.env[key]?.trim();\n return value || undefined;\n}\n\nfunction appEnvPrefix(): string | undefined {\n return envValue(\"APP_NAME\")?.toUpperCase().replace(/-/g, \"_\");\n}\n\nfunction databaseUrlSource(): string {\n const appName = appEnvPrefix();\n if (appName && envValue(`${appName}_DATABASE_URL`)) {\n return `${appName}_DATABASE_URL`;\n }\n if (envValue(\"DATABASE_URL\")) return \"DATABASE_URL\";\n if (envValue(\"NETLIFY_DATABASE_URL\")) return \"NETLIFY_DATABASE_URL\";\n return \"default\";\n}\n\nfunction databaseAuthTokenConfigured(): boolean {\n const appName = appEnvPrefix();\n return Boolean(\n (appName && envValue(`${appName}_DATABASE_AUTH_TOKEN`)) ||\n envValue(\"DATABASE_AUTH_TOKEN\") ||\n envValue(\"NETLIFY_DATABASE_AUTH_TOKEN\"),\n );\n}\n\nfunction shortHash(value: string): string | undefined {\n if (!value) return undefined;\n return createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 12);\n}\n\nfunction parseDatabaseUrl(url: string): Partial<DatabaseRuntimeFingerprint> {\n if (!url) return {};\n if (url.startsWith(\"pglite:\")) {\n return { protocol: \"pglite\", database: url.slice(\"pglite:\".length) };\n }\n if (url.startsWith(\"file:\")) {\n return { protocol: \"file\", database: url.slice(\"file:\".length) };\n }\n if (!/^[a-z][a-z0-9+.-]*:\\/\\//i.test(url)) {\n return { protocol: \"sqlite\", database: url };\n }\n\n try {\n const parsed = new URL(url);\n const host = parsed.hostname || undefined;\n const neonMatch = host?.match(/^(ep-[a-z0-9-]+?)(-pooler)?\\.(.+)$/i);\n const isNeon = Boolean(host?.endsWith(\".neon.tech\") && neonMatch);\n return {\n protocol: parsed.protocol.replace(/:$/, \"\"),\n host,\n database: parsed.pathname.replace(/^\\//, \"\") || undefined,\n ...(isNeon\n ? {\n neon: {\n endpointId: neonMatch?.[1],\n pooled: Boolean(neonMatch?.[2]),\n projectHost: neonMatch?.[3],\n },\n }\n : {}),\n };\n } catch {\n return {};\n }\n}\n\nexport function getDatabaseRuntimeFingerprint(): DatabaseRuntimeFingerprint {\n const url = getDatabaseUrl();\n const parsed = parseDatabaseUrl(url);\n return {\n configured: Boolean(url),\n source: databaseUrlSource(),\n dialect: getDialect(),\n urlHash: shortHash(url),\n appName: envValue(\"APP_NAME\"),\n authTokenConfigured: databaseAuthTokenConfigured(),\n netlifyDatabaseUrlConfigured: Boolean(envValue(\"NETLIFY_DATABASE_URL\")),\n ...parsed,\n };\n}\n\nexport function getRuntimeDebugFingerprint(): RuntimeDebugFingerprint {\n return {\n app: envValue(\"APP_NAME\") ?? \"unknown\",\n environment: envValue(\"NODE_ENV\") ?? \"unknown\",\n deployContext: envValue(\"CONTEXT\") ?? envValue(\"VERCEL_ENV\"),\n deployId: envValue(\"DEPLOY_ID\") ?? envValue(\"VERCEL_DEPLOYMENT_ID\"),\n commitRef:\n envValue(\"COMMIT_REF\") ??\n envValue(\"NETLIFY_COMMIT_REF\") ??\n envValue(\"VERCEL_GIT_COMMIT_SHA\") ??\n envValue(\"GIT_COMMIT_SHA\"),\n branch:\n envValue(\"BRANCH\") ??\n envValue(\"HEAD\") ??\n envValue(\"VERCEL_GIT_COMMIT_REF\"),\n siteName: envValue(\"SITE_NAME\") ?? envValue(\"NETLIFY_SITE_NAME\"),\n database: getDatabaseRuntimeFingerprint(),\n };\n}\n\nfunction assertSafeIdentifier(value: string): void {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {\n throw new Error(`Unsafe schema identifier: ${value}`);\n }\n}\n\nasync function postgresTableColumns(\n exec: DbExec,\n table: string,\n): Promise<Set<string> | null> {\n const result = await exec.execute({\n sql: `SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ?`,\n args: [table],\n });\n if (!result.rows.length) return null;\n return new Set(result.rows.map((row) => String(row.column_name)));\n}\n\nasync function sqliteTableColumns(\n exec: DbExec,\n table: string,\n): Promise<Set<string> | null> {\n assertSafeIdentifier(table);\n const exists = await exec.execute({\n sql: `SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1`,\n args: [table],\n });\n if (!exists.rows.length) return null;\n const result = await exec.execute(`PRAGMA table_info(${table})`);\n return new Set(result.rows.map((row) => String(row.name)));\n}\n\nasync function tableColumns(\n exec: DbExec,\n dialect: Dialect,\n table: string,\n): Promise<Set<string> | null> {\n return dialect === \"postgres\"\n ? postgresTableColumns(exec, table)\n : sqliteTableColumns(exec, table);\n}\n\nexport async function runDatabaseSchemaHealthCheck(\n options: {\n exec?: DbExec;\n dialect?: Dialect;\n required?: RequiredSchemaTable[];\n } = {},\n): Promise<DatabaseSchemaHealthResult> {\n const dialect = options.dialect ?? getDialect();\n const exec = options.exec ?? getDbExec();\n const required = options.required ?? DEFAULT_REQUIRED_SCHEMA;\n const missingTables: string[] = [];\n const missingColumns: Array<{ table: string; column: string }> = [];\n\n try {\n for (const requirement of required) {\n const columns = await tableColumns(exec, dialect, requirement.table);\n if (!columns) {\n missingTables.push(requirement.table);\n continue;\n }\n for (const column of requirement.columns) {\n if (!columns.has(column)) {\n missingColumns.push({ table: requirement.table, column });\n }\n }\n }\n } catch (err) {\n return {\n ok: false,\n checked: false,\n dialect,\n missingTables,\n missingColumns,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n\n return {\n ok: missingTables.length === 0 && missingColumns.length === 0,\n checked: true,\n dialect,\n missingTables,\n missingColumns,\n };\n}\n\nexport function formatRuntimeDebugFingerprint(\n fingerprint: RuntimeDebugFingerprint = getRuntimeDebugFingerprint(),\n): string {\n const db = fingerprint.database;\n return [\n `app: ${fingerprint.app}`,\n `environment: ${fingerprint.environment}`,\n fingerprint.deployContext\n ? `deploy_context: ${fingerprint.deployContext}`\n : \"\",\n fingerprint.deployId ? `deploy_id: ${fingerprint.deployId}` : \"\",\n fingerprint.commitRef ? `commit_ref: ${fingerprint.commitRef}` : \"\",\n fingerprint.branch ? `branch: ${fingerprint.branch}` : \"\",\n fingerprint.siteName ? `site_name: ${fingerprint.siteName}` : \"\",\n `db_configured: ${db.configured}`,\n `db_source: ${db.source}`,\n `db_dialect: ${db.dialect}`,\n db.protocol ? `db_protocol: ${db.protocol}` : \"\",\n db.host ? `db_host: ${db.host}` : \"\",\n db.database ? `db_database: ${db.database}` : \"\",\n db.urlHash ? `db_url_hash: ${db.urlHash}` : \"\",\n db.neon?.endpointId ? `db_neon_endpoint: ${db.neon.endpointId}` : \"\",\n db.neon ? `db_neon_pooled: ${db.neon.pooled}` : \"\",\n db.neon?.projectHost ? `db_neon_project_host: ${db.neon.projectHost}` : \"\",\n `db_auth_token_configured: ${db.authTokenConfigured}`,\n `netlify_database_url_configured: ${db.netlifyDatabaseUrlConfigured}`,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n"]}
|
|
@@ -41,28 +41,28 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
error?: undefined;
|
|
45
|
-
ok?: undefined;
|
|
46
44
|
summary: import("./types.js").TraceSummary;
|
|
47
45
|
spans: import("./types.js").TraceSpan[];
|
|
48
46
|
id?: undefined;
|
|
49
|
-
} | {
|
|
50
47
|
error?: undefined;
|
|
51
48
|
ok?: undefined;
|
|
49
|
+
} | {
|
|
52
50
|
summary?: undefined;
|
|
53
51
|
spans?: undefined;
|
|
54
52
|
id: string;
|
|
55
|
-
|
|
53
|
+
error?: undefined;
|
|
56
54
|
ok?: undefined;
|
|
55
|
+
} | {
|
|
57
56
|
summary?: undefined;
|
|
58
57
|
spans?: undefined;
|
|
59
58
|
id?: undefined;
|
|
60
59
|
error: any;
|
|
60
|
+
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
-
error?: undefined;
|
|
63
62
|
summary?: undefined;
|
|
64
63
|
spans?: undefined;
|
|
65
64
|
id?: undefined;
|
|
65
|
+
error?: undefined;
|
|
66
66
|
ok: boolean;
|
|
67
67
|
}>>;
|
|
68
68
|
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -73,7 +73,6 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
|
|
|
73
73
|
offset?: unknown;
|
|
74
74
|
limit?: unknown;
|
|
75
75
|
}, {
|
|
76
|
-
deleted?: undefined;
|
|
77
76
|
jobs: {
|
|
78
77
|
id: string;
|
|
79
78
|
name: string;
|
|
@@ -84,6 +83,7 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
|
|
|
84
83
|
updatedAt: string;
|
|
85
84
|
}[];
|
|
86
85
|
total: number;
|
|
86
|
+
deleted?: undefined;
|
|
87
87
|
jobId?: undefined;
|
|
88
88
|
} | {
|
|
89
89
|
jobs?: undefined;
|
|
@@ -91,9 +91,9 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
|
|
|
91
91
|
deleted: boolean;
|
|
92
92
|
jobId: string;
|
|
93
93
|
} | {
|
|
94
|
-
deleted?: undefined;
|
|
95
94
|
jobs?: undefined;
|
|
96
95
|
total?: undefined;
|
|
96
|
+
deleted?: undefined;
|
|
97
97
|
jobId?: undefined;
|
|
98
98
|
hits: Record<string, unknown>[];
|
|
99
99
|
offset: number;
|
|
@@ -52,8 +52,8 @@ export declare function handleDeleteResource(event: any): Promise<{
|
|
|
52
52
|
error: string;
|
|
53
53
|
ok?: undefined;
|
|
54
54
|
} | {
|
|
55
|
-
ok: boolean;
|
|
56
55
|
error?: undefined;
|
|
56
|
+
ok: boolean;
|
|
57
57
|
}>;
|
|
58
58
|
/** POST /_agent-native/resources/upload — upload a file as a resource */
|
|
59
59
|
export declare function handleUploadResource(event: any): Promise<import("./store.js").Resource | {
|
|
@@ -73,9 +73,9 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
|
|
|
73
73
|
runId: string | null;
|
|
74
74
|
expiresAt: number | null;
|
|
75
75
|
metadata: string | null;
|
|
76
|
+
error?: undefined;
|
|
76
77
|
url: string;
|
|
77
78
|
provider: string;
|
|
78
|
-
error?: undefined;
|
|
79
79
|
}>;
|
|
80
80
|
export {};
|
|
81
81
|
//# sourceMappingURL=handlers.d.ts.map
|