@agent-native/core 0.145.8 → 0.146.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 +1 -1
- package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +20 -4
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +59 -24
- package/corpus/templates/analytics/changelog/2026-08-07-analytics-migrations-now-use-bounded-pressure-aware-backgrou.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-07-template-activity-charts-now-show-only-core-agent-native-app.md +6 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +110 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +13 -13
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +448 -0
- package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +2 -2
- package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +4 -5
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +35 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +3 -5
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +10 -4
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +4 -11
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +74 -28
- package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +2 -5
- package/corpus/templates/analytics/server/plugins/db.ts +42 -0
- package/corpus/templates/analytics/server/routes/api/analytics-bigquery/backfill.post.ts +55 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +22 -4
- package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/brain/server/lib/connectors.ts +38 -14
- package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +13 -0
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -0
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +5 -0
- package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +5 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
- package/corpus/templates/slides/app/pages/Presentation.tsx +3 -0
- package/corpus/templates/slides/app/pages/SharedPresentation.tsx +5 -0
- package/corpus/templates/slides/app/routes/slide.tsx +10 -5
- package/corpus/templates/slides/changelog/2026-08-07-slide-previews-and-present-now-keep-deck-colors-consistent.md +6 -0
- package/corpus/templates/slides/server/db/schema.ts +1 -0
- package/corpus/templates/slides/server/handlers/share.ts +51 -3
- package/corpus/templates/slides/server/plugins/db.ts +5 -0
- package/corpus/templates/slides/shared/api.ts +2 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/cli/code-agent-executor.js +8 -3
- package/dist/client/ConnectBuilderCard.d.ts +7 -7
- package/dist/client/ConnectBuilderCard.js +45 -20
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/config.d.ts +9 -2
- package/dist/config.js +38 -2
- package/dist/db/client.d.ts +13 -1
- package/dist/db/client.js +76 -2
- package/dist/db/create-get-db.js +2 -2
- package/dist/deploy/build.js +5 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/docs/search.js +3 -2
- package/dist/server/agent-chat/prompt-resources.js +4 -3
- package/dist/server/agents-bundle.d.ts +20 -3
- package/dist/server/agents-bundle.js +57 -30
- package/dist/server/better-auth-instance.js +2 -2
- package/dist/shared/runtime-config.js +1 -1
- package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/vite/agent-native-config-loader.d.ts +10 -0
- package/dist/vite/agent-native-config-loader.js +56 -0
- package/dist/vite/agents-bundle-plugin.d.ts +5 -1
- package/dist/vite/agents-bundle-plugin.js +29 -6
- package/dist/vite/client.d.ts +4 -2
- package/dist/vite/client.js +4 -49
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +1 -1
- package/docs/content/agent-native-config.mdx +43 -17
- package/docs/content/environment-variables.mdx +1 -1
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
|
@@ -82,6 +82,8 @@ export interface SharedDeckResponse {
|
|
|
82
82
|
title: string;
|
|
83
83
|
slides: SharedDeckSlide[];
|
|
84
84
|
aspectRatio?: import("./aspect-ratios").AspectRatio;
|
|
85
|
+
/** Resolved at share creation so public links keep the deck's styling. */
|
|
86
|
+
designSystem?: DesignSystemData;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
export type SharedSlideTransition =
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Turn visible project context, a proven thread, skill, or workflow into a
|
|
5
5
|
runnable Agent-Native app with simple buttons, visible agent steps, preview,
|
|
6
6
|
and deployment handoff. Use when a user invokes `/turn-into-app` or asks to
|
|
7
|
-
make a workflow into an app.
|
|
7
|
+
make a workflow into an app, including from Claude or ChatGPT on the web.
|
|
8
8
|
user-invocable: true
|
|
9
9
|
scope: both
|
|
10
10
|
metadata:
|
|
@@ -13,9 +13,32 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
# Turn Into App
|
|
15
15
|
|
|
16
|
+
## Host execution boundary
|
|
17
|
+
|
|
18
|
+
The build location depends on the host. Treat this as a hard routing rule:
|
|
19
|
+
|
|
20
|
+
- In Claude Web, ChatGPT Web, or a Claude/ChatGPT Project on the web, you are
|
|
21
|
+
the source analyst and handoff orchestrator. You must not build the app in
|
|
22
|
+
the host sandbox. Do not run `npm`, `pnpm`, `npx`, `agent-native create`, or
|
|
23
|
+
`add-app`; do not edit files, create artifacts, or start a local dev server.
|
|
24
|
+
After writing the bounded source brief, call the connected Dispatch action
|
|
25
|
+
`start-workspace-app-creation`. Pass the brief and repeatable workflow in
|
|
26
|
+
`prompt`, plus the inferred `appId`, `description`, `template`, and selected
|
|
27
|
+
`resourceIds` when available. This is the Builder handoff.
|
|
28
|
+
- Do not substitute the generic `create_workspace_app` MCP tool in an online
|
|
29
|
+
host. That tool is a local workspace scaffolder, not the Builder handoff.
|
|
30
|
+
- If `start-workspace-app-creation` is not available or Dispatch is not
|
|
31
|
+
authenticated, stop with the connector setup needed. Do not fall back to a
|
|
32
|
+
local build or claim that the app exists.
|
|
33
|
+
- In Claude Code, Codex Code, or another local code-agent runtime with a target
|
|
34
|
+
workspace, follow the local implementation steps below. The local agent may
|
|
35
|
+
scaffold, edit, run, and verify the app there.
|
|
36
|
+
|
|
16
37
|
## Default behavior
|
|
17
38
|
|
|
18
|
-
|
|
39
|
+
For a local code-agent runtime this is an end-to-end build skill, not a request
|
|
40
|
+
for an app proposal. For an online host, the end-to-end result is a verified
|
|
41
|
+
Builder handoff and the resulting workspace app, not code written in the host.
|
|
19
42
|
|
|
20
43
|
- With no argument, choose the source in this order: visible project context,
|
|
21
44
|
then the current thread. A fresh Claude or ChatGPT Project is a valid source
|
|
@@ -47,9 +70,12 @@ private web access, invent an importer, add fake OAuth, or scrape a logged-in
|
|
|
47
70
|
page. If the needed context is not visible, ask for an export, transcript, or
|
|
48
71
|
attachment and treat that artifact as imported source material.
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
buttons and agent handoffs, start its dev
|
|
52
|
-
continue through build/deployment handoff. Do
|
|
73
|
+
For local code-agent runtimes, deliver a fresh app in a new directory,
|
|
74
|
+
implement the repeatable workflow with buttons and agent handoffs, start its dev
|
|
75
|
+
server, verify the main path, and continue through build/deployment handoff. Do
|
|
76
|
+
not stop at a plan. For online hosts, call Dispatch first after the source brief,
|
|
77
|
+
then report the returned Builder branch/path and verify the workspace app through
|
|
78
|
+
Dispatch when it becomes available.
|
|
53
79
|
|
|
54
80
|
## Fresh project context mode
|
|
55
81
|
|
|
@@ -190,12 +216,12 @@ Do not use `create` for an existing workspace; it scaffolds a new standalone
|
|
|
190
216
|
workspace rather than adding an app to the current one.
|
|
191
217
|
|
|
192
218
|
When the source came from a fresh external Project and the target is a Builder
|
|
193
|
-
workspace,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
workspace, the online host path is mandatory: after writing the source brief,
|
|
220
|
+
call `start-workspace-app-creation` with a concise prompt, the inferred app id,
|
|
221
|
+
and the brief's repeatable workflow. Pass selected workspace resource IDs when
|
|
222
|
+
they are available, rather than pasting entire knowledge files. Continue from
|
|
223
|
+
the returned workspace path or Builder branch handoff and report the actual
|
|
224
|
+
verification result. Do not run the local scaffold commands in this host mode.
|
|
199
225
|
|
|
200
226
|
Use a first-party template only when it materially fits the workflow. Keep the
|
|
201
227
|
new app independent from the source thread's working tree unless the user
|
|
@@ -51,13 +51,24 @@ also place the skill instructions or this reference in the Project
|
|
|
51
51
|
instructions/knowledge files. Then add and authenticate the Dispatch MCP
|
|
52
52
|
connector from the packaged `adapters/chatgpt-mcp/connector.json`.
|
|
53
53
|
|
|
54
|
+
For Claude Web, ChatGPT Web, and their web Projects, the host is an
|
|
55
|
+
orchestrator, not the build environment. After forming the source brief, call
|
|
56
|
+
the Dispatch `start-workspace-app-creation` action so Builder creates the app
|
|
57
|
+
in the connected workspace. Do not use the host's code interpreter, shell,
|
|
58
|
+
artifact editor, `create_workspace_app`, or local `pnpm`/`npm` commands. If the
|
|
59
|
+
handoff action is unavailable, stop and ask the user to authenticate Dispatch;
|
|
60
|
+
do not fall back to a local sandbox build. Claude Code and Codex Code are the
|
|
61
|
+
local-agent exception and may follow the implementation steps in the main
|
|
62
|
+
skill.
|
|
63
|
+
|
|
54
64
|
In a new Project chat, say:
|
|
55
65
|
|
|
56
66
|
```text
|
|
57
67
|
Turn this project into an app. Use the visible Project instructions,
|
|
58
68
|
knowledge files, and any selected successful runs as the source. Create the
|
|
59
|
-
app in the connected Agent-Native workspace
|
|
60
|
-
and report the real path and
|
|
69
|
+
app in the connected Agent-Native workspace through Dispatch and Builder, keep
|
|
70
|
+
the source brief bounded, and report the real Builder branch/path and
|
|
71
|
+
verification result. Do not build it in this chat's sandbox.
|
|
61
72
|
```
|
|
62
73
|
|
|
63
74
|
If the host does not show the Project instructions or files to the model, stop
|
|
@@ -13,6 +13,7 @@ import { buildMergedConfig, McpClientManager, mcpToolsToActionEntries, } from ".
|
|
|
13
13
|
import { readAgentsBundleFromFs, generateDevelopmentSkillsPromptBlock, } from "../server/agents-bundle.js";
|
|
14
14
|
import { getAmbientOrgId, getAmbientUserEmail, runWithRequestContext, } from "../server/request-context.js";
|
|
15
15
|
import { isReasoningEffort, } from "../shared/reasoning-effort.js";
|
|
16
|
+
import { createAgentNativeConfigContext, loadResolvedAgentNativeConfig, } from "../vite/agent-native-config-loader.js";
|
|
16
17
|
import { createCodeAgentOutputSmoother } from "./code-agent-output-smoother.js";
|
|
17
18
|
import { addCodeAgentCommandToAllowlist, appendCodeAgentTranscriptEvent, dequeueCodeAgentFollowUp, getCodeAgentRunRecord, isCodeAgentCommandAllowed, listCodeAgentTranscriptEvents, updateCodeAgentRunRecord, } from "./code-agent-runs.js";
|
|
18
19
|
const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
|
|
@@ -1295,11 +1296,15 @@ const AGENTS_MD_INLINE_CAP = 16_000;
|
|
|
1295
1296
|
*/
|
|
1296
1297
|
/** @internal exported for unit tests */
|
|
1297
1298
|
export async function buildCodeAgentSystemPrompt(cwd, permissionMode) {
|
|
1298
|
-
const
|
|
1299
|
+
const appConfig = await loadResolvedAgentNativeConfig(cwd, createAgentNativeConfigContext("serve", "development"));
|
|
1300
|
+
const bundle = readAgentsBundleFromFs(cwd, null, {
|
|
1301
|
+
instructions: appConfig.instructions,
|
|
1302
|
+
});
|
|
1299
1303
|
// If the bundle has no AGENTS.md, try CLAUDE.md as a fallback — many repos
|
|
1300
1304
|
// use that name for agent instructions (e.g. Claude Code projects).
|
|
1301
|
-
let agentsMdContent = bundle.agentsMd;
|
|
1302
|
-
if (!agentsMdContent.trim()
|
|
1305
|
+
let agentsMdContent = bundle.developmentAgentsMd ?? bundle.agentsMd;
|
|
1306
|
+
if (!agentsMdContent.trim() &&
|
|
1307
|
+
appConfig.instructions?.development === undefined) {
|
|
1303
1308
|
try {
|
|
1304
1309
|
const fs = await import("node:fs");
|
|
1305
1310
|
const path = await import("node:path");
|
|
@@ -3,19 +3,19 @@ export interface ConnectBuilderCardProps {
|
|
|
3
3
|
configured: boolean;
|
|
4
4
|
/**
|
|
5
5
|
* True when the server has a Builder branch project configured for this
|
|
6
|
-
* request. When false, the card shows a waitlist CTA
|
|
7
|
-
*
|
|
6
|
+
* request. When false, the card shows a hosted waitlist CTA or a local code
|
|
7
|
+
* external coding-agent handoff instead of a Send button.
|
|
8
8
|
*/
|
|
9
9
|
builderEnabled?: boolean;
|
|
10
10
|
connectUrl: string;
|
|
11
11
|
orgName?: string | null;
|
|
12
|
-
/** The user's feature/change request, forwarded to
|
|
13
|
-
* when they click Send. Empty for generic "connect Builder" prompts. */
|
|
12
|
+
/** The user's feature/change request, forwarded to the selected coding
|
|
13
|
+
* agent when they click Send. Empty for generic "connect Builder" prompts. */
|
|
14
14
|
prompt?: string;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Rich inline card rendered for the `connect-builder` tool call. Shows
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Rich inline card rendered for the `connect-builder` tool call. Shows the
|
|
18
|
+
* Builder handoff when available, or routes local development requests to the
|
|
19
|
+
* external coding agent.
|
|
20
20
|
*/
|
|
21
21
|
export declare function ConnectBuilderCard({ configured: initialConfigured, builderEnabled: initialBuilderEnabled, connectUrl: initialConnectUrl, orgName: initialOrgName, prompt, }: ConnectBuilderCardProps): React.JSX.Element;
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { IconExternalLink, IconLoader2 } from "@tabler/icons-react";
|
|
2
|
+
import { IconCheck, IconCode, IconCopy, IconExternalLink, IconLoader2, } from "@tabler/icons-react";
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { withBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
|
|
5
5
|
import { agentNativePath } from "./api-path.js";
|
|
6
6
|
import { BuilderBMark } from "./builder-mark.js";
|
|
7
|
+
import { writeClipboardText } from "./clipboard.js";
|
|
7
8
|
import { getCallbackOrigin } from "./frame.js";
|
|
8
9
|
import { useBuilderConnectFlow } from "./settings/useBuilderStatus.js";
|
|
9
10
|
import { cn } from "./utils.js";
|
|
10
11
|
const DESKTOP_DOWNLOAD_URL = "https://www.agent-native.com/download";
|
|
11
12
|
const CODE_CHANGE_FALLBACK_DETAIL = "Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.";
|
|
12
13
|
const CODE_CHANGE_FALLBACK_TEXT = `This requires a code change. ${CODE_CHANGE_FALLBACK_DETAIL}`;
|
|
13
|
-
function
|
|
14
|
-
if (typeof window === "undefined"
|
|
14
|
+
function isLocalDevelopment() {
|
|
15
|
+
if (typeof window === "undefined") {
|
|
15
16
|
return false;
|
|
16
17
|
}
|
|
17
18
|
const hostname = window.location.hostname;
|
|
18
|
-
|
|
19
|
-
return local && !/AgentNativeDesktop/i.test(navigator.userAgent || "");
|
|
19
|
+
return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1");
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* Rich inline card rendered for the `connect-builder` tool call. Shows
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Rich inline card rendered for the `connect-builder` tool call. Shows the
|
|
23
|
+
* Builder handoff when available, or routes local development requests to the
|
|
24
|
+
* external coding agent.
|
|
25
25
|
*/
|
|
26
26
|
export function ConnectBuilderCard({ configured: initialConfigured, builderEnabled: initialBuilderEnabled = true, connectUrl: initialConnectUrl, orgName: initialOrgName, prompt = "", }) {
|
|
27
27
|
// The connect-poll state machine is shared — the tool-call result is
|
|
@@ -46,8 +46,11 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
|
|
|
46
46
|
const [sending, setSending] = useState(false);
|
|
47
47
|
const [runResult, setRunResult] = useState(null);
|
|
48
48
|
const [sendErr, setSendErr] = useState(null);
|
|
49
|
-
const [
|
|
49
|
+
const [copyErr, setCopyErr] = useState(null);
|
|
50
|
+
const [promptCopied, setPromptCopied] = useState(false);
|
|
51
|
+
const [localDevelopment] = useState(() => isLocalDevelopment());
|
|
50
52
|
const mountedRef = useRef(true);
|
|
53
|
+
const copyResetRef = useRef(null);
|
|
51
54
|
// Tracks whether the user clicked "Connect Builder" *this session*. When
|
|
52
55
|
// the connect-then-poll round-trip lands `configured=true`, we use this
|
|
53
56
|
// flag to decide whether to retry the user's pending prompt automatically
|
|
@@ -58,9 +61,10 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
|
|
|
58
61
|
const wasConnectingRef = useRef(false);
|
|
59
62
|
useEffect(() => {
|
|
60
63
|
mountedRef.current = true;
|
|
61
|
-
setLocalBrowser(isLocalBrowserOutsideDesktop());
|
|
62
64
|
return () => {
|
|
63
65
|
mountedRef.current = false;
|
|
66
|
+
if (copyResetRef.current)
|
|
67
|
+
clearTimeout(copyResetRef.current);
|
|
64
68
|
};
|
|
65
69
|
}, []);
|
|
66
70
|
const handleSend = useCallback(async () => {
|
|
@@ -127,8 +131,27 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
|
|
|
127
131
|
setJoiningWaitlist(false);
|
|
128
132
|
}
|
|
129
133
|
}, [orgName, prompt]);
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
const handleCopyPrompt = useCallback(async () => {
|
|
135
|
+
if (!prompt.trim())
|
|
136
|
+
return;
|
|
137
|
+
setCopyErr(null);
|
|
138
|
+
const copied = await writeClipboardText(prompt);
|
|
139
|
+
if (!mountedRef.current)
|
|
140
|
+
return;
|
|
141
|
+
if (!copied) {
|
|
142
|
+
setCopyErr("Couldn't copy the prompt");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
setPromptCopied(true);
|
|
146
|
+
if (copyResetRef.current)
|
|
147
|
+
clearTimeout(copyResetRef.current);
|
|
148
|
+
copyResetRef.current = setTimeout(() => {
|
|
149
|
+
if (mountedRef.current)
|
|
150
|
+
setPromptCopied(false);
|
|
151
|
+
}, 1600);
|
|
152
|
+
}, [prompt]);
|
|
153
|
+
// Combine connect-flow errors, send errors, waitlist errors, and copy errors.
|
|
154
|
+
const err = sendErr ?? waitlistErr ?? copyErr ?? flow.error;
|
|
132
155
|
const hasPrompt = prompt.trim().length > 0;
|
|
133
156
|
const canSend = configured && builderEnabled && hasPrompt;
|
|
134
157
|
// Auto-send the user's pending prompt the moment connecting finishes
|
|
@@ -149,7 +172,8 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
|
|
|
149
172
|
}, [flow.connecting, canSend, sending, runResult, sendErr, handleSend]);
|
|
150
173
|
// Branch creation is gated by a server-side project id, which may come
|
|
151
174
|
// from deployment config or org-scoped secrets.
|
|
152
|
-
const
|
|
175
|
+
const showExternalAgentHandoff = localDevelopment && !builderEnabled && hasPrompt;
|
|
176
|
+
const showWaitlist = !localDevelopment && !builderEnabled && hasPrompt;
|
|
153
177
|
// Title + subtitle depend on which mode we're in. We compute them up front
|
|
154
178
|
// so the render tree below stays flat.
|
|
155
179
|
const connectedCapabilityText = builderEnabled
|
|
@@ -161,13 +185,14 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
|
|
|
161
185
|
title = "Builder is working on it";
|
|
162
186
|
subtitle = (_jsxs(_Fragment, { children: ["Working on branch", " ", _jsx("span", { className: "font-mono text-foreground", children: runResult.branchName }), ". Click through to watch progress in the Visual Editor."] }));
|
|
163
187
|
}
|
|
188
|
+
else if (showExternalAgentHandoff) {
|
|
189
|
+
title = "This requires a code change";
|
|
190
|
+
subtitle =
|
|
191
|
+
"Open your coding agent in this project, then paste this request.";
|
|
192
|
+
}
|
|
164
193
|
else if (showWaitlist) {
|
|
165
194
|
title = "This requires a code change";
|
|
166
|
-
subtitle = waitlistJoined ? (_jsxs(_Fragment, { children: ["You're on the waitlist. ", CODE_CHANGE_FALLBACK_DETAIL, " ",
|
|
167
|
-
? "Since this project is already running locally, open it in the desktop app for local coding tools or keep editing from your clone."
|
|
168
|
-
: "You can still clone the project locally and use the desktop app for code changes."] })) : (_jsxs(_Fragment, { children: [CODE_CHANGE_FALLBACK_DETAIL, " ", localBrowser
|
|
169
|
-
? "Since this project is already running locally, open it in the desktop app for local coding tools or keep editing from your clone."
|
|
170
|
-
: "You can still clone the project locally and use the desktop app for code changes."] }));
|
|
195
|
+
subtitle = waitlistJoined ? (_jsxs(_Fragment, { children: ["You're on the waitlist. ", CODE_CHANGE_FALLBACK_DETAIL, " You can still clone the project locally and use the desktop app for code changes."] })) : (_jsxs(_Fragment, { children: [CODE_CHANGE_FALLBACK_DETAIL, " You can still clone the project locally and use the desktop app for code changes."] }));
|
|
171
196
|
}
|
|
172
197
|
else if (canSend) {
|
|
173
198
|
title = "Send this to Builder";
|
|
@@ -181,8 +206,8 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
|
|
|
181
206
|
title = "Connect Builder.io";
|
|
182
207
|
subtitle = _jsx(_Fragment, { children: "Builder.io's free tier includes AI credits." });
|
|
183
208
|
}
|
|
184
|
-
return (_jsx("div", { className: cn("my-2 rounded-lg border border-border overflow-hidden"), children: _jsxs("div", { className: "flex items-start gap-3 px-4 py-3.5 bg-gradient-to-br from-teal-500/5 via-transparent to-transparent", children: [_jsx("div", { className: cn("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", "bg-foreground text-background"), children: runResult ? (_jsx(IconLoader2, { className: "h-5 w-5 animate-spin" })) : (_jsx(BuilderBMark, { className: "h-5 w-5" })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "flex items-center gap-2 flex-wrap", children: _jsx("span", { className: "text-sm font-semibold text-foreground", children: title }) }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground leading-relaxed", children: subtitle }), showWaitlist && (_jsxs("a", { href: DESKTOP_DOWNLOAD_URL, target: "_blank", rel: "noopener noreferrer", className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground no-underline hover:text-foreground", children: ["Download desktop app", _jsx(IconExternalLink, { className: "h-3 w-3" })] })), err && _jsx("div", { className: "mt-2 text-xs text-destructive", children: err }), _jsx("div", { className: "mt-3", children: runResult ? (_jsxs("a", { href: withBuilderUtmTrackingParams(runResult.url, {
|
|
209
|
+
return (_jsx("div", { className: cn("my-2 rounded-lg border border-border overflow-hidden"), children: _jsxs("div", { className: "flex items-start gap-3 px-4 py-3.5 bg-gradient-to-br from-teal-500/5 via-transparent to-transparent", children: [_jsx("div", { className: cn("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", "bg-foreground text-background"), children: runResult ? (_jsx(IconLoader2, { className: "h-5 w-5 animate-spin" })) : showExternalAgentHandoff ? (_jsx(IconCode, { className: "h-5 w-5" })) : (_jsx(BuilderBMark, { className: "h-5 w-5" })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "flex items-center gap-2 flex-wrap", children: _jsx("span", { className: "text-sm font-semibold text-foreground", children: title }) }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground leading-relaxed", children: subtitle }), showWaitlist && (_jsxs("a", { href: DESKTOP_DOWNLOAD_URL, target: "_blank", rel: "noopener noreferrer", className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground no-underline hover:text-foreground", children: ["Download desktop app", _jsx(IconExternalLink, { className: "h-3 w-3" })] })), err && _jsx("div", { className: "mt-2 text-xs text-destructive", children: err }), _jsx("div", { className: "mt-3", children: runResult ? (_jsxs("a", { href: withBuilderUtmTrackingParams(runResult.url, {
|
|
185
210
|
campaign: "product",
|
|
186
211
|
content: "connect_builder_card_branch",
|
|
187
|
-
}), target: "_blank", rel: "noopener noreferrer", className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: ["Open branch in Builder", _jsx(IconExternalLink, { className: "h-3.5 w-3.5" })] })) : canSend ? (_jsx("button", { type: "button", onClick: handleSend, disabled: sending, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", sending && "opacity-70 cursor-wait"), children: sending ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Sending to Builder\u2026"] })) : (_jsx(_Fragment, { children: "Send to Builder" })) })) : showWaitlist && !waitlistJoined ? (_jsx("button", { type: "button", onClick: handleJoinWaitlist, disabled: joiningWaitlist, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", joiningWaitlist && "opacity-70 cursor-wait"), children: joiningWaitlist ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Joining\u2026"] })) : (_jsx(_Fragment, { children: "Join the waitlist" })) })) : !configured ? (_jsx("button", { type: "button", onClick: () => flow.start(), disabled: connecting, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", connecting && "opacity-70 cursor-wait"), children: connecting ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Waiting for Builder\u2026"] })) : ("Connect Builder") })) : null })] })] }) }));
|
|
212
|
+
}), target: "_blank", rel: "noopener noreferrer", className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: ["Open branch in Builder", _jsx(IconExternalLink, { className: "h-3.5 w-3.5" })] })) : canSend ? (_jsx("button", { type: "button", onClick: handleSend, disabled: sending, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", sending && "opacity-70 cursor-wait"), children: sending ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Sending to Builder\u2026"] })) : (_jsx(_Fragment, { children: "Send to Builder" })) })) : showExternalAgentHandoff ? (_jsx("button", { type: "button", onClick: () => void handleCopyPrompt(), className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: promptCopied ? (_jsxs(_Fragment, { children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), "Prompt copied"] })) : (_jsxs(_Fragment, { children: [_jsx(IconCopy, { className: "h-3.5 w-3.5" }), "Copy prompt"] })) })) : showWaitlist && !waitlistJoined ? (_jsx("button", { type: "button", onClick: handleJoinWaitlist, disabled: joiningWaitlist, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", joiningWaitlist && "opacity-70 cursor-wait"), children: joiningWaitlist ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Joining\u2026"] })) : (_jsx(_Fragment, { children: "Join the waitlist" })) })) : !configured ? (_jsx("button", { type: "button", onClick: () => flow.start(), disabled: connecting, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", connecting && "opacity-70 cursor-wait"), children: connecting ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Waiting for Builder\u2026"] })) : ("Connect Builder") })) : null })] })] }) }));
|
|
188
213
|
}
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error: string;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
package/dist/config.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Public, non-secret configuration for an Agent-Native app.
|
|
3
3
|
*
|
|
4
4
|
* This module is intentionally free of Node and framework imports so it can be
|
|
5
|
-
* used from a typed `agent-native.ts` file and from browser code after Vite
|
|
5
|
+
* used from a typed `agent-native.config.ts` file and from browser code after Vite
|
|
6
6
|
* serializes the resolved config into the client bundle.
|
|
7
7
|
*/
|
|
8
8
|
export declare const AGENT_NATIVE_CONFIG_VERSION: 1;
|
|
@@ -42,11 +42,18 @@ export interface AgentNativeDiagnosticsConfig {
|
|
|
42
42
|
/** Fail a production Vite build when runtime configuration has issues. */
|
|
43
43
|
failOnBuild?: boolean;
|
|
44
44
|
}
|
|
45
|
+
export interface AgentNativeInstructionsConfig {
|
|
46
|
+
/** Relative Markdown file loaded by the in-app runtime agent. */
|
|
47
|
+
runtime?: string;
|
|
48
|
+
/** Relative Markdown file loaded by development/coding agents. */
|
|
49
|
+
development?: string;
|
|
50
|
+
}
|
|
45
51
|
export interface AgentNativeConfig {
|
|
46
52
|
version?: typeof AGENT_NATIVE_CONFIG_VERSION;
|
|
47
53
|
onboarding?: AgentNativeOnboardingConfig;
|
|
48
54
|
runtime?: AgentNativeRuntimeConfig;
|
|
49
55
|
diagnostics?: AgentNativeDiagnosticsConfig;
|
|
56
|
+
instructions?: AgentNativeInstructionsConfig;
|
|
50
57
|
}
|
|
51
58
|
export interface AgentNativeConfigContext {
|
|
52
59
|
command: "serve" | "build";
|
|
@@ -57,7 +64,7 @@ export interface AgentNativeConfigContext {
|
|
|
57
64
|
export type AgentNativeConfigFactory = (context: AgentNativeConfigContext) => AgentNativeConfig;
|
|
58
65
|
export type AgentNativeConfigInput = AgentNativeConfig | AgentNativeConfigFactory;
|
|
59
66
|
/**
|
|
60
|
-
* Type-safe authoring helper for `agent-native.ts`.
|
|
67
|
+
* Type-safe authoring helper for `agent-native.config.ts`.
|
|
61
68
|
*
|
|
62
69
|
* Like Next's typed config file, this is deliberately identity-like: the
|
|
63
70
|
* framework evaluates the exported object or factory in the Vite config
|
package/dist/config.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Public, non-secret configuration for an Agent-Native app.
|
|
3
3
|
*
|
|
4
4
|
* This module is intentionally free of Node and framework imports so it can be
|
|
5
|
-
* used from a typed `agent-native.ts` file and from browser code after Vite
|
|
5
|
+
* used from a typed `agent-native.config.ts` file and from browser code after Vite
|
|
6
6
|
* serializes the resolved config into the client bundle.
|
|
7
7
|
*/
|
|
8
8
|
export const AGENT_NATIVE_CONFIG_VERSION = 1;
|
|
9
9
|
/**
|
|
10
|
-
* Type-safe authoring helper for `agent-native.ts`.
|
|
10
|
+
* Type-safe authoring helper for `agent-native.config.ts`.
|
|
11
11
|
*
|
|
12
12
|
* Like Next's typed config file, this is deliberately identity-like: the
|
|
13
13
|
* framework evaluates the exported object or factory in the Vite config
|
|
@@ -27,6 +27,7 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
|
|
|
27
27
|
const onboardingValue = input.onboarding;
|
|
28
28
|
const runtimeValue = input.runtime;
|
|
29
29
|
const diagnosticsValue = input.diagnostics;
|
|
30
|
+
const instructionsValue = input.instructions;
|
|
30
31
|
const normalized = {
|
|
31
32
|
...(input.version === undefined
|
|
32
33
|
? {}
|
|
@@ -45,6 +46,9 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
|
|
|
45
46
|
if (diagnosticsValue !== undefined) {
|
|
46
47
|
normalized.diagnostics = normalizeDiagnosticsConfig(diagnosticsValue, `${source}.diagnostics`);
|
|
47
48
|
}
|
|
49
|
+
if (instructionsValue !== undefined) {
|
|
50
|
+
normalized.instructions = normalizeInstructionsConfig(instructionsValue, `${source}.instructions`);
|
|
51
|
+
}
|
|
48
52
|
return normalized;
|
|
49
53
|
}
|
|
50
54
|
export function mergeAgentNativeConfigs(base, override) {
|
|
@@ -91,6 +95,12 @@ export function mergeAgentNativeConfigs(base, override) {
|
|
|
91
95
|
...override.diagnostics,
|
|
92
96
|
}
|
|
93
97
|
: undefined,
|
|
98
|
+
instructions: base.instructions || override.instructions
|
|
99
|
+
? {
|
|
100
|
+
...base.instructions,
|
|
101
|
+
...override.instructions,
|
|
102
|
+
}
|
|
103
|
+
: undefined,
|
|
94
104
|
};
|
|
95
105
|
}
|
|
96
106
|
export function resolveAgentNativeConfig(input, context) {
|
|
@@ -175,6 +185,32 @@ function normalizeDiagnosticsConfig(value, source) {
|
|
|
175
185
|
? {}
|
|
176
186
|
: { failOnBuild: value.failOnBuild };
|
|
177
187
|
}
|
|
188
|
+
function normalizeInstructionsConfig(value, source) {
|
|
189
|
+
if (!isRecord(value)) {
|
|
190
|
+
throw new Error(`${source} must be an object`);
|
|
191
|
+
}
|
|
192
|
+
const result = {};
|
|
193
|
+
for (const audience of ["runtime", "development"]) {
|
|
194
|
+
const pathValue = value[audience];
|
|
195
|
+
if (pathValue === undefined)
|
|
196
|
+
continue;
|
|
197
|
+
if (typeof pathValue !== "string") {
|
|
198
|
+
throw new Error(`${source}.${audience} must be a relative file path`);
|
|
199
|
+
}
|
|
200
|
+
result[audience] = normalizeRelativeFilePath(pathValue, `${source}.${audience}`);
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
function normalizeRelativeFilePath(value, source) {
|
|
205
|
+
const normalized = value.trim().replaceAll("\\", "/");
|
|
206
|
+
if (!normalized ||
|
|
207
|
+
normalized.startsWith("/") ||
|
|
208
|
+
/^[A-Za-z]:\//.test(normalized) ||
|
|
209
|
+
normalized.split("/").some((segment) => segment === "..")) {
|
|
210
|
+
throw new Error(`${source} must be a non-empty relative file path inside the app root`);
|
|
211
|
+
}
|
|
212
|
+
return normalized;
|
|
213
|
+
}
|
|
178
214
|
function normalizeRequiredEnvKeys(value, source) {
|
|
179
215
|
if (value === undefined)
|
|
180
216
|
return undefined;
|
package/dist/db/client.d.ts
CHANGED
|
@@ -227,7 +227,19 @@ export declare function isBackgroundFunctionPoolContext(): boolean;
|
|
|
227
227
|
* actual context.
|
|
228
228
|
*/
|
|
229
229
|
export declare function describeDbError(err: unknown): string;
|
|
230
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Thrown INSTEAD of attempting, while an endpoint is in cooldown.
|
|
232
|
+
*
|
|
233
|
+
* The refused attempt's error is described in the message and deliberately NOT
|
|
234
|
+
* attached as `cause`: isConnectionError() walks `cause.code`/`cause.message`,
|
|
235
|
+
* so a cause would reclassify this as retryable and retryOnConnectionError
|
|
236
|
+
* would drive the storm straight back.
|
|
237
|
+
*/
|
|
238
|
+
export declare class DbConnectCooldownError extends Error {
|
|
239
|
+
code: string;
|
|
240
|
+
constructor(remainingMs: number, refusedBy: string);
|
|
241
|
+
}
|
|
242
|
+
export declare function guardNeonPool(pool: unknown, url: string, label?: string): void;
|
|
231
243
|
interface ClosablePool {
|
|
232
244
|
end(): Promise<unknown>;
|
|
233
245
|
}
|
package/dist/db/client.js
CHANGED
|
@@ -613,6 +613,9 @@ export function isTransientDatabaseError(err) {
|
|
|
613
613
|
const error = err;
|
|
614
614
|
const code = String(error?.code ?? error?.cause?.code ?? "");
|
|
615
615
|
if (code === "ECHECKOUTTIMEOUT" ||
|
|
616
|
+
// Shed load during a connect cooldown is transient by construction — the
|
|
617
|
+
// endpoint refused, we chose not to re-ask yet. Surfaces as 503, not 500.
|
|
618
|
+
code === "DB_CONNECT_COOLDOWN" ||
|
|
616
619
|
code === "EMAXCONN" ||
|
|
617
620
|
code === "53300" ||
|
|
618
621
|
code === "57014" ||
|
|
@@ -960,7 +963,77 @@ export function describeDbError(err) {
|
|
|
960
963
|
}
|
|
961
964
|
return String(err);
|
|
962
965
|
}
|
|
963
|
-
|
|
966
|
+
/**
|
|
967
|
+
* How long one endpoint stops ATTEMPTING to connect after an attempt failed.
|
|
968
|
+
* Jittered so concurrent function instances do not re-probe in lockstep.
|
|
969
|
+
*/
|
|
970
|
+
function connectCooldownMs() {
|
|
971
|
+
const raw = Number(process.env.DB_CONNECT_COOLDOWN_MS);
|
|
972
|
+
const base = Number.isFinite(raw) && raw > 0 ? raw : 2_000;
|
|
973
|
+
return Math.round(base * (0.5 + Math.random()));
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Thrown INSTEAD of attempting, while an endpoint is in cooldown.
|
|
977
|
+
*
|
|
978
|
+
* The refused attempt's error is described in the message and deliberately NOT
|
|
979
|
+
* attached as `cause`: isConnectionError() walks `cause.code`/`cause.message`,
|
|
980
|
+
* so a cause would reclassify this as retryable and retryOnConnectionError
|
|
981
|
+
* would drive the storm straight back.
|
|
982
|
+
*/
|
|
983
|
+
export class DbConnectCooldownError extends Error {
|
|
984
|
+
code = "DB_CONNECT_COOLDOWN";
|
|
985
|
+
constructor(remainingMs, refusedBy) {
|
|
986
|
+
super(`Database is refusing connection attempts; not attempting again for ${remainingMs}ms. Last failure: ${refusedBy}`);
|
|
987
|
+
this.name = "DbConnectCooldownError";
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
const connectCooldowns = new Map();
|
|
991
|
+
/**
|
|
992
|
+
* Neon rejects a connection ATTEMPT, not a connection: "Failed to acquire
|
|
993
|
+
* permit to connect to the database. Too many database connection attempts are
|
|
994
|
+
* currently ongoing." A failed acquire leaves the pool with zero idle clients,
|
|
995
|
+
* so the next execute() calls connect() again — and retryOnConnectionError
|
|
996
|
+
* backs off only 100ms. The process answers a refusal by manufacturing the next
|
|
997
|
+
* attempt, which is what keeps the refusal true. Production sat in this loop
|
|
998
|
+
* until the compute was restarted by hand.
|
|
999
|
+
*
|
|
1000
|
+
* Hold a short cooldown per endpoint so a refused attempt cannot produce the
|
|
1001
|
+
* next one. Checking out an ALREADY-IDLE client is not an attempt and passes
|
|
1002
|
+
* through, so a cooldown degrades throughput instead of taking the process
|
|
1003
|
+
* offline.
|
|
1004
|
+
*/
|
|
1005
|
+
function gateNeonConnect(pool, url, label) {
|
|
1006
|
+
const connect = pool.connect;
|
|
1007
|
+
if (typeof connect !== "function")
|
|
1008
|
+
return;
|
|
1009
|
+
const gated = function gatedConnect(...args) {
|
|
1010
|
+
const idle = typeof pool.idleCount === "number" ? pool.idleCount : 0;
|
|
1011
|
+
const gate = connectCooldowns.get(url);
|
|
1012
|
+
if (idle === 0 && gate && Date.now() < gate.until) {
|
|
1013
|
+
return Promise.reject(new DbConnectCooldownError(gate.until - Date.now(), gate.refusedBy));
|
|
1014
|
+
}
|
|
1015
|
+
return Promise.resolve(connect.apply(this, args)).then((client) => {
|
|
1016
|
+
connectCooldowns.delete(url);
|
|
1017
|
+
return client;
|
|
1018
|
+
}, (err) => {
|
|
1019
|
+
const refusedBy = describeDbError(err);
|
|
1020
|
+
const ms = connectCooldownMs();
|
|
1021
|
+
// One line per cooldown window, not per shed request.
|
|
1022
|
+
if (!gate || Date.now() >= gate.until) {
|
|
1023
|
+
console.warn(`[${label}] connection attempt refused; pausing attempts ${ms}ms:`, refusedBy);
|
|
1024
|
+
}
|
|
1025
|
+
connectCooldowns.set(url, { until: Date.now() + ms, refusedBy });
|
|
1026
|
+
throw err;
|
|
1027
|
+
});
|
|
1028
|
+
};
|
|
1029
|
+
// Carry the wrapped function's own properties onto the wrapper. A pool's
|
|
1030
|
+
// `connect` may be instrumented — metrics, tracing, a test spy — and
|
|
1031
|
+
// replacing it with a bare closure would silently drop that instrumentation
|
|
1032
|
+
// along with any assertions built on it.
|
|
1033
|
+
Object.assign(gated, connect);
|
|
1034
|
+
pool.connect = gated;
|
|
1035
|
+
}
|
|
1036
|
+
export function guardNeonPool(pool, url, label = "db/neon") {
|
|
964
1037
|
if (!pool || typeof pool !== "object")
|
|
965
1038
|
return;
|
|
966
1039
|
if (loggedNeonPools.has(pool))
|
|
@@ -969,6 +1042,7 @@ export function attachNeonPoolErrorLogger(pool, label = "db/neon") {
|
|
|
969
1042
|
if (typeof withEvents.on !== "function")
|
|
970
1043
|
return;
|
|
971
1044
|
loggedNeonPools.add(pool);
|
|
1045
|
+
gateNeonConnect(pool, url, label);
|
|
972
1046
|
withEvents.on("error", (err) => {
|
|
973
1047
|
console.warn(`[${label}] pool error (will reconnect on next query):`, describeDbError(err));
|
|
974
1048
|
});
|
|
@@ -1177,7 +1251,7 @@ async function createDbExecInternal(config = {}, trackSingletonResources = false
|
|
|
1177
1251
|
const pool = trackSingletonResources
|
|
1178
1252
|
? sharedDbPool("neon", url, makePool)
|
|
1179
1253
|
: makePool();
|
|
1180
|
-
|
|
1254
|
+
guardNeonPool(pool, url);
|
|
1181
1255
|
const httpSql = bgHttp ? neon(url, { fullResults: true }) : null;
|
|
1182
1256
|
async function queryNeonClient(client, sql, timeoutOverrideMs) {
|
|
1183
1257
|
const { rawSql, args } = sqlAndArgs(sql);
|
package/dist/db/create-get-db.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { drizzle as drizzleD1 } from "drizzle-orm/d1";
|
|
3
|
-
import { getDialect, getCloudflareD1Binding, getDatabaseUrl, getDatabaseAuthToken, isLocalSqliteUrl, isPgliteUrl, isConnectionError, getPgliteClient, loadPgliteDrizzle, prepareLocalSqliteUrl, sqliteFilenameFromUrl, pgPoolOptions, neonPoolOptions,
|
|
3
|
+
import { getDialect, getCloudflareD1Binding, getDatabaseUrl, getDatabaseAuthToken, isLocalSqliteUrl, isPgliteUrl, isConnectionError, getPgliteClient, loadPgliteDrizzle, prepareLocalSqliteUrl, sqliteFilenameFromUrl, pgPoolOptions, neonPoolOptions, guardNeonPool, withDbTimeout, retryOnConnectionError, dbOpTimeoutMs, sharedDbPool, onSharedDbPoolsClosed, onSharedDbPoolReplaced, } from "./client.js";
|
|
4
4
|
// Lazy driver loaders — cached promises so dynamic import only runs once.
|
|
5
5
|
let _pgDrizzle;
|
|
6
6
|
function getPgDrizzle() {
|
|
@@ -408,7 +408,7 @@ export function createGetDb(schema) {
|
|
|
408
408
|
// schema module. See `sharedDbPool` in client.ts.
|
|
409
409
|
resetOnPoolClose("neon", url);
|
|
410
410
|
const rawPool = sharedDbPool("neon", url, () => new Pool({ connectionString: url, ...neonPoolOptions() }));
|
|
411
|
-
|
|
411
|
+
guardNeonPool(rawPool, url);
|
|
412
412
|
// Wrap the pool with the resilience layer so Drizzle queries get the
|
|
413
413
|
// same withDbTimeout + retryOnConnectionError protection as the raw
|
|
414
414
|
// DbExec path in client.ts. Reads retry freely; writes only retry on
|
package/dist/deploy/build.js
CHANGED
|
@@ -27,6 +27,7 @@ import { DEFAULT_SPECULATION_RULES_PATH, resolveSsrCacheHeaders, } from "../shar
|
|
|
27
27
|
import { mcpEmbedStaticAssetRouteRules } from "../shared/mcp-embed-headers.js";
|
|
28
28
|
import { AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, } from "../shared/social-meta.js";
|
|
29
29
|
import { generateActionRegistryForProject } from "../vite/action-types-plugin.js";
|
|
30
|
+
import { createAgentNativeConfigContext, loadResolvedAgentNativeConfig, } from "../vite/agent-native-config-loader.js";
|
|
30
31
|
import { cloneServerBundleForFunction, copyDir } from "./function-bundle.js";
|
|
31
32
|
import { collectImmutableAssetPaths, IMMUTABLE_ASSET_CACHE_CONTROL, IMMUTABLE_ASSET_CACHE_HEADERS, prefixAssetPath, } from "./immutable-assets.js";
|
|
32
33
|
import { discoverApiRoutes, discoverPlugins, discoverActionFiles, getMissingDefaultPlugins, DEFAULT_PLUGIN_REGISTRY, } from "./route-discovery.js";
|
|
@@ -3493,6 +3494,7 @@ async function buildWithNitro() {
|
|
|
3493
3494
|
// own virtual module registration. Both paths reuse `readAgentsBundleFromFs`
|
|
3494
3495
|
// from `server/agents-bundle.ts` to guarantee identical content.
|
|
3495
3496
|
const { readAgentsBundleFromFs } = await import("../server/agents-bundle.js");
|
|
3497
|
+
const nitroAgentConfig = await loadResolvedAgentNativeConfig(cwd, createAgentNativeConfigContext("build", process.env.NODE_ENV === "development" ? "development" : "production"));
|
|
3496
3498
|
// Resolve the workspace core (if present) up front so the bundle embeds
|
|
3497
3499
|
// enterprise-wide AGENTS.md + skills alongside the template's.
|
|
3498
3500
|
const nitroWorkspaceCore = await getWorkspaceCoreExports(cwd);
|
|
@@ -3504,7 +3506,9 @@ async function buildWithNitro() {
|
|
|
3504
3506
|
}
|
|
3505
3507
|
: null;
|
|
3506
3508
|
const agentsBundleModuleSource = () => {
|
|
3507
|
-
const bundle = readAgentsBundleFromFs(cwd, nitroWorkspaceSource
|
|
3509
|
+
const bundle = readAgentsBundleFromFs(cwd, nitroWorkspaceSource, {
|
|
3510
|
+
instructions: nitroAgentConfig.instructions,
|
|
3511
|
+
});
|
|
3508
3512
|
return `// AUTO-GENERATED by @agent-native/core deploy build (Nitro virtual)
|
|
3509
3513
|
// Contains the inlined AGENTS.md + .agents/skills/ content from the template,
|
|
3510
3514
|
// merged with the workspace core's AGENTS.md + skills/ when present.
|
|
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
17
17
|
id?: undefined;
|
|
18
18
|
provider?: undefined;
|
|
19
19
|
} | {
|
|
20
|
-
error?: undefined;
|
|
21
20
|
configured?: undefined;
|
|
22
21
|
connectPath?: undefined;
|
|
23
22
|
url: string;
|
|
24
23
|
id: string;
|
|
25
24
|
provider: string;
|
|
25
|
+
error?: undefined;
|
|
26
26
|
}>;
|
|
27
27
|
export default _default;
|