@agent-native/core 0.80.9 → 0.80.11
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/core/CHANGELOG.md +13 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +108 -15
- package/corpus/core/src/cli/design-connect.ts +28 -2
- package/corpus/core/src/cli/skills.ts +45 -24
- package/corpus/core/src/client/AssistantChat.tsx +123 -26
- package/corpus/core/src/client/sse-event-processor.ts +9 -0
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
- package/corpus/templates/assets/AGENTS.md +4 -0
- package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
- package/corpus/templates/assets/actions/generate-image.ts +118 -67
- package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
- package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
- package/corpus/templates/assets/app/i18n-data.ts +61 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +145 -38
- package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
- package/corpus/templates/assets/server/lib/generation.ts +135 -11
- package/corpus/templates/assets/shared/api.ts +4 -0
- package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
- package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
- package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +441 -0
- package/corpus/templates/design/actions/apply-design-state.ts +213 -0
- package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
- package/corpus/templates/design/actions/apply-motion-edit.ts +413 -0
- package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +191 -5
- package/corpus/templates/design/actions/capture-design-state.ts +224 -0
- package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
- package/corpus/templates/design/actions/create-component.ts +447 -0
- package/corpus/templates/design/actions/create-design-branch.ts +263 -0
- package/corpus/templates/design/actions/create-design-state.ts +162 -0
- package/corpus/templates/design/actions/delete-design-state.ts +55 -0
- package/corpus/templates/design/actions/delete-design.ts +20 -0
- package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
- package/corpus/templates/design/actions/get-component-details.ts +251 -0
- package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
- package/corpus/templates/design/actions/get-design-review.ts +314 -0
- package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
- package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
- package/corpus/templates/design/actions/index-components.ts +258 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +277 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
- package/corpus/templates/design/actions/list-design-states.ts +90 -0
- package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
- package/corpus/templates/design/actions/open-component-source.ts +242 -0
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +260 -0
- package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
- package/corpus/templates/design/actions/preview-shader-fill.ts +187 -0
- package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
- package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
- package/corpus/templates/design/actions/run-design-audit.ts +436 -0
- package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
- package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1550 -82
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +649 -43
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1788 -52
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +114 -33
- package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +1071 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1053 -99
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +766 -0
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +581 -0
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +578 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
- package/corpus/templates/design/app/components/design/index.ts +16 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +15 -6
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +66 -3
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
- package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
- package/corpus/templates/design/app/i18n-data.ts +280 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +3087 -490
- package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-token-edits-now-stay-visible-in-previews-and-token-li.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +94 -2
- package/corpus/templates/design/e2e/helpers.ts +10 -4
- package/corpus/templates/design/server/db/schema.ts +123 -0
- package/corpus/templates/design/server/plugins/db.ts +90 -0
- package/corpus/templates/design/shared/builder-app.ts +297 -0
- package/corpus/templates/design/shared/capability-resolver.ts +123 -0
- package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
- package/corpus/templates/design/shared/code-layer.ts +1016 -71
- package/corpus/templates/design/shared/component-model.ts +274 -0
- package/corpus/templates/design/shared/design-review.ts +275 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
- package/corpus/templates/design/shared/design-state.ts +112 -0
- package/corpus/templates/design/shared/design-surface-index.ts +258 -0
- package/corpus/templates/design/shared/motion-compiler.ts +349 -0
- package/corpus/templates/design/shared/motion-timeline.ts +193 -0
- package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
- package/corpus/templates/design/shared/responsive-classes.ts +452 -0
- package/corpus/templates/design/shared/shader-fill.ts +323 -0
- package/corpus/templates/design/shared/source-mode.ts +245 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
- package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +94 -10
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +28 -2
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +37 -21
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +14 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +115 -22
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +8 -8
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server/request-context";
|
|
6
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
7
|
+
import { eq } from "drizzle-orm";
|
|
8
|
+
import { nanoid } from "nanoid";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
12
|
+
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
13
|
+
import { hasCapability } from "../shared/design-source-capabilities.js";
|
|
14
|
+
import type { DesignSourceCapabilities } from "../shared/design-source-capabilities.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the capabilities for the design's source type.
|
|
18
|
+
* Falls back to inline capabilities when not present in stored data.
|
|
19
|
+
*/
|
|
20
|
+
function resolveCapabilities(
|
|
21
|
+
designData: string | null,
|
|
22
|
+
): DesignSourceCapabilities | null {
|
|
23
|
+
if (!designData) return null;
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(designData) as Record<string, unknown>;
|
|
26
|
+
if (parsed.capabilities && typeof parsed.capabilities === "object") {
|
|
27
|
+
return parsed.capabilities as DesignSourceCapabilities;
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// ignore parse errors
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Maximum serialised size of a captured/replayed `captureData` payload.
|
|
37
|
+
* Captured DOM snapshots are arbitrary caller markup; cap them so a single
|
|
38
|
+
* capture can't bloat the design row (and the shareable content it feeds).
|
|
39
|
+
*/
|
|
40
|
+
const CAPTURE_DATA_MAX_BYTES = 256 * 1024; // 256KB
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Strip stored-XSS vectors out of an HTML/markup string before it is persisted
|
|
44
|
+
* and later replayed into shareable design content. Mirrors the framework's
|
|
45
|
+
* text-edit HTML sanitiser: removes script/style/iframe/object/embed/link/meta/
|
|
46
|
+
* base tags, inline `on*` handlers, and `javascript:` / `vbscript:` / `data:`
|
|
47
|
+
* URLs in `href` / `src` / `xlink:href`.
|
|
48
|
+
*/
|
|
49
|
+
function sanitizeMarkup(html: string): string {
|
|
50
|
+
return html
|
|
51
|
+
.replace(
|
|
52
|
+
/<\s*(script|style|iframe|object|embed|link|meta|base)\b[\s\S]*?<\s*\/\s*\1\s*>/gi,
|
|
53
|
+
"",
|
|
54
|
+
)
|
|
55
|
+
.replace(
|
|
56
|
+
/<\s*(script|style|iframe|object|embed|link|meta|base)\b[^>]*\/?\s*>/gi,
|
|
57
|
+
"",
|
|
58
|
+
)
|
|
59
|
+
.replace(/\s+on[A-Za-z]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/g, "")
|
|
60
|
+
.replace(
|
|
61
|
+
/\s+(href|src|xlink:href)\s*=\s*(?:(["'])\s*(?:javascript|vbscript|data):[\s\S]*?\2|(?:javascript|vbscript|data):[^\s>]*)/gi,
|
|
62
|
+
"",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A string "looks like markup" — and is therefore worth sanitising — when it
|
|
68
|
+
* contains an angle-bracket tag opener or an Alpine `x-`/`@`/`:` binding that
|
|
69
|
+
* could carry script. Plain data strings (route names, ids) are left untouched.
|
|
70
|
+
*/
|
|
71
|
+
function looksLikeMarkup(value: string): boolean {
|
|
72
|
+
return /<[a-zA-Z!/]/.test(value) || value.includes("</");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Recursively sanitise every string value inside a captured/replayed
|
|
77
|
+
* `captureData` object (e.g. `domHtml`, `domSnapshot`, `x-data` markup) so no
|
|
78
|
+
* untrusted DOM is persisted raw. Non-string leaves pass through unchanged.
|
|
79
|
+
*/
|
|
80
|
+
function sanitizeCaptureData(value: unknown): unknown {
|
|
81
|
+
if (typeof value === "string") {
|
|
82
|
+
return looksLikeMarkup(value) ? sanitizeMarkup(value) : value;
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return value.map((entry) => sanitizeCaptureData(entry));
|
|
86
|
+
}
|
|
87
|
+
if (value && typeof value === "object") {
|
|
88
|
+
const out: Record<string, unknown> = {};
|
|
89
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
90
|
+
out[k] = sanitizeCaptureData(v);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default defineAction({
|
|
98
|
+
description:
|
|
99
|
+
"Capture the running app's current route, props, and API data into a " +
|
|
100
|
+
"design_state row of kind 'capture'. Requires the design's source to " +
|
|
101
|
+
"advertise the 'captureState' capability (localhost and fusion tiers). " +
|
|
102
|
+
"The bridge's captureState operation is called and the result is persisted " +
|
|
103
|
+
"as a new design state that can be loaded in the States panel. " +
|
|
104
|
+
"For inline designs without a live bridge, use create-design-state instead.",
|
|
105
|
+
schema: z.object({
|
|
106
|
+
designId: z.string().describe("Design project ID"),
|
|
107
|
+
name: z
|
|
108
|
+
.string()
|
|
109
|
+
.min(1)
|
|
110
|
+
.describe(
|
|
111
|
+
"Human-readable label for the captured state (e.g. 'Checkout – empty cart', 'Profile – logged in').",
|
|
112
|
+
),
|
|
113
|
+
breakpoint: z
|
|
114
|
+
.enum(["auto", "desktop", "tablet", "mobile"])
|
|
115
|
+
.default("auto")
|
|
116
|
+
.describe("Breakpoint context active at the time of capture."),
|
|
117
|
+
sourceRef: z
|
|
118
|
+
.string()
|
|
119
|
+
.optional()
|
|
120
|
+
.describe(
|
|
121
|
+
"Source connection ref (routeId for localhost/fusion). Omit if the design has only one source.",
|
|
122
|
+
),
|
|
123
|
+
route: z
|
|
124
|
+
.string()
|
|
125
|
+
.optional()
|
|
126
|
+
.describe("App route path at capture time (e.g. '/checkout')."),
|
|
127
|
+
captureData: z
|
|
128
|
+
.preprocess(
|
|
129
|
+
(v) => (typeof v === "string" ? JSON.parse(v) : v),
|
|
130
|
+
z.record(z.string(), z.unknown()),
|
|
131
|
+
)
|
|
132
|
+
.describe(
|
|
133
|
+
"The payload returned by the bridge captureState op — typically " +
|
|
134
|
+
"{ route, props, queryParams, apiResponses, domSnapshot }. " +
|
|
135
|
+
"The caller (UI or agent) must have already invoked the bridge and pass the result here.",
|
|
136
|
+
),
|
|
137
|
+
previewRef: z
|
|
138
|
+
.string()
|
|
139
|
+
.optional()
|
|
140
|
+
.describe(
|
|
141
|
+
"Snapshot image URL or design_version id produced immediately before " +
|
|
142
|
+
"or after the capture (optional).",
|
|
143
|
+
),
|
|
144
|
+
}),
|
|
145
|
+
run: async ({
|
|
146
|
+
designId,
|
|
147
|
+
name,
|
|
148
|
+
breakpoint,
|
|
149
|
+
sourceRef,
|
|
150
|
+
route,
|
|
151
|
+
captureData,
|
|
152
|
+
previewRef,
|
|
153
|
+
}) => {
|
|
154
|
+
await assertAccess("design", designId, "editor");
|
|
155
|
+
|
|
156
|
+
const db = getDb();
|
|
157
|
+
|
|
158
|
+
// Capability gate — captureState must be available for this design source.
|
|
159
|
+
const [design] = await db
|
|
160
|
+
.select({ data: schema.designs.data })
|
|
161
|
+
.from(schema.designs)
|
|
162
|
+
.where(eq(schema.designs.id, designId))
|
|
163
|
+
.limit(1);
|
|
164
|
+
|
|
165
|
+
if (!design) {
|
|
166
|
+
throw new Error(`Design '${designId}' not found.`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const caps = resolveCapabilities(design.data);
|
|
170
|
+
if (caps !== null && !hasCapability(caps, "captureState")) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
"The design's source does not support captureState. " +
|
|
173
|
+
"Connect Builder or a localhost bridge to enable live captures.",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const id = nanoid();
|
|
178
|
+
const now = new Date().toISOString();
|
|
179
|
+
const ownerEmail = getRequestUserEmail();
|
|
180
|
+
if (!ownerEmail) throw new Error("no authenticated user");
|
|
181
|
+
const orgId = getRequestOrgId();
|
|
182
|
+
|
|
183
|
+
// Sanitise captured DOM/markup (stored-XSS guard) and enforce a size cap so
|
|
184
|
+
// a single capture can't bloat the row / the shareable content it feeds.
|
|
185
|
+
const sanitizedCaptureData = sanitizeCaptureData(captureData);
|
|
186
|
+
const captureDataJson = JSON.stringify(sanitizedCaptureData);
|
|
187
|
+
if (Buffer.byteLength(captureDataJson, "utf8") > CAPTURE_DATA_MAX_BYTES) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`captureData exceeds the ${Math.round(
|
|
190
|
+
CAPTURE_DATA_MAX_BYTES / 1024,
|
|
191
|
+
)}KB limit. Capture a smaller DOM snapshot or trim the payload.`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await db.insert(schema.designState).values({
|
|
196
|
+
id,
|
|
197
|
+
designId,
|
|
198
|
+
sourceRef: sourceRef ?? null,
|
|
199
|
+
name,
|
|
200
|
+
kind: "capture",
|
|
201
|
+
breakpoint,
|
|
202
|
+
route: route ?? null,
|
|
203
|
+
fixtureData: null,
|
|
204
|
+
captureData: captureDataJson,
|
|
205
|
+
previewRef: previewRef ?? null,
|
|
206
|
+
createdAt: now,
|
|
207
|
+
updatedAt: now,
|
|
208
|
+
ownerEmail,
|
|
209
|
+
orgId,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
id,
|
|
214
|
+
designId,
|
|
215
|
+
name,
|
|
216
|
+
kind: "capture",
|
|
217
|
+
breakpoint,
|
|
218
|
+
route: route ?? null,
|
|
219
|
+
sourceRef: sourceRef ?? null,
|
|
220
|
+
previewRef: previewRef ?? null,
|
|
221
|
+
createdAt: now,
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* connect-builder-app — return the Builder connection state / CTA payload for
|
|
3
|
+
* a design so the UI can render the appropriate inline card.
|
|
4
|
+
*
|
|
5
|
+
* This action intentionally does NOT start the OAuth / cli-auth flow; the
|
|
6
|
+
* existing `connect-builder` agent-chat tool owns that flow and renders the
|
|
7
|
+
* interactive card in chat. What this action does instead:
|
|
8
|
+
*
|
|
9
|
+
* 1. Check whether Builder is currently configured (credentials + project ID)
|
|
10
|
+
* via the shared `resolveBuilderStatus` helper (no credential values leak).
|
|
11
|
+
* 2. Return a structured payload the UI can use to decide whether to render
|
|
12
|
+
* an "already connected" summary, a "connect to unlock" CTA, or a
|
|
13
|
+
* "Builder enabled — ready to migrate" state.
|
|
14
|
+
*
|
|
15
|
+
* The `connectUrl` field is the pre-built URL that opens the Builder cli-auth
|
|
16
|
+
* popup from the current app origin (same shape the agent-chat plugin returns
|
|
17
|
+
* in the `kind: "connect-builder-card"` tool result). The UI should open this
|
|
18
|
+
* in a popup and poll `/builder/status` for completion, matching the existing
|
|
19
|
+
* connect flow.
|
|
20
|
+
*
|
|
21
|
+
* Gate: any design the caller can view is sufficient — the action is read-only
|
|
22
|
+
* and returns only connection-level metadata, not design content.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { defineAction } from "@agent-native/core";
|
|
26
|
+
import { getBuilderBranchProjectId } from "@agent-native/core/server";
|
|
27
|
+
import { getRequestContext } from "@agent-native/core/server/request-context";
|
|
28
|
+
import { resolveAccess } from "@agent-native/core/sharing";
|
|
29
|
+
import { z } from "zod";
|
|
30
|
+
|
|
31
|
+
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
32
|
+
import { resolveBuilderStatus } from "../shared/builder-app.js";
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Helpers
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/** The default Builder app host — mirrors the constant in builder-browser.ts. */
|
|
39
|
+
const DEFAULT_BUILDER_APP_HOST = "https://builder.io";
|
|
40
|
+
|
|
41
|
+
/** Resolve the Builder app host from env, matching core builder-browser.ts. */
|
|
42
|
+
function resolveBuilderAppHost(): string {
|
|
43
|
+
return (
|
|
44
|
+
process.env.BUILDER_APP_HOST ||
|
|
45
|
+
process.env.BUILDER_PUBLIC_APP_HOST ||
|
|
46
|
+
DEFAULT_BUILDER_APP_HOST
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build the connect URL for the current deployment's origin.
|
|
52
|
+
* Mirrors the shape returned by `getBuilderBrowserConnectUrl` in core, but
|
|
53
|
+
* without requiring the H3 event — uses the request-context origin instead.
|
|
54
|
+
*/
|
|
55
|
+
function buildConnectUrl(origin: string): string {
|
|
56
|
+
const base = origin.replace(/\/+$/, "");
|
|
57
|
+
return `${base}/_agent-native/builder/connect`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Action
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
export default defineAction({
|
|
65
|
+
description:
|
|
66
|
+
"Return the Builder connection state and CTA payload for a design. " +
|
|
67
|
+
"Use this to check whether Builder is configured before offering the " +
|
|
68
|
+
"'Make it real' upgrade flow. Returns { connected, builderEnabled, " +
|
|
69
|
+
"connectUrl, appHost, branchProjectId } so the UI can render the correct " +
|
|
70
|
+
"inline card without making a separate status fetch. " +
|
|
71
|
+
"When connected is false, direct the user to the connectUrl to start the " +
|
|
72
|
+
"Builder OAuth flow. When builderEnabled is true, the Builder cloud agent " +
|
|
73
|
+
"can accept a migration job via migrate-inline-design-to-app.",
|
|
74
|
+
schema: z.object({
|
|
75
|
+
designId: z
|
|
76
|
+
.string()
|
|
77
|
+
.describe("Design project ID to check Builder connection for"),
|
|
78
|
+
}),
|
|
79
|
+
readOnly: true,
|
|
80
|
+
http: { method: "GET" },
|
|
81
|
+
run: async ({ designId }) => {
|
|
82
|
+
// Require at least viewer access so unauthenticated callers cannot
|
|
83
|
+
// probe Builder connection state.
|
|
84
|
+
const access = await resolveAccess("design", designId);
|
|
85
|
+
if (!access) {
|
|
86
|
+
throw new Error("Design not found");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const status = await resolveBuilderStatus();
|
|
90
|
+
|
|
91
|
+
// Resolve the connect URL from the current request origin so it points
|
|
92
|
+
// to this exact deployment (same origin as the signed connect token the
|
|
93
|
+
// server mints for cli-auth flows).
|
|
94
|
+
const origin = getRequestContext()?.requestOrigin ?? "";
|
|
95
|
+
const connectUrl = buildConnectUrl(origin);
|
|
96
|
+
|
|
97
|
+
const appHost = resolveBuilderAppHost();
|
|
98
|
+
|
|
99
|
+
// Surface the env-level branch project id for informational use only.
|
|
100
|
+
// (Credential values are never included.)
|
|
101
|
+
const branchProjectId =
|
|
102
|
+
status.branchProjectId || getBuilderBranchProjectId() || undefined;
|
|
103
|
+
|
|
104
|
+
if (!status.connected) {
|
|
105
|
+
return {
|
|
106
|
+
connected: false,
|
|
107
|
+
builderEnabled: false,
|
|
108
|
+
connectUrl,
|
|
109
|
+
appHost,
|
|
110
|
+
branchProjectId,
|
|
111
|
+
cta: {
|
|
112
|
+
kind: "connect-builder" as const,
|
|
113
|
+
label: "Make this a real app",
|
|
114
|
+
description:
|
|
115
|
+
"Connect Builder.io to unlock React components, live props, " +
|
|
116
|
+
"data states, branches, and one-click deploys.",
|
|
117
|
+
primaryAction: "Connect Builder.io",
|
|
118
|
+
connectUrl,
|
|
119
|
+
},
|
|
120
|
+
message:
|
|
121
|
+
"Builder is not connected. Open connectUrl to start the OAuth flow.",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!status.builderEnabled) {
|
|
126
|
+
return {
|
|
127
|
+
connected: true,
|
|
128
|
+
builderEnabled: false,
|
|
129
|
+
connectUrl,
|
|
130
|
+
appHost,
|
|
131
|
+
branchProjectId,
|
|
132
|
+
cta: {
|
|
133
|
+
kind: "configure-project" as const,
|
|
134
|
+
label: "Configure Builder project",
|
|
135
|
+
description:
|
|
136
|
+
"Builder credentials are present but no branch project is " +
|
|
137
|
+
"configured. Set DISPATCH_BUILDER_PROJECT_ID, " +
|
|
138
|
+
"BUILDER_BRANCH_PROJECT_ID, or BUILDER_PROJECT_ID to enable " +
|
|
139
|
+
"the cloud agent.",
|
|
140
|
+
primaryAction: "Open Builder settings",
|
|
141
|
+
connectUrl: `${appHost}/account-settings`,
|
|
142
|
+
},
|
|
143
|
+
message:
|
|
144
|
+
"Builder credentials are configured but no branch project ID is set. " +
|
|
145
|
+
"Set DISPATCH_BUILDER_PROJECT_ID to enable cloud agent migration.",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Fully configured — ready for migration.
|
|
150
|
+
return {
|
|
151
|
+
connected: true,
|
|
152
|
+
builderEnabled: true,
|
|
153
|
+
connectUrl,
|
|
154
|
+
appHost,
|
|
155
|
+
branchProjectId,
|
|
156
|
+
cta: null,
|
|
157
|
+
message:
|
|
158
|
+
"Builder is connected and cloud agents are available. " +
|
|
159
|
+
"Call migrate-inline-design-to-app to generate a real React app branch.",
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
});
|