@agent-native/core 0.169.1 → 0.171.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/agent-native.eject.json +4 -1
- package/bin/agent-native.js +23 -5
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +28 -0
- package/corpus/templates/analytics/app/pages/Agents.tsx +25 -1
- package/corpus/templates/analytics/app/routes/$.tsx +10 -0
- package/corpus/templates/analytics/app/routes.ts +3 -1
- package/corpus/templates/analytics/changelog/2026-08-22-fixed-the-agent-native-templates-first-party-dashboard-panel.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-22-org-admin-panels-no-longer-report-a-database-error-as-a-per.md +9 -0
- package/corpus/templates/analytics/changelog/2026-08-22-slack-requests-report-invalid-inputs.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-22-unknown-analytics-links-now-return-a-real-not-found-status.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +2 -1
- package/corpus/templates/analytics/docs/schemas/tracked-events.md +2 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +29 -7
- package/corpus/templates/analytics/server/handlers/slack.ts +59 -5
- package/corpus/templates/analytics/server/lib/db-admin-connections.ts +11 -6
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +132 -2
- package/corpus/templates/analytics/server/lib/slack.ts +4 -2
- package/corpus/templates/analytics/vitest.config.ts +6 -1
- package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/assets/app/routes/library.tsx +5 -3
- package/corpus/templates/assets/app/routes.ts +3 -1
- package/corpus/templates/assets/changelog/2026-08-22-library-page-loads-again-instead-of-crashing-on-open.md +6 -0
- package/corpus/templates/assets/vitest.config.ts +6 -1
- package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/brain/app/components/ui/pointer-lock.ts +1 -0
- package/corpus/templates/brain/app/routes/sources.tsx +8 -3
- package/corpus/templates/brain/changelog/2026-08-22-tuning-or-adding-a-source-no-longer-leaves-the-sources-page.md +6 -0
- package/corpus/templates/brain/vitest.config.ts +1 -0
- package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/calendar/actions/list-booking-links.ts +83 -4
- package/corpus/templates/calendar/actions/update-booking-link.ts +4 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +11 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +1 -1
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +103 -44
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +28 -24
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +9 -2
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +47 -58
- package/corpus/templates/calendar/app/hooks/use-booking-links.ts +5 -1
- package/corpus/templates/calendar/app/lib/event-layout.ts +52 -26
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +126 -98
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +1 -0
- package/corpus/templates/calendar/app/pages/Settings.tsx +88 -81
- package/corpus/templates/calendar/app/routes.ts +3 -1
- package/corpus/templates/calendar/changelog/2026-08-22-booking-link-visibility-respects-shared-access.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-clicking-an-out-of-office-event-s-marker-no-longer-creates-a.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-event-detail-views-now-show-your-calendar-timezone.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-google-calendar-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-overlapping-events-now-show-each-others-boundaries.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-public-booking-pages-now-offer-time-slots-from-the-host-s-we.md +6 -0
- package/corpus/templates/calendar/server/handlers/bookings.ts +5 -7
- package/corpus/templates/calendar/server/handlers/google-auth.ts +10 -2
- package/corpus/templates/calendar/server/lib/google-calendar.ts +139 -14
- package/corpus/templates/calendar/server/onboarding.ts +85 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/calendar/shared/api.ts +9 -1
- package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/chat/app/routes.ts +3 -1
- package/corpus/templates/chat/vitest.config.ts +8 -0
- package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/clips/actions/get-meeting.ts +9 -4
- package/corpus/templates/clips/actions/list-recordings.ts +16 -3
- package/corpus/templates/clips/actions/search-meetings.ts +1 -0
- package/corpus/templates/clips/actions/view-screen.ts +8 -1
- package/corpus/templates/clips/app/components/meetings/meeting-history-row.tsx +32 -1
- package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +29 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +5 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +33 -4
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +1 -0
- package/corpus/templates/clips/app/routes/access-request.approve.tsx +16 -9
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +17 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +17 -7
- package/corpus/templates/clips/changelog/2026-08-21-camera-bubble-black-circle.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-22-screen-recordings-respect-the-microphone-toggle.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-22-the-recordings-library-no-longer-fails-right-after-server-start.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-23-the-agent-can-see-your-library-right-after-server-start.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +1 -2
- package/corpus/templates/clips/chrome-extension/src/native-transcription.ts +39 -7
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +21 -25
- package/corpus/templates/clips/desktop/src/app.tsx +53 -24
- package/corpus/templates/clips/desktop/src/lib/bubble-playback.ts +68 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +3 -0
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +72 -14
- package/corpus/templates/clips/desktop/src/lib/updater.ts +8 -3
- package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +162 -20
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +43 -6
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +150 -13
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +12 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +28 -3
- package/corpus/templates/clips/server/lib/emails.ts +17 -33
- package/corpus/templates/content/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +140 -15
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +12 -14
- package/corpus/templates/content/app/components/ui/pointer-lock.ts +1 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +18 -2
- package/corpus/templates/content/app/hooks/use-local-storage.ts +38 -22
- package/corpus/templates/content/app/i18n-data.ts +25 -0
- package/corpus/templates/content/app/routes.ts +3 -1
- package/corpus/templates/content/changelog/2026-08-22-code-blocks-inserted-from-the-slash-menu-no-longer-get-stuck.md +6 -0
- package/corpus/templates/content/changelog/2026-08-22-the-sidebar-page-tree-no-longer-flashes-empty-after-creating-a-page.md +6 -0
- package/corpus/templates/content/docs/solutions/2026-08-21-content-mermaid-rendering-shape.md +233 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +9 -2
- package/corpus/templates/content/vitest.config.ts +7 -1
- package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/design/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1383 -88
- package/corpus/templates/design/actions/generate-design.ts +98 -47
- package/corpus/templates/design/actions/get-design-system.ts +6 -1
- package/corpus/templates/design/actions/refresh-design-system-with-builder.ts +146 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +70 -26
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +284 -136
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2057 -141
- package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +6 -2
- package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +4 -4
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +10 -1
- package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +19 -2
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +9 -2
- package/corpus/templates/design/app/components/layout/Header.tsx +15 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +7 -2
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +11 -4
- package/corpus/templates/design/app/i18n-data.ts +1 -0
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +12 -0
- package/corpus/templates/design/app/lib/design-system-data.ts +68 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +77 -35
- package/corpus/templates/design/app/pages/DesignSystems.tsx +136 -42
- package/corpus/templates/design/app/pages/Index.tsx +2 -0
- package/corpus/templates/design/app/pages/Templates.tsx +0 -10
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +10 -0
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +50 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +10 -8
- package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +6 -6
- package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +18 -7
- package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +4 -90
- package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +2 -1
- package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +4 -3
- package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +12 -2
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +9 -2
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +11 -1
- package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +11 -19
- package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +6 -6
- package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +12 -15
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -59
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/types.ts +3 -3
- package/corpus/templates/design/app/routes.ts +3 -1
- package/corpus/templates/design/changelog/2026-08-20-canvas-edits-patch-the-frame-instead-of-rebuilding-it.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-builder-design-system-imports-reconcile-after-indexing.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-draw-mode-needs-shift-y.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-eyedropper-shortcut.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-figma-style-alignment-guides-and-snapping.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-group-transforms-keep-the-selection.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-inspector-fields-keep-focus-on-enter.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-pen-alt-keeps-the-incoming-handle.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-pen-on-the-board-creates-one-vector.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-scale-a-multi-selection-from-the-group-handles.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-scale-resizes-text-with-the-object.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-selecting-another-object-keeps-the-scale-tool-armed.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-the-shape-tool-remembers-the-shape-you-picked.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-vectors-drawn-in-a-frame-land-where-you-drew-them.md +6 -0
- package/corpus/templates/design/changelog/2026-08-22-multi-screen-generation-no-longer-loses-a-saved-screens-layo.md +6 -0
- package/corpus/templates/design/changelog/2026-08-22-rename-and-search-fields-are-labeled-for-screen-readers.md +6 -0
- package/corpus/templates/design/package.json +2 -0
- package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +311 -0
- package/corpus/templates/design/shared/canvas-math.ts +612 -206
- package/corpus/templates/design/shared/pen-path.ts +37 -0
- package/corpus/templates/design/vitest.config.ts +7 -1
- package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/dispatch/actions/list-workspace-connections.ts +34 -3
- package/corpus/templates/dispatch/app/routes/integrations.tsx +55 -21
- package/corpus/templates/dispatch/app/routes.ts +3 -1
- package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/factory/.env.example +2 -7
- package/corpus/templates/factory/AGENTS.md +3 -0
- package/corpus/templates/factory/README.md +5 -3
- package/corpus/templates/factory/actions/create-factory.ts +16 -3
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +2 -0
- package/corpus/templates/factory/actions/get-triage-item.ts +2 -0
- package/corpus/templates/factory/actions/list-factory-audit.ts +116 -36
- package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
- package/corpus/templates/factory/actions/list-triage-items.ts +22 -19
- package/corpus/templates/factory/actions/run-factory-automation.ts +7 -3
- package/corpus/templates/factory/actions/save-factory-automation.ts +5 -2
- package/corpus/templates/factory/actions/save-triage-config.ts +2 -8
- package/corpus/templates/factory/actions/start-builder-for-item.ts +21 -0
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +229 -297
- package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +397 -247
- package/corpus/templates/factory/app/components/factory/FactorySourceSettingsGroup.tsx +33 -0
- package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +202 -162
- package/corpus/templates/factory/app/i18n/en-US.ts +32 -4
- package/corpus/templates/factory/app/routes/factory.tsx +84 -11
- package/corpus/templates/factory/app/routes.ts +3 -1
- package/corpus/templates/factory/changelog/2026-08-21-activity-runs-use-automation-display-names.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-create-and-settings-source-cards.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-keep-automations-visible-while-running.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-nested-factory-automations-can-poll.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-settings-sticky-unsaved-bar.md +6 -0
- package/corpus/templates/factory/server/connectors/credentials.ts +10 -5
- package/corpus/templates/factory/server/lib/factory-audit-report.ts +412 -0
- package/corpus/templates/factory/server/lib/factory-config-reconcile.ts +321 -0
- package/corpus/templates/factory/server/lib/factory-scope.ts +25 -2
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +13 -4
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +148 -4
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +4 -6
- package/corpus/templates/factory/vitest.config.ts +17 -2
- package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/forms/app/routes/_app.settings.tsx +4 -1
- package/corpus/templates/forms/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +11 -1
- package/corpus/templates/forms/vitest.config.ts +1 -0
- package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/macros/app/routes.ts +3 -1
- package/corpus/templates/macros/vitest.config.ts +6 -1
- package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/mail/actions/manage-gmail-filters.ts +10 -2
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -37
- package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +14 -2
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +66 -36
- package/corpus/templates/mail/app/hooks/use-google-auth.ts +2 -0
- package/corpus/templates/mail/app/lib/google-oauth-setup.ts +17 -0
- package/corpus/templates/mail/app/pages/InboxPage.tsx +69 -9
- package/corpus/templates/mail/app/routes/$.tsx +9 -0
- package/corpus/templates/mail/app/routes/$view.$threadId.tsx +23 -1
- package/corpus/templates/mail/app/routes/$view.tsx +47 -1
- package/corpus/templates/mail/app/routes.ts +3 -1
- package/corpus/templates/mail/changelog/2026-08-22-gmail-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/mail/changelog/2026-08-22-gmail-filters-now-shows-the-real-error.md +6 -0
- package/corpus/templates/mail/changelog/2026-08-22-unknown-mail-urls-now-show-the-404-page.md +6 -0
- package/corpus/templates/mail/server/handlers/google-auth.ts +11 -2
- package/corpus/templates/mail/server/lib/google-auth.ts +92 -12
- package/corpus/templates/mail/server/onboarding.ts +66 -18
- package/corpus/templates/mail/vitest.config.ts +6 -1
- package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/plan/app/routes/settings.tsx +4 -1
- package/corpus/templates/plan/app/routes.ts +3 -1
- package/corpus/templates/plan/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/plan/netlify.toml +1 -1
- package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/slides/actions/patch-deck.ts +79 -10
- package/corpus/templates/slides/actions/update-slide.ts +11 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +14 -4
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +10 -52
- package/corpus/templates/slides/app/components/editor/GoogleDocImportHint.tsx +8 -53
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +9 -75
- package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +8 -60
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +10 -2
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +19 -7
- package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +49 -36
- package/corpus/templates/slides/app/routes/access-request.approve.tsx +16 -9
- package/corpus/templates/slides/app/routes.ts +3 -1
- package/corpus/templates/slides/changelog/2026-08-22-already-uploaded-chat-images-are-no-longer-dropped.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-google-drive-and-slides-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-multi-slide-repairs-now-report-slides-that-still-overflow.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-update-slide-now-reports-which-batched-edit-was-skipped.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +52 -1
- package/corpus/templates/slides/server/lib/chat-attachments.ts +29 -3
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +66 -16
- package/corpus/templates/slides/server/lib/slide-content-patch.ts +13 -2
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -2
- package/corpus/templates/slides/tsconfig.json +5 -0
- package/corpus/templates/slides/vitest.config.ts +6 -1
- package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/tasks/app/routes/settings.tsx +4 -1
- package/corpus/templates/tasks/app/routes.ts +3 -1
- package/corpus/templates/tasks/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/tasks/vitest.config.ts +7 -1
- package/dist/action.d.ts +91 -0
- package/dist/action.js +50 -0
- package/dist/agent/abort-reasons.d.ts +38 -0
- package/dist/agent/abort-reasons.js +49 -0
- package/dist/agent/engine/ai-sdk-engine.js +8 -5
- package/dist/agent/engine/anthropic-engine.js +7 -5
- package/dist/agent/engine/first-event-timeout.d.ts +42 -17
- package/dist/agent/engine/first-event-timeout.js +67 -25
- package/dist/agent/production-agent.d.ts +23 -0
- package/dist/agent/production-agent.js +107 -102
- package/dist/agent/run-loop-with-resume.d.ts +2 -28
- package/dist/agent/run-loop-with-resume.js +25 -40
- package/dist/agent/run-manager.js +5 -4
- package/dist/agent/run-store.d.ts +55 -0
- package/dist/agent/run-store.js +124 -1
- package/dist/agent/tool-search.js +2 -1
- package/dist/agent/types.d.ts +9 -4
- package/dist/app-config/auth.d.ts +1 -0
- package/dist/app-config/auth.js +7 -0
- package/dist/app-config/configuration-error.d.ts +11 -0
- package/dist/app-config/configuration-error.js +14 -0
- package/dist/app-config/index.d.ts +1 -0
- package/dist/app-config/index.js +1 -0
- package/dist/app-config/integrations.d.ts +1 -0
- package/dist/app-config/integrations.js +10 -0
- package/dist/app-config/plugins.d.ts +34 -0
- package/dist/app-config/plugins.js +39 -0
- package/dist/app-config/run-lifecycle-invariants.d.ts +18 -9
- package/dist/app-config/run-lifecycle-invariants.js +18 -25
- package/dist/app-config/schema.d.ts +17 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/chat-threads/schema-migrations.js +20 -0
- package/dist/chat-threads/store.js +21 -4
- package/dist/cli/native-dependencies.d.ts +4 -0
- package/dist/cli/native-dependencies.js +15 -0
- package/dist/client/EnvironmentBadge.d.ts +1 -1
- package/dist/client/EnvironmentBadge.js +40 -5
- package/dist/client/MultiTabAssistantChat.d.ts +1 -1
- package/dist/client/MultiTabAssistantChat.js +35 -14
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/agent-page/use-jobs.d.ts +2 -1
- package/dist/client/agent-page/use-jobs.js +3 -5
- package/dist/client/agent-sidebar-state.js +3 -1
- package/dist/client/blocks/library/code-tabs.js +5 -0
- package/dist/client/blocks/library/code.js +1 -0
- package/dist/client/chat-first/apps-rail.js +2 -2
- package/dist/client/chat-model-groups.d.ts +12 -0
- package/dist/client/chat-model-groups.js +3 -2
- package/dist/client/integrations/GoogleProductLogo.d.ts +5 -0
- package/dist/client/integrations/GoogleProductLogo.js +45 -0
- package/dist/client/integrations/index.d.ts +2 -0
- package/dist/client/integrations/index.js +2 -0
- package/dist/client/integrations/workspace-provider-oauth.d.ts +8 -0
- package/dist/client/integrations/workspace-provider-oauth.js +13 -0
- package/dist/client/resources/mcp-integration-catalog.js +0 -29
- package/dist/client/settings/SettingsTabsPage.js +10 -6
- package/dist/client/sharing/ShareButton.js +11 -2
- package/dist/client/use-chat-models.d.ts +6 -0
- package/dist/client/use-chat-models.js +10 -0
- package/dist/client/use-db-sync.js +27 -9
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +111 -9
- package/dist/connections/catalog.js +86 -30
- package/dist/db/create-get-db.js +18 -0
- package/dist/db/ddl-guard.d.ts +0 -4
- package/dist/db/ddl-guard.js +46 -0
- package/dist/deploy/build.d.ts +19 -0
- package/dist/deploy/build.js +190 -16
- package/dist/deploy/function-bundle.d.ts +45 -0
- package/dist/deploy/function-bundle.js +337 -0
- package/dist/deploy/workspace-deploy.js +19 -1
- package/dist/eject/workspace-connections.js +4 -0
- package/dist/email-catalog/registry.d.ts +4 -0
- package/dist/email-catalog/registry.js +79 -14
- package/dist/extensions/content-patch.js +14 -2
- package/dist/integrations/plugin.d.ts +10 -0
- package/dist/integrations/plugin.js +40 -6
- package/dist/jobs/actions/run-automation-now.d.ts +2 -1
- package/dist/jobs/actions/run-automation-now.js +11 -5
- package/dist/jobs/frontmatter.js +53 -2
- package/dist/jobs/run-now.d.ts +11 -1
- package/dist/jobs/run-now.js +42 -7
- package/dist/jobs/scheduler.d.ts +1 -0
- package/dist/jobs/scheduler.js +2 -1
- package/dist/localization/core-messages/ar-SA.js +3 -0
- package/dist/localization/core-messages/de-DE.js +3 -0
- package/dist/localization/core-messages/en-US.d.ts +3 -0
- package/dist/localization/core-messages/en-US.js +3 -0
- package/dist/localization/core-messages/es-ES.js +3 -0
- package/dist/localization/core-messages/fr-FR.js +3 -0
- package/dist/localization/core-messages/hi-IN.js +3 -0
- package/dist/localization/core-messages/ja-JP.js +3 -0
- package/dist/localization/core-messages/ko-KR.js +3 -0
- package/dist/localization/core-messages/pt-BR.js +3 -0
- package/dist/localization/core-messages/zh-CN.js +3 -0
- package/dist/localization/core-messages/zh-TW.js +3 -0
- package/dist/localization/core-messages.d.ts +3 -0
- package/dist/localization/default-messages.d.ts +2 -5
- package/dist/localization/default-messages.js +2 -5
- package/dist/mcp/build-server.d.ts +9 -0
- package/dist/mcp/build-server.js +37 -8
- package/dist/mcp-client/oauth-client.d.ts +7 -0
- package/dist/mcp-client/oauth-client.js +12 -1
- package/dist/oauth-tokens/index.d.ts +1 -1
- package/dist/oauth-tokens/index.js +1 -1
- package/dist/oauth-tokens/lifecycle.d.ts +11 -0
- package/dist/oauth-tokens/lifecycle.js +17 -0
- package/dist/oauth-tokens/store.js +7 -0
- package/dist/observability/posthog-ai.d.ts +15 -1
- package/dist/observability/posthog-ai.js +24 -12
- package/dist/observability/traces.js +158 -28
- package/dist/onboarding/app-profile.js +2 -2
- package/dist/onboarding/plugin.js +10 -2
- package/dist/onboarding/types.d.ts +2 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/provider-api/index.js +2 -0
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/resources/store.js +13 -4
- package/dist/scripts/agent-engines/list-agent-engines.js +29 -3
- package/dist/secrets/register-framework-secrets.js +1 -1
- package/dist/server/action-discovery.js +5 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts +27 -3
- package/dist/server/agent-chat/action-filters-a2a.js +48 -9
- package/dist/server/agent-chat-plugin.js +36 -9
- package/dist/server/agent-discovery.js +43 -20
- package/dist/server/auth.js +18 -16
- package/dist/server/beta-opt-out-html.js +45 -4
- package/dist/server/better-auth-instance.d.ts +2 -1
- package/dist/server/better-auth-instance.js +37 -7
- package/dist/server/builder-design-systems.d.ts +4 -1
- package/dist/server/builder-design-systems.js +87 -6
- package/dist/server/builder-oauth.d.ts +1 -7
- package/dist/server/builder-oauth.js +80 -163
- package/dist/server/core-routes-plugin.d.ts +29 -0
- package/dist/server/core-routes-plugin.js +100 -20
- package/dist/server/default-plugins.d.ts +18 -0
- package/dist/server/default-plugins.js +22 -0
- package/dist/server/edge.d.ts +1 -0
- package/dist/server/edge.js +1 -0
- package/dist/server/framework-request-handler.js +31 -3
- package/dist/server/google-oauth.d.ts +3 -0
- package/dist/server/google-oauth.js +3 -0
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +3 -3
- package/dist/server/onboarding-html.js +37 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/workspace-provider-oauth.d.ts +6 -1
- package/dist/server/workspace-provider-oauth.js +72 -20
- package/dist/shared/agent-sidebar-url.d.ts +1 -0
- package/dist/shared/agent-sidebar-url.js +1 -0
- package/dist/shared/environment-lanes.d.ts +2 -0
- package/dist/shared/environment-lanes.js +3 -1
- package/dist/shared/index.d.ts +3 -1
- package/dist/shared/index.js +3 -1
- package/dist/shared/surface-visibility.d.ts +31 -0
- package/dist/shared/surface-visibility.js +57 -0
- package/dist/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/chat/app/routes.ts +3 -1
- package/dist/templates/chat/vitest.config.ts +8 -0
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/default/app/routes.ts +3 -1
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/tracking/registry.d.ts +11 -0
- package/dist/tracking/registry.js +5 -2
- package/dist/triggers/actions.js +10 -3
- package/dist/usage/store.js +23 -3
- package/dist/user-profile/actions/get-auth-methods.js +18 -5
- package/dist/vite/client.js +33 -7
- package/docs/content/actions-access-control.mdx +43 -4
- package/docs/content/actions-defining.mdx +16 -0
- package/docs/content/authentication.mdx +40 -21
- package/docs/content/environment-variables.mdx +1 -0
- package/docs/content/template-clips-capture-everywhere.mdx +29 -30
- package/docs/content/template-clips-developers.mdx +4 -4
- package/docs/content/template-clips.mdx +3 -1
- package/package.json +2 -2
- package/src/eject/workspace-connections.ts +4 -0
- package/src/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/chat/app/routes.ts +3 -1
- package/src/templates/chat/vitest.config.ts +8 -0
- package/src/templates/default/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/default/app/routes.ts +3 -1
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { DEFAULT_FACTORY_ID } from "../factory-graph/store.js";
|
|
2
|
+
import { factoryConfigRowId } from "./factory-scope.js";
|
|
3
|
+
|
|
4
|
+
export type FactoryConfigSqlRow = {
|
|
5
|
+
id: string;
|
|
6
|
+
org_id: string | null;
|
|
7
|
+
factory_id: string | null;
|
|
8
|
+
slack_workspace: string | null;
|
|
9
|
+
slack_channel_id: string | null;
|
|
10
|
+
slack_channel_name: string | null;
|
|
11
|
+
builder_slack_user_id: string | null;
|
|
12
|
+
polling_enabled: number | null;
|
|
13
|
+
last_slack_ts: string | null;
|
|
14
|
+
slack_history_cursor: string | null;
|
|
15
|
+
repository: string | null;
|
|
16
|
+
github_polling_enabled: number | null;
|
|
17
|
+
sentry_polling_enabled: number | null;
|
|
18
|
+
sentry_org_slug: string | null;
|
|
19
|
+
sentry_project_slug: string | null;
|
|
20
|
+
sentry_environment: string | null;
|
|
21
|
+
last_sentry_seen_at: string | null;
|
|
22
|
+
automation_failure_alerts_enabled: number | null;
|
|
23
|
+
automation_failure_alert_email: string | null;
|
|
24
|
+
last_automation_failure_alert_key: string | null;
|
|
25
|
+
last_automation_failure_alert_at: string | null;
|
|
26
|
+
owner_email: string | null;
|
|
27
|
+
created_at: string | null;
|
|
28
|
+
updated_at: string | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type FactoryConfigReconcilePlan = {
|
|
32
|
+
fromId: string;
|
|
33
|
+
row: FactoryConfigSqlRow;
|
|
34
|
+
deleteIds: string[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function rowText(row: Record<string, unknown>, key: string): string | null {
|
|
38
|
+
return asText(row[key]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function rowFlag(row: Record<string, unknown>, key: string): number | null {
|
|
42
|
+
const value = row[key];
|
|
43
|
+
if (value === null || value === undefined || value === "") return null;
|
|
44
|
+
return Number(value) === 1 ? 1 : 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function factoryConfigSqlRowFromQuery(
|
|
48
|
+
row: Record<string, unknown>,
|
|
49
|
+
): FactoryConfigSqlRow | null {
|
|
50
|
+
const id = rowText(row, "id");
|
|
51
|
+
if (!id) return null;
|
|
52
|
+
return {
|
|
53
|
+
id,
|
|
54
|
+
org_id: rowText(row, "org_id"),
|
|
55
|
+
factory_id: rowText(row, "factory_id"),
|
|
56
|
+
slack_workspace: rowText(row, "slack_workspace"),
|
|
57
|
+
slack_channel_id: rowText(row, "slack_channel_id"),
|
|
58
|
+
slack_channel_name: rowText(row, "slack_channel_name"),
|
|
59
|
+
builder_slack_user_id: rowText(row, "builder_slack_user_id"),
|
|
60
|
+
polling_enabled: rowFlag(row, "polling_enabled"),
|
|
61
|
+
last_slack_ts: rowText(row, "last_slack_ts"),
|
|
62
|
+
slack_history_cursor: rowText(row, "slack_history_cursor"),
|
|
63
|
+
repository: rowText(row, "repository"),
|
|
64
|
+
github_polling_enabled: rowFlag(row, "github_polling_enabled"),
|
|
65
|
+
sentry_polling_enabled: rowFlag(row, "sentry_polling_enabled"),
|
|
66
|
+
sentry_org_slug: rowText(row, "sentry_org_slug"),
|
|
67
|
+
sentry_project_slug: rowText(row, "sentry_project_slug"),
|
|
68
|
+
sentry_environment: rowText(row, "sentry_environment"),
|
|
69
|
+
last_sentry_seen_at: rowText(row, "last_sentry_seen_at"),
|
|
70
|
+
automation_failure_alerts_enabled: rowFlag(
|
|
71
|
+
row,
|
|
72
|
+
"automation_failure_alerts_enabled",
|
|
73
|
+
),
|
|
74
|
+
automation_failure_alert_email: rowText(
|
|
75
|
+
row,
|
|
76
|
+
"automation_failure_alert_email",
|
|
77
|
+
),
|
|
78
|
+
last_automation_failure_alert_key: rowText(
|
|
79
|
+
row,
|
|
80
|
+
"last_automation_failure_alert_key",
|
|
81
|
+
),
|
|
82
|
+
last_automation_failure_alert_at: rowText(
|
|
83
|
+
row,
|
|
84
|
+
"last_automation_failure_alert_at",
|
|
85
|
+
),
|
|
86
|
+
owner_email: rowText(row, "owner_email"),
|
|
87
|
+
created_at: rowText(row, "created_at"),
|
|
88
|
+
updated_at: rowText(row, "updated_at"),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function asText(value: unknown): string | null {
|
|
93
|
+
if (typeof value === "string") {
|
|
94
|
+
const text = value.trim();
|
|
95
|
+
return text.length > 0 ? text : null;
|
|
96
|
+
}
|
|
97
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function asFlag(value: unknown): number {
|
|
102
|
+
return Number(value) === 1 ? 1 : 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function pickText(
|
|
106
|
+
keep: string | null | undefined,
|
|
107
|
+
other: string | null | undefined,
|
|
108
|
+
): string | null {
|
|
109
|
+
return asText(keep) ?? asText(other);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function pickLater(
|
|
113
|
+
keep: string | null | undefined,
|
|
114
|
+
other: string | null | undefined,
|
|
115
|
+
): string | null {
|
|
116
|
+
const left = asText(keep);
|
|
117
|
+
const right = asText(other);
|
|
118
|
+
if (!left) return right;
|
|
119
|
+
if (!right) return left;
|
|
120
|
+
const leftNum = Number(left);
|
|
121
|
+
const rightNum = Number(right);
|
|
122
|
+
if (Number.isFinite(leftNum) && Number.isFinite(rightNum)) {
|
|
123
|
+
return leftNum >= rightNum ? left : right;
|
|
124
|
+
}
|
|
125
|
+
return left >= right ? left : right;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function isUniqueConstraintError(error: unknown): boolean {
|
|
129
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
130
|
+
return /unique constraint failed|duplicate key|primary key/i.test(message);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function mergeFactoryConfigRows(
|
|
134
|
+
keep: FactoryConfigSqlRow,
|
|
135
|
+
other: FactoryConfigSqlRow,
|
|
136
|
+
): FactoryConfigSqlRow {
|
|
137
|
+
const slackIdentity = mergeSlackIdentity(keep, other);
|
|
138
|
+
return {
|
|
139
|
+
...keep,
|
|
140
|
+
slack_workspace: slackIdentity.slack_workspace,
|
|
141
|
+
slack_channel_id: slackIdentity.slack_channel_id,
|
|
142
|
+
slack_channel_name: slackIdentity.slack_channel_name,
|
|
143
|
+
builder_slack_user_id: pickText(
|
|
144
|
+
keep.builder_slack_user_id,
|
|
145
|
+
other.builder_slack_user_id,
|
|
146
|
+
),
|
|
147
|
+
polling_enabled: Math.max(
|
|
148
|
+
asFlag(keep.polling_enabled),
|
|
149
|
+
asFlag(other.polling_enabled),
|
|
150
|
+
),
|
|
151
|
+
last_slack_ts: slackIdentity.last_slack_ts,
|
|
152
|
+
slack_history_cursor: slackIdentity.slack_history_cursor,
|
|
153
|
+
repository: pickText(keep.repository, other.repository),
|
|
154
|
+
github_polling_enabled: Math.max(
|
|
155
|
+
asFlag(keep.github_polling_enabled),
|
|
156
|
+
asFlag(other.github_polling_enabled),
|
|
157
|
+
),
|
|
158
|
+
sentry_polling_enabled: Math.max(
|
|
159
|
+
asFlag(keep.sentry_polling_enabled),
|
|
160
|
+
asFlag(other.sentry_polling_enabled),
|
|
161
|
+
),
|
|
162
|
+
sentry_org_slug: pickText(keep.sentry_org_slug, other.sentry_org_slug),
|
|
163
|
+
sentry_project_slug: pickText(
|
|
164
|
+
keep.sentry_project_slug,
|
|
165
|
+
other.sentry_project_slug,
|
|
166
|
+
),
|
|
167
|
+
sentry_environment: pickText(
|
|
168
|
+
keep.sentry_environment,
|
|
169
|
+
other.sentry_environment,
|
|
170
|
+
),
|
|
171
|
+
last_sentry_seen_at: pickLater(
|
|
172
|
+
keep.last_sentry_seen_at,
|
|
173
|
+
other.last_sentry_seen_at,
|
|
174
|
+
),
|
|
175
|
+
automation_failure_alerts_enabled: Math.max(
|
|
176
|
+
asFlag(keep.automation_failure_alerts_enabled),
|
|
177
|
+
asFlag(other.automation_failure_alerts_enabled),
|
|
178
|
+
),
|
|
179
|
+
automation_failure_alert_email: pickText(
|
|
180
|
+
keep.automation_failure_alert_email,
|
|
181
|
+
other.automation_failure_alert_email,
|
|
182
|
+
),
|
|
183
|
+
last_automation_failure_alert_key: pickText(
|
|
184
|
+
keep.last_automation_failure_alert_key,
|
|
185
|
+
other.last_automation_failure_alert_key,
|
|
186
|
+
),
|
|
187
|
+
last_automation_failure_alert_at: pickLater(
|
|
188
|
+
keep.last_automation_failure_alert_at,
|
|
189
|
+
other.last_automation_failure_alert_at,
|
|
190
|
+
),
|
|
191
|
+
owner_email: pickText(keep.owner_email, other.owner_email),
|
|
192
|
+
created_at: pickText(keep.created_at, other.created_at) ?? keep.created_at,
|
|
193
|
+
updated_at: pickLater(keep.updated_at, other.updated_at),
|
|
194
|
+
org_id: keep.org_id || other.org_id,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function planDefaultFactoryConfigReconciliation(
|
|
199
|
+
rows: FactoryConfigSqlRow[],
|
|
200
|
+
defaultFactoryId = DEFAULT_FACTORY_ID,
|
|
201
|
+
): FactoryConfigReconcilePlan[] {
|
|
202
|
+
const byOrg = new Map<string, FactoryConfigSqlRow[]>();
|
|
203
|
+
for (const row of rows) {
|
|
204
|
+
const orgId = asText(row.org_id);
|
|
205
|
+
const id = asText(row.id);
|
|
206
|
+
if (!orgId || !id) continue;
|
|
207
|
+
const group = byOrg.get(orgId) ?? [];
|
|
208
|
+
group.push({ ...row, id, org_id: orgId });
|
|
209
|
+
byOrg.set(orgId, group);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const plans: FactoryConfigReconcilePlan[] = [];
|
|
213
|
+
for (const [orgId, group] of byOrg) {
|
|
214
|
+
const canonicalId = factoryConfigRowId(orgId, defaultFactoryId);
|
|
215
|
+
const keepSource = group.find((row) => row.id === canonicalId) ?? group[0]!;
|
|
216
|
+
let merged: FactoryConfigSqlRow = {
|
|
217
|
+
...keepSource,
|
|
218
|
+
id: canonicalId,
|
|
219
|
+
org_id: orgId,
|
|
220
|
+
factory_id: defaultFactoryId,
|
|
221
|
+
};
|
|
222
|
+
const deleteIds: string[] = [];
|
|
223
|
+
for (const row of group) {
|
|
224
|
+
if (row.id === keepSource.id) continue;
|
|
225
|
+
merged = mergeFactoryConfigRows(merged, row);
|
|
226
|
+
deleteIds.push(row.id);
|
|
227
|
+
}
|
|
228
|
+
plans.push({
|
|
229
|
+
fromId: keepSource.id,
|
|
230
|
+
row: merged,
|
|
231
|
+
deleteIds,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return plans;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export type SlackChannelConflictClear = {
|
|
238
|
+
id: string;
|
|
239
|
+
org_id: string;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
function mergeSlackIdentity(
|
|
243
|
+
keep: FactoryConfigSqlRow,
|
|
244
|
+
other: FactoryConfigSqlRow,
|
|
245
|
+
): Pick<
|
|
246
|
+
FactoryConfigSqlRow,
|
|
247
|
+
| "slack_workspace"
|
|
248
|
+
| "slack_channel_id"
|
|
249
|
+
| "slack_channel_name"
|
|
250
|
+
| "last_slack_ts"
|
|
251
|
+
| "slack_history_cursor"
|
|
252
|
+
> {
|
|
253
|
+
const keepChannel = asText(keep.slack_channel_id);
|
|
254
|
+
const otherChannel = asText(other.slack_channel_id);
|
|
255
|
+
if (!otherChannel || keepChannel === otherChannel) {
|
|
256
|
+
return {
|
|
257
|
+
slack_workspace: pickText(keep.slack_workspace, other.slack_workspace),
|
|
258
|
+
slack_channel_id: keepChannel ?? otherChannel,
|
|
259
|
+
slack_channel_name: pickText(
|
|
260
|
+
keep.slack_channel_name,
|
|
261
|
+
other.slack_channel_name,
|
|
262
|
+
),
|
|
263
|
+
last_slack_ts: pickLater(keep.last_slack_ts, other.last_slack_ts),
|
|
264
|
+
slack_history_cursor: pickText(
|
|
265
|
+
keep.slack_history_cursor,
|
|
266
|
+
other.slack_history_cursor,
|
|
267
|
+
),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (!keepChannel) {
|
|
271
|
+
return {
|
|
272
|
+
slack_workspace: other.slack_workspace,
|
|
273
|
+
slack_channel_id: other.slack_channel_id,
|
|
274
|
+
slack_channel_name: other.slack_channel_name,
|
|
275
|
+
last_slack_ts: other.last_slack_ts,
|
|
276
|
+
slack_history_cursor: other.slack_history_cursor,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
slack_workspace: keep.slack_workspace,
|
|
281
|
+
slack_channel_id: keep.slack_channel_id,
|
|
282
|
+
slack_channel_name: keep.slack_channel_name,
|
|
283
|
+
last_slack_ts: keep.last_slack_ts,
|
|
284
|
+
slack_history_cursor: keep.slack_history_cursor,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function planSlackChannelConflictClears(
|
|
289
|
+
rows: FactoryConfigSqlRow[],
|
|
290
|
+
): SlackChannelConflictClear[] {
|
|
291
|
+
const groups = new Map<string, FactoryConfigSqlRow[]>();
|
|
292
|
+
for (const row of rows) {
|
|
293
|
+
const orgId = asText(row.org_id);
|
|
294
|
+
const channelId = asText(row.slack_channel_id);
|
|
295
|
+
const id = asText(row.id);
|
|
296
|
+
if (!orgId || !channelId || !id) continue;
|
|
297
|
+
const key = `${orgId}\0${channelId}`;
|
|
298
|
+
const group = groups.get(key) ?? [];
|
|
299
|
+
group.push({ ...row, id, org_id: orgId });
|
|
300
|
+
groups.set(key, group);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const clears: SlackChannelConflictClear[] = [];
|
|
304
|
+
for (const group of groups.values()) {
|
|
305
|
+
if (group.length < 2) continue;
|
|
306
|
+
const ranked = [...group].sort((left, right) => {
|
|
307
|
+
const polling =
|
|
308
|
+
asFlag(right.polling_enabled) - asFlag(left.polling_enabled);
|
|
309
|
+
if (polling !== 0) return polling;
|
|
310
|
+
const updated = (right.updated_at ?? "").localeCompare(
|
|
311
|
+
left.updated_at ?? "",
|
|
312
|
+
);
|
|
313
|
+
if (updated !== 0) return updated;
|
|
314
|
+
return left.id.localeCompare(right.id);
|
|
315
|
+
});
|
|
316
|
+
for (const row of ranked.slice(1)) {
|
|
317
|
+
clears.push({ id: row.id, org_id: row.org_id! });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return clears;
|
|
321
|
+
}
|
|
@@ -23,6 +23,14 @@ export const factoryIdSchema = z
|
|
|
23
23
|
.max(120)
|
|
24
24
|
.regex(/^[a-z0-9][a-z0-9-]*$/);
|
|
25
25
|
|
|
26
|
+
export const builderSlackUserIdSchema = z
|
|
27
|
+
.string()
|
|
28
|
+
.trim()
|
|
29
|
+
.max(32)
|
|
30
|
+
.refine((value) => value === "" || /^[UW][A-Z0-9]+$/i.test(value), {
|
|
31
|
+
message: "Builder Slack member id must look like U01234567.",
|
|
32
|
+
});
|
|
33
|
+
|
|
26
34
|
export function factoryConfigRowId(orgId: string, factoryId: string): string {
|
|
27
35
|
return `${orgId}:${factoryId}`;
|
|
28
36
|
}
|
|
@@ -222,7 +230,13 @@ function readFrontmatterFactoryId(content: string): string | undefined {
|
|
|
222
230
|
return value.replace(/^(\"|')|((\"|')$)/g, "");
|
|
223
231
|
}
|
|
224
232
|
|
|
225
|
-
export function readAutomationFactoryId(
|
|
233
|
+
export function readAutomationFactoryId(
|
|
234
|
+
meta: object,
|
|
235
|
+
content: string,
|
|
236
|
+
path: string,
|
|
237
|
+
): string {
|
|
238
|
+
const fromPath = readFactoryIdFromAutomationPath(path);
|
|
239
|
+
if (fromPath) return fromPath;
|
|
226
240
|
const fromContent = readFrontmatterFactoryId(content);
|
|
227
241
|
return resolveAutomationFactoryId(
|
|
228
242
|
fromContent ? { ...meta, factoryId: fromContent } : meta,
|
|
@@ -247,6 +261,15 @@ export function isLegacyFactoryAutomationPath(path: string): boolean {
|
|
|
247
261
|
return /^jobs\/factory-[^/]+\.md$/.test(path);
|
|
248
262
|
}
|
|
249
263
|
|
|
264
|
+
/** Scheduler trigger names keep the nested path; role allowlists use the leaf. */
|
|
265
|
+
export function factoryAutomationLeafName(nameOrPath: string): string {
|
|
266
|
+
const withoutJobsPrefix = nameOrPath
|
|
267
|
+
.replace(/^jobs\//, "")
|
|
268
|
+
.replace(/\.md$/, "");
|
|
269
|
+
const slash = withoutJobsPrefix.lastIndexOf("/");
|
|
270
|
+
return slash === -1 ? withoutJobsPrefix : withoutJobsPrefix.slice(slash + 1);
|
|
271
|
+
}
|
|
272
|
+
|
|
250
273
|
export function readFactoryIdFromAutomationPath(path: string): string | null {
|
|
251
274
|
const match = path.match(/^jobs\/factories\/([^/]+)\/factory-[^/]+\.md$/);
|
|
252
275
|
return match?.[1] ?? null;
|
|
@@ -294,7 +317,7 @@ export async function assertUniqueSlackChannelForFactory(
|
|
|
294
317
|
eq(triageConfig.slackChannelId, normalized),
|
|
295
318
|
),
|
|
296
319
|
)
|
|
297
|
-
).find((row) => row.factoryId
|
|
320
|
+
).find((row) => row.factoryId !== factoryId);
|
|
298
321
|
if (conflict) {
|
|
299
322
|
throw new Error(
|
|
300
323
|
"That Slack channel is already used by another Factory in this workspace.",
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { ActionRunContext } from "@agent-native/core/action";
|
|
2
2
|
import { listAutomationDefinitions } from "@agent-native/core/triggers";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
factoryAutomationLeafName,
|
|
6
|
+
readAutomationFactoryId,
|
|
7
|
+
} from "./factory-scope.js";
|
|
5
8
|
import type { WorkspaceMemberIdentity } from "./require-workspace-member.js";
|
|
6
9
|
|
|
7
10
|
const FACTORY_AUTOMATION_NAMES = {
|
|
@@ -39,7 +42,10 @@ export async function requireFactoryAutomation(
|
|
|
39
42
|
}
|
|
40
43
|
const lineage = context.automation;
|
|
41
44
|
const expectedNames = FACTORY_AUTOMATION_NAMES[role];
|
|
42
|
-
if (
|
|
45
|
+
if (
|
|
46
|
+
!lineage ||
|
|
47
|
+
!expectedNames.has(factoryAutomationLeafName(lineage.triggerName))
|
|
48
|
+
) {
|
|
43
49
|
throw new Error(
|
|
44
50
|
"The action was not invoked by a governed Factory automation.",
|
|
45
51
|
);
|
|
@@ -71,8 +77,11 @@ export async function requireFactoryAutomation(
|
|
|
71
77
|
}
|
|
72
78
|
if (
|
|
73
79
|
expectedFactoryId &&
|
|
74
|
-
readAutomationFactoryId(
|
|
75
|
-
|
|
80
|
+
readAutomationFactoryId(
|
|
81
|
+
definition.meta,
|
|
82
|
+
definition.resource.content,
|
|
83
|
+
definition.resource.path,
|
|
84
|
+
) !== expectedFactoryId
|
|
76
85
|
) {
|
|
77
86
|
throw new Error(
|
|
78
87
|
"The action was not invoked by a governed Factory automation.",
|
|
@@ -1,6 +1,128 @@
|
|
|
1
|
-
import { runMigrations } from "@agent-native/core/db";
|
|
1
|
+
import { runMigrations, type DbExec } from "@agent-native/core/db";
|
|
2
2
|
import { defineNitroPlugin } from "@agent-native/core/server";
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
factoryConfigSqlRowFromQuery,
|
|
6
|
+
isUniqueConstraintError,
|
|
7
|
+
planDefaultFactoryConfigReconciliation,
|
|
8
|
+
planSlackChannelConflictClears,
|
|
9
|
+
type FactoryConfigSqlRow,
|
|
10
|
+
} from "../lib/factory-config-reconcile.js";
|
|
11
|
+
|
|
12
|
+
async function writeReconciledFactoryConfig(
|
|
13
|
+
exec: DbExec,
|
|
14
|
+
plan: {
|
|
15
|
+
fromId: string;
|
|
16
|
+
row: FactoryConfigSqlRow;
|
|
17
|
+
deleteIds: string[];
|
|
18
|
+
},
|
|
19
|
+
): Promise<void> {
|
|
20
|
+
const { row } = plan;
|
|
21
|
+
await exec.execute({
|
|
22
|
+
sql: `UPDATE factory_config SET
|
|
23
|
+
id = ?,
|
|
24
|
+
factory_id = ?,
|
|
25
|
+
slack_workspace = ?,
|
|
26
|
+
slack_channel_id = ?,
|
|
27
|
+
slack_channel_name = ?,
|
|
28
|
+
builder_slack_user_id = ?,
|
|
29
|
+
polling_enabled = ?,
|
|
30
|
+
last_slack_ts = ?,
|
|
31
|
+
slack_history_cursor = ?,
|
|
32
|
+
repository = ?,
|
|
33
|
+
github_polling_enabled = ?,
|
|
34
|
+
sentry_polling_enabled = ?,
|
|
35
|
+
sentry_org_slug = ?,
|
|
36
|
+
sentry_project_slug = ?,
|
|
37
|
+
sentry_environment = ?,
|
|
38
|
+
last_sentry_seen_at = ?,
|
|
39
|
+
automation_failure_alerts_enabled = ?,
|
|
40
|
+
automation_failure_alert_email = ?,
|
|
41
|
+
last_automation_failure_alert_key = ?,
|
|
42
|
+
last_automation_failure_alert_at = ?,
|
|
43
|
+
owner_email = ?,
|
|
44
|
+
created_at = ?,
|
|
45
|
+
updated_at = ?
|
|
46
|
+
WHERE id = ? AND org_id = ?`,
|
|
47
|
+
args: [
|
|
48
|
+
row.id,
|
|
49
|
+
row.factory_id,
|
|
50
|
+
row.slack_workspace ?? "primary",
|
|
51
|
+
row.slack_channel_id,
|
|
52
|
+
row.slack_channel_name,
|
|
53
|
+
row.builder_slack_user_id,
|
|
54
|
+
row.polling_enabled ?? 0,
|
|
55
|
+
row.last_slack_ts,
|
|
56
|
+
row.slack_history_cursor,
|
|
57
|
+
row.repository,
|
|
58
|
+
row.github_polling_enabled ?? 0,
|
|
59
|
+
row.sentry_polling_enabled ?? 0,
|
|
60
|
+
row.sentry_org_slug,
|
|
61
|
+
row.sentry_project_slug,
|
|
62
|
+
row.sentry_environment,
|
|
63
|
+
row.last_sentry_seen_at,
|
|
64
|
+
row.automation_failure_alerts_enabled ?? 1,
|
|
65
|
+
row.automation_failure_alert_email,
|
|
66
|
+
row.last_automation_failure_alert_key,
|
|
67
|
+
row.last_automation_failure_alert_at,
|
|
68
|
+
row.owner_email ?? "",
|
|
69
|
+
row.created_at ?? new Date().toISOString(),
|
|
70
|
+
row.updated_at ?? new Date().toISOString(),
|
|
71
|
+
plan.fromId,
|
|
72
|
+
row.org_id,
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
for (const deleteId of plan.deleteIds) {
|
|
76
|
+
await exec.execute({
|
|
77
|
+
sql: `DELETE FROM factory_config WHERE id = ? AND org_id = ?`,
|
|
78
|
+
args: [deleteId, row.org_id],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function reconcileDefaultFactoryConfigRows(): Promise<void> {
|
|
84
|
+
const { getDbExec } = await import("@agent-native/core/db");
|
|
85
|
+
const exec = getDbExec();
|
|
86
|
+
const defaultFactoryId = "product-feedback";
|
|
87
|
+
const configRows = await exec.execute({
|
|
88
|
+
sql: `SELECT * FROM factory_config
|
|
89
|
+
WHERE factory_id IS NULL OR factory_id = '' OR factory_id = ?`,
|
|
90
|
+
args: [defaultFactoryId],
|
|
91
|
+
});
|
|
92
|
+
const rows = (configRows.rows ?? [])
|
|
93
|
+
.map((row) => factoryConfigSqlRowFromQuery(row as Record<string, unknown>))
|
|
94
|
+
.filter((row): row is FactoryConfigSqlRow => row !== null);
|
|
95
|
+
for (const plan of planDefaultFactoryConfigReconciliation(
|
|
96
|
+
rows,
|
|
97
|
+
defaultFactoryId,
|
|
98
|
+
)) {
|
|
99
|
+
await writeReconciledFactoryConfig(exec, plan);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function clearDuplicateSlackChannelAssignments(): Promise<void> {
|
|
104
|
+
const { getDbExec } = await import("@agent-native/core/db");
|
|
105
|
+
const exec = getDbExec();
|
|
106
|
+
const configRows = await exec.execute({
|
|
107
|
+
sql: `SELECT * FROM factory_config`,
|
|
108
|
+
args: [],
|
|
109
|
+
});
|
|
110
|
+
const rows = (configRows.rows ?? [])
|
|
111
|
+
.map((row) => factoryConfigSqlRowFromQuery(row as Record<string, unknown>))
|
|
112
|
+
.filter((row): row is FactoryConfigSqlRow => row !== null);
|
|
113
|
+
for (const clear of planSlackChannelConflictClears(rows)) {
|
|
114
|
+
await exec.execute({
|
|
115
|
+
sql: `UPDATE factory_config SET
|
|
116
|
+
slack_channel_id = NULL,
|
|
117
|
+
slack_channel_name = NULL,
|
|
118
|
+
last_slack_ts = NULL,
|
|
119
|
+
slack_history_cursor = NULL
|
|
120
|
+
WHERE id = ? AND org_id = ?`,
|
|
121
|
+
args: [clear.id, clear.org_id],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
4
126
|
const migrations = [
|
|
5
127
|
{
|
|
6
128
|
version: 1,
|
|
@@ -339,10 +461,20 @@ const migrations = [
|
|
|
339
461
|
const id = String(row.id ?? "");
|
|
340
462
|
if (!orgId || id.includes(":")) continue;
|
|
341
463
|
const nextId = `${orgId}:${defaultFactoryId}`;
|
|
342
|
-
await exec.execute({
|
|
343
|
-
sql: `
|
|
344
|
-
args: [nextId,
|
|
464
|
+
const existing = await exec.execute({
|
|
465
|
+
sql: `SELECT id FROM factory_config WHERE id = ? AND org_id = ?`,
|
|
466
|
+
args: [nextId, orgId],
|
|
345
467
|
});
|
|
468
|
+
if ((existing.rows?.length ?? 0) > 0) continue;
|
|
469
|
+
try {
|
|
470
|
+
await exec.execute({
|
|
471
|
+
sql: `UPDATE factory_config SET id = ? WHERE id = ? AND org_id = ?`,
|
|
472
|
+
args: [nextId, id, orgId],
|
|
473
|
+
});
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (isUniqueConstraintError(error)) continue;
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
346
478
|
}
|
|
347
479
|
},
|
|
348
480
|
},
|
|
@@ -368,12 +500,24 @@ const migrations = [
|
|
|
368
500
|
{
|
|
369
501
|
version: 24,
|
|
370
502
|
name: "factory-config-org-slack-channel-unique",
|
|
503
|
+
run: async () => {
|
|
504
|
+
await reconcileDefaultFactoryConfigRows();
|
|
505
|
+
await clearDuplicateSlackChannelAssignments();
|
|
506
|
+
},
|
|
371
507
|
sql: `
|
|
372
508
|
CREATE UNIQUE INDEX IF NOT EXISTS factory_config_org_slack_channel_idx
|
|
373
509
|
ON factory_config (org_id, slack_channel_id)
|
|
374
510
|
WHERE slack_channel_id IS NOT NULL AND slack_channel_id != '';
|
|
375
511
|
`,
|
|
376
512
|
},
|
|
513
|
+
{
|
|
514
|
+
version: 25,
|
|
515
|
+
name: "factory-config-reconcile-legacy-rows",
|
|
516
|
+
sql: {},
|
|
517
|
+
run: async () => {
|
|
518
|
+
await reconcileDefaultFactoryConfigRows();
|
|
519
|
+
},
|
|
520
|
+
},
|
|
377
521
|
];
|
|
378
522
|
|
|
379
523
|
export const runFactoryMigrations = runMigrations(migrations, {
|
|
@@ -39,8 +39,6 @@ import {
|
|
|
39
39
|
} from "../triage/review-skill-alignment.js";
|
|
40
40
|
|
|
41
41
|
const LEGACY_JOB_PATH = "jobs/factory-observation-scheduler.md";
|
|
42
|
-
const DEFAULT_SLACK_CHANNEL_ID = "C0ATH3CCZT4";
|
|
43
|
-
const DEFAULT_SLACK_CHANNEL_NAME = "product-agent-native-feedback";
|
|
44
42
|
const FAILURE_ALERT_COOLDOWN_MS = 15 * 60_000;
|
|
45
43
|
|
|
46
44
|
type AutomationRunFinishedEvent = {
|
|
@@ -627,6 +625,7 @@ export async function syncFactoryAutomationEnabledStates(
|
|
|
627
625
|
readAutomationFactoryId(
|
|
628
626
|
definition.meta,
|
|
629
627
|
definition.resource.content,
|
|
628
|
+
definition.resource.path,
|
|
630
629
|
) === factoryId,
|
|
631
630
|
)
|
|
632
631
|
.map(async (definition) => {
|
|
@@ -687,16 +686,15 @@ async function ensureDefaultTriageConfig(
|
|
|
687
686
|
}
|
|
688
687
|
const now = new Date().toISOString();
|
|
689
688
|
const repository = defaultRepository();
|
|
690
|
-
const pollingEnabled = 1;
|
|
691
689
|
const githubPollingEnabled = defaultGithubPollingEnabled() ? 1 : 0;
|
|
692
690
|
await db.insert(triageConfig).values({
|
|
693
691
|
id: factoryConfigRowId(orgId, factoryId),
|
|
694
692
|
factoryId,
|
|
695
693
|
slackWorkspace: "primary",
|
|
696
|
-
slackChannelId:
|
|
697
|
-
slackChannelName:
|
|
694
|
+
slackChannelId: null,
|
|
695
|
+
slackChannelName: null,
|
|
698
696
|
builderSlackUserId: null,
|
|
699
|
-
pollingEnabled,
|
|
697
|
+
pollingEnabled: 0,
|
|
700
698
|
githubPollingEnabled,
|
|
701
699
|
sentryPollingEnabled: 0,
|
|
702
700
|
lastSlackTs: null,
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import baseConfig from "@agent-native/core/vitest-config";
|
|
2
|
-
import { mergeConfig } from "vitest/config";
|
|
2
|
+
import { defineConfig, mergeConfig } from "vitest/config";
|
|
3
3
|
|
|
4
4
|
import viteConfig from "./vite.config";
|
|
5
5
|
|
|
6
6
|
// Kept separate from vite.config.ts so the production config never imports
|
|
7
7
|
// vitest: `agent-native build` loads vite.config.ts in installs where vitest
|
|
8
8
|
// is absent.
|
|
9
|
-
export default mergeConfig(
|
|
9
|
+
export default mergeConfig(
|
|
10
|
+
viteConfig,
|
|
11
|
+
mergeConfig(
|
|
12
|
+
baseConfig,
|
|
13
|
+
defineConfig({
|
|
14
|
+
test: {
|
|
15
|
+
exclude: [
|
|
16
|
+
"**/node_modules/**",
|
|
17
|
+
"**/.git/**",
|
|
18
|
+
"**/dist/**",
|
|
19
|
+
"**/.react-router/**",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
),
|
|
24
|
+
);
|
|
@@ -19,10 +19,17 @@ data, and generated extension/app content may mention credential **names** such
|
|
|
19
19
|
as `OPENAI_API_KEY`, but must not contain real API keys, tokens, webhook URLs,
|
|
20
20
|
signing secrets, OAuth refresh tokens, or private Builder/customer data.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`${keys.NAME}` substitution.
|
|
25
|
-
|
|
22
|
+
Provider secret values are supplied at runtime through the encrypted
|
|
23
|
+
`app_secrets` vault, `saveCredential` / `resolveCredential`, OAuth, or
|
|
24
|
+
`${keys.NAME}` substitution. Deployment configuration is reserved for
|
|
25
|
+
deploy-level secrets and non-provider configuration. Examples must use obvious
|
|
26
|
+
placeholders such as `<OPENAI_API_KEY>` or `${keys.SLACK_WEBHOOK}`, not
|
|
27
|
+
real-looking copied values.
|
|
28
|
+
|
|
29
|
+
Provider credentials and provider account identifiers are workspace data. Use
|
|
30
|
+
standard workspace connections and org/workspace vault scopes; never put them
|
|
31
|
+
in `.env` or deployment environment variables, and never add a provider-specific
|
|
32
|
+
action or startup bootstrap just to write a credential for one organization.
|
|
26
33
|
|
|
27
34
|
## Credential Modeling Preflight
|
|
28
35
|
|
|
@@ -22,7 +22,10 @@ export function meta() {
|
|
|
22
22
|
|
|
23
23
|
export default function SettingsRoute() {
|
|
24
24
|
const t = useT();
|
|
25
|
-
|
|
25
|
+
// /extensions redirects here and the agent's own navigate instructions list
|
|
26
|
+
// "extensions" as a workspace view, so the settings tab must exist too —
|
|
27
|
+
// otherwise /settings/extensions silently falls back to General.
|
|
28
|
+
const agentSettingsTabs = useAgentSettingsTabs({ extensionTools: true });
|
|
26
29
|
useSetPageTitle(t("settings.title"));
|
|
27
30
|
|
|
28
31
|
const generalSearchEntries = useMemo<SettingsSearchEntry[]>(
|