@agent-native/core 0.149.6 → 0.151.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.
Files changed (177) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  3. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +83 -45
  4. package/corpus/templates/analytics/changelog/2026-08-11-analytics-dashboards-keep-the-selected-tab-when-reopened.md +6 -0
  5. package/corpus/templates/assets/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  6. package/corpus/templates/brain/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  7. package/corpus/templates/calendar/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  8. package/corpus/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  9. package/corpus/templates/clips/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  10. package/corpus/templates/clips/changelog/2026-08-11-clips-recovers-from-transient-updates-and-avoids-macos-priva.md +6 -0
  11. package/corpus/templates/clips/desktop/src/app.tsx +32 -176
  12. package/corpus/templates/clips/desktop/src/lib/updater.ts +52 -37
  13. package/corpus/templates/clips/desktop/src/styles.css +1 -166
  14. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -0
  15. package/corpus/templates/clips/desktop/src-tauri/Info.plist +4 -0
  16. package/corpus/templates/clips/desktop/src-tauri/src/eventkit.rs +28 -1
  17. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
  18. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +48 -1
  19. package/corpus/templates/clips/desktop/src-tauri/src/permission_status.rs +3 -0
  20. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +5 -0
  21. package/corpus/templates/clips/server/plugins/auth.ts +3 -0
  22. package/corpus/templates/content/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  23. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +2 -1
  24. package/corpus/templates/design/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  25. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +5 -1
  26. package/corpus/templates/design/changelog/2026-08-11-design-agent-prompts-copy-successfully-in-the-desktop-app.md +6 -0
  27. package/corpus/templates/dispatch/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  28. package/corpus/templates/dispatch/app/global.css +2 -2
  29. package/corpus/templates/dispatch/changelog/2026-08-10-desktop-workspace-sign-in.md +6 -0
  30. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-no-longer-shows-the-generic-chat-starter-among-defa.md +6 -0
  31. package/corpus/templates/dispatch/changelog/2026-08-11-integrations-and-scheduled-work-open-as-first-class-electron-pages.md +6 -0
  32. package/corpus/templates/dispatch/changelog/2026-08-11-sidebar-text-is-slightly-easier-to-read-in-light-and-dark-mo.md +6 -0
  33. package/corpus/templates/dispatch/server/lib/identity-sso.ts +57 -31
  34. package/corpus/templates/dispatch/server/plugins/feature-flags.ts +5 -0
  35. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +82 -33
  36. package/corpus/templates/dispatch/server/plugins/org-apps-directory.ts +7 -15
  37. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +9 -1
  38. package/corpus/templates/dispatch/shared/feature-flags.ts +15 -0
  39. package/corpus/templates/factory/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  40. package/corpus/templates/forms/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  41. package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +8 -7
  42. package/corpus/templates/forms/app/hooks/use-forms.ts +8 -1
  43. package/corpus/templates/forms/changelog/2026-08-11-fixed-public-forms-so-conditional-questions-stay-hidden-unti.md +6 -0
  44. package/corpus/templates/forms/server/handlers/submissions.ts +2 -1
  45. package/corpus/templates/forms/server/lib/public-form-ssr.ts +37 -2
  46. package/corpus/templates/forms/shared/page-url.ts +54 -0
  47. package/corpus/templates/forms/shared/types.ts +4 -4
  48. package/corpus/templates/mail/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  49. package/corpus/templates/plan/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  50. package/corpus/templates/slides/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  51. package/corpus/templates/slides/actions/add-slide.ts +0 -27
  52. package/corpus/templates/slides/actions/import-file.ts +28 -9
  53. package/corpus/templates/slides/actions/view-screen.ts +4 -4
  54. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +8 -1
  55. package/corpus/templates/slides/app/components/editor/AiEditingMarker.tsx +25 -0
  56. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +43 -48
  57. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +9 -6
  58. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -9
  59. package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +2 -0
  60. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +0 -26
  61. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +904 -217
  62. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
  63. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +11 -0
  64. package/corpus/templates/slides/app/components/editor/inline-edit-session.ts +16 -0
  65. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +22 -6
  66. package/corpus/templates/slides/app/components/editor/style-clipboard.ts +61 -0
  67. package/corpus/templates/slides/app/components/layout/Layout.tsx +26 -2
  68. package/corpus/templates/slides/app/components/ui/context-menu.tsx +1 -0
  69. package/corpus/templates/slides/app/context/DeckContext.tsx +75 -25
  70. package/corpus/templates/slides/app/global.css +1 -1
  71. package/corpus/templates/slides/app/i18n/en-US.ts +6 -1
  72. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -1
  73. package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +12 -12
  74. package/corpus/templates/slides/app/lib/slide-agent-context.ts +52 -0
  75. package/corpus/templates/slides/app/lib/slide-image-replacement.ts +61 -0
  76. package/corpus/templates/slides/app/pages/DeckEditor.tsx +55 -116
  77. package/corpus/templates/slides/app/pages/Index.tsx +28 -3
  78. package/corpus/templates/slides/changelog/2026-08-11-agent-chat-now-follows-the-current-slide-and-selection.md +6 -0
  79. package/corpus/templates/slides/changelog/2026-08-11-deck-titles-now-use-slide-content-instead-of-placeholder-filenames.md +6 -0
  80. package/corpus/templates/slides/changelog/2026-08-11-history-now-opens-from-the-deck-overflow-menu.md +6 -0
  81. package/corpus/templates/slides/changelog/2026-08-11-images-can-be-dropped-directly-onto-slides-and-resized-or-re.md +6 -0
  82. package/corpus/templates/slides/changelog/2026-08-11-new-decks-now-transition-directly-to-a-full-page-editor-load.md +6 -0
  83. package/corpus/templates/slides/changelog/2026-08-11-selected-slide-layers-can-be-nudged-precisely-with-arrow-key.md +6 -0
  84. package/corpus/templates/slides/changelog/2026-08-11-slides-can-copy-and-paste-element-styles-with-cmd-option-c-v.md +6 -0
  85. package/corpus/templates/slides/changelog/2026-08-11-slides-shows-one-compact-ai-marker-and-keeps-your-current-sl.md +6 -0
  86. package/corpus/templates/slides/changelog/2026-08-11-text-edits-preserve-slide-layout-when-no-changes-are-made.md +6 -0
  87. package/corpus/templates/slides/changelog/2026-08-11-the-style-toolbar-now-uses-the-full-available-width.md +6 -0
  88. package/corpus/templates/slides/changelog/2026-08-11-undo-and-redo-now-move-one-visible-slide-state-at-a-time.md +6 -0
  89. package/corpus/templates/slides/server/lib/chat-attachments.ts +6 -2
  90. package/corpus/templates/slides/shared/deck-title.ts +56 -0
  91. package/corpus/templates/tasks/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  92. package/dist/agent/run-store.js +6 -1
  93. package/dist/agent/thread-data-builder.js +19 -44
  94. package/dist/agent/types.d.ts +13 -0
  95. package/dist/catalog.json +1 -0
  96. package/dist/cli/create.js +1 -1
  97. package/dist/client/AssistantChat.js +58 -10
  98. package/dist/client/FileStorageSetupCard.d.ts +6 -0
  99. package/dist/client/FileStorageSetupCard.js +18 -0
  100. package/dist/client/NewWorkspaceAppFlow.js +10 -0
  101. package/dist/client/agent-chat-adapter.js +87 -11
  102. package/dist/client/agent-chat.d.ts +1 -1
  103. package/dist/client/agent-chat.js +8 -3
  104. package/dist/client/chat/attachment-adapters.js +10 -0
  105. package/dist/client/chat/message-components.d.ts +4 -1
  106. package/dist/client/chat/message-components.js +37 -13
  107. package/dist/client/chat/tool-call-display.js +14 -0
  108. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  109. package/dist/client/chat-first/apps-rail.js +6 -6
  110. package/dist/client/chat-first/copy.js +1 -0
  111. package/dist/client/chat-first/types.d.ts +1 -0
  112. package/dist/client/extensions/EmbeddedExtension.js +1 -1
  113. package/dist/client/extensions/ExtensionViewer.js +1 -1
  114. package/dist/client/extensions/InlineExtensionFrame.js +1 -1
  115. package/dist/client/onboarding/FirstRunOnboarding.js +17 -2
  116. package/dist/client/settings/FileStorageSettingsForm.d.ts +2 -0
  117. package/dist/client/settings/FileStorageSettingsForm.js +113 -0
  118. package/dist/client/settings/SettingsPanel.js +2 -1
  119. package/dist/client/sse-event-processor.js +21 -0
  120. package/dist/collab/awareness.d.ts +2 -2
  121. package/dist/collab/struct-routes.d.ts +1 -1
  122. package/dist/extensions/actions.js +2 -2
  123. package/dist/extensions/html-shell.js +3 -1
  124. package/dist/file-upload/pre-upload-attachments.d.ts +15 -10
  125. package/dist/file-upload/pre-upload-attachments.js +68 -24
  126. package/dist/file-upload/registry.d.ts +4 -5
  127. package/dist/file-upload/registry.js +7 -14
  128. package/dist/file-upload/s3.d.ts +9 -0
  129. package/dist/file-upload/s3.js +168 -0
  130. package/dist/file-upload/types.d.ts +3 -3
  131. package/dist/file-upload/types.js +2 -2
  132. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  133. package/dist/notifications/routes.d.ts +6 -6
  134. package/dist/observability/routes.d.ts +3 -3
  135. package/dist/onboarding/app-profile.js +23 -1
  136. package/dist/onboarding/default-steps.js +69 -0
  137. package/dist/onboarding/types.d.ts +2 -0
  138. package/dist/progress/routes.d.ts +1 -1
  139. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  140. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  141. package/dist/secrets/routes.d.ts +3 -3
  142. package/dist/server/agent-chat/browser-team-tools.js +20 -0
  143. package/dist/server/agent-chat-plugin.js +12 -3
  144. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  145. package/dist/server/auth.js +45 -21
  146. package/dist/server/better-auth-instance.d.ts +2 -0
  147. package/dist/server/better-auth-instance.js +2 -2
  148. package/dist/server/core-routes-plugin.d.ts +1 -0
  149. package/dist/server/core-routes-plugin.js +26 -16
  150. package/dist/server/identity-sso-store.d.ts +3 -0
  151. package/dist/server/identity-sso-store.js +45 -0
  152. package/dist/server/identity-sso.d.ts +20 -10
  153. package/dist/server/identity-sso.js +79 -31
  154. package/dist/server/index.d.ts +1 -1
  155. package/dist/server/index.js +1 -1
  156. package/dist/server/realtime-token.d.ts +1 -1
  157. package/dist/server/transcribe-voice.d.ts +1 -1
  158. package/dist/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  159. package/dist/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  160. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
  161. package/dist/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  162. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
  163. package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
  164. package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
  165. package/docs/content/authentication.mdx +27 -0
  166. package/docs/content/cross-app-sso.mdx +38 -8
  167. package/docs/content/extensions.mdx +15 -15
  168. package/docs/content/file-uploads.mdx +10 -12
  169. package/docs/content/generative-ui.mdx +6 -1
  170. package/package.json +2 -2
  171. package/src/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  172. package/src/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  173. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
  174. package/src/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  175. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
  176. package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
  177. package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
@@ -1,7 +1,8 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // Owns: message-timestamp helpers, SelectionAttachedPill, UserMessage,
3
3
  // AssistantMessage, MessageBranchPicker, CheckpointContext, MessageActionsContext,
4
- // RunningActivityStatus, ThinkingIndicator, and displayableUserMessageText.
4
+ // UserStoppedRunContext, RunningActivityStatus, ThinkingIndicator, and
5
+ // displayableUserMessageText.
5
6
  import { isPastedTextAttachmentName } from "@agent-native/toolkit/composer/pasted-text";
6
7
  import { PastedTextChip } from "@agent-native/toolkit/composer/PastedTextChip";
7
8
  import { useThread, useMessageRuntime, useComposer, MessagePrimitive, ActionBarPrimitive, BranchPickerPrimitive, ComposerPrimitive, useMessagePartReasoning, useMessagePartRuntime, useAuiState, } from "@assistant-ui/react";
@@ -181,6 +182,10 @@ export function assistantMessageTurnId(message) {
181
182
  ? metadata.turnId
182
183
  : undefined;
183
184
  }
185
+ export function assistantMessageWasUserStopped(message) {
186
+ const metadata = message?.metadata;
187
+ return (metadata?.custom?.userStopped === true || metadata?.userStopped === true);
188
+ }
184
189
  export function resolveAssistantRequestId(message, activeRun, threadId) {
185
190
  return (assistantMessageRunId(message) ||
186
191
  (activeRun?.threadId === threadId ? activeRun.runId : undefined));
@@ -630,7 +635,10 @@ export function shouldShowAssistantMessageFooter({ isLast, chatRunning, activeRu
630
635
  * that the agent stopped.
631
636
  */
632
637
  export const ServerRunActiveContext = React.createContext(false);
633
- export function shouldShowMissingFinalResponse({ isCurrentTurnRunning, serverRunActive, statusIsTerminal, hasAssistantText, hasUnresolvedTool, hasCompletedCustomUi, }) {
638
+ export const UserStoppedRunContext = React.createContext(() => false);
639
+ export function shouldShowMissingFinalResponse({ isCurrentTurnRunning, serverRunActive, statusIsTerminal, hasAssistantText, hasUnresolvedTool, hasCompletedCustomUi, userStoppedRun, }) {
640
+ if (userStoppedRun)
641
+ return false;
634
642
  if (serverRunActive)
635
643
  return false;
636
644
  // A completed tool can make the latest message look terminal before the
@@ -703,7 +711,10 @@ function ReasoningMessagePart() {
703
711
  }, [isStreaming]);
704
712
  return (_jsx(ReasoningCell, { text: part.text, isStreaming: isStreaming, resetKey: `message-reasoning-${partIndex}`, durationMs: durationMs, defaultOpen: partIndex === latestReasoningPartIndex, collapseWhenReplaced: partIndex < latestReasoningPartIndex }));
705
713
  }
706
- const ALWAYS_VISIBLE_ASSISTANT_TOOLS = new Set(["connect-builder"]);
714
+ const ALWAYS_VISIBLE_ASSISTANT_TOOLS = new Set([
715
+ "connect-builder",
716
+ "connect-file-storage",
717
+ ]);
707
718
  export function isAlwaysVisibleAssistantTool(part) {
708
719
  return (part.type === "tool-call" &&
709
720
  typeof part.toolName === "string" &&
@@ -805,6 +816,10 @@ export function AssistantMessage() {
805
816
  const isLast = thread.messages.length > 0 &&
806
817
  thread.messages[thread.messages.length - 1].id === msg.id;
807
818
  const wasLiveRef = useRef(false);
819
+ const messageRunId = assistantMessageRunId(msg);
820
+ const messageTurnId = assistantMessageTurnId(msg);
821
+ const userStoppedRun = React.useContext(UserStoppedRunContext);
822
+ const isUserStoppedRun = assistantMessageWasUserStopped(msg) || userStoppedRun(messageRunId);
808
823
  const hasRenderableContent = assistantMessageHasRenderableContent(msg);
809
824
  const hasUnresolvedTool = assistantMessageHasUnresolvedTool(msg.content);
810
825
  const missingWarningText = missingFinalResponseWarningText(msg.content);
@@ -812,15 +827,14 @@ export function AssistantMessage() {
812
827
  const statusIsTerminal = assistantMessageStatusIsTerminal(msg);
813
828
  const hasCompletedCustomUi = assistantMessageHasCompletedCustomUi(msg.content);
814
829
  const hasCustomUi = assistantMessageHasCustomUi(msg.content);
815
- const messageRunId = assistantMessageRunId(msg);
816
- const messageTurnId = assistantMessageTurnId(msg);
817
830
  const serverRunActive = React.useContext(ServerRunActiveContext);
818
831
  const messageRunError = getRunErrorMetadata(msg);
819
832
  const messageActions = React.useContext(MessageActionsContext);
820
- const showInlineRunError = shouldShowInlineRunError({
821
- runError: messageRunError,
822
- bannerRunErrorKey: messageActions?.bannerRunErrorKey,
823
- });
833
+ const showInlineRunError = !isUserStoppedRun &&
834
+ shouldShowInlineRunError({
835
+ runError: messageRunError,
836
+ bannerRunErrorKey: messageActions?.bannerRunErrorKey,
837
+ });
824
838
  const missingFinalResponseCandidate = missingWarningText == null &&
825
839
  shouldShowMissingFinalResponse({
826
840
  isCurrentTurnRunning: isLast && chatRunning,
@@ -829,12 +843,18 @@ export function AssistantMessage() {
829
843
  hasAssistantText: responseConnectionText.trim().length > 0,
830
844
  hasUnresolvedTool,
831
845
  hasCompletedCustomUi,
846
+ userStoppedRun: isUserStoppedRun,
832
847
  });
833
848
  const showMissingFinalResponse = useSettledFlag(missingFinalResponseCandidate, isLast ? MISSING_FINAL_RESPONSE_SETTLE_MS : 0);
834
- const missingFinalResponseNoticeText = missingWarningText ??
835
- (showMissingFinalResponse
836
- ? "The agent stopped without sending a final message. Ask the agent to continue or retry."
837
- : null);
849
+ const shouldShowUserStoppedNotice = isUserStoppedRun && isLast && responseConnectionText.trim().length === 0;
850
+ const missingFinalResponseNoticeText = shouldShowUserStoppedNotice
851
+ ? "Stopped"
852
+ : isUserStoppedRun
853
+ ? null
854
+ : (missingWarningText ??
855
+ (showMissingFinalResponse
856
+ ? "The agent stopped without sending a final message. Ask the agent to continue or retry."
857
+ : null));
838
858
  const animateMissingFinalResponse = Boolean(isLast && missingFinalResponseNoticeText && wasLiveRef.current);
839
859
  const missingFinalResponseAnimationKey = animateMissingFinalResponse
840
860
  ? missingFinalResponseNoticeText
@@ -999,6 +1019,10 @@ export function AssistantMessage() {
999
1019
  return (_jsx(RanToolsSummary, { toolCount: toolCount, children: children }));
1000
1020
  }
1001
1021
  case "text":
1022
+ if (isUserStoppedRun &&
1023
+ isMissingFinalResponseWarningText(part.text)) {
1024
+ return shouldShowUserStoppedNotice ? (_jsx(MissingFinalResponseNotice, { messageId: msg.id, text: "Stopped", animate: false })) : null;
1025
+ }
1002
1026
  if (missingWarningText != null &&
1003
1027
  part.text === missingWarningText) {
1004
1028
  return (_jsx(MissingFinalResponseNotice, { messageId: msg.id, text: part.text, animate: animateMissingFinalResponse, onRevealComplete: handleMissingFinalResponseReveal }));
@@ -5,6 +5,7 @@ import { AgentTaskCard } from "../AgentTaskCard.js";
5
5
  import { writeClipboardText } from "../clipboard.js";
6
6
  import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
7
7
  import { ConnectBuilderCard } from "../ConnectBuilderCard.js";
8
+ import { FileStorageSetupCard } from "../FileStorageSetupCard.js";
8
9
  import { useT } from "../i18n.js";
9
10
  import { McpAppRenderer } from "../mcp-apps/McpAppRenderer.js";
10
11
  import { findMcpIntegrationForToolName } from "../resources/mcp-integration-catalog.js";
@@ -323,6 +324,19 @@ function ToolCallDisplayGeneric({ toolName, toolCallId, argsText, args, result,
323
324
  }
324
325
  }
325
326
  catch {
327
+ // coercion-ok: malformed tool output should fall through to the default tool pill
328
+ // fall through to default pill rendering
329
+ }
330
+ }
331
+ if (toolName === "connect-file-storage" && result) {
332
+ try {
333
+ const parsed = JSON.parse(result);
334
+ if (parsed?.kind === "connect-file-storage-card") {
335
+ return _jsx(FileStorageSetupCard, {});
336
+ }
337
+ }
338
+ catch {
339
+ // coercion-ok: malformed storage tool output should fall through to the default tool pill
326
340
  // fall through to default pill rendering
327
341
  }
328
342
  }
@@ -1,2 +1,2 @@
1
1
  import type { ChatFirstAppRailProps } from "./types.js";
2
- export declare const ChatFirstAppsRail: import("react").MemoExoticComponent<({ apps, activeAppId, loading, error, collapsed, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy, }: ChatFirstAppRailProps) => import("react").JSX.Element>;
2
+ export declare const ChatFirstAppsRail: import("react").MemoExoticComponent<({ apps, activeAppId, loading, error, collapsed, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onRemoveApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy, }: ChatFirstAppRailProps) => import("react").JSX.Element>;
@@ -1,11 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, Skeleton, Tooltip, TooltipContent, TooltipTrigger, } from "@agent-native/toolkit/ui";
3
- import { IconApps, IconChevronDown, IconChevronUp, IconPin, IconPlus, } from "@tabler/icons-react";
3
+ import { IconApps, IconChevronDown, IconChevronUp, IconPin, IconPlus, IconTrash, } from "@tabler/icons-react";
4
4
  import { memo, useMemo, useState } from "react";
5
5
  import { orderChatFirstAppIds, readChatFirstAppLayout, writeChatFirstAppLayout, } from "../chat-first.js";
6
6
  import { cn } from "../utils.js";
7
7
  import { defaultChatFirstCopy } from "./copy.js";
8
- function AppRows({ apps, activeAppId, layout, onDragStart, onDrop, onDragEnd, onOpenApp, onTogglePinned, onMove, renderIcon, copy, }) {
8
+ function AppRows({ apps, activeAppId, layout, onDragStart, onDrop, onDragEnd, onOpenApp, onRemoveApp, onTogglePinned, onMove, renderIcon, copy, }) {
9
9
  const orderedIds = orderChatFirstAppIds(apps.map((app) => app.id), layout);
10
10
  const appsById = new Map(apps.map((app) => [app.id, app]));
11
11
  const orderedApps = orderedIds
@@ -33,10 +33,10 @@ function AppRows({ apps, activeAppId, layout, onDragStart, onDrop, onDragEnd, on
33
33
  }
34
34
  }, "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown", "aria-label": copy("openApp", { name: app.name }), children: [renderIcon(app), _jsx("span", { className: "truncate", children: app.name })] }), _jsx("button", { type: "button", className: cn("flex size-6 shrink-0 items-center justify-center rounded text-sidebar-foreground/45 opacity-0 hover:bg-sidebar-accent hover:text-sidebar-foreground group-hover:opacity-100 focus-visible:opacity-100", pinned && "text-sidebar-foreground/70 opacity-100"), "aria-label": copy(pinned ? "unpinApp" : "pinApp", {
35
35
  name: app.name,
36
- }), "aria-pressed": pinned, onClick: () => onTogglePinned(app.id), children: _jsx(IconPin, { size: 13, strokeWidth: pinned ? 2.2 : 1.6, "aria-hidden": "true" }) }), _jsx("span", { className: "sr-only", children: `${index + 1} of ${orderedApps.length}` })] }) }), _jsxs(ContextMenuContent, { children: [_jsxs(ContextMenuItem, { onSelect: () => onTogglePinned(app.id), children: [_jsx(IconPin, { size: 14, "aria-hidden": "true" }), pinned ? copy("removePinned") : copy("pinTop")] }), _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { disabled: index === 0, onSelect: () => onMove(app.id, -1), children: [_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" }), copy("moveUp")] }), _jsxs(ContextMenuItem, { disabled: index === orderedApps.length - 1, onSelect: () => onMove(app.id, 1), children: [_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" }), copy("moveDown")] })] })] }, app.id));
36
+ }), "aria-pressed": pinned, onClick: () => onTogglePinned(app.id), children: _jsx(IconPin, { size: 13, strokeWidth: pinned ? 2.2 : 1.6, "aria-hidden": "true" }) }), _jsx("span", { className: "sr-only", children: `${index + 1} of ${orderedApps.length}` })] }) }), _jsxs(ContextMenuContent, { children: [_jsxs(ContextMenuItem, { onSelect: () => onTogglePinned(app.id), children: [_jsx(IconPin, { size: 14, "aria-hidden": "true" }), pinned ? copy("removePinned") : copy("pinTop")] }), _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { disabled: index === 0, onSelect: () => onMove(app.id, -1), children: [_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" }), copy("moveUp")] }), _jsxs(ContextMenuItem, { disabled: index === orderedApps.length - 1, onSelect: () => onMove(app.id, 1), children: [_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" }), copy("moveDown")] }), onRemoveApp ? (_jsxs(_Fragment, { children: [_jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { onSelect: () => onRemoveApp(app), className: "text-destructive focus:text-destructive", children: [_jsx(IconTrash, { size: 14, "aria-hidden": "true" }), copy("removeApp")] })] })) : null] })] }, app.id));
37
37
  }) }));
38
38
  }
39
- export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, activeAppId, loading = false, error, collapsed = false, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy = defaultChatFirstCopy, }) {
39
+ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, activeAppId, loading = false, error, collapsed = false, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onRemoveApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy = defaultChatFirstCopy, }) {
40
40
  const [localLayout, setLocalLayout] = useState(() => readChatFirstAppLayout());
41
41
  const layout = controlledLayout ?? localLayout;
42
42
  const [draggedAppId, setDraggedAppId] = useState(null);
@@ -99,5 +99,5 @@ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, activeA
99
99
  ? "bg-sidebar-accent text-sidebar-accent-foreground"
100
100
  : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), title: app.name, children: renderIcon(app) }, app.id))), onOpenAllApps ? (_jsx("button", { type: "button", "data-chat-first-all-apps": true, className: "flex size-9 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: onOpenAllApps, "aria-label": copy("allApps"), title: copy("allApps"), children: _jsx(IconApps, { size: 16, "aria-hidden": "true" }) })) : null, error ? (_jsx("span", { className: "size-1.5 rounded-full bg-destructive", title: error, "aria-label": error })) : null] }));
101
101
  }
102
- return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-2 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-2 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-2", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-8 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-2", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-2 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
102
+ return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-2 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-2 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-2", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-8 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-2", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onRemoveApp: onRemoveApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-2 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
103
103
  });
@@ -18,6 +18,7 @@ const DEFAULT_COPY = {
18
18
  pinTop: "Pin app to the top",
19
19
  moveUp: "Move up",
20
20
  moveDown: "Move down",
21
+ removeApp: "Remove app from workspace",
21
22
  showMore: "Show more",
22
23
  showLess: "Show less",
23
24
  appOrderUnavailable: "App order could not be saved on this device.",
@@ -47,6 +47,7 @@ export interface ChatFirstAppRailProps {
47
47
  onLayoutError?: (reason: "unavailable" | "write-failed") => void;
48
48
  onRetry?: () => void;
49
49
  onOpenApp: (app: ChatFirstAppItem) => void;
50
+ onRemoveApp?: (app: ChatFirstAppItem) => void;
50
51
  onOpenAllApps?: () => void;
51
52
  onCreateApp?: () => void;
52
53
  createAppTrigger?: ReactNode;
@@ -235,7 +235,7 @@ export function EmbeddedExtension({ extensionId, slotId, context, className, ini
235
235
  sendToAgentChat({
236
236
  message: text,
237
237
  context: serializeChatValue(message.context),
238
- submit: message.submit !== false,
238
+ submit: message.submit === true,
239
239
  openSidebar: message.openSidebar !== false,
240
240
  });
241
241
  return;
@@ -476,7 +476,7 @@ export function ExtensionViewer({ extensionId }) {
476
476
  sendToAgentChat({
477
477
  message: text,
478
478
  context: serializeChatValue(message.context),
479
- submit: message.submit !== false,
479
+ submit: message.submit === true,
480
480
  openSidebar: message.openSidebar !== false,
481
481
  });
482
482
  return;
@@ -361,7 +361,7 @@ export function InlineExtensionFrame({ extensionId, extension: providedExtension
361
361
  sendToAgentChat({
362
362
  message: text,
363
363
  context: serializeChatValue(message.context),
364
- submit: message.submit !== false,
364
+ submit: message.submit === true,
365
365
  openSidebar: message.openSidebar !== false,
366
366
  });
367
367
  return;
@@ -226,7 +226,16 @@ function OnboardingSkeleton() {
226
226
  return (_jsxs("div", { className: "fixed inset-0 z-[100] flex h-full min-h-0 flex-col bg-background", "data-onboarding-loading": "true", "aria-busy": "true", children: [_jsx(Skeleton, { className: "h-0.5 w-full shrink-0" }), _jsxs("div", { className: "mx-auto flex w-full max-w-lg flex-1 flex-col items-center justify-center px-5 sm:px-8", children: [_jsx(Skeleton, { className: "h-10 w-64" }), _jsx(Skeleton, { className: "mt-3 h-10 w-52" }), _jsx(Skeleton, { className: "mt-7 h-3 w-44" }), _jsx(Skeleton, { className: "mt-7 h-10 w-24 rounded-lg" })] })] }));
227
227
  }
228
228
  function CapabilityList({ capabilities, compact = false, className, }) {
229
- const visibleCapabilities = useMemo(() => (compact ? capabilities.slice(0, 4) : capabilities), [capabilities, compact]);
229
+ const visibleCapabilities = useMemo(() => {
230
+ if (!compact)
231
+ return capabilities;
232
+ const suggested = capabilities.filter((capability) => capability.suggested);
233
+ const leading = capabilities.filter((capability) => !capability.suggested);
234
+ return [
235
+ ...leading.slice(0, Math.max(0, 4 - suggested.length)),
236
+ ...suggested,
237
+ ].slice(0, 4);
238
+ }, [capabilities, compact]);
230
239
  return (_jsxs("div", { className: cn("grid", className), children: [!compact && (_jsx("p", { className: "mb-2 text-xs font-medium text-muted-foreground", children: "Keys and integrations" })), _jsx("div", { className: "divide-y divide-border", children: visibleCapabilities.map((capability) => (_jsx(CapabilityRow, { capability: capability, compact: compact }, capability.id))) })] }));
231
240
  }
232
241
  function McpIntegrationCard({ integration, connected, busy, onConnect, }) {
@@ -234,7 +243,13 @@ function McpIntegrationCard({ integration, connected, busy, onConnect, }) {
234
243
  return (_jsxs("article", { className: "flex min-w-0 items-center gap-3 border-b border-border/70 py-3.5 sm:pe-4", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [_jsx(McpIntegrationLogo, { name: integration.name, logoUrl: integration.logoUrl, integrationId: integration.id, className: "size-8 rounded-md", imageClassName: "size-full p-1" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "truncate text-sm font-medium text-foreground", children: integration.name }), connected ? (_jsx("span", { className: "rounded-full border border-border bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground", children: "Connected" })) : null] }), _jsx("p", { className: "mt-1 line-clamp-2 text-[11px] leading-4 text-muted-foreground", children: integration.description })] })] }), _jsxs("button", { type: "button", onClick: () => onConnect(integration), disabled: connected || busy, "aria-label": `${actionLabel} ${integration.name}`, className: "inline-flex min-h-8 shrink-0 items-center justify-center gap-1.5 rounded-md border border-border bg-background px-3 text-[11px] font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", children: [busy ? _jsx(IconLoader2, { className: "animate-spin", size: 13 }) : null, connected ? "Connected" : actionLabel] })] }));
235
244
  }
236
245
  function CapabilityRow({ capability, compact, }) {
237
- return (_jsxs("div", { className: cn("flex items-start justify-between gap-3", compact ? "py-2" : "py-3"), children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: cn("font-medium", compact ? "text-[11px]" : "text-sm"), children: capability.label }), _jsx(CapabilityInfoButton, { capability: capability, ariaLabel: `Why ${capability.label} is needed` })] }), _jsx("p", { className: "mt-1 text-[11px] leading-4 text-muted-foreground", children: capability.keySummary })] }), _jsx("span", { className: cn("shrink-0 text-[10px] uppercase tracking-[0.08em]", capability.required ? "text-primary" : "text-muted-foreground"), children: capability.required ? "Required" : "Optional" })] }));
246
+ return (_jsxs("div", { className: cn("flex items-start justify-between gap-3", compact ? "py-2" : "py-3"), children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: cn("font-medium", compact ? "text-[11px]" : "text-sm"), children: capability.label }), _jsx(CapabilityInfoButton, { capability: capability, ariaLabel: `Why ${capability.label} is needed` })] }), _jsx("p", { className: "mt-1 text-[11px] leading-4 text-muted-foreground", children: capability.keySummary })] }), _jsx("span", { className: cn("shrink-0 text-[10px] uppercase tracking-[0.08em]", capability.required || capability.suggested
247
+ ? "text-primary"
248
+ : "text-muted-foreground"), children: capability.required
249
+ ? "Required"
250
+ : capability.suggested
251
+ ? "Suggested"
252
+ : "Optional" })] }));
238
253
  }
239
254
  function CapabilityInfoButton({ capability, ariaLabel, }) {
240
255
  return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": ariaLabel, className: "inline-flex size-4 items-center justify-center rounded-full text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: _jsx(IconInfoCircle, { size: 13 }) }) }), _jsx(TooltipContent, { side: "top", className: "max-w-xs text-xs", children: capability.why })] }));
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function FileStorageSettingsForm(): React.JSX.Element;
@@ -0,0 +1,113 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TextField } from "@agent-native/toolkit/design-system";
3
+ import { Button as ToolkitButton } from "@agent-native/toolkit/ui/button";
4
+ import { IconCheck, IconLoader2 } from "@tabler/icons-react";
5
+ import { useEffect, useState } from "react";
6
+ import { agentNativePath } from "../api-path.js";
7
+ const STATUS_ENDPOINT = agentNativePath("/_agent-native/file-upload/status");
8
+ const SECRET_ENDPOINT = agentNativePath("/_agent-native/secrets/adhoc");
9
+ const STORAGE_FIELDS = [
10
+ {
11
+ key: "S3_ENDPOINT",
12
+ label: "Endpoint URL",
13
+ placeholder: "https://s3.us-east-1.amazonaws.com",
14
+ },
15
+ { key: "S3_BUCKET", label: "Bucket name", placeholder: "my-uploads-bucket" },
16
+ {
17
+ key: "S3_ACCESS_KEY_ID",
18
+ label: "Access key ID",
19
+ placeholder: "AKIA...",
20
+ },
21
+ {
22
+ key: "S3_SECRET_ACCESS_KEY",
23
+ label: "Secret access key",
24
+ placeholder: "Paste your secret access key",
25
+ secret: true,
26
+ },
27
+ { key: "S3_REGION", label: "Region", placeholder: "auto" },
28
+ {
29
+ key: "S3_PUBLIC_BASE_URL",
30
+ label: "Public base URL",
31
+ placeholder: "https://cdn.example.com",
32
+ },
33
+ ];
34
+ export function FileStorageSettingsForm() {
35
+ const [values, setValues] = useState({});
36
+ const [configured, setConfigured] = useState(null);
37
+ const [saving, setSaving] = useState(false);
38
+ const [error, setError] = useState(null);
39
+ const [saved, setSaved] = useState(false);
40
+ useEffect(() => {
41
+ let cancelled = false;
42
+ fetch(STATUS_ENDPOINT)
43
+ .then(async (response) => {
44
+ if (!response.ok)
45
+ throw new Error("Storage status unavailable");
46
+ return (await response.json());
47
+ })
48
+ .then((status) => {
49
+ if (!cancelled)
50
+ setConfigured(status.configured === true);
51
+ })
52
+ .catch(() => {
53
+ if (!cancelled)
54
+ setConfigured(null);
55
+ });
56
+ return () => {
57
+ cancelled = true;
58
+ };
59
+ }, [saved]);
60
+ const setValue = (key, value) => {
61
+ setValues((current) => ({ ...current, [key]: value }));
62
+ setSaved(false);
63
+ setError(null);
64
+ };
65
+ const handleSubmit = async (event) => {
66
+ event.preventDefault();
67
+ if (saving)
68
+ return;
69
+ const missing = STORAGE_FIELDS.filter((field) => field.key !== "S3_REGION" && !(values[field.key] ?? "").trim());
70
+ if (missing.length > 0) {
71
+ setError(`Enter ${missing[0]?.label.toLowerCase() ?? "all required values"}.`);
72
+ return;
73
+ }
74
+ setSaving(true);
75
+ setError(null);
76
+ try {
77
+ for (const field of STORAGE_FIELDS) {
78
+ const value = values[field.key]?.trim();
79
+ if (!value)
80
+ continue;
81
+ const response = await fetch(SECRET_ENDPOINT, {
82
+ method: "POST",
83
+ headers: { "Content-Type": "application/json" },
84
+ body: JSON.stringify({
85
+ name: field.key,
86
+ value,
87
+ scope: "workspace",
88
+ description: "S3-compatible object storage for file uploads",
89
+ }),
90
+ });
91
+ if (!response.ok) {
92
+ const body = (await response.json());
93
+ throw new Error(typeof body?.error === "string"
94
+ ? body.error
95
+ : `Could not save ${field.label}`);
96
+ }
97
+ }
98
+ setValues({});
99
+ setSaved(true);
100
+ setConfigured(true);
101
+ window.dispatchEvent(new CustomEvent("agent-engine:configured-changed"));
102
+ }
103
+ catch (cause) {
104
+ setError(cause instanceof Error ? cause.message : "Could not save storage keys");
105
+ }
106
+ finally {
107
+ setSaving(false);
108
+ }
109
+ };
110
+ return (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-2.5", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [configured ? (_jsx(IconCheck, { size: 13, className: "text-emerald-500" })) : null, configured
111
+ ? "Object storage is connected. Re-enter keys below only to rotate them."
112
+ : "Use a public base URL so attachment links remain stable throughout the thread."] }), _jsx("div", { className: "grid gap-2 sm:grid-cols-2", children: STORAGE_FIELDS.map((field) => (_jsx(TextField, { type: "secret" in field && field.secret ? "password" : "text", value: values[field.key] ?? "", onChange: (value) => setValue(field.key, value), "aria-label": field.label, placeholder: field.placeholder, className: "w-full text-[11px]" }, field.key))) }), error ? _jsx("p", { className: "text-[11px] text-destructive", children: error }) : null, saved ? (_jsx("p", { className: "text-[11px] text-emerald-600", children: "Storage keys saved." })) : null, _jsxs(ToolkitButton, { type: "submit", variant: "default", disabled: saving, className: "inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-[11px] font-medium", children: [saving ? _jsx(IconLoader2, { size: 13, className: "animate-spin" }) : null, saving ? "Saving…" : "Save storage keys"] })] }));
113
+ }
@@ -25,6 +25,7 @@ import { AgentsSection } from "./AgentsSection.js";
25
25
  import { AutomationsSection } from "./AutomationsSection.js";
26
26
  import { DemoModeSection } from "./DemoModeSection.js";
27
27
  import { ExtensionsSettingsContent } from "./ExtensionsSettingsContent.js";
28
+ import { FileStorageSettingsForm } from "./FileStorageSettingsForm.js";
28
29
  import { AgentProviderPicker } from "./ProviderSetupForm.js";
29
30
  import { SecretsSection } from "./SecretsSection.js";
30
31
  import { SettingsGroup, SettingsRow } from "./SettingsRow.js";
@@ -1054,7 +1055,7 @@ function SettingsPanelContent({ isDevMode, initialSection, sectionRequestKey, se
1054
1055
  const isWorkspacePage = isPage && sections.includes("hosting");
1055
1056
  return (_jsx(SettingsSurfaceProvider, { surface: surface, children: _jsxs("div", { className: cn(isPage ? "space-y-8" : "flex-1 min-h-0 overflow-y-auto p-3 space-y-2", className), style: isPage ? undefined : { overflowY: "auto" }, children: [showCapabilityStrip && (_jsx(CapabilityStatusStrip, { isDevMode: isDevMode, builderConnected: connected, builderLoading: builderLoading, builderBranchesAvailable: builderBranchesAvailable, onOpenLlm: () => openSettingsSection("llm", true) })), isPage &&
1056
1057
  ["llm", "app-models", "limits", "voice"].some((section) => shouldShowSection(section)) && (_jsxs(SettingsGroup, { title: "Agent", children: [shouldShowSection("llm") && (_jsx(LLMSectionInner, { builderFlow: builderFlow, builderLoading: builderLoading, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, grouped: true, open: openSection === "llm", onToggle: () => toggle("llm") })), shouldShowSection("app-models") && (_jsx(AppModelDefaultsSectionInner, { grouped: true, open: openSection === "app-models", onToggle: () => toggle("app-models") })), shouldShowSection("limits") && (_jsx(AgentLimitsSectionInner, { grouped: true, open: openSection === "limits", onToggle: () => toggle("limits") })), shouldShowSection("voice") && (_jsx(SettingsSection, { id: settingsSectionDomId("voice"), icon: _jsx(IconMicrophone, { size: 14 }), title: "Voice Transcription", subtitle: "Choose how voice input is transcribed.", grouped: true, flat: true, open: openSection === "voice", onToggle: () => toggle("voice"), children: _jsx(VoiceTranscriptionSection, { compact: true }) }))] })), isPage && agentAdditionalContent ? (_jsx(SettingsGroup, { title: "Notifications", children: agentAdditionalContent })) : null, isPage &&
1057
- ["automations", "background"].some((section) => shouldShowSection(section)) && (_jsxs(SettingsGroup, { title: "Agent workflows", children: [shouldShowSection("automations") && (_jsx(SettingsSection, { id: settingsSectionDomId("automations"), icon: _jsx(IconBolt, { size: 14 }), title: "Automations", subtitle: "Scheduled and event-triggered agent tasks.", grouped: true, flat: true, open: openSection === "automations", onToggle: () => toggle("automations"), children: _jsx(SettingsRow, { label: "Automations", description: "Schedule agent tasks or run them from events.", control: _jsxs("a", { href: "/settings/agent/automations", className: "inline-flex items-center gap-1 rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground no-underline transition-colors hover:bg-accent/40", children: ["Open automations", _jsx(IconExternalLink, { size: 14 })] }) }) })), shouldShowSection("background") && (_jsx(SettingsSection, { id: settingsSectionDomId("background"), icon: _jsx(IconGitBranch, { size: 14 }), title: "Background Agent", subtitle: "Make code changes from production mode via Builder.", grouped: true, flat: true, connected: connected, open: openSection === "background", onToggle: () => toggle("background"), children: _jsx(SettingsRow, { label: "Background agent", description: "Make code changes from production mode via Builder.", control: _jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "background_agent_settings", trackingFlow: "background_agent", compact: true }) }) }))] })), isWorkspacePage && (_jsxs(SettingsGroup, { title: "Workspace", children: [shouldShowSection("demo-mode") && (_jsx(SettingsRow, { id: settingsSectionDomId("demo-mode"), label: "Demo mode", description: "Use sample data in this browser for presentations.", control: _jsx(DemoModeSection, { compact: true }) })), shouldShowSection("hosting") && (_jsx(SettingsRow, { id: settingsSectionDomId("hosting"), label: "Hosting", description: "Deploy the app to the cloud.", control: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "hosting_settings", trackingFlow: "hosting", compact: true }), _jsx(ManualSetupCard, { title: "Set up manually", hint: "Deploy manually to Netlify, Vercel, Cloudflare, or any Nitro-supported target.", docsUrl: "https://www.builder.io/c/docs/agent-native-deployment?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=deployment_settings", dim: connected, bare: true, popover: true, popoverLabel: "Manage" })] }) })), shouldShowSection("database") && (_jsx(SettingsRow, { id: settingsSectionDomId("database"), label: "Database", description: "Connect persistent app storage.", control: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "database_settings", trackingFlow: "database", compact: true }), _jsx(ManualSetupCard, { title: "Set up manually", hint: "Set DATABASE_URL in your .env to connect a supported database.", docsUrl: "https://www.builder.io/c/docs/agent-native-database?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=database_settings", dim: connected, bare: true, popover: true, popoverLabel: "Manage" })] }) })), shouldShowSection("uploads") && (_jsx(SettingsRow, { id: settingsSectionDomId("uploads"), label: "File uploads", description: "Store avatars and chat attachments.", control: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "file_upload_settings", trackingFlow: "file_upload", compact: true }), _jsx(ManualSetupCard, { title: "Set up manually", hint: "Configure your own file or object storage provider.", docsUrl: "https://www.builder.io/c/docs/agent-native-file-uploads?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=file_upload_settings", dim: connected, bare: true, popover: true, popoverLabel: "Manage" })] }) })), shouldShowSection("auth") && (_jsx(SettingsRow, { id: settingsSectionDomId("auth"), label: "Authentication", description: "Set up sign-in and access control.", control: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "auth_settings", trackingFlow: "auth", compact: true }), _jsx(ManualSetupCard, { title: "Set up manually", hint: "Configure Better Auth and optional Google or GitHub providers.", docsUrl: "https://www.builder.io/c/docs/agent-native-authentication?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=authentication_settings", dim: connected, bare: true, popover: true, popoverLabel: "Manage" })] }) }))] })), !isPage && shouldShowSection("llm") && (_jsx(LLMSectionInner, { builderFlow: builderFlow, builderLoading: builderLoading, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, open: openSection === "llm", onToggle: () => toggle("llm") })), !isPage && shouldShowSection("app-models") && (_jsx(AppModelDefaultsSectionInner, { open: openSection === "app-models", onToggle: () => toggle("app-models") })), !isPage && shouldShowSection("limits") && (_jsx(AgentLimitsSectionInner, { open: openSection === "limits", onToggle: () => toggle("limits") })), !isPage && shouldShowSection("voice") && (_jsx(SettingsSection, { id: settingsSectionDomId("voice"), icon: _jsx(IconMicrophone, { size: 14 }), title: "Voice Transcription", subtitle: "How the composer microphone turns your voice into text.", flat: true, open: openSection === "voice", onToggle: () => toggle("voice"), children: _jsx(VoiceTranscriptionSection, {}) })), !isPage && shouldShowSection("demo-mode") && (_jsx(SettingsSection, { id: settingsSectionDomId("demo-mode"), icon: _jsx(IconEyeOff, { size: 14 }), title: "Demo mode", subtitle: "Replace displayed emails with realistic fake data in this browser and reshape supported charts for presentations. Backend, agent integrations, and agent results stay real and access-scoped.", flat: true, open: openSection === "demo-mode", onToggle: () => toggle("demo-mode"), children: _jsx(DemoModeSection, {}) })), !isPage && shouldShowSection("automations") && (_jsx(SettingsSection, { id: settingsSectionDomId("automations"), icon: _jsx(IconBolt, { size: 14 }), title: "Automations", subtitle: "Scheduled and event-triggered agent tasks.", flat: true, open: openSection === "automations", onToggle: () => toggle("automations"), children: _jsx(AutomationsSection, {}) })), shouldShowSection("secrets") && (_jsx(SettingsSection, { id: settingsSectionDomId("secrets"), icon: _jsx(IconKey, { size: 14 }), title: "API keys", subtitle: "Service credentials and automation keys.", flat: true, open: openSection === "secrets", onToggle: () => toggle("secrets"), children: _jsx(SecretsSection, { focusKey: focusSecretKey }) })), !isPage && shouldShowSection("hosting") && (_jsx(SettingsSection, { id: settingsSectionDomId("hosting"), icon: _jsx(IconCloud, { size: 14 }), title: "Hosting", subtitle: "Deploy your app to the cloud.", flat: true, connected: connected, open: openSection === "hosting", onToggle: () => toggle("hosting"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "hosting_settings", trackingFlow: "hosting" }), _jsx(ManualSetupCard, { hint: "Deploy manually to Netlify, Vercel, Cloudflare, or any Nitro-supported target.", docsUrl: "https://www.builder.io/c/docs/agent-native-deployment?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=deployment_settings", dim: connected })] }) })), !isPage && shouldShowSection("database") && (_jsx(SettingsSection, { id: settingsSectionDomId("database"), icon: _jsx(IconDatabase, { size: 14 }), title: "Database", subtitle: "Connect a cloud database for persistent storage.", flat: true, connected: connected, open: openSection === "database", onToggle: () => toggle("database"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "database_settings", trackingFlow: "database" }), _jsx(ManualSetupCard, { hint: "Set DATABASE_URL in your .env to connect Neon, Supabase, Turso, any Postgres/SQLite database, or local PGlite with pglite:./data/pglite.", docsUrl: "https://www.builder.io/c/docs/agent-native-database?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=database_settings", dim: connected })] }) })), !isPage && shouldShowSection("uploads") && (_jsx(SettingsSection, { id: settingsSectionDomId("uploads"), icon: _jsx(IconUpload, { size: 14 }), title: "File uploads", subtitle: "Where user-uploaded files (avatars, chat attachments) are stored.", flat: true, connected: connected, open: openSection === "uploads", onToggle: () => toggle("uploads"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "file_upload_settings", trackingFlow: "file_upload" }), _jsx(ManualSetupCard, { hint: "Without a provider, files are stored as base64 in your database. Fine for dev, not recommended for production.", docsUrl: "https://www.builder.io/c/docs/agent-native-file-uploads?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=file_upload_settings", dim: connected })] }) })), !isPage && shouldShowSection("auth") && (_jsx(SettingsSection, { id: settingsSectionDomId("auth"), icon: _jsx(IconShield, { size: 14 }), title: "Authentication", subtitle: "Set up user authentication and access control.", flat: true, connected: connected, open: openSection === "auth", onToggle: () => toggle("auth"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "auth_settings", trackingFlow: "auth" }), _jsx(ManualSetupCard, { hint: "Configure Better Auth with BETTER_AUTH_SECRET and optional Google/GitHub OAuth providers.", docsUrl: "https://www.builder.io/c/docs/agent-native-authentication?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=authentication_settings", dim: connected })] }) })), shouldShowSection("email") && (_jsx(EmailSectionInner, { open: openSection === "email", onToggle: () => toggle("email") })), shouldShowSection("browser") && (_jsx(SettingsSection, { id: settingsSectionDomId("browser"), icon: _jsx(IconBrowser, { size: 14 }), title: "Browser Automation", subtitle: "Let agents control a real browser for web tasks.", flat: true, connected: builderConnectionOwnedExternally ? undefined : connected, open: openSection === "browser", onToggle: () => toggle("browser"), children: !builderConnectionOwnedExternally ? (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "browser_settings", trackingFlow: "browser_automation" })) : null })), !isPage &&
1058
+ ["automations", "background"].some((section) => shouldShowSection(section)) && (_jsxs(SettingsGroup, { title: "Agent workflows", children: [shouldShowSection("automations") && (_jsx(SettingsSection, { id: settingsSectionDomId("automations"), icon: _jsx(IconBolt, { size: 14 }), title: "Automations", subtitle: "Scheduled and event-triggered agent tasks.", grouped: true, flat: true, open: openSection === "automations", onToggle: () => toggle("automations"), children: _jsx(SettingsRow, { label: "Automations", description: "Schedule agent tasks or run them from events.", control: _jsxs("a", { href: "/settings/agent/automations", className: "inline-flex items-center gap-1 rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground no-underline transition-colors hover:bg-accent/40", children: ["Open automations", _jsx(IconExternalLink, { size: 14 })] }) }) })), shouldShowSection("background") && (_jsx(SettingsSection, { id: settingsSectionDomId("background"), icon: _jsx(IconGitBranch, { size: 14 }), title: "Background Agent", subtitle: "Make code changes from production mode via Builder.", grouped: true, flat: true, connected: connected, open: openSection === "background", onToggle: () => toggle("background"), children: _jsx(SettingsRow, { label: "Background agent", description: "Make code changes from production mode via Builder.", control: _jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "background_agent_settings", trackingFlow: "background_agent", compact: true }) }) }))] })), isWorkspacePage && (_jsxs(SettingsGroup, { title: "Workspace", children: [shouldShowSection("demo-mode") && (_jsx(SettingsRow, { id: settingsSectionDomId("demo-mode"), label: "Demo mode", description: "Use sample data in this browser for presentations.", control: _jsx(DemoModeSection, { compact: true }) })), shouldShowSection("hosting") && (_jsx(SettingsRow, { id: settingsSectionDomId("hosting"), label: "Hosting", description: "Deploy the app to the cloud.", control: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "hosting_settings", trackingFlow: "hosting", compact: true }), _jsx(ManualSetupCard, { title: "Set up manually", hint: "Deploy manually to Netlify, Vercel, Cloudflare, or any Nitro-supported target.", docsUrl: "https://www.builder.io/c/docs/agent-native-deployment?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=deployment_settings", dim: connected, bare: true, popover: true, popoverLabel: "Manage" })] }) })), shouldShowSection("database") && (_jsx(SettingsRow, { id: settingsSectionDomId("database"), label: "Database", description: "Connect persistent app storage.", control: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "database_settings", trackingFlow: "database", compact: true }), _jsx(ManualSetupCard, { title: "Set up manually", hint: "Set DATABASE_URL in your .env to connect a supported database.", docsUrl: "https://www.builder.io/c/docs/agent-native-database?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=database_settings", dim: connected, bare: true, popover: true, popoverLabel: "Manage" })] }) })), shouldShowSection("uploads") && (_jsx(SettingsRow, { id: settingsSectionDomId("uploads"), label: "File uploads", description: "Store avatars and chat attachments.", control: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "file_upload_settings", trackingFlow: "file_upload", compact: true }), _jsx(ManualSetupCard, { title: "Set up manually", hint: "Use an S3-compatible bucket with a stable public URL for durable chat attachments.", docsUrl: "https://www.builder.io/c/docs/agent-native-file-uploads?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=file_upload_settings", dim: connected, bare: true, popover: true, popoverLabel: "Manage", children: _jsx(FileStorageSettingsForm, {}) })] }) })), shouldShowSection("auth") && (_jsx(SettingsRow, { id: settingsSectionDomId("auth"), label: "Authentication", description: "Set up sign-in and access control.", control: _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "auth_settings", trackingFlow: "auth", compact: true }), _jsx(ManualSetupCard, { title: "Set up manually", hint: "Configure Better Auth and optional Google or GitHub providers.", docsUrl: "https://www.builder.io/c/docs/agent-native-authentication?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=authentication_settings", dim: connected, bare: true, popover: true, popoverLabel: "Manage" })] }) }))] })), !isPage && shouldShowSection("llm") && (_jsx(LLMSectionInner, { builderFlow: builderFlow, builderLoading: builderLoading, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, open: openSection === "llm", onToggle: () => toggle("llm") })), !isPage && shouldShowSection("app-models") && (_jsx(AppModelDefaultsSectionInner, { open: openSection === "app-models", onToggle: () => toggle("app-models") })), !isPage && shouldShowSection("limits") && (_jsx(AgentLimitsSectionInner, { open: openSection === "limits", onToggle: () => toggle("limits") })), !isPage && shouldShowSection("voice") && (_jsx(SettingsSection, { id: settingsSectionDomId("voice"), icon: _jsx(IconMicrophone, { size: 14 }), title: "Voice Transcription", subtitle: "How the composer microphone turns your voice into text.", flat: true, open: openSection === "voice", onToggle: () => toggle("voice"), children: _jsx(VoiceTranscriptionSection, {}) })), !isPage && shouldShowSection("demo-mode") && (_jsx(SettingsSection, { id: settingsSectionDomId("demo-mode"), icon: _jsx(IconEyeOff, { size: 14 }), title: "Demo mode", subtitle: "Replace displayed emails with realistic fake data in this browser and reshape supported charts for presentations. Backend, agent integrations, and agent results stay real and access-scoped.", flat: true, open: openSection === "demo-mode", onToggle: () => toggle("demo-mode"), children: _jsx(DemoModeSection, {}) })), !isPage && shouldShowSection("automations") && (_jsx(SettingsSection, { id: settingsSectionDomId("automations"), icon: _jsx(IconBolt, { size: 14 }), title: "Automations", subtitle: "Scheduled and event-triggered agent tasks.", flat: true, open: openSection === "automations", onToggle: () => toggle("automations"), children: _jsx(AutomationsSection, {}) })), shouldShowSection("secrets") && (_jsx(SettingsSection, { id: settingsSectionDomId("secrets"), icon: _jsx(IconKey, { size: 14 }), title: "API keys", subtitle: "Service credentials and automation keys.", flat: true, open: openSection === "secrets", onToggle: () => toggle("secrets"), children: _jsx(SecretsSection, { focusKey: focusSecretKey }) })), !isPage && shouldShowSection("hosting") && (_jsx(SettingsSection, { id: settingsSectionDomId("hosting"), icon: _jsx(IconCloud, { size: 14 }), title: "Hosting", subtitle: "Deploy your app to the cloud.", flat: true, connected: connected, open: openSection === "hosting", onToggle: () => toggle("hosting"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "hosting_settings", trackingFlow: "hosting" }), _jsx(ManualSetupCard, { hint: "Deploy manually to Netlify, Vercel, Cloudflare, or any Nitro-supported target.", docsUrl: "https://www.builder.io/c/docs/agent-native-deployment?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=deployment_settings", dim: connected })] }) })), !isPage && shouldShowSection("database") && (_jsx(SettingsSection, { id: settingsSectionDomId("database"), icon: _jsx(IconDatabase, { size: 14 }), title: "Database", subtitle: "Connect a cloud database for persistent storage.", flat: true, connected: connected, open: openSection === "database", onToggle: () => toggle("database"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "database_settings", trackingFlow: "database" }), _jsx(ManualSetupCard, { hint: "Set DATABASE_URL in your .env to connect Neon, Supabase, Turso, any Postgres/SQLite database, or local PGlite with pglite:./data/pglite.", docsUrl: "https://www.builder.io/c/docs/agent-native-database?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=database_settings", dim: connected })] }) })), !isPage && shouldShowSection("uploads") && (_jsx(SettingsSection, { id: settingsSectionDomId("uploads"), icon: _jsx(IconUpload, { size: 14 }), title: "File uploads", subtitle: "Where user-uploaded files (avatars, chat attachments) are stored.", flat: true, connected: connected, open: openSection === "uploads", onToggle: () => toggle("uploads"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "file_upload_settings", trackingFlow: "file_upload" }), _jsx(ManualSetupCard, { hint: "Object storage keeps uploaded files durable and their URLs reusable throughout the thread. Connect Builder or use an S3-compatible bucket below.", docsUrl: "https://www.builder.io/c/docs/agent-native-file-uploads?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=file_upload_settings", dim: connected, children: _jsx(FileStorageSettingsForm, {}) })] }) })), !isPage && shouldShowSection("auth") && (_jsx(SettingsSection, { id: settingsSectionDomId("auth"), icon: _jsx(IconShield, { size: 14 }), title: "Authentication", subtitle: "Set up user authentication and access control.", flat: true, connected: connected, open: openSection === "auth", onToggle: () => toggle("auth"), children: _jsxs("div", { className: "space-y-2", children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "auth_settings", trackingFlow: "auth" }), _jsx(ManualSetupCard, { hint: "Configure Better Auth with BETTER_AUTH_SECRET and optional Google/GitHub OAuth providers.", docsUrl: "https://www.builder.io/c/docs/agent-native-authentication?utm_source=agent-native&utm_medium=product&utm_campaign=onboarding&utm_content=authentication_settings", dim: connected })] }) })), shouldShowSection("email") && (_jsx(EmailSectionInner, { open: openSection === "email", onToggle: () => toggle("email") })), shouldShowSection("browser") && (_jsx(SettingsSection, { id: settingsSectionDomId("browser"), icon: _jsx(IconBrowser, { size: 14 }), title: "Browser Automation", subtitle: "Let agents control a real browser for web tasks.", flat: true, connected: builderConnectionOwnedExternally ? undefined : connected, open: openSection === "browser", onToggle: () => toggle("browser"), children: !builderConnectionOwnedExternally ? (_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "browser_settings", trackingFlow: "browser_automation" })) : null })), !isPage &&
1058
1059
  builderBranchesAvailable &&
1059
1060
  shouldShowSection("background") && (_jsx(SettingsSection, { id: settingsSectionDomId("background"), icon: _jsx(IconGitBranch, { size: 14 }), title: "Background Agent", subtitle: "Make code changes from production mode via Builder.", flat: true, connected: connected, open: openSection === "background", onToggle: () => toggle("background"), children: _jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "background_agent_settings", trackingFlow: "background_agent" }) })), shouldShowSection("integrations") && (_jsx(SettingsSection, { id: settingsSectionDomId("integrations"), icon: _jsx(IconPlugConnected, { size: 14 }), title: "Integrations", subtitle: "Connect messaging platforms and external services.", flat: true, open: openSection === "integrations", onToggle: () => toggle("integrations"), children: _jsx(Suspense, { fallback: null, children: _jsx(IntegrationsPanel, {}) }) })), shouldShowSection("usage") && (_jsx(SettingsSection, { id: settingsSectionDomId("usage"), icon: _jsx(IconCoin, { size: 14 }), title: "Usage", subtitle: "Track token consumption and estimated cost \u2014 broken down by chat, automations, and background jobs.", flat: true, open: openSection === "usage", onToggle: () => toggle("usage"), children: _jsx(UsageSection, {}) })), shouldShowSection("a2a") && (_jsx(SettingsSection, { id: settingsSectionDomId("a2a"), icon: _jsx(IconTopologyRing2, { size: 14 }), title: "Connected Agents (A2A)", subtitle: "Manage remote agents connected via the A2A protocol.", flat: true, open: openSection === "a2a", onToggle: () => toggle("a2a"), children: _jsx(AgentsSection, {}) }))] }) }));
1060
1061
  }
@@ -1430,6 +1430,7 @@ export function processEvent(ev, content, toolCallCounter, tabId, state) {
1430
1430
  // a wrap-up marker. Clear any preparation label before inspecting pending
1431
1431
  // tools so both success and interrupted-terminal paths settle the UI.
1432
1432
  dispatchActivityClear(tabId);
1433
+ const userStoppedRun = ev.reason === "user";
1433
1434
  const interruptedTools = pendingToolNames(content);
1434
1435
  const allInterruptedTools = [
1435
1436
  ...interruptedTools.running,
@@ -1437,6 +1438,16 @@ export function processEvent(ev, content, toolCallCounter, tabId, state) {
1437
1438
  ];
1438
1439
  if (allInterruptedTools.length > 0) {
1439
1440
  settleInterruptedToolCalls(content, undefined, { includeActivity: true });
1441
+ if (userStoppedRun) {
1442
+ return {
1443
+ action: "done",
1444
+ result: {
1445
+ content: contentSnapshot(content),
1446
+ status: { type: "complete", reason: "stop" },
1447
+ metadata: { custom: { userStopped: true } },
1448
+ },
1449
+ };
1450
+ }
1440
1451
  const message = interruptedToolMessage(interruptedTools);
1441
1452
  const runError = {
1442
1453
  message,
@@ -1462,6 +1473,16 @@ export function processEvent(ev, content, toolCallCounter, tabId, state) {
1462
1473
  },
1463
1474
  };
1464
1475
  }
1476
+ if (userStoppedRun) {
1477
+ return {
1478
+ action: "done",
1479
+ result: {
1480
+ content: contentSnapshot(content),
1481
+ status: { type: "complete", reason: "stop" },
1482
+ metadata: { custom: { userStopped: true } },
1483
+ },
1484
+ };
1485
+ }
1465
1486
  const runWarning = appendMissingFinalResponseWarning(content, state ? state.completedToolsAfterLastAssistantText : undefined);
1466
1487
  if (runWarning) {
1467
1488
  return {
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  states: {
66
67
  clientId: number;
67
68
  state: string;
68
69
  }[];
69
- error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
+ error?: undefined;
80
81
  users: {
81
82
  clientId: number;
82
83
  lastSeen: number;
83
84
  }[];
84
- error?: undefined;
85
85
  }>>;
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -243,7 +243,7 @@ export function createExtensionActionEntries() {
243
243
  },
244
244
  "render-inline-extension": {
245
245
  tool: {
246
- description: "Render a one-time, transient sandboxed Alpine.js mini-app directly inside the chat. Use this for generated UI that should answer the current turn inline without saving anything to the Extensions view: calculators, adjustable controls, knobs, pickers, visualizers, temporary dashboards, and interactive results. The content must be a self-contained Alpine.js HTML body snippet that can use appAction(), appFetch(), dbQuery(), extensionFetch(), extensionData, agentNative.ui.output(value, opts?), and agentNative.chat.send()/sendToAgentChat(). Use appAction() or extensionData for writes; dbQuery() is for read-only inspection of known app SQL tables. Use agentNative.ui.output for passive current values from knobs, sliders, and selections; it writes application state at inline-ui:<inline extension id>:output, which the agent can read later with readAppState when the user says to use that value. Use agentNative.chat.send for visible submit/apply actions. For transient UIs, extensionData is browser-local throwaway state; use application_state/appFetch, appAction, ui.output, or chat.send for anything the agent or app must observe. Use semantic Tailwind colors (bg-background, text-foreground, bg-primary, etc.) so it inherits the parent app theme. Use create-extension instead when the user wants the UI saved or reusable.",
246
+ description: "Render a one-time, transient sandboxed Alpine.js mini-app directly inside the chat. Use this for generated UI that should answer the current turn inline without saving anything to the Extensions view: calculators, adjustable controls, knobs, pickers, visualizers, temporary dashboards, and interactive results. The content must be a self-contained Alpine.js HTML body snippet that can use appAction(), appFetch(), dbQuery(), extensionFetch(), extensionData, agentNative.ui.output(value, opts?), and agentNative.chat.send()/sendToAgentChat(). Use appAction() or extensionData for writes; dbQuery() is for read-only inspection of known app SQL tables. Use agentNative.ui.output for passive current values from knobs, sliders, and selections; it writes application state at inline-ui:<inline extension id>:output, which the agent can read later with readAppState when the user says to use that value. Use agentNative.chat.send(message, { submit: true }) only for visible user-triggered submit/apply actions; omit submit or set it to false for a draft, and never call chat.send from polling, refresh, or error handlers. For transient UIs, extensionData is browser-local throwaway state; use application_state/appFetch, appAction, ui.output, or chat.send for anything the agent or app must observe. Use semantic Tailwind colors (bg-background, text-foreground, bg-primary, etc.) so it inherits the parent app theme. Use create-extension instead when the user wants the UI saved or reusable.",
247
247
  parameters: {
248
248
  type: "object",
249
249
  properties: {
@@ -412,7 +412,7 @@ export function createExtensionActionEntries() {
412
412
  },
413
413
  "create-extension": {
414
414
  tool: {
415
- description: 'Create a persisted sandboxed Alpine.js mini-app extension and render it inline in the chat. Use this when the user wants generated UI that should be saved, reusable, or visible in the Extensions view: extensions, widgets, dashboards, calculators, mini-apps, and reusable interactive utilities. For one-time chat-only UI, use render-inline-extension instead. The content must be a self-contained Alpine.js HTML body snippet that can use appAction(), appFetch(), dbQuery(), extensionFetch(), extensionData, agentNative.ui.output(value, opts?), and agentNative.chat.send()/sendToAgentChat(). Use appAction() for app data writes and extensionData for extension-owned persisted UI state; dbQuery() is for read-only inspection of known app SQL tables. Use agentNative.ui.output for passive current values from knobs, sliders, and selections; it writes application state at inline-ui:<extension id>:output, which the agent can read later with readAppState when the user says to use that value. Use agentNative.chat.send for visible submit/apply actions. Persist reusable user-edited state with extensionData: if the extension has checkboxes, todos, notes, filters, preferences, or any control whose value should survive reload/reopen, load that state on init and save changes with extensionData, usually at user scope, instead of keeping it only in Alpine state. IMPORTANT — hosting a pasted file: if the user pasted a large HTML/Alpine file (it appears in your context as an <attachment name="pasted-text-…"> block) and asked you to host it as-is, do NOT copy that file into `content`. Instead leave `content` empty and pass `contentFromAttachment` set to that attachment\'s name (or the literal "latest" for the most recent pasted block) — the server reads the file verbatim. Re-emitting a large pasted file as `content` regularly gets cut off mid-stream and stalls the turn. IMPORTANT — cloning a large extension that lives as a workspace resource (not a chat attachment): leave `content` empty and pass `contentFromWorkspaceFile` set to the resource path (e.g. "intuit-analytics-extension.html"); the server reads the full file. Do NOT try to reconstruct the body with run-code or route create-extension through run-code (mutating actions are not callable there). Prefer appAction(name, params) for app data and actions, including read actions mounted as GET; do not call template /api/* routes from appFetch because the extension bridge only allows framework /_agent-native/* paths. Parse JSON string action results before aggregating; use dbQuery() only for known existing SQL tables and never for writes. Keep the initial create-extension payload compact and working; for complex extensions, create a useful v1 first, then use focused update-extension edits for refinements rather than assembling one enormous initial tool input. For any non-trivial component (more than a couple of state fields, any methods, any string formatting, any branching) put the component in a <script> block via Alpine.data(\'name\', () => ({...})) and reference it with x-data="name" — do NOT cram methods, template literals, or branching logic into an inline x-data="{...}" attribute (HTML parser pitfalls cause ReferenceError failures). Define every variable referenced from x-text/x-show/x-if/x-for on the data object\'s initial state. If the extension\'s value depends on an LLM call, require a real key via \\${keys.OPENAI_API_KEY}/\\${keys.ANTHROPIC_API_KEY} (and tell the user to add it in the Dispatch Vault, or in app Settings → API Keys & Connections for standalone apps, if missing) or route the AI work to the agent chat — never ship a stubbed analysis step that renders a placeholder/boolean as the result.',
415
+ description: 'Create a persisted sandboxed Alpine.js mini-app extension and render it inline in the chat. Use this when the user wants generated UI that should be saved, reusable, or visible in the Extensions view: extensions, widgets, dashboards, calculators, mini-apps, and reusable interactive utilities. For one-time chat-only UI, use render-inline-extension instead. The content must be a self-contained Alpine.js HTML body snippet that can use appAction(), appFetch(), dbQuery(), extensionFetch(), extensionData, agentNative.ui.output(value, opts?), and agentNative.chat.send()/sendToAgentChat(). Use appAction() for app data writes and extensionData for extension-owned persisted UI state; dbQuery() is for read-only inspection of known app SQL tables. Use agentNative.ui.output for passive current values from knobs, sliders, and selections; it writes application state at inline-ui:<extension id>:output, which the agent can read later with readAppState when the user says to use that value. Use agentNative.chat.send(message, { submit: true }) only for visible user-triggered submit/apply actions; omit submit or set it to false for a draft, and never call chat.send from polling, refresh, or error handlers. Persist reusable user-edited state with extensionData: if the extension has checkboxes, todos, notes, filters, preferences, or any control whose value should survive reload/reopen, load that state on init and save changes with extensionData, usually at user scope, instead of keeping it only in Alpine state. IMPORTANT — hosting a pasted file: if the user pasted a large HTML/Alpine file (it appears in your context as an <attachment name="pasted-text-…"> block) and asked you to host it as-is, do NOT copy that file into `content`. Instead leave `content` empty and pass `contentFromAttachment` set to that attachment\'s name (or the literal "latest" for the most recent pasted block) — the server reads the file verbatim. Re-emitting a large pasted file as `content` regularly gets cut off mid-stream and stalls the turn. IMPORTANT — cloning a large extension that lives as a workspace resource (not a chat attachment): leave `content` empty and pass `contentFromWorkspaceFile` set to the resource path (e.g. "intuit-analytics-extension.html"); the server reads the full file. Do NOT try to reconstruct the body with run-code or route create-extension through run-code (mutating actions are not callable there). Prefer appAction(name, params) for app data and actions, including read actions mounted as GET; do not call template /api/* routes from appFetch because the extension bridge only allows framework /_agent-native/* paths. Parse JSON string action results before aggregating; use dbQuery() only for known existing SQL tables and never for writes. Keep the initial create-extension payload compact and working; for complex extensions, create a useful v1 first, then use focused update-extension edits for refinements rather than assembling one enormous initial tool input. For any non-trivial component (more than a couple of state fields, any methods, any string formatting, any branching) put the component in a <script> block via Alpine.data(\'name\', () => ({...})) and reference it with x-data="name" — do NOT cram methods, template literals, or branching logic into an inline x-data="{...}" attribute (HTML parser pitfalls cause ReferenceError failures). Define every variable referenced from x-text/x-show/x-if/x-for on the data object\'s initial state. If the extension\'s value depends on an LLM call, require a real key via \\${keys.OPENAI_API_KEY}/\\${keys.ANTHROPIC_API_KEY} (and tell the user to add it in the Dispatch Vault, or in app Settings → API Keys & Connections for standalone apps, if missing) or route the AI work to the agent chat — never ship a stubbed analysis step that renders a placeholder/boolean as the result.',
416
416
  parameters: {
417
417
  type: "object",
418
418
  properties: {