@agent-native/core 0.92.11 → 0.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +45 -0
- package/corpus/core/docs/content/automation-connectors.mdx +80 -0
- package/corpus/core/docs/content/extensions.mdx +8 -0
- package/corpus/core/docs/content/locales/ar-SA/extensions.mdx +8 -0
- package/corpus/core/docs/content/locales/ar-SA/messaging.mdx +84 -15
- package/corpus/core/docs/content/locales/ar-SA/template-clips.mdx +1 -0
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +1 -0
- package/corpus/core/docs/content/locales/de-DE/extensions.mdx +10 -0
- package/corpus/core/docs/content/locales/de-DE/messaging.mdx +92 -15
- package/corpus/core/docs/content/locales/de-DE/template-clips.mdx +1 -0
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +1 -0
- package/corpus/core/docs/content/locales/es-ES/extensions.mdx +10 -0
- package/corpus/core/docs/content/locales/es-ES/messaging.mdx +91 -15
- package/corpus/core/docs/content/locales/es-ES/template-clips.mdx +1 -0
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +1 -0
- package/corpus/core/docs/content/locales/fr-FR/extensions.mdx +10 -0
- package/corpus/core/docs/content/locales/fr-FR/messaging.mdx +91 -15
- package/corpus/core/docs/content/locales/fr-FR/template-clips.mdx +1 -0
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +1 -0
- package/corpus/core/docs/content/locales/hi-IN/extensions.mdx +8 -0
- package/corpus/core/docs/content/locales/hi-IN/messaging.mdx +81 -15
- package/corpus/core/docs/content/locales/hi-IN/template-clips.mdx +1 -0
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +1 -0
- package/corpus/core/docs/content/locales/ja-JP/extensions.mdx +9 -0
- package/corpus/core/docs/content/locales/ja-JP/messaging.mdx +81 -15
- package/corpus/core/docs/content/locales/ja-JP/template-clips.mdx +1 -0
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +1 -0
- package/corpus/core/docs/content/locales/ko-KR/extensions.mdx +8 -0
- package/corpus/core/docs/content/locales/ko-KR/messaging.mdx +80 -15
- package/corpus/core/docs/content/locales/ko-KR/template-clips.mdx +1 -0
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +1 -0
- package/corpus/core/docs/content/locales/pt-BR/extensions.mdx +9 -0
- package/corpus/core/docs/content/locales/pt-BR/messaging.mdx +88 -15
- package/corpus/core/docs/content/locales/pt-BR/template-clips.mdx +1 -0
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +1 -0
- package/corpus/core/docs/content/locales/zh-CN/extensions.mdx +6 -0
- package/corpus/core/docs/content/locales/zh-CN/messaging.mdx +78 -15
- package/corpus/core/docs/content/locales/zh-CN/template-clips.mdx +1 -0
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +1 -0
- package/corpus/core/docs/content/locales/zh-TW/extensions.mdx +6 -0
- package/corpus/core/docs/content/locales/zh-TW/messaging.mdx +78 -15
- package/corpus/core/docs/content/locales/zh-TW/template-clips.mdx +1 -0
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +1 -0
- package/corpus/core/docs/content/messaging.mdx +227 -45
- package/corpus/core/docs/content/template-clips.mdx +3 -0
- package/corpus/core/docs/content/template-design.mdx +4 -0
- package/corpus/core/package.json +5 -1
- package/corpus/core/src/a2a/artifact-response.ts +131 -2
- package/corpus/core/src/a2a/client.ts +10 -0
- package/corpus/core/src/a2a/handlers.ts +40 -76
- package/corpus/core/src/a2a/types.ts +2 -0
- package/corpus/core/src/agent/durable-background.ts +24 -0
- package/corpus/core/src/agent/production-agent.ts +48 -5
- package/corpus/core/src/agent/run-manager.ts +171 -20
- package/corpus/core/src/agent/run-store.ts +297 -42
- package/corpus/core/src/agent/types.ts +23 -0
- package/corpus/core/src/audit/record.ts +31 -0
- package/corpus/core/src/audit/store.ts +69 -3
- package/corpus/core/src/audit/types.ts +13 -0
- package/corpus/core/src/automation/index.ts +802 -0
- package/corpus/core/src/cli/create.ts +12 -5
- package/corpus/core/src/cli/design-connect.ts +596 -36
- package/corpus/core/src/client/AgentPanel.tsx +51 -6
- package/corpus/core/src/client/AssistantChat.tsx +274 -147
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +69 -20
- package/corpus/core/src/client/agent-chat-adapter.ts +110 -17
- package/corpus/core/src/client/automation.ts +22 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +67 -50
- package/corpus/core/src/client/composer/TiptapComposer.tsx +8 -0
- package/corpus/core/src/client/index.ts +30 -0
- package/corpus/core/src/client/integrations/api.ts +295 -0
- package/corpus/core/src/client/integrations/index.ts +26 -0
- package/corpus/core/src/client/transcription/use-live-transcription.ts +68 -5
- package/corpus/core/src/client/use-run-stuck-detection.ts +41 -3
- package/corpus/core/src/deploy/build.ts +59 -3
- package/corpus/core/src/deploy/workspace-deploy.ts +37 -2
- package/corpus/core/src/file-upload/types.ts +2 -0
- package/corpus/core/src/index.ts +37 -0
- package/corpus/core/src/integrations/a2a-continuations-store.ts +6 -4
- package/corpus/core/src/integrations/adapters/discord.ts +362 -0
- package/corpus/core/src/integrations/adapters/index.ts +29 -0
- package/corpus/core/src/integrations/adapters/microsoft-teams.ts +411 -0
- package/corpus/core/src/integrations/adapters/slack.ts +861 -27
- package/corpus/core/src/integrations/adapters/telegram.ts +54 -15
- package/corpus/core/src/integrations/adapters/whatsapp.ts +41 -17
- package/corpus/core/src/integrations/catalog.ts +571 -0
- package/corpus/core/src/integrations/controls-store.ts +187 -0
- package/corpus/core/src/integrations/index.ts +100 -0
- package/corpus/core/src/integrations/installations-store.ts +677 -0
- package/corpus/core/src/integrations/integration-memory.ts +187 -0
- package/corpus/core/src/integrations/pending-tasks-retry-job.ts +6 -6
- package/corpus/core/src/integrations/pending-tasks-store.ts +83 -5
- package/corpus/core/src/integrations/plugin.ts +685 -47
- package/corpus/core/src/integrations/scope-store.ts +498 -0
- package/corpus/core/src/integrations/slack-manifest.ts +78 -0
- package/corpus/core/src/integrations/slack-oauth.ts +343 -0
- package/corpus/core/src/integrations/types.ts +179 -0
- package/corpus/core/src/integrations/usage-budget-store.ts +906 -0
- package/corpus/core/src/integrations/webhook-handler.ts +562 -51
- package/corpus/core/src/jobs/scheduler.ts +100 -2
- package/corpus/core/src/jobs/tools.ts +45 -9
- package/corpus/core/src/provider-api/index.ts +72 -1
- package/corpus/core/src/resources/handlers.ts +88 -14
- package/corpus/core/src/resources/script-helpers.ts +36 -7
- package/corpus/core/src/resources/store.ts +59 -52
- package/corpus/core/src/scripts/call-agent.ts +107 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +294 -119
- package/corpus/core/src/server/auth.ts +10 -14
- package/corpus/core/src/server/builder-design-systems.ts +16 -1
- package/corpus/core/src/server/edge.ts +18 -0
- package/corpus/core/src/server/index.ts +26 -0
- package/corpus/core/src/server/request-context.ts +18 -0
- package/corpus/core/src/server/social-og-image.ts +3 -2
- package/corpus/core/src/styles/agent-native.css +62 -44
- package/corpus/core/src/templates/default/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/core/src/templates/default/AGENTS.md +7 -3
- package/corpus/core/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/core/src/templates/workspace-core/.agents/skills/extension-points/SKILL.md +6 -3
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +25 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +15 -0
- package/corpus/core/src/templates/workspace-core/AGENTS.md +3 -2
- package/corpus/core/src/usage/store.ts +37 -2
- package/corpus/templates/analytics/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/analytics/.agents/skills/provider-api/SKILL.md +23 -0
- package/corpus/templates/analytics/.builder/skills/provider-api/SKILL.md +15 -0
- package/corpus/templates/analytics/actions/save-monitor.ts +25 -2
- package/corpus/templates/analytics/app/lib/data-sources.ts +28 -0
- package/corpus/templates/analytics/changelog/2026-07-10-agent-created-uptime-monitors-now-return-a-direct-link-to-th.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-10-dashboard-email-reports-still-arrive-when-screenshot-capture-temporarily-fails.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +3 -7
- package/corpus/templates/analytics/server/lib/credential-keys.ts +12 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +5 -1
- package/corpus/templates/analytics/server/lib/provider-credentials.ts +2 -0
- package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +16 -0
- package/corpus/templates/assets/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/brain/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/calendar/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/calendar/app/components/calendar/CommandPalette.tsx +98 -108
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +97 -90
- package/corpus/templates/calendar/changelog/2026-07-10-event-guest-details-and-option-menus-now-use-valid-accessibl.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-10-natural-language-event-phrases-stay-available-as-quick-creat.md +6 -0
- package/corpus/templates/chat/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/chat/changelog/2026-07-10-chat-now-makes-ai-connection-setup-clear-without-shifting-th.md +6 -0
- package/corpus/templates/clips/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +17 -1
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +8 -0
- package/corpus/templates/clips/AGENTS.md +8 -3
- package/corpus/templates/clips/actions/finalize-recording.ts +63 -23
- package/corpus/templates/clips/actions/get-recording-player-data.ts +6 -2
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +72 -9
- package/corpus/templates/clips/actions/list-recordings.ts +19 -3
- package/corpus/templates/clips/actions/navigate.ts +2 -0
- package/corpus/templates/clips/actions/reprocess-recording.ts +20 -6
- package/corpus/templates/clips/actions/request-transcript.ts +16 -31
- package/corpus/templates/clips/actions/view-screen.ts +57 -5
- package/corpus/templates/clips/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/clips/app/components/library/empty-state.tsx +2 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +69 -42
- package/corpus/templates/clips/app/components/library/library-layout.tsx +15 -1
- package/corpus/templates/clips/app/components/library/recording-card.tsx +81 -68
- package/corpus/templates/clips/app/components/library/search-bar.tsx +4 -3
- package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +9 -3
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +1 -1
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +27 -5
- package/corpus/templates/clips/app/hooks/use-library.ts +1 -1
- package/corpus/templates/clips/app/hooks/use-navigation-state.ts +10 -2
- package/corpus/templates/clips/app/hooks/use-player-shortcuts.ts +1 -0
- package/corpus/templates/clips/app/i18n/ar-SA.ts +7 -1
- package/corpus/templates/clips/app/i18n/de-DE.ts +7 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +7 -1
- package/corpus/templates/clips/app/i18n/es-ES.ts +7 -1
- package/corpus/templates/clips/app/i18n/fr-FR.ts +7 -1
- package/corpus/templates/clips/app/i18n/hi-IN.ts +7 -1
- package/corpus/templates/clips/app/i18n/ja-JP.ts +7 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +7 -1
- package/corpus/templates/clips/app/i18n/pt-BR.ts +7 -1
- package/corpus/templates/clips/app/i18n/zh-CN.ts +8 -1
- package/corpus/templates/clips/app/i18n/zh-TW.ts +8 -1
- package/corpus/templates/clips/app/lib/recording-visibility.ts +70 -0
- package/corpus/templates/clips/app/routes/_app.shared.tsx +20 -0
- package/corpus/templates/clips/app/routes/download.tsx +24 -8
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +115 -2
- package/corpus/templates/clips/app/routes/record.tsx +62 -0
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +25 -16
- package/corpus/templates/clips/changelog/2026-07-10-clips-desktop-is-now-available-for-linux-with-appimage-debia.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-clips-downloads-updates-sooner-in-the-background-and-offers-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-clips-shared-with-you-now-appear-in-a-searchable-shared-with.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-clips-with-missing-mobile-video-frames-can-be-repaired-witho.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-fixed-copying-agent-responses-and-transcript-text-from-recor.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-mobile-camera-recordings-pause-while-backgrounded.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-mp4-downloads-now-show-progress-immediately-after-the-option.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-recording-pages-now-open-as-soon-as-you-stop-with-a-share-li.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-recordings-now-seek-smoothly-downloads-use-the-correct-file-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-s3-compatible-storage-now-saves-hosted-recordings-without-re.md +6 -0
- package/corpus/templates/clips/desktop/README.md +22 -6
- package/corpus/templates/clips/desktop/package.json +1 -1
- package/corpus/templates/clips/desktop/src/app.tsx +32 -19
- package/corpus/templates/clips/desktop/src/components/ReadinessPanel.tsx +15 -9
- package/corpus/templates/clips/desktop/src/lib/finalization-guard.ts +22 -0
- package/corpus/templates/clips/desktop/src/lib/permissions.ts +3 -1
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +97 -120
- package/corpus/templates/clips/desktop/src/lib/updater.ts +26 -4
- package/corpus/templates/clips/desktop/src/overlays/finalizing.tsx +4 -8
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +2 -2
- package/corpus/templates/clips/desktop/src-tauri/src/permission_status.rs +11 -7
- package/corpus/templates/clips/desktop/src-tauri/tauri.linux.conf.json +11 -0
- package/corpus/templates/clips/netlify.toml +3 -0
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +106 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +9 -1
- package/corpus/templates/clips/server/lib/resumable-upload-provider.ts +52 -0
- package/corpus/templates/clips/server/lib/s3-upload-provider.ts +433 -85
- package/corpus/templates/clips/server/lib/video-remux.ts +114 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +109 -0
- package/corpus/templates/clips/server/routes/api/clips-updater.json.get.ts +22 -11
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +4 -2
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +47 -5
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +10 -2
- package/corpus/templates/clips/shared/share-loader-response.ts +12 -0
- package/corpus/templates/clips/shared/transcript-status.ts +40 -0
- package/corpus/templates/content/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/content/.agents/skills/content/SKILL.md +57 -5
- package/corpus/templates/content/AGENTS.md +65 -55
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +92 -23
- package/corpus/templates/content/actions/_database-source-utils.ts +207 -41
- package/corpus/templates/content/actions/_property-utils.ts +30 -2
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +316 -121
- package/corpus/templates/content/actions/attach-content-database-source.ts +41 -17
- package/corpus/templates/content/actions/change-content-database-source-role.ts +46 -8
- package/corpus/templates/content/actions/submit-content-database-form.ts +497 -0
- package/corpus/templates/content/actions/suggest-source-join-key.ts +8 -1
- package/corpus/templates/content/actions/update-content-database-view.ts +8 -1
- package/corpus/templates/content/actions/view-screen.ts +4 -1
- package/corpus/templates/content/agent-native.app-skill.json +1 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +250 -64
- package/corpus/templates/content/app/components/editor/database/FormView.tsx +460 -0
- package/corpus/templates/content/app/components/editor/database/navigation-state.ts +3 -0
- package/corpus/templates/content/app/components/editor/database/settings.tsx +30 -37
- package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
- package/corpus/templates/content/app/components/editor/database/view-config.ts +29 -1
- package/corpus/templates/content/app/components/ui/textarea.tsx +1 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +22 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/content/app/i18n-data.ts +225 -0
- package/corpus/templates/content/changelog/2026-07-09-builder-source-refreshes-now-keep-mapped-fields-such-as-topi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-backed-properties-now-load-current-source-values-as-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-database-refreshes-now-stay-explicit-and-accurate.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-database-form-views-now-collect-ordered-required-answers-and.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-database-tables-now-use-a-quieter-more-compact-layout-with-c.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-slack-intake-routes-to-content-database-forms.md +6 -0
- package/corpus/templates/content/parity/matrix.md +1 -0
- package/corpus/templates/content/parity/matrix.ts +18 -0
- package/corpus/templates/content/shared/api.ts +28 -1
- package/corpus/templates/content/shared/database-form.ts +53 -0
- package/corpus/templates/design/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +26 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +85 -12
- package/corpus/templates/design/AGENTS.md +72 -18
- package/corpus/templates/design/DESIGN.md +1 -1
- package/corpus/templates/design/DEVELOPING.md +1 -1
- package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +116 -0
- package/corpus/templates/design/actions/create-design-from-template.ts +220 -0
- package/corpus/templates/design/actions/create-design-system.ts +92 -16
- package/corpus/templates/design/actions/delete-design-template.ts +28 -0
- package/corpus/templates/design/actions/delete-file.ts +7 -0
- package/corpus/templates/design/actions/edit-design.ts +3 -0
- package/corpus/templates/design/actions/export-design-as-figma-svg.ts +137 -5
- package/corpus/templates/design/actions/export-pdf.ts +1 -1
- package/corpus/templates/design/actions/generate-design.ts +3 -0
- package/corpus/templates/design/actions/get-design-snapshot.ts +9 -0
- package/corpus/templates/design/actions/get-figma-connection-status.ts +26 -0
- package/corpus/templates/design/actions/get-figma-design-context.ts +138 -0
- package/corpus/templates/design/actions/get-figma-styles.ts +2 -2
- package/corpus/templates/design/actions/import-figma-clipboard.ts +112 -16
- package/corpus/templates/design/actions/import-figma-frame.ts +15 -4
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +4 -0
- package/corpus/templates/design/actions/list-design-templates.ts +118 -0
- package/corpus/templates/design/actions/navigate.ts +16 -2
- package/corpus/templates/design/actions/provider-api-request.ts +11 -1
- package/corpus/templates/design/actions/save-design-as-template.ts +156 -0
- package/corpus/templates/design/actions/set-active-breakpoint.ts +13 -4
- package/corpus/templates/design/actions/take-design-screenshot.ts +73 -3
- package/corpus/templates/design/actions/update-file.ts +39 -22
- package/corpus/templates/design/actions/view-screen.ts +36 -1
- package/corpus/templates/design/actions/write-local-file.ts +50 -38
- package/corpus/templates/design/app/components/design/AutoLayoutSuggestionDialog.tsx +151 -0
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +20 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +251 -22
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +3 -3
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +384 -9
- package/corpus/templates/design/app/components/design/EditPanel.tsx +105 -32
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +58 -28
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +6 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +539 -53
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +40 -0
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +1 -0
- package/corpus/templates/design/app/components/design/code-workbench/explorer/ExplorerView.tsx +1 -0
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +5 -1
- package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +2 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/localhost-provider.ts +3 -1
- package/corpus/templates/design/app/components/design/code-workbench/workspace/types.ts +2 -0
- package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +19 -0
- package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +1 -0
- package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +27 -0
- package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +137 -81
- package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +25 -5
- package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +1 -1
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +220 -3
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +13 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +22 -1
- package/corpus/templates/design/app/components/design/types.ts +4 -0
- package/corpus/templates/design/app/components/design-system/ProductionDesignSystemShowcase.tsx +208 -0
- package/corpus/templates/design/app/components/editor/FigmaLinkComposerBubble.tsx +321 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +24 -4
- package/corpus/templates/design/app/components/layout/Layout.tsx +16 -2
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +2 -0
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +72 -0
- package/corpus/templates/design/app/global.css +2 -35
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +10 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +127 -5
- package/corpus/templates/design/app/i18n-breakpoints.ts +209 -0
- package/corpus/templates/design/app/i18n-data.ts +1317 -59
- package/corpus/templates/design/app/i18n-template-feature.ts +458 -0
- package/corpus/templates/design/app/lib/design-clipboard.ts +202 -0
- package/corpus/templates/design/app/lib/design-file-upload.ts +87 -0
- package/corpus/templates/design/app/lib/design-import.ts +168 -11
- package/corpus/templates/design/app/lib/desktop-design-preview.ts +423 -0
- package/corpus/templates/design/app/lib/figma-clipboard.ts +74 -8
- package/corpus/templates/design/app/lib/figma-connection.ts +161 -0
- package/corpus/templates/design/app/lib/figma-svg-copy.ts +814 -22
- package/corpus/templates/design/app/lib/figma-url.ts +77 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +35 -0
- package/corpus/templates/design/app/pages/DesignSystems.tsx +236 -177
- package/corpus/templates/design/app/pages/Index.tsx +38 -0
- package/corpus/templates/design/app/pages/Templates.tsx +498 -0
- package/corpus/templates/design/app/pages/design-editor/auto-layout-suggestion.ts +328 -0
- package/corpus/templates/design/app/pages/design-editor/clone-idrefs.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/created-screen-navigation.ts +55 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +32 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +49 -2
- package/corpus/templates/design/app/pages/design-editor/export-capture.ts +296 -0
- package/corpus/templates/design/app/pages/design-editor/history.ts +101 -2
- package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +28 -1
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +34 -0
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +202 -6
- package/corpus/templates/design/app/pages/design-editor/pending-structure-verification.ts +92 -0
- package/corpus/templates/design/app/pages/design-editor/save-failure.ts +58 -0
- package/corpus/templates/design/app/routes/templates.tsx +4 -6
- package/corpus/templates/design/changelog/2026-07-10-app-workspaces-now-use-clean-borderless-main-surfaces-throug.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-ask-chat-what-s-in-a-figma-file-or-frame-structure-screensho.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-canvas-edits-now-reject-malformed-html-before-it-can-corrupt.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-complex-designs-now-export-as-valid-secure-svgs-and-crisp-ra.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-complex-figma-masks-arcs-rich-text-transformed-images-and-ad.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-copied-layers-paste-across-designs-and-browser-tabs.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-copy-and-download-editable-figma-svg.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-copying-design-text-now-pastes-readable-text-into-other-apps.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-deleted-screens-can-be-restored-with-undo-and-removed-again-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-design-changes-now-save-before-switching-apps-and-retry-when.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-design-stays-usable-on-phones-by-keeping-editor-panels-and-f.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-download-all-screens-as-one-multi-page-pdf-from-the-overview.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-duplicated-layers-now-keep-labels-aria-relationships-fragmen.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-fig-import-results-stay-compact.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-figma-imports-now-load-real-fonts-fix-rotated-gradient-dashe.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-figma-links-in-chat-and-the-import-tab-now-offer-secure-one-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-fixed-cmd-z-not-restoring-content-after-an-agent-driven-desi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-fixed-download-pdf-being-unreachable-from-the-export-panel-e.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-fixed-dragging-a-layer-out-of-an-auto-layout-row-column-no-l.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-fixed-figma-svg-export-text-wrapping-sizing-and-inner-layer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-fixed-hiding-or-locking-one-layer-no-longer-hides-or-blocks-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-fixed-png-screenshots-of-tall-screens-being-cropped-to-one-v.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-fixed-png-svg-exports-capturing-broken-layout-when-webfonts-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-frame-tool-now-offers-standard-ad-unit-size-presets-medium-r.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-full-view-controls-stay-within-their-frame-instead-of-coveri.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-hover-focus-focus-visible-pressed-and-disabled-states-now-pr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-layer-drops-now-land-in-the-intended-grid-row-stay-visually-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-local-code-workspaces-now-keep-unsaved-edits-across-panel-sw.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-local-visual-editing-now-keeps-authenticated-sessions-shared.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-long-running-agent-work-no-longer-shows-a-premature-stuck-wa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-managed-figma-connections-are-recognized-automatically.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-new-designs-can-open-directly-in-the-editor-without-a-pr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-new-screens-are-selected-and-revealed-immediately.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-new-text-layers-undo-as-one-safe-creation.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-preview-and-apply-inferred-auto-layout-without-changing-yo.md +5 -0
- package/corpus/templates/design/changelog/2026-07-10-print-pdf-exports-and-paper-size-presets-letter-a4-now-match.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-responsive-frame-editing.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-reusable-design-templates.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-screen-overview-frames-now-use-a-clearer-interact-action-wit.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-start-designs-from-source-linked-material-3-carbon-or-primer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-tweaks-guidance.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-uploaded-fig-files-can-now-become-editable-design-screens-wi.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +3 -3
- package/corpus/templates/design/package.json +2 -0
- package/corpus/templates/design/scripts/qa-figma-stress-import.ts +96 -0
- package/corpus/templates/design/server/db/index.ts +10 -0
- package/corpus/templates/design/server/db/schema.ts +44 -0
- package/corpus/templates/design/server/handlers/import-design-file.ts +114 -80
- package/corpus/templates/design/server/handlers/index-design-system-with-builder.ts +19 -6
- package/corpus/templates/design/server/lib/design-export.ts +240 -3
- package/corpus/templates/design/server/lib/design-template-data.ts +89 -0
- package/corpus/templates/design/server/lib/design-to-figma-svg.ts +250 -26
- package/corpus/templates/design/server/lib/fig-file-decoder.ts +839 -0
- package/corpus/templates/design/server/lib/fig-file-import.ts +287 -0
- package/corpus/templates/design/server/lib/fig-file-limits.ts +3 -0
- package/corpus/templates/design/server/lib/fig-file-to-html.ts +2568 -0
- package/corpus/templates/design/server/lib/figma-design-context.ts +359 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +478 -22
- package/corpus/templates/design/server/lib/figma-node-to-html.ts +554 -29
- package/corpus/templates/design/server/lib/local-figma-qa-upload.ts +112 -0
- package/corpus/templates/design/server/lib/verify-write-grant.ts +16 -2
- package/corpus/templates/design/server/lib/visible-clipboard-html.ts +13 -3
- package/corpus/templates/design/server/plugins/agent-chat.ts +7 -0
- package/corpus/templates/design/server/plugins/db.ts +42 -0
- package/corpus/templates/design/server/plugins/local-figma-qa-upload.ts +7 -0
- package/corpus/templates/design/server/register-secrets.ts +8 -8
- package/corpus/templates/design/server/routes/api/qa-figma-import-assets/[assetId].get.ts +46 -0
- package/corpus/templates/design/server/source-workspace.ts +104 -12
- package/corpus/templates/design/shared/code-layer.ts +105 -28
- package/corpus/templates/design/shared/design-system-templates.ts +301 -0
- package/corpus/templates/design/shared/design-template-presets.ts +195 -0
- package/corpus/templates/design/shared/figma-url.ts +8 -0
- package/corpus/templates/design/shared/html-integrity.ts +306 -0
- package/corpus/templates/design/shared/interaction-states.ts +318 -23
- package/corpus/templates/design/shared/locked-layers.ts +151 -0
- package/corpus/templates/dispatch/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/dispatch/app/i18n/zh-TW.ts +48 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +536 -1
- package/corpus/templates/dispatch/changelog/2026-07-10-connect-slack-workspaces-with-oauth-and-manage-channel-ident.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-10-microsoft-teams-bots-and-discord-slash-commands-can-now-conn.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-10-slack-and-telegram-now-route-uptime-monitor-requests-to-anal.md +6 -0
- package/corpus/templates/forms/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +7 -0
- package/corpus/templates/forms/AGENTS.md +4 -0
- package/corpus/templates/forms/actions/create-form.ts +44 -7
- package/corpus/templates/forms/actions/lib/assert-publishable-form.ts +38 -0
- package/corpus/templates/forms/actions/patch-form-fields.ts +4 -0
- package/corpus/templates/forms/actions/update-form.ts +2 -36
- package/corpus/templates/forms/app/i18n/ar-SA.ts +3 -0
- package/corpus/templates/forms/app/i18n/de-DE.ts +3 -0
- package/corpus/templates/forms/app/i18n/en-US.ts +3 -0
- package/corpus/templates/forms/app/i18n/es-ES.ts +3 -0
- package/corpus/templates/forms/app/i18n/fr-FR.ts +3 -0
- package/corpus/templates/forms/app/i18n/hi-IN.ts +3 -0
- package/corpus/templates/forms/app/i18n/ja-JP.ts +3 -0
- package/corpus/templates/forms/app/i18n/ko-KR.ts +3 -0
- package/corpus/templates/forms/app/i18n/pt-BR.ts +3 -0
- package/corpus/templates/forms/app/i18n/zh-CN.ts +2 -0
- package/corpus/templates/forms/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +17 -0
- package/corpus/templates/forms/changelog/2026-07-10-agent-created-published-forms-now-return-the-anonymous-publi.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-10-published-forms-can-no-longer-be-edited-into-an-unusable-emp.md +6 -0
- package/corpus/templates/forms/server/handlers/submissions.ts +4 -3
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/forms/shared/types.ts +5 -0
- package/corpus/templates/mail/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +2 -2
- package/corpus/templates/mail/changelog/2026-07-10-mail-navigation-now-uses-a-clean-borderless-drawer.md +6 -0
- package/corpus/templates/plan/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/corpus/templates/plan/app/lib/plan-local-bridge.ts +4 -1
- package/corpus/templates/plan/changelog/2026-07-10-local-plans-now-keep-valid-content-visible-when-one-generate.md +6 -0
- package/corpus/templates/plan/server/lib/local-plan-files.ts +4 -1
- package/corpus/templates/slides/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +88 -2
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/a2a/client.d.ts +9 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +1 -0
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +24 -69
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/types.d.ts +2 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/agent/durable-background.d.ts +11 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +17 -0
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts +5 -3
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +34 -8
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +44 -2
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +124 -18
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +109 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +274 -37
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +22 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/audit/record.d.ts.map +1 -1
- package/dist/audit/record.js +29 -0
- package/dist/audit/record.js.map +1 -1
- package/dist/audit/store.d.ts.map +1 -1
- package/dist/audit/store.js +62 -3
- package/dist/audit/store.js.map +1 -1
- package/dist/audit/types.d.ts +13 -0
- package/dist/audit/types.d.ts.map +1 -1
- package/dist/audit/types.js.map +1 -1
- package/dist/automation/index.d.ts +193 -0
- package/dist/automation/index.d.ts.map +1 -0
- package/dist/automation/index.js +464 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +10 -3
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +492 -35
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +7 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +32 -8
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +15 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +115 -47
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts +14 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +35 -7
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts +4 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +97 -16
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/automation.d.ts +14 -0
- package/dist/client/automation.d.ts.map +1 -0
- package/dist/client/automation.js +9 -0
- package/dist/client/automation.js.map +1 -0
- package/dist/client/chat/run-recovery.d.ts +4 -0
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +12 -10
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +8 -0
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/integrations/api.d.ts +115 -0
- package/dist/client/integrations/api.d.ts.map +1 -0
- package/dist/client/integrations/api.js +123 -0
- package/dist/client/integrations/api.js.map +1 -0
- package/dist/client/integrations/index.d.ts +1 -0
- package/dist/client/integrations/index.d.ts.map +1 -1
- package/dist/client/integrations/index.js +1 -0
- package/dist/client/integrations/index.js.map +1 -1
- package/dist/client/transcription/use-live-transcription.d.ts.map +1 -1
- package/dist/client/transcription/use-live-transcription.js +59 -5
- package/dist/client/transcription/use-live-transcription.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +16 -1
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +23 -4
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +49 -4
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/workspace-deploy.d.ts.map +1 -1
- package/dist/deploy/workspace-deploy.js +31 -2
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/file-upload/types.d.ts +2 -0
- package/dist/file-upload/types.d.ts.map +1 -1
- package/dist/file-upload/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +6 -4
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/adapters/discord.d.ts +3 -0
- package/dist/integrations/adapters/discord.d.ts.map +1 -0
- package/dist/integrations/adapters/discord.js +261 -0
- package/dist/integrations/adapters/discord.js.map +1 -0
- package/dist/integrations/adapters/index.d.ts +4 -0
- package/dist/integrations/adapters/index.d.ts.map +1 -0
- package/dist/integrations/adapters/index.js +26 -0
- package/dist/integrations/adapters/index.js.map +1 -0
- package/dist/integrations/adapters/microsoft-teams.d.ts +5 -0
- package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -0
- package/dist/integrations/adapters/microsoft-teams.js +295 -0
- package/dist/integrations/adapters/microsoft-teams.js.map +1 -0
- package/dist/integrations/adapters/slack.d.ts +8 -2
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +736 -27
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/adapters/telegram.d.ts +0 -2
- package/dist/integrations/adapters/telegram.d.ts.map +1 -1
- package/dist/integrations/adapters/telegram.js +49 -15
- package/dist/integrations/adapters/telegram.js.map +1 -1
- package/dist/integrations/adapters/whatsapp.d.ts +0 -2
- package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
- package/dist/integrations/adapters/whatsapp.js +40 -17
- package/dist/integrations/adapters/whatsapp.js.map +1 -1
- package/dist/integrations/catalog.d.ts +75 -0
- package/dist/integrations/catalog.d.ts.map +1 -0
- package/dist/integrations/catalog.js +451 -0
- package/dist/integrations/catalog.js.map +1 -0
- package/dist/integrations/controls-store.d.ts +44 -0
- package/dist/integrations/controls-store.d.ts.map +1 -0
- package/dist/integrations/controls-store.js +129 -0
- package/dist/integrations/controls-store.js.map +1 -0
- package/dist/integrations/index.d.ts +13 -1
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +9 -0
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/installations-store.d.ts +112 -0
- package/dist/integrations/installations-store.d.ts.map +1 -0
- package/dist/integrations/installations-store.js +493 -0
- package/dist/integrations/installations-store.js.map +1 -0
- package/dist/integrations/integration-memory.d.ts +24 -0
- package/dist/integrations/integration-memory.d.ts.map +1 -0
- package/dist/integrations/integration-memory.js +136 -0
- package/dist/integrations/integration-memory.js.map +1 -0
- package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-retry-job.js +6 -5
- package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
- package/dist/integrations/pending-tasks-store.d.ts +17 -0
- package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-store.js +71 -5
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +520 -35
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/scope-store.d.ts +65 -0
- package/dist/integrations/scope-store.d.ts.map +1 -0
- package/dist/integrations/scope-store.js +361 -0
- package/dist/integrations/scope-store.js.map +1 -0
- package/dist/integrations/slack-manifest.d.ts +66 -0
- package/dist/integrations/slack-manifest.d.ts.map +1 -0
- package/dist/integrations/slack-manifest.js +70 -0
- package/dist/integrations/slack-manifest.js.map +1 -0
- package/dist/integrations/slack-oauth.d.ts +99 -0
- package/dist/integrations/slack-oauth.d.ts.map +1 -0
- package/dist/integrations/slack-oauth.js +226 -0
- package/dist/integrations/slack-oauth.js.map +1 -0
- package/dist/integrations/types.d.ts +144 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js +16 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/usage-budget-store.d.ts +84 -0
- package/dist/integrations/usage-budget-store.d.ts.map +1 -0
- package/dist/integrations/usage-budget-store.js +650 -0
- package/dist/integrations/usage-budget-store.js.map +1 -0
- package/dist/integrations/webhook-handler.d.ts +4 -0
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +415 -47
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/jobs/scheduler.d.ts +6 -0
- package/dist/jobs/scheduler.d.ts.map +1 -1
- package/dist/jobs/scheduler.js +83 -4
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/jobs/tools.d.ts.map +1 -1
- package/dist/jobs/tools.js +43 -11
- package/dist/jobs/tools.js.map +1 -1
- package/dist/provider-api/index.d.ts +4 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +72 -1
- package/dist/provider-api/index.js.map +1 -1
- package/dist/resources/handlers.d.ts.map +1 -1
- package/dist/resources/handlers.js +57 -15
- package/dist/resources/handlers.js.map +1 -1
- package/dist/resources/script-helpers.d.ts.map +1 -1
- package/dist/resources/script-helpers.js +34 -8
- package/dist/resources/script-helpers.js.map +1 -1
- package/dist/resources/store.d.ts +10 -0
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +54 -47
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +106 -1
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +15 -4
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +248 -113
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +10 -14
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-design-systems.d.ts +3 -1
- package/dist/server/builder-design-systems.d.ts.map +1 -1
- package/dist/server/builder-design-systems.js +9 -2
- package/dist/server/builder-design-systems.js.map +1 -1
- package/dist/server/edge.d.ts +1 -1
- package/dist/server/edge.d.ts.map +1 -1
- package/dist/server/edge.js +1 -1
- package/dist/server/edge.js.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/request-context.d.ts +18 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/social-og-image.d.ts.map +1 -1
- package/dist/server/social-og-image.js +3 -2
- package/dist/server/social-og-image.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +62 -44
- package/dist/templates/default/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/dist/templates/default/AGENTS.md +7 -3
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/dist/templates/workspace-core/.agents/skills/extension-points/SKILL.md +6 -3
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +25 -0
- package/dist/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +15 -0
- package/dist/templates/workspace-core/AGENTS.md +3 -2
- package/dist/usage/store.d.ts +7 -0
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +24 -3
- package/dist/usage/store.js.map +1 -1
- package/docs/content/automation-connectors.mdx +80 -0
- package/docs/content/extensions.mdx +8 -0
- package/docs/content/locales/ar-SA/extensions.mdx +8 -0
- package/docs/content/locales/ar-SA/messaging.mdx +84 -15
- package/docs/content/locales/ar-SA/template-clips.mdx +1 -0
- package/docs/content/locales/ar-SA/template-design.mdx +1 -0
- package/docs/content/locales/de-DE/extensions.mdx +10 -0
- package/docs/content/locales/de-DE/messaging.mdx +92 -15
- package/docs/content/locales/de-DE/template-clips.mdx +1 -0
- package/docs/content/locales/de-DE/template-design.mdx +1 -0
- package/docs/content/locales/es-ES/extensions.mdx +10 -0
- package/docs/content/locales/es-ES/messaging.mdx +91 -15
- package/docs/content/locales/es-ES/template-clips.mdx +1 -0
- package/docs/content/locales/es-ES/template-design.mdx +1 -0
- package/docs/content/locales/fr-FR/extensions.mdx +10 -0
- package/docs/content/locales/fr-FR/messaging.mdx +91 -15
- package/docs/content/locales/fr-FR/template-clips.mdx +1 -0
- package/docs/content/locales/fr-FR/template-design.mdx +1 -0
- package/docs/content/locales/hi-IN/extensions.mdx +8 -0
- package/docs/content/locales/hi-IN/messaging.mdx +81 -15
- package/docs/content/locales/hi-IN/template-clips.mdx +1 -0
- package/docs/content/locales/hi-IN/template-design.mdx +1 -0
- package/docs/content/locales/ja-JP/extensions.mdx +9 -0
- package/docs/content/locales/ja-JP/messaging.mdx +81 -15
- package/docs/content/locales/ja-JP/template-clips.mdx +1 -0
- package/docs/content/locales/ja-JP/template-design.mdx +1 -0
- package/docs/content/locales/ko-KR/extensions.mdx +8 -0
- package/docs/content/locales/ko-KR/messaging.mdx +80 -15
- package/docs/content/locales/ko-KR/template-clips.mdx +1 -0
- package/docs/content/locales/ko-KR/template-design.mdx +1 -0
- package/docs/content/locales/pt-BR/extensions.mdx +9 -0
- package/docs/content/locales/pt-BR/messaging.mdx +88 -15
- package/docs/content/locales/pt-BR/template-clips.mdx +1 -0
- package/docs/content/locales/pt-BR/template-design.mdx +1 -0
- package/docs/content/locales/zh-CN/extensions.mdx +6 -0
- package/docs/content/locales/zh-CN/messaging.mdx +78 -15
- package/docs/content/locales/zh-CN/template-clips.mdx +1 -0
- package/docs/content/locales/zh-CN/template-design.mdx +1 -0
- package/docs/content/locales/zh-TW/extensions.mdx +6 -0
- package/docs/content/locales/zh-TW/messaging.mdx +78 -15
- package/docs/content/locales/zh-TW/template-clips.mdx +1 -0
- package/docs/content/locales/zh-TW/template-design.mdx +1 -0
- package/docs/content/messaging.mdx +227 -45
- package/docs/content/template-clips.mdx +3 -0
- package/docs/content/template-design.mdx +4 -0
- package/package.json +5 -1
- package/src/templates/default/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/src/templates/default/AGENTS.md +7 -3
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/capture-learnings/SKILL.md +22 -9
- package/src/templates/workspace-core/.agents/skills/extension-points/SKILL.md +6 -3
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +25 -0
- package/src/templates/workspace-core/.agents/skills/integration-webhooks/SKILL.md +15 -0
- package/src/templates/workspace-core/AGENTS.md +3 -2
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import * as zlib from "node:zlib";
|
|
3
|
+
|
|
4
|
+
import { Decompress as ZstdDecompress } from "fzstd";
|
|
5
|
+
import {
|
|
6
|
+
ByteBuffer,
|
|
7
|
+
compileSchemaJS,
|
|
8
|
+
decodeBinarySchema,
|
|
9
|
+
type Schema,
|
|
10
|
+
} from "kiwi-schema";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
MAX_FIG_DECOMPRESSED_BYTES,
|
|
14
|
+
MAX_FIG_FILE_BYTES,
|
|
15
|
+
} from "./fig-file-limits.js";
|
|
16
|
+
|
|
17
|
+
/** Route and decoder caps are intentionally conservative: `.fig` is untrusted input. */
|
|
18
|
+
const MAX_DECOMPRESSED_CHUNK_BYTES = 48 * 1024 * 1024;
|
|
19
|
+
const MAX_SCHEMA_BYTES = 4 * 1024 * 1024;
|
|
20
|
+
const MAX_KIWI_CHUNKS = 4_096;
|
|
21
|
+
const MAX_ZIP_ENTRIES = 2_048;
|
|
22
|
+
const MAX_ZIP_NAME_BYTES = 512;
|
|
23
|
+
const MAX_COMPRESSION_RATIO = 1_000;
|
|
24
|
+
const MAX_DECODED_OBJECTS = 250_000;
|
|
25
|
+
const MAX_DECODE_DEPTH = 256;
|
|
26
|
+
const MAX_COLLECTION_LENGTH = 250_000;
|
|
27
|
+
const MAX_COLLECTION_ITEMS = 1_000_000;
|
|
28
|
+
const MAX_DECODE_READS = 64 * 1024 * 1024;
|
|
29
|
+
const MAX_SANITIZED_BINARY_BYTES = 32 * 1024 * 1024;
|
|
30
|
+
const MAX_DECODED_STRING_BYTES = 4 * 1024 * 1024;
|
|
31
|
+
const MAX_TOTAL_STRING_BYTES = 32 * 1024 * 1024;
|
|
32
|
+
const ZSTD_MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd]);
|
|
33
|
+
const FIG_KIWI_MAGIC = Buffer.from("fig-kiwi", "utf8");
|
|
34
|
+
const ZIP_MAGIC = Buffer.from([0x50, 0x4b, 0x03, 0x04]);
|
|
35
|
+
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
36
|
+
const JPEG_MAGIC = Buffer.from([0xff, 0xd8, 0xff]);
|
|
37
|
+
|
|
38
|
+
export interface DecodedFigKiwi {
|
|
39
|
+
version: number;
|
|
40
|
+
schema: Buffer;
|
|
41
|
+
document: Buffer;
|
|
42
|
+
blobs: Buffer[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DecodedFigImage {
|
|
46
|
+
/** SHA1 of the blob bytes — matches what the document references. */
|
|
47
|
+
hash: string;
|
|
48
|
+
ext: string;
|
|
49
|
+
bytes: Buffer;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface DecodedFig {
|
|
53
|
+
format: "kiwi" | "zip";
|
|
54
|
+
version?: number;
|
|
55
|
+
document: unknown;
|
|
56
|
+
images: DecodedFigImage[];
|
|
57
|
+
thumbnail: Buffer | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function sha1(buf: Buffer): string {
|
|
61
|
+
return crypto.createHash("sha1").update(buf).digest("hex");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function detectImageExt(buf: Buffer): string {
|
|
65
|
+
if (buf.length >= 8 && buf.subarray(0, 8).equals(PNG_MAGIC)) return "png";
|
|
66
|
+
if (buf.length >= 3 && buf.subarray(0, 3).equals(JPEG_MAGIC)) return "jpg";
|
|
67
|
+
if (
|
|
68
|
+
buf.length >= 12 &&
|
|
69
|
+
buf.subarray(0, 4).toString("ascii") === "RIFF" &&
|
|
70
|
+
buf.subarray(8, 12).toString("ascii") === "WEBP"
|
|
71
|
+
) {
|
|
72
|
+
return "webp";
|
|
73
|
+
}
|
|
74
|
+
if (buf.length >= 4 && buf.subarray(0, 4).toString("ascii") === "GIF8") {
|
|
75
|
+
return "gif";
|
|
76
|
+
}
|
|
77
|
+
return "bin";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function checkDecompressedSize(buf: Buffer): Buffer {
|
|
81
|
+
if (buf.length > MAX_DECOMPRESSED_CHUNK_BYTES) {
|
|
82
|
+
throw new Error("Decompressed .fig chunk is too large (max 48 MB).");
|
|
83
|
+
}
|
|
84
|
+
return buf;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reject Zstandard frames that advertise a window or content size above our
|
|
89
|
+
* cap before the decoder allocates that window. The Figma container uses a
|
|
90
|
+
* single standard Zstd frame per chunk.
|
|
91
|
+
*/
|
|
92
|
+
function assertSafeZstdFrameHeader(buf: Buffer): void {
|
|
93
|
+
if (buf.length < 6) throw new Error("Truncated Zstandard .fig chunk.");
|
|
94
|
+
const descriptor = buf[4]!;
|
|
95
|
+
if ((descriptor & 0x08) !== 0) {
|
|
96
|
+
throw new Error("Invalid reserved bit in Zstandard .fig chunk.");
|
|
97
|
+
}
|
|
98
|
+
const singleSegment = (descriptor & 0x20) !== 0;
|
|
99
|
+
const contentSizeFlag = descriptor >>> 6;
|
|
100
|
+
const dictionaryIdFlag = descriptor & 0x03;
|
|
101
|
+
let offset = 5;
|
|
102
|
+
let windowSize: number | undefined;
|
|
103
|
+
if (!singleSegment) {
|
|
104
|
+
const windowDescriptor = buf[offset++];
|
|
105
|
+
if (windowDescriptor === undefined) {
|
|
106
|
+
throw new Error("Truncated Zstandard window descriptor.");
|
|
107
|
+
}
|
|
108
|
+
const exponent = windowDescriptor >>> 3;
|
|
109
|
+
const mantissa = windowDescriptor & 0x07;
|
|
110
|
+
const windowBase = 2 ** (10 + exponent);
|
|
111
|
+
windowSize = windowBase + (windowBase / 8) * mantissa;
|
|
112
|
+
}
|
|
113
|
+
const dictionaryIdBytes = [0, 1, 2, 4][dictionaryIdFlag]!;
|
|
114
|
+
offset += dictionaryIdBytes;
|
|
115
|
+
const contentSizeBytes =
|
|
116
|
+
contentSizeFlag === 0
|
|
117
|
+
? singleSegment
|
|
118
|
+
? 1
|
|
119
|
+
: 0
|
|
120
|
+
: contentSizeFlag === 1
|
|
121
|
+
? 2
|
|
122
|
+
: contentSizeFlag === 2
|
|
123
|
+
? 4
|
|
124
|
+
: 8;
|
|
125
|
+
if (offset + contentSizeBytes > buf.length) {
|
|
126
|
+
throw new Error("Truncated Zstandard frame header.");
|
|
127
|
+
}
|
|
128
|
+
let contentSize: number | undefined;
|
|
129
|
+
if (contentSizeBytes > 0) {
|
|
130
|
+
let value = 0n;
|
|
131
|
+
for (let index = 0; index < contentSizeBytes; index += 1) {
|
|
132
|
+
value |= BigInt(buf[offset + index]!) << BigInt(index * 8);
|
|
133
|
+
}
|
|
134
|
+
if (contentSizeBytes === 2) value += 256n;
|
|
135
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
136
|
+
throw new Error("Zstandard .fig chunk declares an unsafe size.");
|
|
137
|
+
}
|
|
138
|
+
contentSize = Number(value);
|
|
139
|
+
}
|
|
140
|
+
const declaredSize = contentSize ?? windowSize;
|
|
141
|
+
if (
|
|
142
|
+
declaredSize !== undefined &&
|
|
143
|
+
declaredSize > MAX_DECOMPRESSED_CHUNK_BYTES
|
|
144
|
+
) {
|
|
145
|
+
throw new Error("Decompressed .fig chunk is too large (max 48 MB).");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function decompressZstdChunk(buf: Buffer): Buffer {
|
|
150
|
+
assertSafeZstdFrameHeader(buf);
|
|
151
|
+
const parts: Buffer[] = [];
|
|
152
|
+
let total = 0;
|
|
153
|
+
const decoder = new ZstdDecompress((part) => {
|
|
154
|
+
total += part.byteLength;
|
|
155
|
+
if (total > MAX_DECOMPRESSED_CHUNK_BYTES) {
|
|
156
|
+
throw new Error("Decompressed .fig chunk is too large (max 48 MB).");
|
|
157
|
+
}
|
|
158
|
+
parts.push(Buffer.from(part));
|
|
159
|
+
});
|
|
160
|
+
decoder.push(buf, true);
|
|
161
|
+
return Buffer.concat(parts, total);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function decompressChunk(buf: Buffer): Buffer {
|
|
165
|
+
if (buf.length >= 4 && buf.subarray(0, 4).equals(ZSTD_MAGIC)) {
|
|
166
|
+
return decompressZstdChunk(buf);
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
return zlib.inflateRawSync(buf, {
|
|
170
|
+
maxOutputLength: MAX_DECOMPRESSED_CHUNK_BYTES,
|
|
171
|
+
});
|
|
172
|
+
} catch (e) {
|
|
173
|
+
if (e instanceof RangeError || /too large|max output/i.test(String(e))) {
|
|
174
|
+
throw new Error("Decompressed .fig chunk is too large (max 48 MB).");
|
|
175
|
+
}
|
|
176
|
+
/* fall through for format/data errors */
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
return zlib.inflateSync(buf, {
|
|
180
|
+
maxOutputLength: MAX_DECOMPRESSED_CHUNK_BYTES,
|
|
181
|
+
});
|
|
182
|
+
} catch (e) {
|
|
183
|
+
if (e instanceof RangeError || /too large|max output/i.test(String(e))) {
|
|
184
|
+
throw new Error("Decompressed .fig chunk is too large (max 48 MB).");
|
|
185
|
+
}
|
|
186
|
+
/* fall through */
|
|
187
|
+
}
|
|
188
|
+
return checkDecompressedSize(Buffer.from(buf));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function decodeKiwiContainer(file: Buffer): DecodedFigKiwi {
|
|
192
|
+
if (file.length > MAX_FIG_FILE_BYTES) {
|
|
193
|
+
throw new Error(".fig file is too large (max 50 MB).");
|
|
194
|
+
}
|
|
195
|
+
if (!file.subarray(0, 8).equals(FIG_KIWI_MAGIC)) {
|
|
196
|
+
throw new Error("Not a fig-kiwi file (missing magic header)");
|
|
197
|
+
}
|
|
198
|
+
if (file.length < 12) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
"Truncated kiwi header (file too short to contain version)",
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const version = file.readUInt32LE(8);
|
|
204
|
+
let offset = 12;
|
|
205
|
+
const chunks: Buffer[] = [];
|
|
206
|
+
let decompressedBytes = 0;
|
|
207
|
+
while (offset < file.length) {
|
|
208
|
+
if (chunks.length >= MAX_KIWI_CHUNKS) {
|
|
209
|
+
throw new Error(".fig file has too many binary chunks.");
|
|
210
|
+
}
|
|
211
|
+
if (offset + 4 > file.length) {
|
|
212
|
+
throw new Error(`Truncated chunk header at offset ${offset}`);
|
|
213
|
+
}
|
|
214
|
+
const length = file.readUInt32LE(offset);
|
|
215
|
+
offset += 4;
|
|
216
|
+
if (offset + length > file.length) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Chunk extends past end of file (offset=${offset}, length=${length}, total=${file.length})`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const compressed = file.subarray(offset, offset + length);
|
|
222
|
+
offset += length;
|
|
223
|
+
const decompressed = decompressChunk(compressed);
|
|
224
|
+
decompressedBytes += decompressed.length;
|
|
225
|
+
if (decompressedBytes > MAX_FIG_DECOMPRESSED_BYTES) {
|
|
226
|
+
throw new Error("Decompressed .fig data is too large (max 96 MB).");
|
|
227
|
+
}
|
|
228
|
+
chunks.push(decompressed);
|
|
229
|
+
}
|
|
230
|
+
if (chunks.length < 2) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
`Expected at least 2 chunks (schema + document), got ${chunks.length}`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (chunks[0]!.length > MAX_SCHEMA_BYTES) {
|
|
236
|
+
throw new Error(".fig schema is too large (max 4 MB).");
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
version,
|
|
240
|
+
schema: chunks[0]!,
|
|
241
|
+
document: chunks[1]!,
|
|
242
|
+
blobs: chunks.slice(2),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
interface ZipEntry {
|
|
247
|
+
name: string;
|
|
248
|
+
data: Buffer;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Minimal zip reader: supports stored (method 0) and deflate (method 8)
|
|
253
|
+
* entries, no encryption, no zip64. Sufficient for legacy `.fig` archives.
|
|
254
|
+
*/
|
|
255
|
+
function readZip(file: Buffer): ZipEntry[] {
|
|
256
|
+
const EOCD_SIG = 0x06054b50;
|
|
257
|
+
const maxScan = Math.min(file.length, 65557);
|
|
258
|
+
let eocdOffset = -1;
|
|
259
|
+
for (let i = file.length - 22; i >= file.length - maxScan && i >= 0; i--) {
|
|
260
|
+
if (file.readUInt32LE(i) === EOCD_SIG) {
|
|
261
|
+
eocdOffset = i;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (eocdOffset < 0) throw new Error("Zip EOCD record not found");
|
|
266
|
+
|
|
267
|
+
if (eocdOffset + 22 > file.length) {
|
|
268
|
+
throw new Error("Truncated zip EOCD record.");
|
|
269
|
+
}
|
|
270
|
+
const diskNumber = file.readUInt16LE(eocdOffset + 4);
|
|
271
|
+
const centralDirectoryDisk = file.readUInt16LE(eocdOffset + 6);
|
|
272
|
+
if (diskNumber !== 0 || centralDirectoryDisk !== 0) {
|
|
273
|
+
throw new Error("Multi-disk .fig zip archives are not supported.");
|
|
274
|
+
}
|
|
275
|
+
const totalEntries = file.readUInt16LE(eocdOffset + 10);
|
|
276
|
+
if (totalEntries === 0xffff || totalEntries > MAX_ZIP_ENTRIES) {
|
|
277
|
+
throw new Error(".fig zip has too many entries (max 2048).");
|
|
278
|
+
}
|
|
279
|
+
const cdSize = file.readUInt32LE(eocdOffset + 12);
|
|
280
|
+
const cdOffset = file.readUInt32LE(eocdOffset + 16);
|
|
281
|
+
if (
|
|
282
|
+
cdOffset === 0xffffffff ||
|
|
283
|
+
cdSize === 0xffffffff ||
|
|
284
|
+
cdOffset + cdSize > eocdOffset
|
|
285
|
+
) {
|
|
286
|
+
throw new Error("Invalid or Zip64 .fig central directory.");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const entries: ZipEntry[] = [];
|
|
290
|
+
let p = cdOffset;
|
|
291
|
+
let totalUncompressedBytes = 0;
|
|
292
|
+
for (let i = 0; i < totalEntries; i++) {
|
|
293
|
+
if (p + 46 > file.length) {
|
|
294
|
+
throw new Error(`Truncated central directory entry at offset ${p}`);
|
|
295
|
+
}
|
|
296
|
+
if (file.readUInt32LE(p) !== 0x02014b50) {
|
|
297
|
+
throw new Error(`Bad central directory entry signature at ${p}`);
|
|
298
|
+
}
|
|
299
|
+
const flags = file.readUInt16LE(p + 8);
|
|
300
|
+
const compressionMethod = file.readUInt16LE(p + 10);
|
|
301
|
+
const compressedSize = file.readUInt32LE(p + 20);
|
|
302
|
+
const uncompressedSize = file.readUInt32LE(p + 24);
|
|
303
|
+
const nameLen = file.readUInt16LE(p + 28);
|
|
304
|
+
const extraLen = file.readUInt16LE(p + 30);
|
|
305
|
+
const commentLen = file.readUInt16LE(p + 32);
|
|
306
|
+
const localHeaderOffset = file.readUInt32LE(p + 42);
|
|
307
|
+
if ((flags & 0x01) !== 0) {
|
|
308
|
+
throw new Error("Encrypted .fig zip entries are not supported.");
|
|
309
|
+
}
|
|
310
|
+
if (nameLen > MAX_ZIP_NAME_BYTES) {
|
|
311
|
+
throw new Error(".fig zip entry name is too long.");
|
|
312
|
+
}
|
|
313
|
+
if (compressedSize === 0xffffffff || uncompressedSize === 0xffffffff) {
|
|
314
|
+
throw new Error("Zip64 .fig entries are not supported.");
|
|
315
|
+
}
|
|
316
|
+
if (p + 46 + nameLen + extraLen + commentLen > file.length) {
|
|
317
|
+
throw new Error(`Truncated central directory entry at offset ${p}`);
|
|
318
|
+
}
|
|
319
|
+
const name = file.subarray(p + 46, p + 46 + nameLen).toString("utf8");
|
|
320
|
+
p += 46 + nameLen + extraLen + commentLen;
|
|
321
|
+
if (
|
|
322
|
+
name.includes("\0") ||
|
|
323
|
+
name.startsWith("/") ||
|
|
324
|
+
name.startsWith("\\") ||
|
|
325
|
+
/(^|[\\/])\.\.([\\/]|$)/.test(name)
|
|
326
|
+
) {
|
|
327
|
+
throw new Error("Unsafe path in .fig zip entry.");
|
|
328
|
+
}
|
|
329
|
+
if (uncompressedSize > MAX_DECOMPRESSED_CHUNK_BYTES) {
|
|
330
|
+
throw new Error("Decompressed .fig zip entry is too large (max 48 MB).");
|
|
331
|
+
}
|
|
332
|
+
if (
|
|
333
|
+
uncompressedSize > 1024 * 1024 &&
|
|
334
|
+
(compressedSize === 0 ||
|
|
335
|
+
uncompressedSize / compressedSize > MAX_COMPRESSION_RATIO)
|
|
336
|
+
) {
|
|
337
|
+
throw new Error("Suspicious .fig zip compression ratio.");
|
|
338
|
+
}
|
|
339
|
+
totalUncompressedBytes += uncompressedSize;
|
|
340
|
+
if (totalUncompressedBytes > MAX_FIG_DECOMPRESSED_BYTES) {
|
|
341
|
+
throw new Error("Decompressed .fig data is too large (max 96 MB).");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const lh = localHeaderOffset;
|
|
345
|
+
if (lh + 30 > file.length) {
|
|
346
|
+
throw new Error(`Local header offset ${lh} out of bounds`);
|
|
347
|
+
}
|
|
348
|
+
if (file.readUInt32LE(lh) !== 0x04034b50) {
|
|
349
|
+
throw new Error(`Bad local file header signature at ${lh}`);
|
|
350
|
+
}
|
|
351
|
+
const localFlags = file.readUInt16LE(lh + 6);
|
|
352
|
+
const localCompressionMethod = file.readUInt16LE(lh + 8);
|
|
353
|
+
if (
|
|
354
|
+
(localFlags & 0x01) !== 0 ||
|
|
355
|
+
localCompressionMethod !== compressionMethod
|
|
356
|
+
) {
|
|
357
|
+
throw new Error(`Inconsistent local header for "${name}".`);
|
|
358
|
+
}
|
|
359
|
+
const lhNameLen = file.readUInt16LE(lh + 26);
|
|
360
|
+
const lhExtraLen = file.readUInt16LE(lh + 28);
|
|
361
|
+
const dataStart = lh + 30 + lhNameLen + lhExtraLen;
|
|
362
|
+
if (dataStart + compressedSize > file.length) {
|
|
363
|
+
throw new Error(`Compressed data for "${name}" extends past end of file`);
|
|
364
|
+
}
|
|
365
|
+
const compressed = file.subarray(dataStart, dataStart + compressedSize);
|
|
366
|
+
|
|
367
|
+
let data: Buffer;
|
|
368
|
+
if (compressionMethod === 0) {
|
|
369
|
+
if (compressedSize !== uncompressedSize) {
|
|
370
|
+
throw new Error(`Invalid stored size for "${name}".`);
|
|
371
|
+
}
|
|
372
|
+
data = Buffer.from(compressed);
|
|
373
|
+
} else if (compressionMethod === 8) {
|
|
374
|
+
try {
|
|
375
|
+
data = zlib.inflateRawSync(compressed, {
|
|
376
|
+
maxOutputLength: MAX_DECOMPRESSED_CHUNK_BYTES,
|
|
377
|
+
});
|
|
378
|
+
} catch (error) {
|
|
379
|
+
if (/buffer|length|output|large|max/i.test(String(error))) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
"Decompressed .fig zip entry is too large (max 48 MB).",
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
throw new Error(`Invalid compressed data for "${name}".`);
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Unsupported zip compression method ${compressionMethod} for entry "${name}"`,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
if (data.length !== uncompressedSize) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`Size mismatch for "${name}": expected ${uncompressedSize}, got ${data.length}`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
if (name.endsWith("/")) continue;
|
|
397
|
+
entries.push({ name, data });
|
|
398
|
+
}
|
|
399
|
+
return entries;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function isZip(file: Buffer): boolean {
|
|
403
|
+
return file.length >= 4 && file.subarray(0, 4).equals(ZIP_MAGIC);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Recursively convert non-JSON-serializable values (Uint8Array -> hex string,
|
|
407
|
+
// bigint -> string) without round-tripping through a single JSON string, which
|
|
408
|
+
// would throw "Invalid string length" for large documents (V8 caps strings at
|
|
409
|
+
// ~512MB).
|
|
410
|
+
interface ObjectBudget {
|
|
411
|
+
objects: number;
|
|
412
|
+
items: number;
|
|
413
|
+
binaryBytes: number;
|
|
414
|
+
stringBytes: number;
|
|
415
|
+
active: WeakSet<object>;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function sanitizeForJson(
|
|
419
|
+
value: unknown,
|
|
420
|
+
budget: ObjectBudget,
|
|
421
|
+
depth = 0,
|
|
422
|
+
): unknown {
|
|
423
|
+
if (depth > MAX_DECODE_DEPTH) {
|
|
424
|
+
throw new Error("Decoded .fig document is nested too deeply.");
|
|
425
|
+
}
|
|
426
|
+
if (value instanceof Uint8Array) {
|
|
427
|
+
budget.binaryBytes += value.byteLength;
|
|
428
|
+
if (budget.binaryBytes > MAX_SANITIZED_BINARY_BYTES) {
|
|
429
|
+
throw new Error("Decoded .fig document contains too much binary data.");
|
|
430
|
+
}
|
|
431
|
+
return Buffer.from(value).toString("hex");
|
|
432
|
+
}
|
|
433
|
+
if (typeof value === "string") {
|
|
434
|
+
const bytes = Buffer.byteLength(value, "utf8");
|
|
435
|
+
budget.stringBytes += bytes;
|
|
436
|
+
if (
|
|
437
|
+
bytes > MAX_DECODED_STRING_BYTES ||
|
|
438
|
+
budget.stringBytes > MAX_TOTAL_STRING_BYTES
|
|
439
|
+
) {
|
|
440
|
+
throw new Error("Decoded .fig document contains too much string data.");
|
|
441
|
+
}
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
if (typeof value === "bigint") return value.toString();
|
|
445
|
+
if (Array.isArray(value)) {
|
|
446
|
+
budget.objects += 1;
|
|
447
|
+
budget.items += value.length;
|
|
448
|
+
if (
|
|
449
|
+
budget.objects > MAX_DECODED_OBJECTS ||
|
|
450
|
+
value.length > MAX_COLLECTION_LENGTH ||
|
|
451
|
+
budget.items > MAX_COLLECTION_ITEMS
|
|
452
|
+
) {
|
|
453
|
+
throw new Error("Decoded .fig document exceeds collection limits.");
|
|
454
|
+
}
|
|
455
|
+
if (budget.active.has(value)) {
|
|
456
|
+
throw new Error("Decoded .fig document contains a cycle.");
|
|
457
|
+
}
|
|
458
|
+
budget.active.add(value);
|
|
459
|
+
try {
|
|
460
|
+
return value.map((item) => sanitizeForJson(item, budget, depth + 1));
|
|
461
|
+
} finally {
|
|
462
|
+
budget.active.delete(value);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (value !== null && typeof value === "object") {
|
|
466
|
+
budget.objects += 1;
|
|
467
|
+
if (budget.objects > MAX_DECODED_OBJECTS) {
|
|
468
|
+
throw new Error("Decoded .fig document contains too many objects.");
|
|
469
|
+
}
|
|
470
|
+
if (budget.active.has(value)) {
|
|
471
|
+
throw new Error("Decoded .fig document contains a cycle.");
|
|
472
|
+
}
|
|
473
|
+
budget.active.add(value);
|
|
474
|
+
const out: Record<string, unknown> = {};
|
|
475
|
+
const entries = Object.entries(value);
|
|
476
|
+
budget.items += entries.length;
|
|
477
|
+
if (budget.items > MAX_COLLECTION_ITEMS) {
|
|
478
|
+
throw new Error("Decoded .fig document has too many object fields.");
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
for (const [k, v] of entries) {
|
|
482
|
+
out[k] = sanitizeForJson(v, budget, depth + 1);
|
|
483
|
+
}
|
|
484
|
+
} finally {
|
|
485
|
+
budget.active.delete(value);
|
|
486
|
+
}
|
|
487
|
+
return out;
|
|
488
|
+
}
|
|
489
|
+
return value;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** Re-check decoded/direct-test documents before renderer traversal. */
|
|
493
|
+
export function assertSafeDecodedFigDocument(value: unknown): void {
|
|
494
|
+
const stack: Array<{ value: unknown; depth: number }> = [{ value, depth: 0 }];
|
|
495
|
+
const seen = new WeakSet<object>();
|
|
496
|
+
let objects = 0;
|
|
497
|
+
let items = 0;
|
|
498
|
+
let binaryBytes = 0;
|
|
499
|
+
let stringBytes = 0;
|
|
500
|
+
while (stack.length > 0) {
|
|
501
|
+
const current = stack.pop()!;
|
|
502
|
+
if (current.depth > MAX_DECODE_DEPTH) {
|
|
503
|
+
throw new Error("Decoded .fig document is nested too deeply.");
|
|
504
|
+
}
|
|
505
|
+
if (current.value instanceof Uint8Array) {
|
|
506
|
+
binaryBytes += current.value.byteLength;
|
|
507
|
+
if (binaryBytes > MAX_SANITIZED_BINARY_BYTES) {
|
|
508
|
+
throw new Error("Decoded .fig document contains too much binary data.");
|
|
509
|
+
}
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (typeof current.value === "string") {
|
|
513
|
+
const bytes = Buffer.byteLength(current.value, "utf8");
|
|
514
|
+
stringBytes += bytes;
|
|
515
|
+
if (
|
|
516
|
+
bytes > MAX_DECODED_STRING_BYTES ||
|
|
517
|
+
stringBytes > MAX_TOTAL_STRING_BYTES
|
|
518
|
+
) {
|
|
519
|
+
throw new Error("Decoded .fig document contains too much string data.");
|
|
520
|
+
}
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (current.value === null || typeof current.value !== "object") continue;
|
|
524
|
+
if (seen.has(current.value)) {
|
|
525
|
+
throw new Error("Decoded .fig document contains repeated object cycles.");
|
|
526
|
+
}
|
|
527
|
+
seen.add(current.value);
|
|
528
|
+
objects += 1;
|
|
529
|
+
if (objects > MAX_DECODED_OBJECTS) {
|
|
530
|
+
throw new Error("Decoded .fig document contains too many objects.");
|
|
531
|
+
}
|
|
532
|
+
const children = Array.isArray(current.value)
|
|
533
|
+
? current.value
|
|
534
|
+
: Object.values(current.value);
|
|
535
|
+
if (children.length > MAX_COLLECTION_LENGTH) {
|
|
536
|
+
throw new Error(
|
|
537
|
+
"Decoded .fig document contains an oversized collection.",
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
items += children.length;
|
|
541
|
+
if (items > MAX_COLLECTION_ITEMS) {
|
|
542
|
+
throw new Error("Decoded .fig document exceeds collection limits.");
|
|
543
|
+
}
|
|
544
|
+
for (const child of children) {
|
|
545
|
+
stack.push({ value: child, depth: current.depth + 1 });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
class BudgetByteBuffer extends ByteBuffer {
|
|
551
|
+
private collectionItems = 0;
|
|
552
|
+
private decodedObjects = 0;
|
|
553
|
+
private decodeDepth = 0;
|
|
554
|
+
private readCount = 0;
|
|
555
|
+
|
|
556
|
+
override readByte(): number {
|
|
557
|
+
this.readCount += 1;
|
|
558
|
+
if (this.readCount > MAX_DECODE_READS) {
|
|
559
|
+
throw new Error(".fig document exceeded its decode work budget.");
|
|
560
|
+
}
|
|
561
|
+
return super.readByte();
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
readCollectionLength(): number {
|
|
565
|
+
const length = super.readVarUint();
|
|
566
|
+
this.collectionItems += length;
|
|
567
|
+
if (
|
|
568
|
+
length > MAX_COLLECTION_LENGTH ||
|
|
569
|
+
this.collectionItems > MAX_COLLECTION_ITEMS
|
|
570
|
+
) {
|
|
571
|
+
throw new Error(".fig document declares an oversized collection.");
|
|
572
|
+
}
|
|
573
|
+
return length;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
enterDecodedObject(): void {
|
|
577
|
+
this.decodedObjects += 1;
|
|
578
|
+
this.decodeDepth += 1;
|
|
579
|
+
if (this.decodedObjects > MAX_DECODED_OBJECTS) {
|
|
580
|
+
throw new Error(".fig document contains too many decoded objects.");
|
|
581
|
+
}
|
|
582
|
+
if (this.decodeDepth > MAX_DECODE_DEPTH) {
|
|
583
|
+
throw new Error(".fig document is nested too deeply.");
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
leaveDecodedObject(): void {
|
|
588
|
+
this.decodeDepth -= 1;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
type CompiledDecoder = Record<string, unknown> & {
|
|
593
|
+
ByteBuffer: typeof BudgetByteBuffer;
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
function compileBudgetedSchema(schema: Schema): CompiledDecoder {
|
|
597
|
+
const generated = compileSchemaJS(schema);
|
|
598
|
+
const collectionRead = "var length = bb.readVarUint();";
|
|
599
|
+
const budgetedCollectionRead = "var length = bb.readCollectionLength();";
|
|
600
|
+
const patched = generated.split(collectionRead).join(budgetedCollectionRead);
|
|
601
|
+
const compiled: CompiledDecoder = { ByteBuffer: BudgetByteBuffer };
|
|
602
|
+
new Function("exports", patched)(compiled);
|
|
603
|
+
|
|
604
|
+
for (const key of Object.keys(compiled)) {
|
|
605
|
+
if (!key.startsWith("decode")) continue;
|
|
606
|
+
const original = compiled[key];
|
|
607
|
+
if (typeof original !== "function") continue;
|
|
608
|
+
compiled[key] = function budgetedDecode(
|
|
609
|
+
this: CompiledDecoder,
|
|
610
|
+
bb: BudgetByteBuffer,
|
|
611
|
+
) {
|
|
612
|
+
if (!(bb instanceof BudgetByteBuffer)) {
|
|
613
|
+
throw new Error("Unsafe .fig decoder buffer.");
|
|
614
|
+
}
|
|
615
|
+
bb.enterDecodedObject();
|
|
616
|
+
try {
|
|
617
|
+
return original.call(this, bb);
|
|
618
|
+
} finally {
|
|
619
|
+
bb.leaveDecodedObject();
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
return compiled;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Returns null on any decode failure so callers can still surface the raw
|
|
627
|
+
// document buffer.
|
|
628
|
+
function decodeKiwiDocument(
|
|
629
|
+
schemaBuf: Buffer,
|
|
630
|
+
documentBuf: Buffer,
|
|
631
|
+
): unknown | null {
|
|
632
|
+
let schema: Schema;
|
|
633
|
+
try {
|
|
634
|
+
assertSafeBinarySchemaShape(schemaBuf);
|
|
635
|
+
schema = decodeBinarySchema(schemaBuf);
|
|
636
|
+
} catch {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
// kiwi-schema compiles the schema with `new Function`, so never pass names
|
|
640
|
+
// from an untrusted binary schema to it without strict identifier and size
|
|
641
|
+
// validation. Real Figma schemas use ordinary identifiers and a `Message`
|
|
642
|
+
// root; anything else is an unsupported/probably hostile variant.
|
|
643
|
+
if (schema.definitions.length > 1_024) return null;
|
|
644
|
+
const definitionNames = new Set(schema.definitions.map((d) => d.name));
|
|
645
|
+
const safeIdentifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
646
|
+
const primitiveTypes = new Set([
|
|
647
|
+
"bool",
|
|
648
|
+
"byte",
|
|
649
|
+
"int",
|
|
650
|
+
"uint",
|
|
651
|
+
"float",
|
|
652
|
+
"string",
|
|
653
|
+
"int64",
|
|
654
|
+
"uint64",
|
|
655
|
+
]);
|
|
656
|
+
let fieldCount = 0;
|
|
657
|
+
for (const definition of schema.definitions) {
|
|
658
|
+
if (!safeIdentifier.test(definition.name)) return null;
|
|
659
|
+
if (!Array.isArray(definition.fields) || definition.fields.length > 1_024) {
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
fieldCount += definition.fields.length;
|
|
663
|
+
if (fieldCount > 20_000) return null;
|
|
664
|
+
for (const field of definition.fields) {
|
|
665
|
+
if (!safeIdentifier.test(field.name)) return null;
|
|
666
|
+
if (
|
|
667
|
+
field.type !== null &&
|
|
668
|
+
!primitiveTypes.has(field.type) &&
|
|
669
|
+
!definitionNames.has(field.type)
|
|
670
|
+
) {
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const rootMessage = schema.definitions.find(
|
|
676
|
+
(definition) =>
|
|
677
|
+
definition.name === "Message" && definition.kind === "MESSAGE",
|
|
678
|
+
);
|
|
679
|
+
if (!rootMessage) return null;
|
|
680
|
+
|
|
681
|
+
let compiled: CompiledDecoder;
|
|
682
|
+
try {
|
|
683
|
+
compiled = compileBudgetedSchema(schema);
|
|
684
|
+
} catch {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
const decodeKey = `decode${rootMessage.name}`;
|
|
688
|
+
const decoder = compiled[decodeKey];
|
|
689
|
+
if (typeof decoder !== "function") return null;
|
|
690
|
+
|
|
691
|
+
try {
|
|
692
|
+
const view = new Uint8Array(
|
|
693
|
+
documentBuf.buffer,
|
|
694
|
+
documentBuf.byteOffset,
|
|
695
|
+
documentBuf.byteLength,
|
|
696
|
+
);
|
|
697
|
+
const bb = new BudgetByteBuffer(view);
|
|
698
|
+
const document = decoder.call(compiled, bb);
|
|
699
|
+
return sanitizeForJson(document, {
|
|
700
|
+
objects: 0,
|
|
701
|
+
items: 0,
|
|
702
|
+
binaryBytes: 0,
|
|
703
|
+
stringBytes: 0,
|
|
704
|
+
active: new WeakSet(),
|
|
705
|
+
});
|
|
706
|
+
} catch {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function collectImagesFromBlobs(blobs: Buffer[]): DecodedFigImage[] {
|
|
712
|
+
const seen = new Map<string, DecodedFigImage>();
|
|
713
|
+
for (const blob of blobs) {
|
|
714
|
+
if (blob.length === 0) continue;
|
|
715
|
+
const ext = detectImageExt(blob);
|
|
716
|
+
if (ext === "bin") continue;
|
|
717
|
+
const hash = sha1(blob);
|
|
718
|
+
if (seen.has(hash)) continue;
|
|
719
|
+
seen.set(hash, { hash, ext, bytes: blob });
|
|
720
|
+
}
|
|
721
|
+
return Array.from(seen.values());
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function findThumbnail(documentBuf: Buffer, blobs: Buffer[]): Buffer | null {
|
|
725
|
+
const pngBlobs = blobs
|
|
726
|
+
.filter((b) => b.length >= 8 && b.subarray(0, 8).equals(PNG_MAGIC))
|
|
727
|
+
.sort((a, b) => a.length - b.length);
|
|
728
|
+
if (pngBlobs.length > 0) return pngBlobs[0]!;
|
|
729
|
+
|
|
730
|
+
const idx = documentBuf.indexOf(PNG_MAGIC);
|
|
731
|
+
if (idx >= 0) {
|
|
732
|
+
const iend = Buffer.from([0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]);
|
|
733
|
+
const end = documentBuf.indexOf(iend, idx);
|
|
734
|
+
if (end > idx) return documentBuf.subarray(idx, end + iend.length);
|
|
735
|
+
}
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function assertSafeBinarySchemaShape(schemaBuf: Buffer): void {
|
|
740
|
+
const bb = new ByteBuffer(
|
|
741
|
+
new Uint8Array(
|
|
742
|
+
schemaBuf.buffer,
|
|
743
|
+
schemaBuf.byteOffset,
|
|
744
|
+
schemaBuf.byteLength,
|
|
745
|
+
),
|
|
746
|
+
);
|
|
747
|
+
const definitionCount = bb.readVarUint();
|
|
748
|
+
if (definitionCount > 1_024) {
|
|
749
|
+
throw new Error(".fig schema has too many definitions.");
|
|
750
|
+
}
|
|
751
|
+
let totalFields = 0;
|
|
752
|
+
for (let index = 0; index < definitionCount; index += 1) {
|
|
753
|
+
bb.readString();
|
|
754
|
+
const kind = bb.readByte();
|
|
755
|
+
if (kind > 2)
|
|
756
|
+
throw new Error(".fig schema has an invalid definition kind.");
|
|
757
|
+
const fields = bb.readVarUint();
|
|
758
|
+
if (fields > 1_024) {
|
|
759
|
+
throw new Error(".fig schema definition has too many fields.");
|
|
760
|
+
}
|
|
761
|
+
totalFields += fields;
|
|
762
|
+
if (totalFields > 20_000) {
|
|
763
|
+
throw new Error(".fig schema has too many fields.");
|
|
764
|
+
}
|
|
765
|
+
for (let fieldIndex = 0; fieldIndex < fields; fieldIndex += 1) {
|
|
766
|
+
bb.readString();
|
|
767
|
+
bb.readVarInt();
|
|
768
|
+
bb.readByte();
|
|
769
|
+
bb.readVarUint();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Handles both modern fig-kiwi files and legacy zip-format archives.
|
|
775
|
+
// `document` is null if kiwi decoding failed.
|
|
776
|
+
export function decodeFig(file: Buffer): DecodedFig {
|
|
777
|
+
if (file.length > MAX_FIG_FILE_BYTES) {
|
|
778
|
+
throw new Error(".fig file is too large (max 50 MB).");
|
|
779
|
+
}
|
|
780
|
+
if (isZip(file)) {
|
|
781
|
+
const entries = readZip(file);
|
|
782
|
+
const canvasEntry = entries.find((e) => e.name === "canvas.fig");
|
|
783
|
+
const imageEntries = entries.filter((e) => e.name.startsWith("images/"));
|
|
784
|
+
|
|
785
|
+
let document: unknown = null;
|
|
786
|
+
let version: number | undefined;
|
|
787
|
+
let extraBlobs: Buffer[] = [];
|
|
788
|
+
if (!canvasEntry) throw new Error(".fig zip is missing canvas.fig.");
|
|
789
|
+
const inner = decodeKiwiContainer(canvasEntry.data);
|
|
790
|
+
version = inner.version;
|
|
791
|
+
extraBlobs = inner.blobs;
|
|
792
|
+
document = decodeKiwiDocument(inner.schema, inner.document);
|
|
793
|
+
|
|
794
|
+
const images: DecodedFigImage[] = [];
|
|
795
|
+
const seen = new Set<string>();
|
|
796
|
+
for (const e of imageEntries) {
|
|
797
|
+
const ext = detectImageExt(e.data) || "bin";
|
|
798
|
+
if (ext === "bin") continue;
|
|
799
|
+
const hash = sha1(e.data);
|
|
800
|
+
if (seen.has(hash)) continue;
|
|
801
|
+
seen.add(hash);
|
|
802
|
+
images.push({ hash, ext, bytes: e.data });
|
|
803
|
+
}
|
|
804
|
+
for (const img of collectImagesFromBlobs(extraBlobs)) {
|
|
805
|
+
if (seen.has(img.hash)) continue;
|
|
806
|
+
seen.add(img.hash);
|
|
807
|
+
images.push(img);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const thumbnailEntry = entries.find((e) => e.name === "thumbnail.png");
|
|
811
|
+
return {
|
|
812
|
+
format: "zip",
|
|
813
|
+
version,
|
|
814
|
+
document,
|
|
815
|
+
images,
|
|
816
|
+
thumbnail: thumbnailEntry?.data ?? null,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const decoded = decodeKiwiContainer(file);
|
|
821
|
+
const document = decodeKiwiDocument(decoded.schema, decoded.document);
|
|
822
|
+
const images = collectImagesFromBlobs(decoded.blobs);
|
|
823
|
+
const thumbnail = findThumbnail(decoded.document, decoded.blobs);
|
|
824
|
+
return {
|
|
825
|
+
format: "kiwi",
|
|
826
|
+
version: decoded.version,
|
|
827
|
+
document,
|
|
828
|
+
images,
|
|
829
|
+
thumbnail,
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
export function buildImageMap(images: DecodedFigImage[]): Map<string, string> {
|
|
834
|
+
const map = new Map<string, string>();
|
|
835
|
+
for (const img of images) {
|
|
836
|
+
map.set(img.hash, `${img.hash}.${img.ext}`);
|
|
837
|
+
}
|
|
838
|
+
return map;
|
|
839
|
+
}
|