@agent-native/core 0.80.11 → 0.81.1
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 +53 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/AgentPanel.tsx +33 -1
- package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
- package/corpus/core/src/client/chat-view-transition.ts +33 -2
- package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
- package/corpus/core/src/db/client.ts +29 -2
- package/corpus/core/src/provider-api/index.ts +29 -0
- package/corpus/core/src/server/core-routes-plugin.ts +58 -20
- package/corpus/core/src/styles/agent-native.css +54 -0
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
- package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
- package/corpus/templates/analytics/app/global.css +8 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
- package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
- package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
- package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/app/global.css +15 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +90 -16
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
- package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
- package/corpus/templates/design/AGENTS.md +27 -0
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
- package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
- package/corpus/templates/design/actions/capture-design-state.ts +27 -4
- package/corpus/templates/design/actions/connect-localhost.ts +59 -22
- package/corpus/templates/design/actions/export-zip.ts +23 -8
- package/corpus/templates/design/actions/generate-design.ts +53 -1
- package/corpus/templates/design/actions/get-component-details.ts +5 -19
- package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
- package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
- package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
- package/corpus/templates/design/actions/navigate.ts +18 -3
- package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
- package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
- package/corpus/templates/design/actions/provider-api-request.ts +3 -3
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
- package/corpus/templates/design/actions/run-design-audit.ts +53 -4
- package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
- package/corpus/templates/design/actions/view-screen.ts +1 -1
- package/corpus/templates/design/actions/write-local-file.ts +213 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
- package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
- package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
- package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
- package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
- package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
- package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
- package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/global.css +7 -0
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/design/app/i18n-data.ts +358 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
- package/corpus/templates/design/app/pages/Index.tsx +5 -1
- package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +168 -65
- package/corpus/templates/design/package.json +3 -1
- package/corpus/templates/design/server/db/schema.ts +23 -0
- package/corpus/templates/design/server/handlers/uploads.ts +22 -1
- package/corpus/templates/design/server/lib/provider-api.ts +7 -4
- package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
- package/corpus/templates/design/server/plugins/db.ts +39 -0
- package/corpus/templates/design/server/register-secrets.ts +38 -0
- package/corpus/templates/design/shared/board-file.ts +228 -0
- package/corpus/templates/design/shared/board-objects.ts +288 -0
- package/corpus/templates/design/shared/code-layer.ts +52 -0
- package/corpus/templates/design/shared/motion-compiler.ts +58 -7
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/tsconfig.json +2 -1
- package/corpus/templates/slides/AGENTS.md +9 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
- package/corpus/templates/slides/app/vite-env.d.ts +18 -0
- package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/vite.config.ts +1 -0
- package/dist/cli/design-connect.d.ts +25 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +357 -18
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +18 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +1 -0
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +5 -5
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat-view-transition.d.ts +6 -0
- package/dist/client/chat-view-transition.d.ts.map +1 -1
- package/dist/client/chat-view-transition.js +23 -2
- package/dist/client/chat-view-transition.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +3 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +11 -4
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -3
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +20 -6
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +28 -2
- package/dist/db/client.js.map +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/progress/routes.d.ts +1 -1
- package/dist/provider-api/index.d.ts +5 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -0
- package/dist/provider-api/index.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +25 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +43 -18
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +54 -0
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -108,6 +108,8 @@ function checkMissingLabels(html: string): A11yFinding[] {
|
|
|
108
108
|
let idx = 0;
|
|
109
109
|
for (const m of html.matchAll(inputPattern)) {
|
|
110
110
|
const tag = m[0];
|
|
111
|
+
const inputStart = m.index ?? 0;
|
|
112
|
+
const inputEnd = inputStart + tag.length;
|
|
111
113
|
const typeMatch = tag.match(/\btype\s*=\s*(?:"([^"]*?)"|'([^']*?)')/i);
|
|
112
114
|
const type = (typeMatch?.[1] ?? typeMatch?.[2] ?? "text").toLowerCase();
|
|
113
115
|
// Hidden and submit/button/image inputs don't need visible labels
|
|
@@ -123,8 +125,9 @@ function checkMissingLabels(html: string): A11yFinding[] {
|
|
|
123
125
|
const hasExplicitLabel = inputId
|
|
124
126
|
? new RegExp(`for\\s*=\\s*(?:"${inputId}"|'${inputId}')`, "i").test(html)
|
|
125
127
|
: false;
|
|
128
|
+
const hasImplicitLabel = isWrappedByLabel(html, inputStart, inputEnd);
|
|
126
129
|
|
|
127
|
-
if (!hasAriaLabel && !hasExplicitLabel) {
|
|
130
|
+
if (!hasAriaLabel && !hasExplicitLabel && !hasImplicitLabel) {
|
|
128
131
|
findings.push({
|
|
129
132
|
id: `missing-label:input-${idx}`,
|
|
130
133
|
severity: "error" as A11ySeverity,
|
|
@@ -143,8 +146,53 @@ function checkMissingLabels(html: string): A11yFinding[] {
|
|
|
143
146
|
return findings;
|
|
144
147
|
}
|
|
145
148
|
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
function isWrappedByLabel(
|
|
150
|
+
html: string,
|
|
151
|
+
inputStart: number,
|
|
152
|
+
inputEnd: number,
|
|
153
|
+
): boolean {
|
|
154
|
+
const labelOpen = html.lastIndexOf("<label", inputStart);
|
|
155
|
+
if (labelOpen === -1) return false;
|
|
156
|
+
const labelCloseBeforeInput = html.lastIndexOf("</label", inputStart);
|
|
157
|
+
if (labelCloseBeforeInput > labelOpen) return false;
|
|
158
|
+
const labelCloseAfterInput = html.indexOf("</label", inputEnd);
|
|
159
|
+
return labelCloseAfterInput !== -1;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Whether an interactive element already declares a minimum size that meets the
|
|
164
|
+
* 44px tap-target floor. This recognises exactly what the inline auto-fix adds
|
|
165
|
+
* (`min-h-[44px] min-w-[44px]`) plus equivalents — arbitrary `min-h`/`min-w`
|
|
166
|
+
* values in px/rem/em ≥ 44px, the Tailwind spacing scale (`min-h-11` = 44px on a
|
|
167
|
+
* 4px step), and full-bleed minimums (`min-h-full` / `min-h-screen`). Without
|
|
168
|
+
* this, a fixed element keeps its original tiny `h-4` class and the audit would
|
|
169
|
+
* re-flag it forever, so the audit↔fix loop would never converge.
|
|
170
|
+
*/
|
|
171
|
+
function hasAdequateMinTapSize(tag: string): boolean {
|
|
172
|
+
// Arbitrary values: min-h-[44px], min-w-[2.75rem], etc.
|
|
173
|
+
const arbitraryPattern = /\bmin-(?:h|w)-\[([\d.]+)(px|rem|em)\]/gi;
|
|
174
|
+
let m: RegExpExecArray | null;
|
|
175
|
+
while ((m = arbitraryPattern.exec(tag)) !== null) {
|
|
176
|
+
const value = Number.parseFloat(m[1] ?? "");
|
|
177
|
+
if (!Number.isFinite(value)) continue;
|
|
178
|
+
const px = m[2]?.toLowerCase() === "px" ? value : value * 16;
|
|
179
|
+
if (px >= 44) return true;
|
|
180
|
+
}
|
|
181
|
+
// Tailwind spacing scale: min-h-11 / min-w-11 = 2.75rem = 44px (4px per step).
|
|
182
|
+
const scalePattern = /\bmin-(?:h|w)-(\d+)\b/gi;
|
|
183
|
+
while ((m = scalePattern.exec(tag)) !== null) {
|
|
184
|
+
if (Number.parseInt(m[1] ?? "", 10) * 4 >= 44) return true;
|
|
185
|
+
}
|
|
186
|
+
// Full-bleed minimums always clear the tap floor.
|
|
187
|
+
return /\bmin-(?:h|w)-(?:full|screen)\b/.test(tag);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check interactive elements that are likely too small for touch targets
|
|
192
|
+
* (< ~44px heuristic via Tailwind class). Exported for unit tests that assert
|
|
193
|
+
* the audit↔fix loop converges (a fixed element must stop being flagged).
|
|
194
|
+
*/
|
|
195
|
+
export function checkTapTargets(html: string): A11yFinding[] {
|
|
148
196
|
const findings: A11yFinding[] = [];
|
|
149
197
|
// Heuristic: buttons/links with explicit tiny size classes (h-4, h-5, w-4, w-5, size-4, size-5)
|
|
150
198
|
// and no explicit larger override or sr-only are flagged.
|
|
@@ -158,7 +206,8 @@ function checkTapTargets(html: string): A11yFinding[] {
|
|
|
158
206
|
if (
|
|
159
207
|
tinyPattern.test(tag) &&
|
|
160
208
|
!largePattern.test(tag) &&
|
|
161
|
-
!srOnlyPattern.test(tag)
|
|
209
|
+
!srOnlyPattern.test(tag) &&
|
|
210
|
+
!hasAdequateMinTapSize(tag)
|
|
162
211
|
) {
|
|
163
212
|
findings.push({
|
|
164
213
|
id: `tap-target:interactive-${idx}`,
|
|
@@ -146,7 +146,7 @@ const CAPABILITY_ROUTES: Record<string, CapabilityRoute> = {
|
|
|
146
146
|
"design.motion-presets:apply": {
|
|
147
147
|
action: "apply-motion-edit",
|
|
148
148
|
paramHint:
|
|
149
|
-
"Call apply-motion-edit with { designId, fileId?, timeline: { tracks, durationMs } } for an atomic
|
|
149
|
+
"Call apply-motion-edit with { designId, fileId?, timeline: { tracks, durationMs } } for an atomic managed CSS timeline save.",
|
|
150
150
|
readOnly: false,
|
|
151
151
|
},
|
|
152
152
|
"design.motion-presets:write-source": {
|
|
@@ -95,7 +95,7 @@ function resolveActiveScreen(
|
|
|
95
95
|
|
|
96
96
|
export default defineAction({
|
|
97
97
|
description:
|
|
98
|
-
"See what the user is currently looking at on screen. Returns the current navigation state including which design is open, which view they are on (list, editor, design-systems, present, templates, settings), active/focused design screen, selected element, active inspector tab (design
|
|
98
|
+
"See what the user is currently looking at on screen. Returns the current navigation state including which design is open, which view they are on (list, editor, design-systems, present, templates, settings), active/focused design screen, selected element, active inspector tab (design or tweaks), active left rail panel (file, agent, assets, tools, or tokens), overview canvas state, plus any pending question overlay. Always call this first before taking any action.",
|
|
99
99
|
schema: z.object({}),
|
|
100
100
|
http: false,
|
|
101
101
|
run: async () => {
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* write-local-file — write or patch a local file through the design bridge.
|
|
3
|
+
*
|
|
4
|
+
* Security gates (in order):
|
|
5
|
+
* 1. assertAccess: the caller must have editor access to the design.
|
|
6
|
+
* 2. File extension: only .html, .htm, and .css files are allowed.
|
|
7
|
+
* 3. verifyWriteGrant: a valid (non-expired) user-approved write-consent grant
|
|
8
|
+
* must exist. The agent CANNOT bypass this check.
|
|
9
|
+
* 4. Path confinement: assertPathInside ensures the target stays inside
|
|
10
|
+
* rootPath (pre-bridge check; bridge also validates with realpath).
|
|
11
|
+
* 5. Bridge token: the X-Bridge-Token header is set to the per-grant token
|
|
12
|
+
* minted at consent time so the bridge can reject unauthorized calls.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
|
|
17
|
+
import { defineAction } from "@agent-native/core";
|
|
18
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
19
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
20
|
+
import { and, eq } from "drizzle-orm";
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
|
|
23
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
24
|
+
import { verifyWriteGrant } from "../server/lib/verify-write-grant.js";
|
|
25
|
+
|
|
26
|
+
/** File extensions the agent is permitted to write via the bridge. */
|
|
27
|
+
const ALLOWED_EXTENSIONS = new Set([".html", ".htm", ".css"]);
|
|
28
|
+
|
|
29
|
+
function assertAllowedExtension(relPath: string): void {
|
|
30
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
31
|
+
if (!ALLOWED_EXTENSIONS.has(ext)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`File "${relPath}" has extension "${ext}" which is not allowed. ` +
|
|
34
|
+
"Only .html, .htm, and .css files may be written through the bridge.",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isLoopbackHostname(hostname: string): boolean {
|
|
40
|
+
const normalized = hostname.toLowerCase();
|
|
41
|
+
if (
|
|
42
|
+
normalized === "localhost" ||
|
|
43
|
+
normalized === "::1" ||
|
|
44
|
+
normalized === "[::1]"
|
|
45
|
+
) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const parts = normalized.split(".");
|
|
49
|
+
return (
|
|
50
|
+
parts.length === 4 &&
|
|
51
|
+
parts[0] === "127" &&
|
|
52
|
+
parts.every((part) => /^\d+$/.test(part) && Number(part) <= 255)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeBridgeUrl(value: string): string {
|
|
57
|
+
const parsed = new URL(value.trim());
|
|
58
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
59
|
+
throw new Error("bridgeUrl must be an http(s) URL");
|
|
60
|
+
}
|
|
61
|
+
if (parsed.username || parsed.password) {
|
|
62
|
+
throw new Error("bridgeUrl must not include credentials");
|
|
63
|
+
}
|
|
64
|
+
if (parsed.pathname !== "/" && parsed.pathname !== "") {
|
|
65
|
+
throw new Error("bridgeUrl must not include a path");
|
|
66
|
+
}
|
|
67
|
+
if (!isLoopbackHostname(parsed.hostname)) {
|
|
68
|
+
throw new Error("bridgeUrl must use localhost or a loopback IP address");
|
|
69
|
+
}
|
|
70
|
+
parsed.search = "";
|
|
71
|
+
parsed.hash = "";
|
|
72
|
+
parsed.pathname = "";
|
|
73
|
+
return parsed.toString().replace(/\/$/, "");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default defineAction({
|
|
77
|
+
description:
|
|
78
|
+
"Write or patch a local file (HTML or CSS only) via the localhost design bridge. " +
|
|
79
|
+
"The user MUST have already granted write consent via grant-localhost-write-consent; " +
|
|
80
|
+
"this action will reject the request if no valid grant exists. " +
|
|
81
|
+
"Pass content for a full file write, or {search, replace} for a targeted patch. " +
|
|
82
|
+
"Requires editor access on the design.",
|
|
83
|
+
schema: z.object({
|
|
84
|
+
designId: z.string().describe("Design ID."),
|
|
85
|
+
connectionId: z
|
|
86
|
+
.string()
|
|
87
|
+
.describe("Localhost connection ID (must have an active write grant)."),
|
|
88
|
+
relPath: z
|
|
89
|
+
.string()
|
|
90
|
+
.describe(
|
|
91
|
+
"Path to the file relative to the connection rootPath. " +
|
|
92
|
+
"Only .html, .htm, and .css files are accepted.",
|
|
93
|
+
),
|
|
94
|
+
content: z
|
|
95
|
+
.string()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe(
|
|
98
|
+
"Full replacement file content. Use for new files or complete rewrites.",
|
|
99
|
+
),
|
|
100
|
+
patch: z
|
|
101
|
+
.object({
|
|
102
|
+
search: z
|
|
103
|
+
.string()
|
|
104
|
+
.describe("Exact text to search for (must appear exactly once)."),
|
|
105
|
+
replace: z.string().describe("Replacement text."),
|
|
106
|
+
})
|
|
107
|
+
.optional()
|
|
108
|
+
.describe(
|
|
109
|
+
"Search-and-replace patch. Use for targeted edits. " +
|
|
110
|
+
"Mutually exclusive with content.",
|
|
111
|
+
),
|
|
112
|
+
}),
|
|
113
|
+
run: async ({ designId, connectionId, relPath, content, patch }) => {
|
|
114
|
+
// --- Gate 1: access ---
|
|
115
|
+
await assertAccess("design", designId, "editor");
|
|
116
|
+
|
|
117
|
+
const ownerEmail = getRequestUserEmail();
|
|
118
|
+
if (!ownerEmail) throw new Error("no authenticated user");
|
|
119
|
+
|
|
120
|
+
// --- Gate 2: extension whitelist ---
|
|
121
|
+
assertAllowedExtension(relPath);
|
|
122
|
+
|
|
123
|
+
// --- Gate 3: valid write-consent grant ---
|
|
124
|
+
const grant = await verifyWriteGrant({
|
|
125
|
+
designId,
|
|
126
|
+
connectionId,
|
|
127
|
+
ownerEmail,
|
|
128
|
+
targetPath: relPath,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// --- Gate 4: exactly one of content/patch must be provided ---
|
|
132
|
+
if (content === undefined && patch === undefined) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
"Either content (full file write) or patch (search/replace) must be provided.",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (content !== undefined && patch !== undefined) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
"content and patch are mutually exclusive. Provide one or the other.",
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// --- Resolve bridge URL ---
|
|
144
|
+
const db = getDb();
|
|
145
|
+
const [connection] = await db
|
|
146
|
+
.select({ bridgeUrl: schema.designLocalhostConnections.bridgeUrl })
|
|
147
|
+
.from(schema.designLocalhostConnections)
|
|
148
|
+
.where(
|
|
149
|
+
and(
|
|
150
|
+
eq(schema.designLocalhostConnections.id, connectionId),
|
|
151
|
+
eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
.limit(1);
|
|
155
|
+
|
|
156
|
+
if (!connection?.bridgeUrl) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`No bridge URL found for connection "${connectionId}". ` +
|
|
159
|
+
"Ensure the design bridge is running (npx @agent-native/core@latest design connect).",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const bridgeUrl = normalizeBridgeUrl(connection.bridgeUrl);
|
|
164
|
+
const headers: Record<string, string> = {
|
|
165
|
+
"Content-Type": "application/json",
|
|
166
|
+
"X-Bridge-Token": grant.bridgeToken,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if (content !== undefined) {
|
|
170
|
+
// Full file write
|
|
171
|
+
const res = await fetch(`${bridgeUrl}/write-file`, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers,
|
|
174
|
+
body: JSON.stringify({ relPath, content }),
|
|
175
|
+
});
|
|
176
|
+
if (!res.ok) {
|
|
177
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
178
|
+
throw new Error(`Bridge write-file failed (${res.status}): ${errText}`);
|
|
179
|
+
}
|
|
180
|
+
return { designId, relPath, operation: "write" as const, written: true };
|
|
181
|
+
} else {
|
|
182
|
+
// Search-and-replace patch — first read, then apply-edit
|
|
183
|
+
const readRes = await fetch(`${bridgeUrl}/read-file`, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers,
|
|
186
|
+
body: JSON.stringify({ relPath }),
|
|
187
|
+
});
|
|
188
|
+
if (!readRes.ok) {
|
|
189
|
+
const errText = await readRes.text().catch(() => readRes.statusText);
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Bridge read-file failed (${readRes.status}): ${errText}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const applyRes = await fetch(`${bridgeUrl}/apply-edit`, {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers,
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
relPath,
|
|
200
|
+
search: patch!.search,
|
|
201
|
+
replace: patch!.replace,
|
|
202
|
+
}),
|
|
203
|
+
});
|
|
204
|
+
if (!applyRes.ok) {
|
|
205
|
+
const errText = await applyRes.text().catch(() => applyRes.statusText);
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Bridge apply-edit failed (${applyRes.status}): ${errText}`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return { designId, relPath, operation: "patch" as const, written: true };
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
});
|