@agent-native/core 0.92.9 → 0.92.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +29 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +181 -15
- package/corpus/core/src/agent/run-manager.ts +31 -4
- package/corpus/core/src/agent/run-store.ts +80 -0
- package/corpus/core/src/cli/design-connect.ts +433 -151
- package/corpus/core/src/cli/skills.ts +14 -0
- package/corpus/core/src/client/AssistantChat.tsx +25 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +40 -6
- package/corpus/core/src/client/agent-chat.ts +165 -1
- package/corpus/core/src/client/chat/index.ts +9 -1
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
- package/corpus/core/src/client/index.ts +5 -0
- package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
- package/corpus/core/src/collab/awareness-store.ts +23 -12
- package/corpus/core/src/collab/client.ts +16 -5
- package/corpus/core/src/collab/index.ts +4 -0
- package/corpus/core/src/collab/recent-edits.ts +65 -2
- package/corpus/core/src/deploy/build.ts +133 -5
- package/corpus/core/src/index.browser.ts +1 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
- package/corpus/core/src/server/builder-design-systems.ts +30 -4
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
- package/corpus/templates/clips/AGENTS.md +4 -0
- package/corpus/templates/clips/actions/create-recording.ts +2 -0
- package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
- package/corpus/templates/clips/actions/request-transcript.ts +102 -33
- package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +48 -4
- package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
- package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
- package/corpus/templates/clips/app/routes/record.tsx +17 -4
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
- package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +12 -2
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
- package/corpus/templates/clips/desktop/src/styles.css +6 -3
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
- package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
- package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
- package/corpus/templates/clips/shared/recording-core.ts +12 -0
- package/corpus/templates/content/AGENTS.md +7 -6
- package/corpus/templates/content/actions/_document-flush.ts +132 -17
- package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
- package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
- package/corpus/templates/content/actions/link-notion-page.ts +2 -0
- package/corpus/templates/content/actions/pull-document.ts +5 -7
- package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
- package/corpus/templates/content/actions/push-notion-page.ts +10 -0
- package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
- package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
- package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/content/app/i18n-data.ts +18 -0
- package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
- package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
- package/corpus/templates/design/AGENTS.md +45 -10
- package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
- package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
- package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
- package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
- package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
- package/corpus/templates/design/actions/generate-design.ts +79 -12
- package/corpus/templates/design/actions/generate-screens.ts +57 -12
- package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
- package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
- package/corpus/templates/design/actions/import-design-source.ts +4 -31
- package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
- package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
- package/corpus/templates/design/actions/list-design-components.ts +104 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
- package/corpus/templates/design/actions/open-component-source.ts +2 -4
- package/corpus/templates/design/actions/present-design-variants.ts +412 -16
- package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
- package/corpus/templates/design/actions/run-design-audit.ts +25 -3
- package/corpus/templates/design/actions/show-design-questions.ts +10 -0
- package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
- package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
- package/corpus/templates/design/actions/write-local-file.ts +25 -1
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
- package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
- package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
- package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
- package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
- package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
- package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
- package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
- package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
- package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
- package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
- package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
- package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
- package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
- package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
- package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
- package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
- package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
- package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
- package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
- package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
- package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
- package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
- package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
- package/corpus/templates/design/app/components/design/types.ts +33 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
- package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
- package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
- package/corpus/templates/design/app/i18n-data.ts +1217 -10
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
- package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
- package/corpus/templates/design/app/lib/design-import.ts +24 -0
- package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
- package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
- package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
- package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
- package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
- package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +3 -6
- package/corpus/templates/design/e2e/helpers.ts +27 -6
- package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
- package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
- package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
- package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
- package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
- package/corpus/templates/design/server/plugins/db.ts +41 -0
- package/corpus/templates/design/server/source-workspace.ts +64 -0
- package/corpus/templates/design/shared/board-file.ts +13 -2
- package/corpus/templates/design/shared/canvas-math.ts +231 -33
- package/corpus/templates/design/shared/code-layer.ts +122 -8
- package/corpus/templates/design/shared/component-library.ts +161 -0
- package/corpus/templates/design/shared/db-conflict.ts +27 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
- package/corpus/templates/design/shared/figma-url.ts +149 -0
- package/corpus/templates/design/shared/pen-path.ts +16 -7
- package/corpus/templates/design/shared/source-mode.ts +1 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
- package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
- package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
- package/dist/agent/production-agent.d.ts +79 -6
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +161 -14
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +29 -5
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +46 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +61 -0
- package/dist/agent/run-store.js.map +1 -1
- package/dist/cli/design-connect.d.ts +6 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +336 -139
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +14 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +19 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +38 -18
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +60 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +122 -1
- package/dist/client/agent-chat.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/extensions/EmbeddedExtension.d.ts +12 -0
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +36 -2
- package/dist/client/extensions/EmbeddedExtension.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/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
- package/dist/collab/awareness-store.d.ts +5 -0
- package/dist/collab/awareness-store.d.ts.map +1 -1
- package/dist/collab/awareness-store.js +19 -12
- package/dist/collab/awareness-store.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +13 -5
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/index.d.ts +1 -0
- package/dist/collab/index.d.ts.map +1 -1
- package/dist/collab/index.js +1 -0
- package/dist/collab/index.js.map +1 -1
- package/dist/collab/recent-edits.d.ts +3 -1
- package/dist/collab/recent-edits.d.ts.map +1 -1
- package/dist/collab/recent-edits.js +57 -2
- package/dist/collab/recent-edits.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +24 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +102 -5
- package/dist/deploy/build.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +87 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/builder-design-systems.d.ts +15 -0
- package/dist/server/builder-design-systems.d.ts.map +1 -1
- package/dist/server/builder-design-systems.js +9 -4
- package/dist/server/builder-design-systems.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
import fsSync from "node:fs";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
|
+
import type { FileHandle } from "node:fs/promises";
|
|
5
6
|
import http, {
|
|
6
7
|
type IncomingMessage,
|
|
7
8
|
type Server,
|
|
@@ -123,6 +124,12 @@ export interface DesignConnectBridge {
|
|
|
123
124
|
* bridge-registration endpoints. Safe to hand to the Design browser, but
|
|
124
125
|
* never accepted by filesystem endpoints. */
|
|
125
126
|
previewToken: string;
|
|
127
|
+
/** Random id minted fresh each time this bridge process boots. Also
|
|
128
|
+
* returned by `/health`, `/live-edit-bridge`, and the "unknown bridge key"
|
|
129
|
+
* 409 from `/live-edit` — a client can compare it across those responses
|
|
130
|
+
* to tell a restarted bridge process apart from a genuine registration
|
|
131
|
+
* bug. See the `bridgeInstanceId` doc comment in startDesignConnectBridge. */
|
|
132
|
+
bridgeInstanceId: string;
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
export interface DesignConnectBridgeOptions {
|
|
@@ -679,11 +686,11 @@ export async function prepareDesignConnectManifest(
|
|
|
679
686
|
reason:
|
|
680
687
|
operation === "resolveNodeToFile"
|
|
681
688
|
? // resolveNodeToFile maps a runtime DOM node id (from the editor's
|
|
682
|
-
// 'select' payload) to { file, line, component } provenance.
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
//
|
|
686
|
-
"
|
|
689
|
+
// 'select' payload) to { file, line, component } provenance.
|
|
690
|
+
// React development builds expose jsxDEV call sites through the
|
|
691
|
+
// Fiber debug stack; other runtimes/builds can emit explicit DOM
|
|
692
|
+
// provenance attributes — see the help text below.
|
|
693
|
+
"React development builds resolve jsxDEV call sites automatically; other runtimes can emit data-source-file / data-source-line / data-component-name attributes."
|
|
687
694
|
: undefined,
|
|
688
695
|
})),
|
|
689
696
|
};
|
|
@@ -786,9 +793,11 @@ function sendBytes(
|
|
|
786
793
|
statusCode: number,
|
|
787
794
|
body: Buffer,
|
|
788
795
|
headers: Headers,
|
|
796
|
+
contentLength = body.length,
|
|
789
797
|
) {
|
|
790
798
|
const responseHeaders: Record<string, string> = {
|
|
791
799
|
...bridgeCorsHeaders(res),
|
|
800
|
+
"content-length": String(contentLength),
|
|
792
801
|
};
|
|
793
802
|
for (const name of [
|
|
794
803
|
"content-type",
|
|
@@ -833,6 +842,25 @@ function readHeader(req: IncomingMessage, name: string): string {
|
|
|
833
842
|
return typeof value === "string" ? value : "";
|
|
834
843
|
}
|
|
835
844
|
|
|
845
|
+
/**
|
|
846
|
+
* Preserve only the browser request metadata a local dev server needs to
|
|
847
|
+
* classify Vite source-module and stylesheet requests. Agent Native's dev
|
|
848
|
+
* gateway intentionally varies source-file handling by `Sec-Fetch-Dest`; if
|
|
849
|
+
* the bridge drops it, React Router module URLs such as `/app/root.tsx` fall
|
|
850
|
+
* through to Nitro and 404. Keep this allowlist narrow: cookies, authorization,
|
|
851
|
+
* bridge tokens, origins, and referrers must never be forwarded upstream.
|
|
852
|
+
*/
|
|
853
|
+
function previewProxyRequestHeaders(
|
|
854
|
+
req: IncomingMessage,
|
|
855
|
+
): Record<string, string> {
|
|
856
|
+
const headers: Record<string, string> = {};
|
|
857
|
+
for (const name of ["accept", "sec-fetch-dest"] as const) {
|
|
858
|
+
const value = readHeader(req, name);
|
|
859
|
+
if (value) headers[name] = value;
|
|
860
|
+
}
|
|
861
|
+
return headers;
|
|
862
|
+
}
|
|
863
|
+
|
|
836
864
|
function resolvePreviewSnapshotUrl(
|
|
837
865
|
devServerUrl: string,
|
|
838
866
|
rawUrl: string | null,
|
|
@@ -917,6 +945,7 @@ async function fetchPreviewSnapshot(
|
|
|
917
945
|
async function fetchPreviewProxyResource(
|
|
918
946
|
devServerUrl: string,
|
|
919
947
|
targetUrl: string,
|
|
948
|
+
requestHeaders: Record<string, string> = {},
|
|
920
949
|
redirects = 0,
|
|
921
950
|
): Promise<{
|
|
922
951
|
url: string;
|
|
@@ -934,6 +963,7 @@ async function fetchPreviewProxyResource(
|
|
|
934
963
|
method: "GET",
|
|
935
964
|
redirect: "manual",
|
|
936
965
|
signal: controller.signal,
|
|
966
|
+
headers: requestHeaders,
|
|
937
967
|
});
|
|
938
968
|
const location = response.headers.get("location");
|
|
939
969
|
if (location && response.status >= 300 && response.status < 400) {
|
|
@@ -945,6 +975,7 @@ async function fetchPreviewProxyResource(
|
|
|
945
975
|
return fetchPreviewProxyResource(
|
|
946
976
|
devServerUrl,
|
|
947
977
|
redirected.toString(),
|
|
978
|
+
requestHeaders,
|
|
948
979
|
redirects + 1,
|
|
949
980
|
);
|
|
950
981
|
}
|
|
@@ -1101,6 +1132,236 @@ async function assertPathInside(
|
|
|
1101
1132
|
}
|
|
1102
1133
|
}
|
|
1103
1134
|
|
|
1135
|
+
interface SafeBridgeFileTarget {
|
|
1136
|
+
absolutePath: string;
|
|
1137
|
+
canonicalPath: string;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* Resolve a bridge file to a stable lock key without following a leaf
|
|
1142
|
+
* symlink. Missing parent directories are represented beneath their nearest
|
|
1143
|
+
* existing real ancestor, so aliases through in-root directory symlinks share
|
|
1144
|
+
* one mutex while first-time file creation remains supported.
|
|
1145
|
+
*/
|
|
1146
|
+
async function resolveSafeBridgeFileTarget(
|
|
1147
|
+
rootPath: string,
|
|
1148
|
+
targetPath: string,
|
|
1149
|
+
): Promise<SafeBridgeFileTarget> {
|
|
1150
|
+
await assertPathInside(rootPath, targetPath);
|
|
1151
|
+
const resolvedRoot = await fs.realpath(rootPath);
|
|
1152
|
+
const absolutePath = path.resolve(rootPath, targetPath);
|
|
1153
|
+
let existingAncestor = path.dirname(absolutePath);
|
|
1154
|
+
let resolvedAncestor: string | null = null;
|
|
1155
|
+
for (let depth = 0; depth < 32; depth += 1) {
|
|
1156
|
+
try {
|
|
1157
|
+
resolvedAncestor = await fs.realpath(existingAncestor);
|
|
1158
|
+
break;
|
|
1159
|
+
} catch {
|
|
1160
|
+
const parent = path.dirname(existingAncestor);
|
|
1161
|
+
if (parent === existingAncestor) break;
|
|
1162
|
+
existingAncestor = parent;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (!resolvedAncestor) {
|
|
1166
|
+
throw new Error(`Cannot resolve parent directory: ${absolutePath}`);
|
|
1167
|
+
}
|
|
1168
|
+
// Existing regular files get their own realpath as the lock key. Besides
|
|
1169
|
+
// resolving in-root directory aliases, this canonicalizes case on the
|
|
1170
|
+
// default macOS filesystem so `Button.tsx` and `button.tsx` cannot acquire
|
|
1171
|
+
// separate mutexes for the same inode. assertPathInside already rejected a
|
|
1172
|
+
// symlink leaf before this point.
|
|
1173
|
+
const canonicalPath =
|
|
1174
|
+
(await fs.realpath(absolutePath).catch(() => null)) ??
|
|
1175
|
+
path.resolve(
|
|
1176
|
+
resolvedAncestor,
|
|
1177
|
+
path.relative(existingAncestor, absolutePath),
|
|
1178
|
+
);
|
|
1179
|
+
if (
|
|
1180
|
+
canonicalPath !== resolvedRoot &&
|
|
1181
|
+
!canonicalPath.startsWith(resolvedRoot + path.sep)
|
|
1182
|
+
) {
|
|
1183
|
+
throw new Error("Path traversal detected: resolved target is outside root");
|
|
1184
|
+
}
|
|
1185
|
+
return { absolutePath, canonicalPath };
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const bridgeWriteLocks = new Map<string, Promise<void>>();
|
|
1189
|
+
|
|
1190
|
+
/** Serialize read-check-write sequences for one canonical local file. */
|
|
1191
|
+
async function withBridgeWriteLock<T>(
|
|
1192
|
+
canonicalPath: string,
|
|
1193
|
+
work: () => Promise<T>,
|
|
1194
|
+
): Promise<T> {
|
|
1195
|
+
const previous = bridgeWriteLocks.get(canonicalPath) ?? Promise.resolve();
|
|
1196
|
+
let release!: () => void;
|
|
1197
|
+
const gate = new Promise<void>((resolve) => {
|
|
1198
|
+
release = resolve;
|
|
1199
|
+
});
|
|
1200
|
+
const queued = previous.catch(() => undefined).then(() => gate);
|
|
1201
|
+
bridgeWriteLocks.set(canonicalPath, queued);
|
|
1202
|
+
await previous.catch(() => undefined);
|
|
1203
|
+
try {
|
|
1204
|
+
return await work();
|
|
1205
|
+
} finally {
|
|
1206
|
+
release();
|
|
1207
|
+
if (bridgeWriteLocks.get(canonicalPath) === queued) {
|
|
1208
|
+
bridgeWriteLocks.delete(canonicalPath);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
interface BridgeFileSnapshot {
|
|
1214
|
+
content: string;
|
|
1215
|
+
versionHash: string;
|
|
1216
|
+
mode: number;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function contentVersionHash(content: string | Buffer): string {
|
|
1220
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/** Read an existing regular file without ever following a leaf symlink. */
|
|
1224
|
+
async function readBridgeFileSnapshot(
|
|
1225
|
+
absolutePath: string,
|
|
1226
|
+
): Promise<BridgeFileSnapshot | null> {
|
|
1227
|
+
const noFollow = fsSync.constants.O_NOFOLLOW ?? 0;
|
|
1228
|
+
let handle: FileHandle | null = null;
|
|
1229
|
+
try {
|
|
1230
|
+
handle = await fs.open(absolutePath, fsSync.constants.O_RDONLY | noFollow);
|
|
1231
|
+
const stat = await handle.stat();
|
|
1232
|
+
if (!stat.isFile()) {
|
|
1233
|
+
throw new Error(`Bridge target is not a regular file: ${absolutePath}`);
|
|
1234
|
+
}
|
|
1235
|
+
const bytes = await handle.readFile();
|
|
1236
|
+
return {
|
|
1237
|
+
content: bytes.toString("utf8"),
|
|
1238
|
+
versionHash: contentVersionHash(bytes),
|
|
1239
|
+
mode: stat.mode & 0o777,
|
|
1240
|
+
};
|
|
1241
|
+
} catch (error: unknown) {
|
|
1242
|
+
const code =
|
|
1243
|
+
error instanceof Error && "code" in error
|
|
1244
|
+
? (error as NodeJS.ErrnoException).code
|
|
1245
|
+
: undefined;
|
|
1246
|
+
if (code === "ENOENT") return null;
|
|
1247
|
+
throw error;
|
|
1248
|
+
} finally {
|
|
1249
|
+
await handle?.close().catch(() => undefined);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
class BridgeVersionConflictError extends Error {
|
|
1254
|
+
constructor(readonly currentVersionHash?: string) {
|
|
1255
|
+
super("version conflict");
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
class BridgePreconditionRequiredError extends Error {
|
|
1260
|
+
constructor() {
|
|
1261
|
+
super("expectedVersionHash is required");
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function assertExpectedBridgeVersion(
|
|
1266
|
+
expectedVersionHash: string | undefined,
|
|
1267
|
+
currentVersionHash: string | undefined,
|
|
1268
|
+
requireExpectedVersionHash = false,
|
|
1269
|
+
): void {
|
|
1270
|
+
// Preserve compatibility: callers that omit a hash retain the existing
|
|
1271
|
+
// last-write-wins behavior, including creation. Compiled-source callers opt
|
|
1272
|
+
// into exact compare-and-swap by sending the hash returned by read-file.
|
|
1273
|
+
if (requireExpectedVersionHash && expectedVersionHash === undefined) {
|
|
1274
|
+
throw new BridgePreconditionRequiredError();
|
|
1275
|
+
}
|
|
1276
|
+
if (
|
|
1277
|
+
expectedVersionHash !== undefined &&
|
|
1278
|
+
(currentVersionHash !== undefined
|
|
1279
|
+
? currentVersionHash !== expectedVersionHash
|
|
1280
|
+
: requireExpectedVersionHash)
|
|
1281
|
+
) {
|
|
1282
|
+
throw new BridgeVersionConflictError(currentVersionHash);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* Replace a file durably without exposing a partial write: create an
|
|
1288
|
+
* O_EXCL/O_NOFOLLOW temp sibling, fsync it, revalidate confinement and the
|
|
1289
|
+
* expected content hash, rename atomically, then fsync the parent directory.
|
|
1290
|
+
*/
|
|
1291
|
+
async function atomicWriteBridgeFile(args: {
|
|
1292
|
+
rootPath: string;
|
|
1293
|
+
relPath: string;
|
|
1294
|
+
lockedCanonicalPath: string;
|
|
1295
|
+
content: string;
|
|
1296
|
+
expectedVersionHash?: string;
|
|
1297
|
+
requireExpectedVersionHash?: boolean;
|
|
1298
|
+
originalMode?: number;
|
|
1299
|
+
}): Promise<string> {
|
|
1300
|
+
const parent = path.dirname(path.resolve(args.rootPath, args.relPath));
|
|
1301
|
+
await fs.mkdir(parent, { recursive: true });
|
|
1302
|
+
const revalidated = await resolveSafeBridgeFileTarget(
|
|
1303
|
+
args.rootPath,
|
|
1304
|
+
args.relPath,
|
|
1305
|
+
);
|
|
1306
|
+
if (revalidated.canonicalPath !== args.lockedCanonicalPath) {
|
|
1307
|
+
throw new Error("Bridge target changed while waiting for the write lock");
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const basename = path.basename(revalidated.absolutePath);
|
|
1311
|
+
const tempPath = path.join(
|
|
1312
|
+
parent,
|
|
1313
|
+
`.${basename}.agent-native-${process.pid}-${crypto.randomBytes(8).toString("hex")}.tmp`,
|
|
1314
|
+
);
|
|
1315
|
+
const noFollow = fsSync.constants.O_NOFOLLOW ?? 0;
|
|
1316
|
+
let tempHandle: FileHandle | null = null;
|
|
1317
|
+
try {
|
|
1318
|
+
tempHandle = await fs.open(
|
|
1319
|
+
tempPath,
|
|
1320
|
+
fsSync.constants.O_CREAT |
|
|
1321
|
+
fsSync.constants.O_EXCL |
|
|
1322
|
+
fsSync.constants.O_WRONLY |
|
|
1323
|
+
noFollow,
|
|
1324
|
+
args.originalMode ?? 0o666,
|
|
1325
|
+
);
|
|
1326
|
+
await tempHandle.writeFile(args.content, "utf8");
|
|
1327
|
+
await tempHandle.sync();
|
|
1328
|
+
await tempHandle.close();
|
|
1329
|
+
tempHandle = null;
|
|
1330
|
+
|
|
1331
|
+
// Re-check after the potentially slow temp write/fsync. This catches a
|
|
1332
|
+
// parent/leaf symlink swap and an external content edit before rename.
|
|
1333
|
+
const beforeRenameTarget = await resolveSafeBridgeFileTarget(
|
|
1334
|
+
args.rootPath,
|
|
1335
|
+
args.relPath,
|
|
1336
|
+
);
|
|
1337
|
+
if (beforeRenameTarget.canonicalPath !== args.lockedCanonicalPath) {
|
|
1338
|
+
throw new Error("Bridge target changed during atomic write");
|
|
1339
|
+
}
|
|
1340
|
+
const current = await readBridgeFileSnapshot(
|
|
1341
|
+
beforeRenameTarget.absolutePath,
|
|
1342
|
+
);
|
|
1343
|
+
assertExpectedBridgeVersion(
|
|
1344
|
+
args.expectedVersionHash,
|
|
1345
|
+
current?.versionHash,
|
|
1346
|
+
args.requireExpectedVersionHash,
|
|
1347
|
+
);
|
|
1348
|
+
|
|
1349
|
+
await fs.rename(tempPath, beforeRenameTarget.absolutePath);
|
|
1350
|
+
const directoryHandle = await fs.open(parent, "r").catch(() => null);
|
|
1351
|
+
if (directoryHandle) {
|
|
1352
|
+
try {
|
|
1353
|
+
await directoryHandle.sync();
|
|
1354
|
+
} finally {
|
|
1355
|
+
await directoryHandle.close();
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
return contentVersionHash(args.content);
|
|
1359
|
+
} finally {
|
|
1360
|
+
await tempHandle?.close().catch(() => undefined);
|
|
1361
|
+
await fs.rm(tempPath, { force: true }).catch(() => undefined);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1104
1365
|
/** Allowed file extensions for write/apply-edit operations. */
|
|
1105
1366
|
const ALLOWED_WRITE_EXTENSIONS = new Set([
|
|
1106
1367
|
".html",
|
|
@@ -1166,21 +1427,6 @@ function assertNotBlockedSecretPath(relPath: string): void {
|
|
|
1166
1427
|
}
|
|
1167
1428
|
}
|
|
1168
1429
|
|
|
1169
|
-
/**
|
|
1170
|
-
* Compute a cheap, stable version identifier for a file from its stat: mtime
|
|
1171
|
-
* plus size. Not a content hash — it is only meant to detect "did this file
|
|
1172
|
-
* change on disk since the caller last read it", the same tradeoff the inline
|
|
1173
|
-
* (SQL-backed) workspace provider's versionHash already makes. Returns
|
|
1174
|
-
* undefined when the file does not exist (new-file case).
|
|
1175
|
-
*/
|
|
1176
|
-
async function computeVersionHash(
|
|
1177
|
-
absolutePath: string,
|
|
1178
|
-
): Promise<string | undefined> {
|
|
1179
|
-
const stat = await fs.stat(absolutePath).catch(() => null);
|
|
1180
|
-
if (!stat) return undefined;
|
|
1181
|
-
return `${stat.mtimeMs}-${stat.size}`;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
1430
|
function countOccurrences(haystack: string, needle: string): number {
|
|
1185
1431
|
if (!needle) return 0;
|
|
1186
1432
|
let count = 0;
|
|
@@ -1516,6 +1762,18 @@ export async function startDesignConnectBridge(
|
|
|
1516
1762
|
// other and boot a frame with another frame's identity. Keep keyed scripts
|
|
1517
1763
|
// for modern clients while retaining the unkeyed slot for older clients.
|
|
1518
1764
|
const liveEditBridgeScripts = new Map<string, string>();
|
|
1765
|
+
// Identifies THIS bridge process's in-memory registry, minted fresh every
|
|
1766
|
+
// time the bridge boots. `liveEditBridgeScripts` above only lives in
|
|
1767
|
+
// process memory, so a bridge restart (crash, machine sleep/wake, manual
|
|
1768
|
+
// restart) silently empties it: any screen that registered a bridgeKey
|
|
1769
|
+
// before the restart now gets a 409 "unknown bridge key" from `/live-edit`
|
|
1770
|
+
// even though nothing about that screen actually changed. Echoing this id
|
|
1771
|
+
// on both the registration response and the 409 lets a client tell the two
|
|
1772
|
+
// cases apart — "this exact process never saw my key" (stale/typo, id
|
|
1773
|
+
// matches what it already has cached) vs. "the process restarted since I
|
|
1774
|
+
// registered" (id changed, safe to transparently re-POST `/live-edit-bridge`
|
|
1775
|
+
// and retry) — instead of guessing from the error text or retrying forever.
|
|
1776
|
+
const bridgeInstanceId = crypto.randomBytes(16).toString("hex");
|
|
1519
1777
|
|
|
1520
1778
|
const server = http.createServer(
|
|
1521
1779
|
(req: IncomingMessage, res: ServerResponse) => {
|
|
@@ -1574,6 +1832,7 @@ export async function startDesignConnectBridge(
|
|
|
1574
1832
|
ok: true,
|
|
1575
1833
|
source: manifest.source,
|
|
1576
1834
|
appFingerprint: designConnectAppFingerprint(manifest),
|
|
1835
|
+
bridgeInstanceId,
|
|
1577
1836
|
});
|
|
1578
1837
|
return;
|
|
1579
1838
|
}
|
|
@@ -1628,6 +1887,7 @@ export async function startDesignConnectBridge(
|
|
|
1628
1887
|
}
|
|
1629
1888
|
sendJson(res, 200, {
|
|
1630
1889
|
ok: true,
|
|
1890
|
+
bridgeInstanceId,
|
|
1631
1891
|
...(bridgeKey ? { bridgeKey } : {}),
|
|
1632
1892
|
});
|
|
1633
1893
|
} catch (err: unknown) {
|
|
@@ -1668,8 +1928,17 @@ export async function startDesignConnectBridge(
|
|
|
1668
1928
|
requestedBridgeKey &&
|
|
1669
1929
|
!editorBridgeScript
|
|
1670
1930
|
) {
|
|
1931
|
+
// Machine-readable `code` + echoed `bridgeKey`/`bridgeInstanceId`
|
|
1932
|
+
// let a client distinguish "this bridge process restarted since
|
|
1933
|
+
// I last registered — safe to silently re-POST
|
|
1934
|
+
// /live-edit-bridge and retry" from a genuine caller bug,
|
|
1935
|
+
// instead of string-matching `error` (see bridgeInstanceId's
|
|
1936
|
+
// doc comment above for the full rationale).
|
|
1671
1937
|
sendJson(res, 409, {
|
|
1672
1938
|
ok: false,
|
|
1939
|
+
code: "unknown-bridge-key",
|
|
1940
|
+
bridgeKey: requestedBridgeKey,
|
|
1941
|
+
bridgeInstanceId,
|
|
1673
1942
|
error:
|
|
1674
1943
|
"The requested live-edit bridge script is not registered. Reload the Design frame to register it again.",
|
|
1675
1944
|
});
|
|
@@ -1794,174 +2063,185 @@ export async function startDesignConnectBridge(
|
|
|
1794
2063
|
return;
|
|
1795
2064
|
}
|
|
1796
2065
|
|
|
1797
|
-
await assertPathInside(manifest.rootPath, relPath);
|
|
1798
2066
|
assertNotBlockedSecretPath(relPath);
|
|
1799
|
-
const
|
|
2067
|
+
const initialTarget = await resolveSafeBridgeFileTarget(
|
|
2068
|
+
manifest.rootPath,
|
|
2069
|
+
relPath,
|
|
2070
|
+
);
|
|
1800
2071
|
|
|
1801
2072
|
if (pathname === "/read-file") {
|
|
1802
2073
|
// Read-file: no extension restriction (agents need to read any
|
|
1803
2074
|
// non-secret file), but the secret-path blocklist above still
|
|
1804
2075
|
// applies to .env*, *.pem, *.key, id_rsa*, and anything under .git/.
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
err instanceof Error &&
|
|
1811
|
-
"code" in err &&
|
|
1812
|
-
(err as NodeJS.ErrnoException).code;
|
|
1813
|
-
if (code === "ENOENT") {
|
|
1814
|
-
sendJson(res, 404, { ok: false, error: "file not found" });
|
|
1815
|
-
} else {
|
|
1816
|
-
sendJson(res, 500, {
|
|
1817
|
-
ok: false,
|
|
1818
|
-
error: `read failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1819
|
-
});
|
|
1820
|
-
}
|
|
2076
|
+
const snapshot = await readBridgeFileSnapshot(
|
|
2077
|
+
initialTarget.absolutePath,
|
|
2078
|
+
);
|
|
2079
|
+
if (!snapshot) {
|
|
2080
|
+
sendJson(res, 404, { ok: false, error: "file not found" });
|
|
1821
2081
|
return;
|
|
1822
2082
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
2083
|
+
sendJson(res, 200, {
|
|
2084
|
+
ok: true,
|
|
2085
|
+
content: snapshot.content,
|
|
2086
|
+
versionHash: snapshot.versionHash,
|
|
2087
|
+
});
|
|
1825
2088
|
return;
|
|
1826
2089
|
}
|
|
1827
2090
|
|
|
1828
2091
|
// write-file and apply-edit only allow known text/code extensions.
|
|
1829
2092
|
assertAllowedExtension(relPath);
|
|
1830
2093
|
|
|
1831
|
-
// Optional optimistic-concurrency check: when the caller supplies
|
|
1832
|
-
// expectedVersionHash, compare it against the file's CURRENT hash
|
|
1833
|
-
// before writing. A missing file is treated as no-conflict (new
|
|
1834
|
-
// file case) so first-time writes always succeed.
|
|
1835
2094
|
const expectedVersionHash =
|
|
1836
2095
|
typeof body["expectedVersionHash"] === "string"
|
|
1837
2096
|
? body["expectedVersionHash"]
|
|
1838
2097
|
: undefined;
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2098
|
+
const requireExpectedVersionHash =
|
|
2099
|
+
body["requireExpectedVersionHash"] === true;
|
|
2100
|
+
await withBridgeWriteLock(initialTarget.canonicalPath, async () => {
|
|
2101
|
+
const lockedTarget = await resolveSafeBridgeFileTarget(
|
|
2102
|
+
manifest.rootPath,
|
|
2103
|
+
relPath,
|
|
2104
|
+
);
|
|
2105
|
+
if (lockedTarget.canonicalPath !== initialTarget.canonicalPath) {
|
|
2106
|
+
throw new Error(
|
|
2107
|
+
"Bridge target changed while waiting for the write lock",
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
2110
|
+
const existing = await readBridgeFileSnapshot(
|
|
2111
|
+
lockedTarget.absolutePath,
|
|
2112
|
+
);
|
|
2113
|
+
assertExpectedBridgeVersion(
|
|
2114
|
+
expectedVersionHash,
|
|
2115
|
+
existing?.versionHash,
|
|
2116
|
+
requireExpectedVersionHash,
|
|
2117
|
+
);
|
|
2118
|
+
|
|
2119
|
+
if (pathname === "/write-file") {
|
|
2120
|
+
const content =
|
|
2121
|
+
typeof body["content"] === "string"
|
|
2122
|
+
? body["content"]
|
|
2123
|
+
: undefined;
|
|
2124
|
+
if (content === undefined) {
|
|
2125
|
+
sendJson(res, 400, {
|
|
2126
|
+
ok: false,
|
|
2127
|
+
error: "content is required for write-file",
|
|
2128
|
+
});
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
const versionHash = await atomicWriteBridgeFile({
|
|
2132
|
+
rootPath: manifest.rootPath,
|
|
2133
|
+
relPath,
|
|
2134
|
+
lockedCanonicalPath: initialTarget.canonicalPath,
|
|
2135
|
+
content,
|
|
2136
|
+
expectedVersionHash,
|
|
2137
|
+
requireExpectedVersionHash,
|
|
2138
|
+
originalMode: existing?.mode,
|
|
2139
|
+
});
|
|
2140
|
+
sendJson(res, 200, { ok: true, relPath, versionHash });
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// /apply-edit supports either full replace ({content}) or one
|
|
2145
|
+
// exact search-and-replace. Both stay within this file's lock.
|
|
2146
|
+
if (typeof body["content"] === "string") {
|
|
2147
|
+
const versionHash = await atomicWriteBridgeFile({
|
|
2148
|
+
rootPath: manifest.rootPath,
|
|
2149
|
+
relPath,
|
|
2150
|
+
lockedCanonicalPath: initialTarget.canonicalPath,
|
|
2151
|
+
content: body["content"],
|
|
2152
|
+
expectedVersionHash,
|
|
2153
|
+
requireExpectedVersionHash,
|
|
2154
|
+
originalMode: existing?.mode,
|
|
2155
|
+
});
|
|
2156
|
+
sendJson(res, 200, {
|
|
2157
|
+
ok: true,
|
|
2158
|
+
relPath,
|
|
2159
|
+
method: "replace",
|
|
2160
|
+
versionHash,
|
|
1849
2161
|
});
|
|
1850
2162
|
return;
|
|
1851
2163
|
}
|
|
1852
|
-
}
|
|
1853
2164
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
2165
|
+
const search =
|
|
2166
|
+
typeof body["search"] === "string" ? body["search"] : undefined;
|
|
2167
|
+
const replace =
|
|
2168
|
+
typeof body["replace"] === "string"
|
|
2169
|
+
? body["replace"]
|
|
1858
2170
|
: undefined;
|
|
1859
|
-
if (
|
|
2171
|
+
if (search === undefined || replace === undefined) {
|
|
1860
2172
|
sendJson(res, 400, {
|
|
1861
2173
|
ok: false,
|
|
1862
|
-
error:
|
|
2174
|
+
error:
|
|
2175
|
+
"apply-edit requires either {content} for a full replace, or {search, replace} for a patch",
|
|
1863
2176
|
});
|
|
1864
2177
|
return;
|
|
1865
2178
|
}
|
|
1866
|
-
|
|
1867
|
-
await fs.writeFile(absolutePath, content, "utf8");
|
|
1868
|
-
const versionHash = await computeVersionHash(absolutePath);
|
|
1869
|
-
sendJson(res, 200, { ok: true, relPath, versionHash });
|
|
1870
|
-
return;
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
// /apply-edit: supports either full replace ({content}) or
|
|
1874
|
-
// search-and-replace ({search, replace}).
|
|
1875
|
-
if (typeof body["content"] === "string") {
|
|
1876
|
-
// Full-file replace via apply-edit — same as write-file but keeps
|
|
1877
|
-
// the endpoint semantically separate for callers that want to
|
|
1878
|
-
// distinguish intent.
|
|
1879
|
-
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
1880
|
-
await fs.writeFile(
|
|
1881
|
-
absolutePath,
|
|
1882
|
-
body["content"] as string,
|
|
1883
|
-
"utf8",
|
|
1884
|
-
);
|
|
1885
|
-
const versionHash = await computeVersionHash(absolutePath);
|
|
1886
|
-
sendJson(res, 200, {
|
|
1887
|
-
ok: true,
|
|
1888
|
-
relPath,
|
|
1889
|
-
method: "replace",
|
|
1890
|
-
versionHash,
|
|
1891
|
-
});
|
|
1892
|
-
return;
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
const search =
|
|
1896
|
-
typeof body["search"] === "string" ? body["search"] : undefined;
|
|
1897
|
-
const replace =
|
|
1898
|
-
typeof body["replace"] === "string" ? body["replace"] : undefined;
|
|
1899
|
-
if (search === undefined || replace === undefined) {
|
|
1900
|
-
sendJson(res, 400, {
|
|
1901
|
-
ok: false,
|
|
1902
|
-
error:
|
|
1903
|
-
"apply-edit requires either {content} for a full replace, or {search, replace} for a patch",
|
|
1904
|
-
});
|
|
1905
|
-
return;
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
let existing: string;
|
|
1909
|
-
try {
|
|
1910
|
-
existing = await fs.readFile(absolutePath, "utf8");
|
|
1911
|
-
} catch (err: unknown) {
|
|
1912
|
-
const code =
|
|
1913
|
-
err instanceof Error &&
|
|
1914
|
-
"code" in err &&
|
|
1915
|
-
(err as NodeJS.ErrnoException).code;
|
|
1916
|
-
if (code === "ENOENT") {
|
|
2179
|
+
if (!existing) {
|
|
1917
2180
|
sendJson(res, 404, {
|
|
1918
2181
|
ok: false,
|
|
1919
2182
|
error: "file not found — use write-file to create new files",
|
|
1920
2183
|
});
|
|
1921
|
-
|
|
1922
|
-
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
if (search.length === 0) {
|
|
2187
|
+
sendJson(res, 400, {
|
|
1923
2188
|
ok: false,
|
|
1924
|
-
error:
|
|
2189
|
+
error: "search string must not be empty",
|
|
1925
2190
|
});
|
|
2191
|
+
return;
|
|
1926
2192
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2193
|
+
const occurrenceCount = countOccurrences(
|
|
2194
|
+
existing.content,
|
|
2195
|
+
search,
|
|
2196
|
+
);
|
|
2197
|
+
if (occurrenceCount === 0) {
|
|
2198
|
+
sendJson(res, 422, {
|
|
2199
|
+
ok: false,
|
|
2200
|
+
error: "search string not found in file",
|
|
2201
|
+
});
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
if (occurrenceCount > 1) {
|
|
2205
|
+
sendJson(res, 422, {
|
|
2206
|
+
ok: false,
|
|
2207
|
+
error:
|
|
2208
|
+
"search string is ambiguous; it appears more than once in the file",
|
|
2209
|
+
});
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
const updated = existing.content.replace(search, replace);
|
|
2213
|
+
const versionHash = await atomicWriteBridgeFile({
|
|
2214
|
+
rootPath: manifest.rootPath,
|
|
2215
|
+
relPath,
|
|
2216
|
+
lockedCanonicalPath: initialTarget.canonicalPath,
|
|
2217
|
+
content: updated,
|
|
2218
|
+
expectedVersionHash,
|
|
2219
|
+
requireExpectedVersionHash,
|
|
2220
|
+
originalMode: existing.mode,
|
|
1934
2221
|
});
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
2222
|
+
sendJson(res, 200, {
|
|
2223
|
+
ok: true,
|
|
2224
|
+
relPath,
|
|
2225
|
+
method: "patch",
|
|
2226
|
+
versionHash,
|
|
2227
|
+
});
|
|
2228
|
+
});
|
|
2229
|
+
} catch (err: unknown) {
|
|
2230
|
+
if (err instanceof BridgePreconditionRequiredError) {
|
|
2231
|
+
sendJson(res, 428, {
|
|
1941
2232
|
ok: false,
|
|
1942
|
-
error: "
|
|
2233
|
+
error: "expectedVersionHash is required",
|
|
1943
2234
|
});
|
|
1944
2235
|
return;
|
|
1945
2236
|
}
|
|
1946
|
-
if (
|
|
1947
|
-
sendJson(res,
|
|
2237
|
+
if (err instanceof BridgeVersionConflictError) {
|
|
2238
|
+
sendJson(res, 409, {
|
|
1948
2239
|
ok: false,
|
|
1949
|
-
error:
|
|
1950
|
-
|
|
2240
|
+
error: "version conflict",
|
|
2241
|
+
currentVersionHash: err.currentVersionHash,
|
|
1951
2242
|
});
|
|
1952
2243
|
return;
|
|
1953
2244
|
}
|
|
1954
|
-
|
|
1955
|
-
const updated = existing.replace(search, replace);
|
|
1956
|
-
await fs.writeFile(absolutePath, updated, "utf8");
|
|
1957
|
-
const versionHash = await computeVersionHash(absolutePath);
|
|
1958
|
-
sendJson(res, 200, {
|
|
1959
|
-
ok: true,
|
|
1960
|
-
relPath,
|
|
1961
|
-
method: "patch",
|
|
1962
|
-
versionHash,
|
|
1963
|
-
});
|
|
1964
|
-
} catch (err: unknown) {
|
|
1965
2245
|
sendJson(res, 500, {
|
|
1966
2246
|
ok: false,
|
|
1967
2247
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -1990,12 +2270,14 @@ export async function startDesignConnectBridge(
|
|
|
1990
2270
|
const proxied = await fetchPreviewProxyResource(
|
|
1991
2271
|
manifest.devServerUrl,
|
|
1992
2272
|
targetUrl,
|
|
2273
|
+
previewProxyRequestHeaders(req),
|
|
1993
2274
|
);
|
|
1994
2275
|
sendBytes(
|
|
1995
2276
|
res,
|
|
1996
2277
|
proxied.status >= 400 ? proxied.status : 200,
|
|
1997
2278
|
req.method === "HEAD" ? Buffer.alloc(0) : proxied.body,
|
|
1998
2279
|
proxied.headers,
|
|
2280
|
+
proxied.body.length,
|
|
1999
2281
|
);
|
|
2000
2282
|
} catch (err: unknown) {
|
|
2001
2283
|
sendJson(res, 400, {
|
|
@@ -2019,7 +2301,7 @@ export async function startDesignConnectBridge(
|
|
|
2019
2301
|
});
|
|
2020
2302
|
});
|
|
2021
2303
|
|
|
2022
|
-
return { server, manifest, bridgeToken, previewToken };
|
|
2304
|
+
return { server, manifest, bridgeToken, previewToken, bridgeInstanceId };
|
|
2023
2305
|
}
|
|
2024
2306
|
|
|
2025
2307
|
/**
|
|
@@ -2163,12 +2445,11 @@ Options:
|
|
|
2163
2445
|
|
|
2164
2446
|
Element provenance (resolveNodeToFile):
|
|
2165
2447
|
The design editor can map a selected DOM element back to its source file,
|
|
2166
|
-
line, and
|
|
2167
|
-
attributes at build time. Add one of the following to your app's build:
|
|
2448
|
+
line, and component name using one of these provenance sources:
|
|
2168
2449
|
|
|
2169
|
-
•
|
|
2170
|
-
|
|
2171
|
-
|
|
2450
|
+
• React development builds with jsxDEV enabled (the default):
|
|
2451
|
+
Design reads the selected element's development-only Fiber debug stack.
|
|
2452
|
+
React does not emit data-source-* DOM attributes automatically.
|
|
2172
2453
|
|
|
2173
2454
|
• A Babel source plugin (e.g. babel-plugin-react-source or a custom plugin):
|
|
2174
2455
|
Emits data-source-file="src/Button.tsx" data-source-line="12"
|
|
@@ -2177,7 +2458,8 @@ Element provenance (resolveNodeToFile):
|
|
|
2177
2458
|
• data-loc="src/Button.tsx:12:4" shorthand attribute (Babel source convention):
|
|
2178
2459
|
The bridge parses this as { sourceFile, line, column } automatically.
|
|
2179
2460
|
|
|
2180
|
-
|
|
2461
|
+
In production React builds and other runtimes without explicit attributes,
|
|
2462
|
+
the editor still works but exact element provenance may be absent.
|
|
2181
2463
|
Cross-origin localhost iframes cannot be read regardless of attributes (CSP).`);
|
|
2182
2464
|
}
|
|
2183
2465
|
|