@agent-native/core 0.109.0 → 0.109.2
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/integrations.mdx +87 -253
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +24 -2
- package/corpus/core/src/a2a/correlation.ts +50 -0
- package/corpus/core/src/a2a/handlers.ts +140 -14
- package/corpus/core/src/a2a/index.ts +1 -0
- package/corpus/core/src/a2a/invoke.ts +10 -1
- package/corpus/core/src/a2a/task-store.ts +146 -6
- package/corpus/core/src/a2a/types.ts +16 -0
- package/corpus/core/src/action.ts +2 -0
- package/corpus/core/src/agent/production-agent.ts +23 -0
- package/corpus/core/src/client/blocks/library/diagram.config.ts +10 -1
- package/corpus/core/src/client/blocks/library/diagram.tsx +9 -4
- package/corpus/core/src/client/use-run-stuck-detection.ts +27 -1
- package/corpus/core/src/db/client.ts +55 -0
- package/corpus/core/src/deploy/build.ts +63 -3
- package/corpus/core/src/observability/traces.ts +172 -19
- package/corpus/core/src/scripts/call-agent.ts +53 -2
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +72 -25
- package/corpus/core/src/server/agent-chat-plugin.ts +107 -6
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +21 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/changelog/2026-07-17-analytics-now-includes-a-default-alert-for-spikes-in-stuck-agent-chats.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +23 -14
- package/corpus/templates/analytics/docs/schemas/tracked-events.md +5 -1
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +3 -3
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +209 -87
- package/corpus/templates/clips/.agents/skills/dictate/SKILL.md +28 -5
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +7 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +23 -0
- package/corpus/templates/clips/AGENTS.md +10 -0
- package/corpus/templates/clips/actions/create-dictation.ts +29 -4
- package/corpus/templates/clips/actions/finalize-recording.ts +542 -65
- package/corpus/templates/clips/actions/get-recording-player-data.ts +16 -6
- package/corpus/templates/clips/actions/set-mobile-capture-state.ts +37 -0
- package/corpus/templates/clips/actions/update-dictation.ts +9 -1
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +17 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +14 -2
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +2 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +14 -3
- package/corpus/templates/clips/app/routes/record.tsx +3 -3
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +19 -5
- package/corpus/templates/clips/changelog/2026-07-17-capture-dictation-background-meeting-audio-and-camera-videos.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-clips-now-retries-storage-verification-in-the-background-ins.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-delayed-upload-verification-keeps-local-backups-retryable.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-fixed-broken-thumbnail-icons-appearing-over-playable-clip-em.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-playback-comments-stay-readable-at-faster-playback-speeds.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +3 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +3 -3
- package/corpus/templates/clips/desktop/src/app.tsx +35 -6
- package/corpus/templates/clips/desktop/src/lib/processing-backup-recovery.ts +19 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +141 -18
- package/corpus/templates/clips/desktop/src/lib/upload-verification.ts +28 -7
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -4
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +32 -15
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +59 -18
- package/corpus/templates/clips/server/db/schema.ts +9 -1
- package/corpus/templates/clips/server/jobs/media-verification.ts +130 -0
- package/corpus/templates/clips/server/lib/builder-media-compression.ts +74 -7
- package/corpus/templates/clips/server/lib/media-verification-state.ts +55 -0
- package/corpus/templates/clips/server/lib/player-thumbnail-url.ts +31 -0
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +10 -3
- package/corpus/templates/clips/server/plugins/auth.ts +5 -5
- package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +12 -2
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +13 -1
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +340 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +24 -7
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +91 -7
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +22 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +14 -0
- package/corpus/templates/clips/shared/finalize-recovery.ts +61 -2
- package/corpus/templates/clips/shared/share-meta.ts +1 -1
- package/corpus/templates/plan/changelog/2026-07-17-diagrams-can-opt-into-a-polished-clean-rendering-mode-withou.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +3 -0
- package/dist/a2a/client.d.ts +11 -2
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +16 -3
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/correlation.d.ts +10 -0
- package/dist/a2a/correlation.d.ts.map +1 -0
- package/dist/a2a/correlation.js +40 -0
- package/dist/a2a/correlation.js.map +1 -0
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +100 -15
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/index.d.ts +1 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/invoke.d.ts +4 -1
- package/dist/a2a/invoke.d.ts.map +1 -1
- package/dist/a2a/invoke.js +3 -0
- package/dist/a2a/invoke.js.map +1 -1
- package/dist/a2a/task-store.d.ts +12 -1
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +96 -6
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/a2a/types.d.ts +15 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/action.d.ts +2 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +9 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +14 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/blocks/library/diagram.config.d.ts +2 -0
- package/dist/client/blocks/library/diagram.config.d.ts.map +1 -1
- package/dist/client/blocks/library/diagram.config.js +8 -1
- package/dist/client/blocks/library/diagram.config.js.map +1 -1
- package/dist/client/blocks/library/diagram.d.ts.map +1 -1
- package/dist/client/blocks/library/diagram.js +9 -4
- package/dist/client/blocks/library/diagram.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +25 -1
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +7 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +33 -0
- package/dist/db/client.js.map +1 -1
- package/dist/deploy/build.d.ts +3 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +45 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/observability/traces.d.ts +9 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +137 -19
- package/dist/observability/traces.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +35 -3
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +18 -2
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +35 -7
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +93 -6
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +21 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/docs/content/integrations.mdx +87 -253
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +21 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
|
@@ -2481,6 +2481,9 @@ export interface ExecuteAgentToolCallOptions {
|
|
|
2481
2481
|
orgId?: string | null;
|
|
2482
2482
|
/** Audit/action attribution for this externally selected call. */
|
|
2483
2483
|
caller?: ActionCaller;
|
|
2484
|
+
networkProtocol?: "a2a" | "mcp" | "provider-api";
|
|
2485
|
+
networkId?: string;
|
|
2486
|
+
networkPeer?: string;
|
|
2484
2487
|
threadId?: string;
|
|
2485
2488
|
turnId?: string;
|
|
2486
2489
|
approvedToolCalls?: string[];
|
|
@@ -2576,6 +2579,9 @@ export async function executeAgentToolCall(
|
|
|
2576
2579
|
ownerEmail: options.ownerEmail,
|
|
2577
2580
|
orgId: options.orgId,
|
|
2578
2581
|
actionCaller: options.caller,
|
|
2582
|
+
networkProtocol: options.networkProtocol,
|
|
2583
|
+
networkId: options.networkId,
|
|
2584
|
+
networkPeer: options.networkPeer,
|
|
2579
2585
|
executionMode: "act",
|
|
2580
2586
|
maxIterations: 2,
|
|
2581
2587
|
threadId: options.threadId,
|
|
@@ -3157,6 +3163,12 @@ export async function runAgentLoop(opts: {
|
|
|
3157
3163
|
orgId?: string | null;
|
|
3158
3164
|
/** Action invocation attribution. Defaults to the normal agent tool loop. */
|
|
3159
3165
|
actionCaller?: ActionCaller;
|
|
3166
|
+
/** Concrete execution id used for cross-app trace correlation. */
|
|
3167
|
+
runId?: string;
|
|
3168
|
+
/** Verified/telemetry-only delegated lineage supplied by the transport. */
|
|
3169
|
+
networkProtocol?: "a2a" | "mcp" | "provider-api";
|
|
3170
|
+
networkId?: string;
|
|
3171
|
+
networkPeer?: string;
|
|
3160
3172
|
/**
|
|
3161
3173
|
* Attachments submitted with this turn (pasted text, files, images), passed
|
|
3162
3174
|
* through to each tool's `ActionRunContext.attachments` so actions can
|
|
@@ -4215,6 +4227,9 @@ export async function runAgentLoop(opts: {
|
|
|
4215
4227
|
userEmail: getRequestUserEmail(),
|
|
4216
4228
|
orgId: getRequestOrgId() ?? null,
|
|
4217
4229
|
caller: opts.actionCaller ?? "tool",
|
|
4230
|
+
networkProtocol: opts.networkProtocol,
|
|
4231
|
+
networkId: opts.networkId,
|
|
4232
|
+
networkPeer: opts.networkPeer,
|
|
4218
4233
|
}),
|
|
4219
4234
|
)
|
|
4220
4235
|
: actionEntry.needsApproval === true;
|
|
@@ -4249,6 +4264,9 @@ export async function runAgentLoop(opts: {
|
|
|
4249
4264
|
ctx: {
|
|
4250
4265
|
actionName: toolCall.name,
|
|
4251
4266
|
caller: opts.actionCaller ?? "tool",
|
|
4267
|
+
networkProtocol: opts.networkProtocol,
|
|
4268
|
+
networkId: opts.networkId,
|
|
4269
|
+
networkPeer: opts.networkPeer,
|
|
4252
4270
|
userEmail: getRequestUserEmail(),
|
|
4253
4271
|
orgId: getRequestOrgId() ?? null,
|
|
4254
4272
|
...(opts.threadId ? { threadId: opts.threadId } : {}),
|
|
@@ -4645,12 +4663,16 @@ export async function runAgentLoop(opts: {
|
|
|
4645
4663
|
userEmail: actionUserEmail ?? undefined,
|
|
4646
4664
|
orgId: actionOrgId,
|
|
4647
4665
|
caller: opts.actionCaller ?? "tool",
|
|
4666
|
+
networkProtocol: opts.networkProtocol,
|
|
4667
|
+
networkId: opts.networkId,
|
|
4668
|
+
networkPeer: opts.networkPeer,
|
|
4648
4669
|
attachments: opts.attachments,
|
|
4649
4670
|
signal,
|
|
4650
4671
|
// Audit attribution: the action name + the agent thread/turn that
|
|
4651
4672
|
// triggered this call, so a mutation can be traced to its run.
|
|
4652
4673
|
actionName: toolCall.name,
|
|
4653
4674
|
...(opts.threadId ? { threadId: opts.threadId } : {}),
|
|
4675
|
+
...(opts.runId ? { runId: opts.runId } : {}),
|
|
4654
4676
|
...(opts.turnId ? { turnId: opts.turnId } : {}),
|
|
4655
4677
|
};
|
|
4656
4678
|
const requestContext = getRequestContext();
|
|
@@ -8004,6 +8026,7 @@ export function createProductionAgentHandler(
|
|
|
8004
8026
|
const agentLoopOpts = {
|
|
8005
8027
|
engine,
|
|
8006
8028
|
model: effectiveModel,
|
|
8029
|
+
runId,
|
|
8007
8030
|
systemPrompt: requestSystemPrompt,
|
|
8008
8031
|
tools: requestTools,
|
|
8009
8032
|
availableTools: availableRequestTools,
|
|
@@ -44,6 +44,8 @@ export interface DiagramData {
|
|
|
44
44
|
*/
|
|
45
45
|
html?: string;
|
|
46
46
|
css?: string;
|
|
47
|
+
/** `design` forces clean HTML/CSS rendering without the sketch overlay. */
|
|
48
|
+
renderMode?: "wireframe" | "design";
|
|
47
49
|
caption?: string;
|
|
48
50
|
/** Outer surface frame. `auto` lets the host choose the right default. */
|
|
49
51
|
frame?: BlockVisualFrame;
|
|
@@ -147,6 +149,7 @@ export const diagramSchema = z
|
|
|
147
149
|
message: "Diagram css must not include document or script tags.",
|
|
148
150
|
})
|
|
149
151
|
.optional(),
|
|
152
|
+
renderMode: z.enum(["wireframe", "design"]).optional(),
|
|
150
153
|
caption: z.string().trim().max(600).optional(),
|
|
151
154
|
frame: visualFrameSchema.optional(),
|
|
152
155
|
nodes: z.array(diagramNodeSchema).max(80).optional(),
|
|
@@ -173,7 +176,7 @@ function graphDataForAttr(data: DiagramData): DiagramData | undefined {
|
|
|
173
176
|
if (data.notes?.length) graph.notes = data.notes;
|
|
174
177
|
if (Object.keys(graph).length > 0) return graph;
|
|
175
178
|
if (hasChildFenceData(data)) return undefined;
|
|
176
|
-
const { frame: _frame, ...dataForAttr } = data;
|
|
179
|
+
const { frame: _frame, renderMode: _renderMode, ...dataForAttr } = data;
|
|
177
180
|
return Object.keys(dataForAttr).length > 0 ? dataForAttr : undefined;
|
|
178
181
|
}
|
|
179
182
|
|
|
@@ -193,6 +196,7 @@ export const diagramMdx: BlockMdxConfig<DiagramData> = {
|
|
|
193
196
|
| undefined,
|
|
194
197
|
caption: data.caption,
|
|
195
198
|
frame: data.frame,
|
|
199
|
+
renderMode: data.renderMode,
|
|
196
200
|
}),
|
|
197
201
|
fromAttrs: (attrs) => ({
|
|
198
202
|
...(attrs.object<DiagramData>("data") ?? {}),
|
|
@@ -202,6 +206,11 @@ export const diagramMdx: BlockMdxConfig<DiagramData> = {
|
|
|
202
206
|
...(attrs.string("frame") !== undefined
|
|
203
207
|
? { frame: attrs.string("frame") as BlockVisualFrame }
|
|
204
208
|
: {}),
|
|
209
|
+
...(attrs.string("renderMode") !== undefined
|
|
210
|
+
? {
|
|
211
|
+
renderMode: attrs.string("renderMode") as DiagramData["renderMode"],
|
|
212
|
+
}
|
|
213
|
+
: {}),
|
|
205
214
|
}),
|
|
206
215
|
serializeChildren: (data) =>
|
|
207
216
|
serializeChildCodeFenceFields(data, { html: "html", css: "css" }),
|
|
@@ -88,7 +88,9 @@ function HtmlDiagram({
|
|
|
88
88
|
}) {
|
|
89
89
|
const ref = useRef<HTMLDivElement>(null);
|
|
90
90
|
const isDark = useIsDark();
|
|
91
|
-
const
|
|
91
|
+
const preferredStyle = useWireframeStyle();
|
|
92
|
+
const designMode = data.renderMode === "design";
|
|
93
|
+
const style = designMode ? "clean" : preferredStyle;
|
|
92
94
|
const showFrame = resolveVisualFrame(data.frame, ctx);
|
|
93
95
|
const scopeId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
94
96
|
// Sanitize author HTML/CSS at the render point (defense-in-depth against
|
|
@@ -127,7 +129,7 @@ function HtmlDiagram({
|
|
|
127
129
|
</div>
|
|
128
130
|
<RoughOverlay
|
|
129
131
|
scopeRef={ref}
|
|
130
|
-
enabled={style === "sketchy"}
|
|
132
|
+
enabled={!designMode && style === "sketchy"}
|
|
131
133
|
drawFrame={showFrame}
|
|
132
134
|
selector={DIAGRAM_ROUGH_SELECTOR}
|
|
133
135
|
/>
|
|
@@ -650,7 +652,8 @@ function ExpandableDiagramBody({
|
|
|
650
652
|
compact?: boolean;
|
|
651
653
|
}) {
|
|
652
654
|
const [expanded, setExpanded] = useState(false);
|
|
653
|
-
const
|
|
655
|
+
const designMode = data.renderMode === "design";
|
|
656
|
+
const supportsStyleToggle = Boolean(data.html) && !designMode;
|
|
654
657
|
const style = useWireframeStyle();
|
|
655
658
|
const copy = useBlockCopy();
|
|
656
659
|
const sketchy = style === "sketchy";
|
|
@@ -794,7 +797,9 @@ export function DiagramEdit({
|
|
|
794
797
|
onChange({
|
|
795
798
|
html: html.trim() || undefined,
|
|
796
799
|
css: css.trim() || undefined,
|
|
800
|
+
renderMode: data.renderMode,
|
|
797
801
|
caption: caption.trim() || undefined,
|
|
802
|
+
frame: data.frame,
|
|
798
803
|
nodes: data.nodes,
|
|
799
804
|
edges: data.edges,
|
|
800
805
|
notes: data.notes,
|
|
@@ -946,7 +951,7 @@ export const diagramBlock = defineBlock<DiagramData>({
|
|
|
946
951
|
editSurface: "panel",
|
|
947
952
|
label: "Diagram",
|
|
948
953
|
description:
|
|
949
|
-
"A flexible inline architecture/code diagram. Prefer html/css with SVG or semantic HTML for polished two-dimensional layouts; use .diagram-* primitives and --wf-* tokens for theme/sketch compatibility. Legacy nodes/edges are only for simple previews.",
|
|
954
|
+
"A flexible inline architecture/code diagram. Prefer html/css with SVG or semantic HTML for polished two-dimensional layouts; use renderMode design for clean presentation or .diagram-* primitives and --wf-* tokens for theme/sketch compatibility. Legacy nodes/edges are only for simple previews.",
|
|
950
955
|
// Seed the legacy fallback shape so a fresh block validates while agents can
|
|
951
956
|
// replace it with html/css when layout quality matters.
|
|
952
957
|
empty: () => ({ nodes: [{ id: "n1", label: "Module" }], edges: [] }),
|
|
@@ -80,6 +80,7 @@ export const DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS = 180_000;
|
|
|
80
80
|
export const DEFAULT_LIVE_BACKGROUND_STUCK_THRESHOLD_MS = 13 * 60_000;
|
|
81
81
|
const DEFAULT_POLL_INTERVAL_MS = 5_000;
|
|
82
82
|
const IDLE_BACKOFF_INTERVAL_MS = 15_000;
|
|
83
|
+
const MAX_POLL_ERROR_BACKOFF_MS = 30_000;
|
|
83
84
|
const FRESH_BACKGROUND_HEARTBEAT_MS = 30_000;
|
|
84
85
|
|
|
85
86
|
interface ActiveRunResponse {
|
|
@@ -129,6 +130,22 @@ export function useRunStuckDetection({
|
|
|
129
130
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
130
131
|
let snapshotTransitionTimer: ReturnType<typeof setTimeout> | null = null;
|
|
131
132
|
let snapshotVersion = 0;
|
|
133
|
+
let consecutivePollFailures = 0;
|
|
134
|
+
|
|
135
|
+
const pollFailureDelay = () => {
|
|
136
|
+
consecutivePollFailures += 1;
|
|
137
|
+
return Math.min(
|
|
138
|
+
Math.max(pollIntervalMs, 1) * 2 ** consecutivePollFailures,
|
|
139
|
+
MAX_POLL_ERROR_BACKOFF_MS,
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const clearObservedRun = () => {
|
|
144
|
+
snapshotVersion += 1;
|
|
145
|
+
if (snapshotTransitionTimer) clearTimeout(snapshotTransitionTimer);
|
|
146
|
+
snapshotTransitionTimer = null;
|
|
147
|
+
setState(EMPTY_STATE);
|
|
148
|
+
};
|
|
132
149
|
|
|
133
150
|
type RunHealthSnapshot = {
|
|
134
151
|
active: boolean;
|
|
@@ -269,6 +286,7 @@ export function useRunStuckDetection({
|
|
|
269
286
|
);
|
|
270
287
|
if (cancelled) return;
|
|
271
288
|
if (res.ok) {
|
|
289
|
+
consecutivePollFailures = 0;
|
|
272
290
|
const data = (await res.json()) as ActiveRunResponse;
|
|
273
291
|
const lastProgressAt = data.lastProgressAt ?? null;
|
|
274
292
|
// Measure elapsed against the SERVER clock (serverNow) rather than the
|
|
@@ -336,9 +354,17 @@ export function useRunStuckDetection({
|
|
|
336
354
|
if (!data.active || data.status !== "running") {
|
|
337
355
|
nextDelay = IDLE_BACKOFF_INTERVAL_MS;
|
|
338
356
|
}
|
|
357
|
+
} else if (res.status === 429 || res.status >= 500) {
|
|
358
|
+
nextDelay = pollFailureDelay();
|
|
359
|
+
} else {
|
|
360
|
+
consecutivePollFailures = 0;
|
|
361
|
+
clearObservedRun();
|
|
362
|
+
nextDelay = MAX_POLL_ERROR_BACKOFF_MS;
|
|
339
363
|
}
|
|
340
364
|
} catch {
|
|
341
|
-
// Network blip — leave previous state
|
|
365
|
+
// Network/503 blip — leave previous state and ease polling pressure
|
|
366
|
+
// while the server or database recovers.
|
|
367
|
+
nextDelay = pollFailureDelay();
|
|
342
368
|
}
|
|
343
369
|
if (!cancelled) {
|
|
344
370
|
timer = setTimeout(poll, nextDelay);
|
|
@@ -669,6 +669,61 @@ export function isConnectionError(err: any): boolean {
|
|
|
669
669
|
);
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
+
/**
|
|
673
|
+
* Classify database failures that should temporarily shed request load.
|
|
674
|
+
* Statement timeouts are not included in isConnectionError() because retrying
|
|
675
|
+
* every timed-out mutation would not be safe, but request handlers can still
|
|
676
|
+
* return a retryable service-unavailable response for them.
|
|
677
|
+
*/
|
|
678
|
+
export function isTransientDatabaseError(err: unknown): boolean {
|
|
679
|
+
const error = err as {
|
|
680
|
+
code?: unknown;
|
|
681
|
+
name?: unknown;
|
|
682
|
+
message?: unknown;
|
|
683
|
+
stack?: unknown;
|
|
684
|
+
cause?: {
|
|
685
|
+
code?: unknown;
|
|
686
|
+
name?: unknown;
|
|
687
|
+
message?: unknown;
|
|
688
|
+
stack?: unknown;
|
|
689
|
+
};
|
|
690
|
+
};
|
|
691
|
+
const code = String(error?.code ?? error?.cause?.code ?? "");
|
|
692
|
+
if (
|
|
693
|
+
code === "ECHECKOUTTIMEOUT" ||
|
|
694
|
+
code === "53300" ||
|
|
695
|
+
code === "57014" ||
|
|
696
|
+
/^08/.test(code) ||
|
|
697
|
+
/^57P0[123]$/.test(code)
|
|
698
|
+
) {
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const message = [error?.message, error?.cause?.message]
|
|
703
|
+
.filter((value): value is string => typeof value === "string")
|
|
704
|
+
.join(" ");
|
|
705
|
+
if (
|
|
706
|
+
/\bstatement timeout\b|\bdb (?:query|connect) timed out\b/i.test(message)
|
|
707
|
+
) {
|
|
708
|
+
return true;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const databaseSurface = [
|
|
712
|
+
error?.name,
|
|
713
|
+
error?.cause?.name,
|
|
714
|
+
error?.stack,
|
|
715
|
+
error?.cause?.stack,
|
|
716
|
+
]
|
|
717
|
+
.filter((value): value is string => typeof value === "string")
|
|
718
|
+
.join(" ");
|
|
719
|
+
return (
|
|
720
|
+
isConnectionError(error) &&
|
|
721
|
+
/@neondatabase|@libsql|\bpostgres(?:ql)?\b|\bpg-pool\b|drizzle-orm|\/db\/client\.[cm]?[jt]s/i.test(
|
|
722
|
+
databaseSurface,
|
|
723
|
+
)
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
|
|
672
727
|
export async function retryOnConnectionError<T>(
|
|
673
728
|
fn: () => Promise<T>,
|
|
674
729
|
maxAttempts = 3,
|
|
@@ -138,6 +138,14 @@ export const CLOUDFLARE_WORKER_STUB_MODULES: Record<string, string> = {
|
|
|
138
138
|
"export default { Resvg };",
|
|
139
139
|
"",
|
|
140
140
|
].join("\n"),
|
|
141
|
+
playwright: [
|
|
142
|
+
"const unavailable = async () => { throw new Error('playwright unavailable in Cloudflare Pages worker'); };",
|
|
143
|
+
"export const chromium = { launch: unavailable, connect: unavailable, connectOverCDP: unavailable };",
|
|
144
|
+
"export const firefox = { launch: unavailable, connect: unavailable };",
|
|
145
|
+
"export const webkit = { launch: unavailable, connect: unavailable };",
|
|
146
|
+
"export default { chromium, firefox, webkit };",
|
|
147
|
+
"",
|
|
148
|
+
].join("\n"),
|
|
141
149
|
"playwright-core": [
|
|
142
150
|
"const unavailable = async () => { throw new Error('playwright-core unavailable in Cloudflare Pages worker'); };",
|
|
143
151
|
"export const chromium = { launch: unavailable };",
|
|
@@ -187,6 +195,50 @@ export const CLOUDFLARE_WORKER_STUB_MODULES: Record<string, string> = {
|
|
|
187
195
|
"export default async () => ({ elements: [], files: {} });\n",
|
|
188
196
|
};
|
|
189
197
|
|
|
198
|
+
export const CLOUDFLARE_WORKER_STUB_SUBPATH_MODULES: Record<string, string> = {
|
|
199
|
+
"pdf-parse/worker": [
|
|
200
|
+
"const unavailable = async () => { throw new Error('pdf-parse/worker unavailable in Cloudflare Pages worker'); };",
|
|
201
|
+
"export class CanvasFactory {}",
|
|
202
|
+
"export const getData = unavailable;",
|
|
203
|
+
"export default { CanvasFactory, getData };",
|
|
204
|
+
"",
|
|
205
|
+
].join("\n"),
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export function cloudflareWorkerStubAliasArgs(stubDir: string): string[] {
|
|
209
|
+
const subpathAliases = Object.keys(CLOUDFLARE_WORKER_STUB_SUBPATH_MODULES)
|
|
210
|
+
.sort((a, b) => b.length - a.length)
|
|
211
|
+
.map(
|
|
212
|
+
(mod) =>
|
|
213
|
+
`--alias:${mod}=${path.join(stubDir, `${mod.replace(/\//g, "__")}.js`)}`,
|
|
214
|
+
);
|
|
215
|
+
const packageAliases = Object.keys(CLOUDFLARE_WORKER_STUB_MODULES)
|
|
216
|
+
.sort((a, b) => b.length - a.length)
|
|
217
|
+
.map((mod) => `--alias:${mod}=${path.join(stubDir, mod, "index.js")}`);
|
|
218
|
+
return [...subpathAliases, ...packageAliases];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function assertNoCloudflareWorkerStubDynamicImports(
|
|
222
|
+
code: string,
|
|
223
|
+
sourceName: string,
|
|
224
|
+
): void {
|
|
225
|
+
const stubbedModules = [
|
|
226
|
+
...Object.keys(CLOUDFLARE_WORKER_STUB_SUBPATH_MODULES),
|
|
227
|
+
...Object.keys(CLOUDFLARE_WORKER_STUB_MODULES),
|
|
228
|
+
];
|
|
229
|
+
const pattern = stubbedModules
|
|
230
|
+
.sort((a, b) => b.length - a.length)
|
|
231
|
+
.map((moduleName) => moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
232
|
+
.join("|");
|
|
233
|
+
const unresolvedImport = code.match(
|
|
234
|
+
new RegExp(`\\bimport\\s*\\(\\s*(["'])(${pattern})\\1\\s*\\)`),
|
|
235
|
+
);
|
|
236
|
+
if (!unresolvedImport) return;
|
|
237
|
+
throw new Error(
|
|
238
|
+
`Cloudflare worker output ${sourceName} retained a dynamic import for stubbed module "${unresolvedImport[2]}". Use a literal import so the worker bundler can apply its fail-closed stub.`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
190
242
|
function cloudflareNodeBuiltinStubSource(
|
|
191
243
|
moduleName: string,
|
|
192
244
|
namedExports: string[],
|
|
@@ -1666,9 +1718,15 @@ async function buildCloudflarePages() {
|
|
|
1666
1718
|
JSON.stringify({ name: mod, main: "index.js", type: "module" }),
|
|
1667
1719
|
);
|
|
1668
1720
|
}
|
|
1669
|
-
const
|
|
1670
|
-
|
|
1671
|
-
)
|
|
1721
|
+
for (const [mod, source] of Object.entries(
|
|
1722
|
+
CLOUDFLARE_WORKER_STUB_SUBPATH_MODULES,
|
|
1723
|
+
)) {
|
|
1724
|
+
fs.writeFileSync(
|
|
1725
|
+
path.join(stubDir, `${mod.replace(/\//g, "__")}.js`),
|
|
1726
|
+
source,
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
const stubAliases = cloudflareWorkerStubAliasArgs(stubDir);
|
|
1672
1730
|
const nodeBuiltinStubDir = path.join(tmpDir, "node-builtin-stubs");
|
|
1673
1731
|
fs.mkdirSync(nodeBuiltinStubDir, { recursive: true });
|
|
1674
1732
|
const nodeBuiltinStubAliases: string[] = [];
|
|
@@ -1850,6 +1908,8 @@ async function buildCloudflarePages() {
|
|
|
1850
1908
|
);
|
|
1851
1909
|
}
|
|
1852
1910
|
|
|
1911
|
+
assertNoCloudflareWorkerStubDynamicImports(code, jsFile);
|
|
1912
|
+
|
|
1853
1913
|
fs.writeFileSync(jsFile, code);
|
|
1854
1914
|
}
|
|
1855
1915
|
|