@agent-native/core 0.161.23 → 0.163.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 (206) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +27 -2
  4. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +18 -2
  5. package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
  6. package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +27 -2
  7. package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
  8. package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +27 -2
  9. package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
  10. package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +27 -2
  11. package/corpus/templates/calendar/server/handlers/google-auth.ts +1 -8
  12. package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  13. package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  14. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +88 -35
  15. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +36 -1
  16. package/corpus/templates/clips/app/i18n/en-US.ts +4 -3
  17. package/corpus/templates/clips/app/lib/camera-composite.ts +15 -1
  18. package/corpus/templates/clips/changelog/2026-08-18-clips-no-longer-crashes-when-a-camera-bubble-starts-at-a-tin.md +6 -0
  19. package/corpus/templates/clips/desktop/src/app.tsx +30 -40
  20. package/corpus/templates/clips/desktop/src/components/MicOffConfirmation.tsx +47 -0
  21. package/corpus/templates/clips/desktop/src/styles.css +110 -0
  22. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +114 -7
  23. package/corpus/templates/clips/shared/recording-capture.ts +18 -13
  24. package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
  25. package/corpus/templates/content/.agents/skills/sharing/SKILL.md +27 -2
  26. package/corpus/templates/content/actions/create-content-database.ts +1 -1
  27. package/corpus/templates/content/actions/create-document.ts +1 -1
  28. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -5
  29. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +0 -3
  30. package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +0 -4
  31. package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +4 -1
  32. package/corpus/templates/content/app/hooks/use-notion.ts +17 -15
  33. package/corpus/templates/crm/.agents/skills/real-time-sync/SKILL.md +1 -1
  34. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +27 -2
  35. package/corpus/templates/design/.agents/skills/real-time-sync/SKILL.md +1 -1
  36. package/corpus/templates/design/.agents/skills/sharing/SKILL.md +27 -2
  37. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +6 -1
  38. package/corpus/templates/dispatch/.agents/skills/real-time-sync/SKILL.md +1 -1
  39. package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +27 -2
  40. package/corpus/templates/factory/.agents/skills/real-time-sync/SKILL.md +1 -1
  41. package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +27 -2
  42. package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
  43. package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +27 -2
  44. package/corpus/templates/macros/.agents/skills/real-time-sync/SKILL.md +1 -1
  45. package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +27 -2
  46. package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
  47. package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +27 -2
  48. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +18 -3
  49. package/corpus/templates/mail/server/handlers/google-auth.ts +1 -8
  50. package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
  51. package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +27 -2
  52. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +4 -0
  53. package/corpus/templates/slides/.agents/skills/real-time-sync/SKILL.md +1 -1
  54. package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +27 -2
  55. package/corpus/templates/slides/actions/_await-fit-check.ts +30 -5
  56. package/corpus/templates/slides/actions/add-slide.ts +6 -2
  57. package/corpus/templates/slides/actions/create-deck.ts +2 -1
  58. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +3 -3
  59. package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +3 -3
  60. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -1
  61. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
  62. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +1 -1
  63. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -0
  64. package/corpus/templates/slides/app/pages/DeckEditor.tsx +35 -2
  65. package/corpus/templates/slides/app/routes/p.$id.tsx +3 -1
  66. package/corpus/templates/slides/changelog/2026-08-18-generated-slides-preserve-speaker-notes.md +6 -0
  67. package/corpus/templates/slides/server/handlers/assets.ts +23 -12
  68. package/corpus/templates/slides/server/handlers/design-system-decode-job-status.ts +15 -6
  69. package/corpus/templates/slides/server/handlers/design-system-upload-start.ts +15 -6
  70. package/corpus/templates/slides/server/handlers/index-design-system-sources.ts +34 -19
  71. package/corpus/templates/slides/server/handlers/request-auth-context.ts +1 -0
  72. package/corpus/templates/slides/server/handlers/share.ts +1 -1
  73. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  74. package/corpus/templates/slides/server/routes/api/deck-agent-context.json.get.ts +1 -1
  75. package/corpus/templates/slides/server/routes/api/exports/[filename].get.ts +9 -3
  76. package/corpus/templates/slides/server/routes/api/exports/google-slides.post.ts +12 -5
  77. package/corpus/templates/slides/server/routes/api/exports/html.post.ts +9 -7
  78. package/corpus/templates/slides/server/routes/api/exports/pptx.post.ts +9 -7
  79. package/corpus/templates/slides/shared/api.ts +7 -1
  80. package/corpus/templates/tasks/.agents/skills/real-time-sync/SKILL.md +1 -1
  81. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +27 -2
  82. package/dist/agent/engine/translate-anthropic.js +27 -2
  83. package/dist/agent/production-agent.d.ts +2 -2
  84. package/dist/agent/production-agent.js +16 -8
  85. package/dist/agent/thread-data-builder.d.ts +1 -0
  86. package/dist/agent/thread-data-builder.js +6 -4
  87. package/dist/agent/tool-approval-store.d.ts +10 -1
  88. package/dist/agent/tool-approval-store.js +19 -2
  89. package/dist/agent/types.d.ts +9 -0
  90. package/dist/app-config/schema.d.ts +1 -0
  91. package/dist/app-config/workspace.d.ts +1 -0
  92. package/dist/app-config/workspace.js +8 -0
  93. package/dist/chat-threads/schema.d.ts +2 -2
  94. package/dist/client/AssistantChat.js +14 -6
  95. package/dist/client/EnvironmentBadge.d.ts +15 -0
  96. package/dist/client/EnvironmentBadge.js +79 -0
  97. package/dist/client/MultiTabAssistantChat.js +73 -17
  98. package/dist/client/agent-chat-adapter.js +11 -1
  99. package/dist/client/api-path.js +14 -1
  100. package/dist/client/app-providers.js +2 -1
  101. package/dist/client/chat/tool-call-display.d.ts +12 -2
  102. package/dist/client/chat/tool-call-display.js +17 -7
  103. package/dist/client/chat/widgets/WorkspaceFileWidget.js +1 -1
  104. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts +6 -0
  105. package/dist/client/chat/widgets/builtin-tool-renderers.js +19 -0
  106. package/dist/client/onboarding/FirstRunOnboarding.js +46 -32
  107. package/dist/client/onboarding/use-onboarding.d.ts +8 -1
  108. package/dist/client/onboarding/use-onboarding.js +22 -7
  109. package/dist/client/org/TeamPage.js +15 -2
  110. package/dist/client/org/hooks.d.ts +2 -0
  111. package/dist/client/org/hooks.js +12 -0
  112. package/dist/client/org/index.d.ts +2 -2
  113. package/dist/client/org/index.js +1 -1
  114. package/dist/client/session-replay.js +58 -15
  115. package/dist/client/settings/SettingsTabsPage.js +19 -6
  116. package/dist/client/sharing/ShareButton.js +43 -18
  117. package/dist/client/sharing/useShareButtonController.d.ts +12 -2
  118. package/dist/client/sharing/useShareButtonController.js +38 -8
  119. package/dist/client/sse-event-processor.d.ts +7 -0
  120. package/dist/client/sse-event-processor.js +4 -1
  121. package/dist/client/ui/index.d.ts +1 -0
  122. package/dist/client/ui/index.js +1 -0
  123. package/dist/client/use-action.js +26 -0
  124. package/dist/collab/struct-routes.d.ts +1 -1
  125. package/dist/config.d.ts +15 -0
  126. package/dist/config.js +53 -0
  127. package/dist/dashboard-storage/schema.d.ts +2 -2
  128. package/dist/data-programs/schema.d.ts +2 -2
  129. package/dist/extensions/fetch-tool.js +8 -2
  130. package/dist/extensions/schema.d.ts +2 -2
  131. package/dist/extensions/store.js +3 -0
  132. package/dist/localization/core-messages/ar-SA.js +1 -0
  133. package/dist/localization/core-messages/de-DE.js +1 -0
  134. package/dist/localization/core-messages/en-US.d.ts +1 -0
  135. package/dist/localization/core-messages/en-US.js +1 -0
  136. package/dist/localization/core-messages/es-ES.js +1 -0
  137. package/dist/localization/core-messages/fr-FR.js +1 -0
  138. package/dist/localization/core-messages/hi-IN.js +1 -0
  139. package/dist/localization/core-messages/ja-JP.js +1 -0
  140. package/dist/localization/core-messages/ko-KR.js +1 -0
  141. package/dist/localization/core-messages/pt-BR.js +1 -0
  142. package/dist/localization/core-messages/zh-CN.js +1 -0
  143. package/dist/localization/core-messages/zh-TW.js +1 -0
  144. package/dist/localization/core-messages.d.ts +1 -0
  145. package/dist/localization/default-messages.d.ts +4 -0
  146. package/dist/localization/default-messages.js +4 -0
  147. package/dist/mcp/embed-app.js +1 -1
  148. package/dist/notifications/routes.d.ts +1 -1
  149. package/dist/org/handlers.d.ts +6 -1
  150. package/dist/org/handlers.js +43 -0
  151. package/dist/org/index.d.ts +3 -3
  152. package/dist/org/index.js +2 -2
  153. package/dist/org/migrations.js +33 -0
  154. package/dist/org/plugin.js +10 -1
  155. package/dist/org/schema.d.ts +315 -0
  156. package/dist/org/schema.js +20 -1
  157. package/dist/org/types.d.ts +3 -0
  158. package/dist/org/workspace-app-access.d.ts +10 -0
  159. package/dist/org/workspace-app-access.js +179 -0
  160. package/dist/progress/routes.d.ts +1 -1
  161. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  162. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  163. package/dist/provider-api/index.d.ts +10 -0
  164. package/dist/provider-api/index.js +45 -9
  165. package/dist/resources/handlers.d.ts +1 -1
  166. package/dist/secrets/routes.d.ts +9 -9
  167. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  168. package/dist/server/auth.js +24 -4
  169. package/dist/server/poll.js +13 -3
  170. package/dist/server/sentry.d.ts +1 -1
  171. package/dist/sharing/access.d.ts +2 -2
  172. package/dist/sharing/access.js +56 -3
  173. package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
  174. package/dist/sharing/actions/list-resource-shares.js +26 -1
  175. package/dist/sharing/actions/share-resource.d.ts +1 -1
  176. package/dist/sharing/actions/share-resource.js +23 -4
  177. package/dist/sharing/actions/unshare-resource.d.ts +1 -1
  178. package/dist/sharing/actions/unshare-resource.js +1 -1
  179. package/dist/sharing/registry.d.ts +12 -0
  180. package/dist/sharing/schema.d.ts +4 -4
  181. package/dist/sharing/schema.js +2 -2
  182. package/dist/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  183. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  184. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  185. package/dist/templates/default/.agents/skills/sharing/SKILL.md +27 -2
  186. package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  187. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
  188. package/dist/vite/client.d.ts +5 -0
  189. package/dist/vite/client.js +64 -2
  190. package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +9 -1
  191. package/dist/workspace-connections/actions/list-workspace-user-groups.js +9 -4
  192. package/dist/workspace-connections/groups.d.ts +2 -0
  193. package/dist/workspace-connections/groups.js +16 -5
  194. package/dist/workspace-files/actions.d.ts +1 -8
  195. package/dist/workspace-files/actions.js +5 -21
  196. package/dist/workspace-files/index.d.ts +1 -1
  197. package/dist/workspace-files/index.js +1 -1
  198. package/dist/workspace-files/store.d.ts +18 -0
  199. package/dist/workspace-files/store.js +24 -3
  200. package/package.json +3 -3
  201. package/src/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  202. package/src/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  203. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  204. package/src/templates/default/.agents/skills/sharing/SKILL.md +27 -2
  205. package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  206. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
@@ -65,7 +65,17 @@ const MAX_HISTORY_ATTACHMENT_CHARS = 60_000;
65
65
  // the trailing truncation notice still makes a pathological multi-MB paste
66
66
  // visibly (not silently) capped.
67
67
  const MAX_OUTBOUND_ATTACHMENT_CHARS = 200_000;
68
- const MAX_HISTORY_MESSAGES = 24;
68
+ // An array-length backstop, NOT the reduction policy. Reducing a long thread is
69
+ // Observational Memory's job: it folds older turns into observations/reflections
70
+ // and replaces the raw prefix with a memory block plus a recent-raw window
71
+ // (agent/observational-memory/). But its Observer only engages once a thread
72
+ // passes 30k unobserved tokens, and a count cap of 24 bit long before that — so
73
+ // turns were evicted from the request while compaction still had nothing to say
74
+ // about them, and only reached the model again once thread_data was observed a
75
+ // turn or more later. The two char budgets below are the real bound (they cap
76
+ // what a request can carry regardless of message count); this cap only keeps the
77
+ // array from growing without limit.
78
+ const MAX_HISTORY_MESSAGES = 80;
69
79
  // Every provider prompt cache matches a byte-identical PREFIX. A window that
70
80
  // ends at the newest message and starts MAX_HISTORY_MESSAGES back moves its
71
81
  // START by one message per turn, so from the first turn past the cap onward no
@@ -48,7 +48,20 @@ function workspacePathBasePath() {
48
48
  const segment = window.location.pathname.split("/").find(Boolean);
49
49
  if (!segment || segment === "_agent-native" || segment === "api")
50
50
  return "";
51
- return normalizeBasePath(segment);
51
+ const basePath = normalizeBasePath(segment);
52
+ // Guard against treating an app-local route (e.g. a client-rendered
53
+ // "/settings" page reached via stale client-side navigation) as if it
54
+ // were a sibling app's workspace mount — that built URLs like
55
+ // "/settings/_agent-native/builder/connect", which the workspace gateway
56
+ // 404s (no app is mounted at "/settings") into its app-picker page instead
57
+ // of the real target route. Only trust the segment when it matches a
58
+ // known mount from the deployed app manifest; when the manifest can't be
59
+ // read, fall back to the prior blind-trust behavior (e.g. the same build
60
+ // reused across sibling app ids without a manifest).
61
+ const mounts = workspaceAppMountPaths();
62
+ if (mounts && !mounts.has(basePath))
63
+ return "";
64
+ return basePath;
52
65
  }
53
66
  function externalEmbedTargetBasePath() {
54
67
  if (typeof window === "undefined")
@@ -57,6 +57,7 @@ import { useInRouterContext } from "react-router";
57
57
  import { isHumanReadableDocumentTitle, normalizeDocumentTitle, } from "../shared/document-title.js";
58
58
  import { ClientOnly } from "./ClientOnly.js";
59
59
  import { DefaultSpinner } from "./DefaultSpinner.js";
60
+ import { EnvironmentBadge } from "./EnvironmentBadge.js";
60
61
  import { AgentNativeI18nProvider, } from "./i18n.js";
61
62
  import { FirstRunOnboardingStartupGate } from "./onboarding/first-run-startup-gate.js";
62
63
  import { RequireSession } from "./require-session.js";
@@ -151,5 +152,5 @@ export function AppProviders({ queryClient, isPublicPath = false, clientOnlyFall
151
152
  if (isPublicPath) {
152
153
  return (_jsx(ProvidersInner, { queryClient: queryClient, defaultTheme: defaultTheme, themeAttribute: themeAttribute, tooltipDelayDuration: tooltipDelayDuration, toaster: toaster, disableThemeTransitions: disableThemeTransitions, i18n: i18n, documentTitleFallback: documentTitleFallback, children: children }));
153
154
  }
154
- return (_jsx(ClientOnly, { fallback: fallback, children: _jsx(ProvidersInner, { queryClient: queryClient, defaultTheme: defaultTheme, themeAttribute: themeAttribute, tooltipDelayDuration: tooltipDelayDuration, toaster: toaster, disableThemeTransitions: disableThemeTransitions, i18n: i18n, documentTitleFallback: documentTitleFallback, children: _jsx(RequireSession, { bypass: sessionBypass, fallback: fallback, children: sessionBypass ? (children) : (_jsx(FirstRunOnboardingStartupGate, { children: children })) }) }) }));
155
+ return (_jsx(ClientOnly, { fallback: fallback, children: _jsx(ProvidersInner, { queryClient: queryClient, defaultTheme: defaultTheme, themeAttribute: themeAttribute, tooltipDelayDuration: tooltipDelayDuration, toaster: toaster, disableThemeTransitions: disableThemeTransitions, i18n: i18n, documentTitleFallback: documentTitleFallback, children: _jsx(RequireSession, { bypass: sessionBypass, fallback: fallback, children: sessionBypass ? (children) : (_jsxs(FirstRunOnboardingStartupGate, { children: [children, _jsx(EnvironmentBadge, {})] })) }) }) }));
155
156
  }
@@ -34,9 +34,17 @@ export type ApprovalContextValue = {
34
34
  * Keep the visible resolution stable while the chat repository refreshes or
35
35
  * remounts the message containing this approval card.
36
36
  */
37
- onApprovalResolved?: (approvalKey: string, resolution: ApprovalResolution, toolCallId?: string) => void;
37
+ onApprovalResolved?: (approvalKey: string, resolution: ApprovalResolution, toolCallId?: string,
38
+ /**
39
+ * Identifies the specific `approval_required` ask being resolved. A
40
+ * remount that replays the SAME ask (e.g. a chat repository refresh)
41
+ * omits nothing new here, so the retained resolution still matches; a
42
+ * fresh ask after a failed resume carries a different `askId` and so
43
+ * looks up as unresolved. See `ApprovalAffordance` below.
44
+ */
45
+ askId?: string) => void;
38
46
  /** Read a resolution retained by the owning chat surface. */
39
- getApprovalResolution?: (approvalKey: string, toolCallId?: string) => ApprovalResolution | null;
47
+ getApprovalResolution?: (approvalKey: string, toolCallId?: string, askId?: string) => ApprovalResolution | null;
40
48
  /**
41
49
  * Optional host hook invoked in addition to the local "denied" state, e.g.
42
50
  * so a Code session can also resolve its own pending approval as denied.
@@ -97,6 +105,7 @@ export declare function ToolCallDisplay({ toolName, toolCallId, argsText, args,
97
105
  approval?: {
98
106
  approvalKey: string;
99
107
  dismissed?: boolean;
108
+ askId?: string;
100
109
  };
101
110
  repeatCount?: number;
102
111
  /** The latest tool shown while the overall chat turn is still active. */
@@ -113,6 +122,7 @@ export declare function ToolCallFallback({ toolName, toolCallId, args, argsText,
113
122
  approval?: {
114
123
  approvalKey: string;
115
124
  dismissed?: boolean;
125
+ askId?: string;
116
126
  };
117
127
  repeatCount?: number;
118
128
  isLatestRunning?: boolean;
@@ -16,7 +16,7 @@ import { cn } from "../utils.js";
16
16
  import { ActionChatUiSurface } from "./action-chat-ui-surface.js";
17
17
  import { SmoothMarkdownText, HighlightedCodeBlock, } from "./markdown-renderer.js";
18
18
  import { resolveToolRenderer } from "./tool-render-registry.js";
19
- import { isBuiltinDataWidgetActionRenderer, resolveBuiltinActionChatRenderer, resolveBuiltinFallbackToolRenderer, } from "./widgets/builtin-tool-renderers.js";
19
+ import { isBuiltinDataWidgetActionRenderer, isBuiltinWorkspaceFileResult, resolveBuiltinActionChatRenderer, resolveBuiltinFallbackToolRenderer, } from "./widgets/builtin-tool-renderers.js";
20
20
  // Exported so AssistantChatInner can provide a context value.
21
21
  export const ChatRunningContext = React.createContext(false);
22
22
  export const ChatRunningRunIdContext = React.createContext(null);
@@ -249,7 +249,7 @@ function ApprovalAffordance({ toolName, toolCallId, approval, }) {
249
249
  const t = useT();
250
250
  const ctx = React.useContext(ApprovalContext);
251
251
  const [localResolution, setLocalResolution] = useState(null);
252
- const retainedResolution = ctx?.getApprovalResolution?.(approval.approvalKey, toolCallId) ?? null;
252
+ const retainedResolution = ctx?.getApprovalResolution?.(approval.approvalKey, toolCallId, approval.askId) ?? null;
253
253
  const resolution = retainedResolution ??
254
254
  localResolution ??
255
255
  (approval.dismissed === true ? "denied" : null);
@@ -266,15 +266,15 @@ function ApprovalAffordance({ toolName, toolCallId, approval, }) {
266
266
  }
267
267
  return (_jsxs("div", { className: "mt-1.5 flex min-w-0 flex-wrap items-center gap-2 rounded-md border border-border bg-muted/40 px-2.5 py-1.5", children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "min-w-0 flex-1 text-xs text-muted-foreground", children: t("agentChat.approval.question", { tool: toolName }) }), ctx && (_jsxs("button", { type: "button", onClick: () => {
268
268
  setLocalResolution("approved");
269
- ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId);
269
+ ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId, approval.askId);
270
270
  ctx.onApprove(approval.approvalKey);
271
271
  }, className: cn("inline-flex shrink-0 items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), t("agentChat.approval.approve")] })), ctx?.onAlwaysAllow && (_jsxs("button", { type: "button", onClick: () => {
272
272
  setLocalResolution("approved");
273
- ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId);
273
+ ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId, approval.askId);
274
274
  ctx.onAlwaysAllow?.(approval.approvalKey);
275
275
  }, title: t("agentChat.approval.alwaysAllowHint"), className: cn("inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted"), children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5" }), t("agentChat.approval.alwaysAllow")] })), _jsxs("button", { type: "button", onClick: () => {
276
276
  setLocalResolution("denied");
277
- ctx?.onApprovalResolved?.(approval.approvalKey, "denied", toolCallId);
277
+ ctx?.onApprovalResolved?.(approval.approvalKey, "denied", toolCallId, approval.askId);
278
278
  ctx?.onDeny?.(approval.approvalKey);
279
279
  }, className: cn("inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted"), children: [_jsx(IconX, { className: "h-3.5 w-3.5" }), t("agentChat.approval.deny")] })] }));
280
280
  }
@@ -403,7 +403,8 @@ function ToolCallDisplayGeneric({ toolName, toolCallId, argsText, args, result,
403
403
  (skipRegistryRenderer ? null : resolveToolRenderer(nativeToolContext)) ??
404
404
  resolveBuiltinFallbackToolRenderer(nativeToolContext));
405
405
  if (NativeToolRenderer) {
406
- return (_jsx(ActionChatUiSurface, { context: nativeToolContext, isBuiltinDataWidget: isBuiltinDataWidgetActionRenderer(nativeToolContext), children: _jsx(NativeToolRenderer, { context: nativeToolContext }) }));
406
+ return (_jsx(ActionChatUiSurface, { context: nativeToolContext, isBuiltinDataWidget: isBuiltinDataWidgetActionRenderer(nativeToolContext) ||
407
+ isBuiltinWorkspaceFileResult(nativeToolContext), children: _jsx(NativeToolRenderer, { context: nativeToolContext }) }));
407
408
  }
408
409
  const inputPayload = hasArgs
409
410
  ? toolInputPayload(toolName, args, {
@@ -432,7 +433,16 @@ function ToolCallDisplayGeneric({ toolName, toolCallId, argsText, args, result,
432
433
  }
433
434
  return (_jsxs("div", { className: "group/tool my-0.5 w-full overflow-hidden", children: [mcpApp && !(suppressInlineOpenApp && toolName === "open_app") && (_jsx(McpAppRenderer, { app: mcpApp, className: "mb-1.5" })), _jsxs("button", { type: "button", onClick: () => canExpand && setExpanded(!isExpanded), "aria-expanded": canExpand ? isExpanded : undefined, className: cn("flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground transition-colors", canExpand && "hover:text-foreground", isRunning && "text-muted-foreground"), children: [_jsx("span", { className: "relative flex size-4 shrink-0 items-center justify-center", children: isRunning ? (_jsx(IconLoader2, { className: "size-3.5 animate-spin" })) : isAgentError ? (_jsx(IconCircleX, { className: "size-3.5 text-destructive" })) : isUnknownOutcome ? (_jsx(IconAlertTriangle, { className: "size-3.5 text-muted-foreground" })) : (_jsxs(_Fragment, { children: [_jsx(ToolIcon, { className: cn("size-3.5 transition-opacity", canExpand && "group-hover/tool:opacity-0") }), canExpand && (_jsx(IconChevronRight, { className: cn("absolute size-3.5 opacity-0 transition-[opacity,transform] group-hover/tool:opacity-100", isExpanded && "rotate-90") }))] })) }), _jsx("span", { className: cn("min-w-0 truncate font-normal", isActiveTail && "agent-running-shimmer"), children: displayName }), repeatCount && repeatCount > 1 && (_jsxs("span", { className: "shrink-0 rounded border border-border/60 px-1.5 py-0.5 text-[10px] leading-none text-muted-foreground", title: t("agentChat.tool.repeated", { count: repeatCount }), children: [repeatCount, "x"] }))] }), _jsx(AnimatedCollapse, { open: isExpanded && !isAgentCall && (hasArgs || result !== undefined), children: _jsxs("div", { className: "mt-1 space-y-2 pl-5", children: [inputPayload && (_jsx(SimpleCodeViewer, { text: inputPayload.text, lang: inputPayload.lang })), resultPayload && (_jsx(ToolOutputPopover, { open: outputOpen, onOpenChange: setOutputOpen, title: outputTitle, payload: resultPayload, children: _jsx("button", { type: "button", "aria-label": t("agentChat.tool.viewOutput", {
434
435
  tool: toolName,
435
- }), className: "inline-flex size-6 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-accent hover:text-foreground", children: _jsx(IconCode, { className: "size-3.5" }) }) }))] }) }), isUnknownOutcome && (_jsx("p", { role: "status", className: "ps-5 text-xs text-muted-foreground", children: t("agentChat.tool.interrupted") })), approval && (_jsx(ApprovalAffordance, { toolName: toolName, toolCallId: toolCallId, approval: approval }))] }));
436
+ }), className: "inline-flex size-6 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-accent hover:text-foreground", children: _jsx(IconCode, { className: "size-3.5" }) }) }))] }) }), isUnknownOutcome && (_jsx("p", { role: "status", className: "ps-5 text-xs text-muted-foreground", children: t("agentChat.tool.interrupted") })), approval && (_jsx(ApprovalAffordance
437
+ // A changed `askId` means the server re-emitted approval_required
438
+ // for this same call (e.g. a failed resume never consumed the
439
+ // prior grant) rather than the same ask re-rendering. Keying on it
440
+ // forces a fresh mount so a stale local "approved" state from the
441
+ // earlier ask can't linger and hide Approve/Deny with no way to
442
+ // retry. Falls back to approvalKey when askId is absent (older
443
+ // events, non-production-agent approval sources) to keep the
444
+ // existing remount-safe behavior unchanged there.
445
+ , { toolName: toolName, toolCallId: toolCallId, approval: approval }, approval.askId ?? approval.approvalKey))] }));
436
446
  }
437
447
  function AgentCallCell({ agentName, activity, progress, responseText, isRunning, isError, durationMs, }) {
438
448
  const t = useT();
@@ -29,5 +29,5 @@ export function WorkspaceFileWidget({ result, }) {
29
29
  const { file } = result;
30
30
  const FileIcon = fileIcon(file.contentType);
31
31
  const href = resourceDownloadUrl(file.resourceId);
32
- return (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", children: _jsx(FileIcon, { className: "size-5", "aria-hidden": "true" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-medium", children: file.name }), _jsxs("div", { className: "truncate text-xs text-muted-foreground", children: [formatFileSize(file.sizeBytes, formatNumber), " \u00B7 ", file.path] })] }), _jsxs("a", { href: href, download: file.name, className: "inline-flex shrink-0 items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground no-underline transition-colors hover:bg-primary/90 hover:no-underline", children: [_jsx(IconDownload, { className: "size-3.5", "aria-hidden": "true" }), t("workspaceFile.download")] })] }));
32
+ return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-border bg-card p-3 text-card-foreground shadow-sm", children: [_jsx("div", { className: "flex size-10 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", children: _jsx(FileIcon, { className: "size-5", "aria-hidden": "true" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-medium", children: file.name }), _jsxs("div", { className: "truncate text-xs text-muted-foreground", children: [formatFileSize(file.sizeBytes, formatNumber), " \u00B7 ", file.path] })] }), _jsxs("a", { href: href, download: file.name, className: "inline-flex shrink-0 items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground no-underline transition-colors hover:bg-primary/90 hover:no-underline", children: [_jsx(IconDownload, { className: "size-3.5", "aria-hidden": "true" }), t("workspaceFile.download")] })] }));
33
33
  }
@@ -1,4 +1,10 @@
1
1
  import { type ToolRendererContext, type ToolRendererComponent } from "../tool-render-registry.js";
2
2
  export declare function isBuiltinDataWidgetActionRenderer(context: ToolRendererContext): boolean;
3
+ /** True whenever a result renders as a workspace-file card — with or
4
+ * without a static chatUI on the action that produced it (see the
5
+ * shape-based fallback registered below). The widget frames itself, so
6
+ * callers use this the same way they use `isBuiltinDataWidgetActionRenderer`
7
+ * to skip the chatUI-gated outer border and avoid a double frame. */
8
+ export declare function isBuiltinWorkspaceFileResult(context: ToolRendererContext): boolean;
3
9
  export declare function resolveBuiltinActionChatRenderer(context: ToolRendererContext): ToolRendererComponent | null;
4
10
  export declare function resolveBuiltinFallbackToolRenderer(context: ToolRendererContext): ToolRendererComponent | null;
@@ -99,6 +99,14 @@ export function isBuiltinDataWidgetActionRenderer(context) {
99
99
  renderer === ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER ||
100
100
  renderer === ACTION_CHAT_UI_DATA_WIDGET_RENDERER);
101
101
  }
102
+ /** True whenever a result renders as a workspace-file card — with or
103
+ * without a static chatUI on the action that produced it (see the
104
+ * shape-based fallback registered below). The widget frames itself, so
105
+ * callers use this the same way they use `isBuiltinDataWidgetActionRenderer`
106
+ * to skip the chatUI-gated outer border and avoid a double frame. */
107
+ export function isBuiltinWorkspaceFileResult(context) {
108
+ return normalizeWorkspaceFileResult(context.resultJson) !== null;
109
+ }
102
110
  export function resolveBuiltinActionChatRenderer(context) {
103
111
  if (context.chatUI?.renderer === ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER &&
104
112
  normalizeInlineExtensionToolResult(context)) {
@@ -143,3 +151,14 @@ registerReservedFallbackToolRenderer({
143
151
  match: (context) => normalizeActionDataWidgetResult(context) !== null,
144
152
  Component: BuiltinDataWidgetRenderer,
145
153
  });
154
+ // Shape-based, not chatUI-based: any tool result — from show-workspace-file,
155
+ // or any other action that spreads `{ file: toWorkspaceFileCard(meta) }` into
156
+ // its own result — renders a download card without a second discretionary
157
+ // call to show-workspace-file. Matching by shape (instead of statically
158
+ // tagging every such action with `chatUI`) also keeps read/list-style calls
159
+ // on the same multi-purpose action collapsible when they don't produce a file.
160
+ registerReservedFallbackToolRenderer({
161
+ id: "core.workspace-file",
162
+ match: (context) => normalizeWorkspaceFileResult(context.resultJson) !== null,
163
+ Component: BuiltinWorkspaceFileRenderer,
164
+ });
@@ -1,7 +1,7 @@
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 { Skeleton } from "@agent-native/toolkit/ui/skeleton";
3
3
  import { IconArrowRight, IconBrandGithub, IconCheck, IconExternalLink, IconInfoCircle, IconKey, IconLoader2, IconSearch, } from "@tabler/icons-react";
4
- import React, { useMemo, useState } from "react";
4
+ import React, { useCallback, useMemo, useState } from "react";
5
5
  import { docsUrl } from "../../shared/docs-url.js";
6
6
  import { appPath } from "../api-path.js";
7
7
  import { Tooltip, TooltipContent, TooltipTrigger, } from "../components/ui/tooltip.js";
@@ -28,7 +28,14 @@ const BUILDER_MORE_SERVICES = [
28
28
  ];
29
29
  export function FirstRunOnboarding({ skipIntegrations = shouldSkipFirstRunIntegrations(), initialFirstRun = false, } = {}) {
30
30
  const previewMode = useOnboardingPreviewMode();
31
- const { firstRun, loading, error, profile, completeFirstRun } = useOnboarding({ preview: previewMode, initialFirstRun });
31
+ const { firstRun, loading, error, profile, completeFirstRun, completeFirstRunError, } = useOnboarding({ preview: previewMode, initialFirstRun });
32
+ // completeFirstRun() rejects on failure — swallow it here so a Skip/
33
+ // Continue click never becomes an unhandled rejection; completeFirstRunError
34
+ // (rendered below) is the real signal, and the user stays on this screen
35
+ // to retry instead of being bounced to an unrelated error screen.
36
+ const finishOnboarding = useCallback(() => {
37
+ completeFirstRun().catch(() => { });
38
+ }, [completeFirstRun]);
32
39
  const [screen, setScreen] = useState("intro");
33
40
  const [extensionIndex, setExtensionIndex] = useState(0);
34
41
  const [integrationQuery, setIntegrationQuery] = useState("");
@@ -86,15 +93,15 @@ export function FirstRunOnboarding({ skipIntegrations = shouldSkipFirstRunIntegr
86
93
  trackingFlow: "connect_llm",
87
94
  });
88
95
  };
89
- const handleOpenSettings = async () => {
96
+ const handleOpenSettings = () => {
90
97
  window.dispatchEvent(new CustomEvent("agent-panel:open-settings", {
91
98
  detail: { section: "integrations" },
92
99
  }));
93
- await completeFirstRun();
100
+ finishOnboarding();
94
101
  };
95
102
  const handleFinish = () => {
96
103
  if (extensions.length === 0) {
97
- void completeFirstRun();
104
+ finishOnboarding();
98
105
  return;
99
106
  }
100
107
  setExtensionIndex(0);
@@ -155,7 +162,7 @@ export function FirstRunOnboarding({ skipIntegrations = shouldSkipFirstRunIntegr
155
162
  if (screen === "extension") {
156
163
  const extension = extensions[extensionIndex];
157
164
  if (!extension) {
158
- void completeFirstRun();
165
+ finishOnboarding();
159
166
  return null;
160
167
  }
161
168
  const Extension = extension.component;
@@ -164,9 +171,9 @@ export function FirstRunOnboarding({ skipIntegrations = shouldSkipFirstRunIntegr
164
171
  setExtensionIndex((current) => current + 1);
165
172
  return;
166
173
  }
167
- void completeFirstRun();
174
+ finishOnboarding();
168
175
  };
169
- return (_jsx(Extension, { onComplete: advanceExtension, onSkip: () => void completeFirstRun() }));
176
+ return (_jsxs(_Fragment, { children: [_jsx(Extension, { onComplete: advanceExtension, onSkip: finishOnboarding }), completeFirstRunError && (_jsx(FirstRunCompletionError, { message: completeFirstRunError, onRetry: finishOnboarding }))] }));
170
177
  }
171
178
  if (screen === "intro") {
172
179
  return (_jsx(OnboardingShell, { profile: profile, screen: "intro", children: _jsxs("div", { className: "mx-auto flex w-full max-w-lg flex-col items-center text-center", children: [_jsxs("h1", { className: "text-3xl font-semibold tracking-[-0.05em] sm:text-4xl", children: ["Free forever.", _jsx("br", {}), _jsx("span", { className: "text-primary", children: "Open source for life." })] }), _jsxs("div", { className: "mt-7 grid w-full gap-2 text-left sm:grid-cols-3", children: [_jsxs("div", { className: "rounded-lg bg-muted/35 px-3 py-3", children: [_jsx("p", { className: "text-xs font-medium", children: "Fully customizable" }), _jsx("p", { className: "mt-1 text-[11px] leading-4 text-muted-foreground", children: "Change the UI, code, and behavior." })] }), _jsxs("div", { className: "rounded-lg bg-muted/35 px-3 py-3", children: [_jsx("p", { className: "text-xs font-medium", children: "Bring your own keys" }), _jsx("p", { className: "mt-1 text-[11px] leading-4 text-muted-foreground", children: "Use your own providers and accounts." })] }), _jsxs("div", { className: "rounded-lg bg-muted/35 px-3 py-3", children: [_jsx("p", { className: "text-xs font-medium", children: "Build your own" }), _jsx("p", { className: "mt-1 text-[11px] leading-4 text-muted-foreground", children: "Mix and match toolkit pieces in your own apps." })] })] }), _jsxs("button", { type: "button", className: cn(primaryButtonClass, "mt-6"), onClick: () => setScreen("choice"), children: ["Continue", _jsx(IconArrowRight, { size: 15 })] }), _jsxs("a", { href: "https://github.com/builderio/agent-native", target: "_blank", rel: "noreferrer", className: "mt-3 inline-flex items-center gap-1.5 text-xs text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx(IconBrandGithub, { size: 14 }), _jsx("span", { children: "View source" }), _jsx(IconExternalLink, { size: 13 })] })] }) }));
@@ -204,29 +211,29 @@ export function FirstRunOnboarding({ skipIntegrations = shouldSkipFirstRunIntegr
204
211
  if (screen === "connecting") {
205
212
  return (_jsx(OnboardingShell, { profile: profile, screen: "choice", children: _jsxs("div", { className: "mx-auto flex w-full max-w-md flex-col items-center text-center", children: [_jsx("div", { className: "flex size-10 items-center justify-center rounded-full bg-primary/10 text-primary", children: _jsx(IconLoader2, { className: "animate-spin", size: 19 }) }), _jsx("h1", { className: "mt-5 text-xl font-semibold tracking-[-0.04em]", children: "Connecting Builder.io free credits" }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "Finish the one-click connection in the new window." }), _jsxs("div", { className: "mt-7 w-full rounded-xl bg-muted/35 p-4 text-left", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Skeleton, { className: "h-3 w-28" }), _jsx(Skeleton, { className: "h-5 w-16 rounded-full" })] }), _jsx(Skeleton, { className: "mt-4 h-8 w-full" }), _jsxs("div", { className: "mt-3 grid grid-cols-3 gap-2", children: [_jsx(Skeleton, { className: "h-7 w-full" }), _jsx(Skeleton, { className: "h-7 w-full" }), _jsx(Skeleton, { className: "h-7 w-full" })] })] }), connectFlow.error && (_jsxs("div", { className: "mt-4 flex flex-col items-center gap-2", children: [_jsx("p", { className: "text-xs text-destructive", children: connectFlow.error }), _jsx("button", { type: "button", className: secondaryButtonClass, onClick: () => setScreen("choice"), children: "Try again" })] }))] }) }));
206
213
  }
207
- return (_jsx(OnboardingShell, { profile: profile, screen: "ready", children: _jsxs("div", { className: "mx-auto flex w-full max-w-2xl flex-col items-center text-center", children: [_jsx("div", { className: "flex size-12 items-center justify-center rounded-2xl bg-primary/10 text-primary", children: _jsx(IconCheck, { size: 20 }) }), _jsx("h1", { className: "mt-5 text-xl font-semibold tracking-[-0.04em]", children: "Your agent is ready." }), _jsx("p", { className: "mt-2 max-w-md text-sm leading-6 text-muted-foreground", children: "Start with a chat, then connect more tools whenever you need them." }), _jsx("div", { className: "mt-7 grid w-full gap-2 text-left sm:grid-cols-3", children: (skipIntegrations
208
- ? [
209
- [
210
- "Workflow actions",
211
- "Use the app's buttons and sidebar to run the workflow.",
212
- ],
213
- [
214
- "AI sidebar",
215
- "Ask the agent to review or refine a step in context.",
216
- ],
217
- [
218
- "Flexible providers",
219
- "Use Builder.io free credits or your own keys.",
220
- ],
221
- ]
222
- : [
223
- ["Chat + actions", "Ask your agent to work across the app."],
224
- ["Agent integrations", "Connect tools from Settings anytime."],
225
- [
226
- "Flexible providers",
227
- "Use Builder.io free credits or your own keys.",
228
- ],
229
- ]).map(([title, description]) => (_jsxs("div", { className: "rounded-xl bg-muted/35 px-4 py-4", children: [_jsx("span", { className: "flex size-7 items-center justify-center rounded-full bg-primary/10 text-primary", children: _jsx(IconCheck, { size: 14 }) }), _jsx("p", { className: "mt-3 text-sm font-medium", children: title }), _jsx("p", { className: "mt-1 text-xs leading-5 text-muted-foreground", children: description })] }, title))) }), _jsxs("button", { type: "button", "data-testid": "first-run-open-app", className: cn(primaryButtonClass, "mt-7"), onClick: handleFinish, children: ["Open app", _jsx(IconArrowRight, { size: 15 })] })] }) }));
214
+ return (_jsxs(OnboardingShell, { profile: profile, screen: "ready", children: [_jsxs("div", { className: "mx-auto flex w-full max-w-2xl flex-col items-center text-center", children: [_jsx("div", { className: "flex size-12 items-center justify-center rounded-2xl bg-primary/10 text-primary", children: _jsx(IconCheck, { size: 20 }) }), _jsx("h1", { className: "mt-5 text-xl font-semibold tracking-[-0.04em]", children: "Your agent is ready." }), _jsx("p", { className: "mt-2 max-w-md text-sm leading-6 text-muted-foreground", children: "Start with a chat, then connect more tools whenever you need them." }), _jsx("div", { className: "mt-7 grid w-full gap-2 text-left sm:grid-cols-3", children: (skipIntegrations
215
+ ? [
216
+ [
217
+ "Workflow actions",
218
+ "Use the app's buttons and sidebar to run the workflow.",
219
+ ],
220
+ [
221
+ "AI sidebar",
222
+ "Ask the agent to review or refine a step in context.",
223
+ ],
224
+ [
225
+ "Flexible providers",
226
+ "Use Builder.io free credits or your own keys.",
227
+ ],
228
+ ]
229
+ : [
230
+ ["Chat + actions", "Ask your agent to work across the app."],
231
+ ["Agent integrations", "Connect tools from Settings anytime."],
232
+ [
233
+ "Flexible providers",
234
+ "Use Builder.io free credits or your own keys.",
235
+ ],
236
+ ]).map(([title, description]) => (_jsxs("div", { className: "rounded-xl bg-muted/35 px-4 py-4", children: [_jsx("span", { className: "flex size-7 items-center justify-center rounded-full bg-primary/10 text-primary", children: _jsx(IconCheck, { size: 14 }) }), _jsx("p", { className: "mt-3 text-sm font-medium", children: title }), _jsx("p", { className: "mt-1 text-xs leading-5 text-muted-foreground", children: description })] }, title))) }), _jsxs("button", { type: "button", "data-testid": "first-run-open-app", className: cn(primaryButtonClass, "mt-7"), onClick: handleFinish, children: ["Open app", _jsx(IconArrowRight, { size: 15 })] })] }), completeFirstRunError && (_jsx(FirstRunCompletionError, { message: completeFirstRunError, onRetry: finishOnboarding }))] }));
230
237
  }
231
238
  function OnboardingShell({ profile, screen, footer, children, }) {
232
239
  return (_jsxs("div", { className: "fixed inset-0 z-[100] flex h-full min-h-0 flex-col bg-background text-foreground", "data-onboarding-screen": screen, role: "dialog", "aria-modal": "true", "aria-label": `${profile?.appName ?? "Your app"} setup`, children: [_jsx("div", { className: "h-0.5 shrink-0 bg-muted", "data-testid": "onboarding-progress", children: _jsx("div", { className: "h-full bg-primary transition-[width] duration-200", style: {
@@ -266,6 +273,13 @@ function CapabilityInfoButton({ capability, ariaLabel, }) {
266
273
  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 })] }));
267
274
  }
268
275
  const primaryButtonClass = "inline-flex min-h-9 items-center justify-center gap-2 rounded-lg bg-primary px-4 text-xs font-medium text-primary-foreground shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
276
+ /** Inline failure signal for a failed completeFirstRun() call — keeps the
277
+ * user on their current screen with a way forward, instead of swapping to
278
+ * an unrelated full-screen error or leaving Skip/Continue looking like it
279
+ * did nothing. */
280
+ function FirstRunCompletionError({ message, onRetry, }) {
281
+ return (_jsxs("div", { className: "fixed inset-x-0 bottom-4 z-50 mx-auto flex w-fit max-w-[90vw] items-center gap-3 rounded-lg border border-destructive/30 bg-background px-4 py-2 text-xs shadow-lg", children: [_jsx("span", { className: "text-destructive", children: message }), _jsx("button", { type: "button", className: "font-medium underline underline-offset-2 hover:no-underline", onClick: onRetry, children: "Try again" })] }));
282
+ }
269
283
  const secondaryButtonClass = "inline-flex min-h-9 items-center justify-center gap-2 rounded-lg border border-border bg-background px-4 text-xs font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
270
284
  function compareUrl(value) {
271
285
  try {
@@ -30,8 +30,15 @@ export interface UseOnboardingResult {
30
30
  reopen: () => Promise<void>;
31
31
  /** True until the post-signup full-screen flow is completed. */
32
32
  firstRun: boolean;
33
- /** Clear the post-signup full-screen flow marker. */
33
+ /** Clear the post-signup full-screen flow marker. Rejects instead of
34
+ * resolving silently when the server call fails — see
35
+ * `completeFirstRunError` for the message to show the user. */
34
36
  completeFirstRun: () => Promise<void>;
37
+ /** Set when the last `completeFirstRun()` call failed. Cleared on the next
38
+ * attempt (success or failure). Distinct from `error` (the initial steps
39
+ * load failure) so a failed Skip/Continue doesn't swap the whole screen
40
+ * for an unrelated "could not load" message. */
41
+ completeFirstRunError: string | null;
35
42
  }
36
43
  export declare function useOnboarding(options?: {
37
44
  preview?: boolean;
@@ -10,6 +10,7 @@ export function useOnboarding(options = {}) {
10
10
  const [error, setError] = useState(null);
11
11
  const [dismissed, setDismissed] = useState(false);
12
12
  const [firstRun, setFirstRun] = useState(preview || initialFirstRun);
13
+ const [completeFirstRunError, setCompleteFirstRunError] = useState(null);
13
14
  const mountedRef = useRef(true);
14
15
  useEffect(() => {
15
16
  setFirstRun(preview || initialFirstRun);
@@ -116,14 +117,27 @@ export function useOnboarding(options = {}) {
116
117
  }
117
118
  return;
118
119
  }
119
- const response = await fetch(agentNativePath("/_agent-native/onboarding/first-run/complete"), {
120
- method: "POST",
121
- headers: { "Content-Type": "application/json" },
122
- body: "{}",
123
- });
120
+ setCompleteFirstRunError(null);
121
+ // Both a rejected fetch (offline, dropped connection) and a non-ok
122
+ // response are real failures — neither may look like success to the
123
+ // caller, so both throw instead of returning as if the step advanced.
124
+ let response;
125
+ try {
126
+ response = await fetch(agentNativePath("/_agent-native/onboarding/first-run/complete"), {
127
+ method: "POST",
128
+ headers: { "Content-Type": "application/json" },
129
+ body: "{}",
130
+ });
131
+ }
132
+ catch (e) {
133
+ const message = e instanceof Error ? e.message : "first-run completion request failed";
134
+ setCompleteFirstRunError(message);
135
+ throw e instanceof Error ? e : new Error(message);
136
+ }
124
137
  if (!response.ok) {
125
- setError(`first-run completion: ${response.status}`);
126
- return;
138
+ const message = `first-run completion failed: ${response.status}`;
139
+ setCompleteFirstRunError(message);
140
+ throw new Error(message);
127
141
  }
128
142
  setFirstRun(false);
129
143
  if (typeof window !== "undefined") {
@@ -153,5 +167,6 @@ export function useOnboarding(options = {}) {
153
167
  reopen,
154
168
  firstRun,
155
169
  completeFirstRun,
170
+ completeFirstRunError,
156
171
  };
157
172
  }
@@ -19,7 +19,7 @@ import { SettingsSkeleton } from "../settings/SettingsSkeleton.js";
19
19
  import { useShareOrgMemberSearch } from "../sharing/share-controller-helpers.js";
20
20
  import { useActionMutation, useActionQuery } from "../use-action.js";
21
21
  import { cn } from "../utils.js";
22
- import { useOrg, useOrgMembers, useOrgInvitations, useCreateOrg, useUpdateOrg, useBulkInviteMembers, useChangeMemberRole, useAcceptInvitation, useRemoveMember, useDeleteOrg, useSwitchOrg, useSetOrgDomain, useSetOrgWorkspaceUrl, useSetOrgAuthProvider, useRevealA2ASecret, useSetA2ASecret, useSyncA2ASecret, useJoinByDomain, useAppRoles, useSetAppMemberRole, ORG_MEMBER_PAGE_SIZE, } from "./hooks.js";
22
+ import { useOrg, useOrgMembers, useOrgInvitations, useCreateOrg, useUpdateOrg, useBulkInviteMembers, useChangeMemberRole, useAcceptInvitation, useRemoveMember, useDeleteOrg, useSwitchOrg, useSetOrgDomain, useSetWorkspaceAppDefaultVisibility, useSetOrgWorkspaceUrl, useSetOrgAuthProvider, useRevealA2ASecret, useSetA2ASecret, useSyncA2ASecret, useJoinByDomain, useAppRoles, useSetAppMemberRole, ORG_MEMBER_PAGE_SIZE, } from "./hooks.js";
23
23
  const Button = forwardRef(({ className, ...props }, ref) => (_jsx(ToolkitButton, { ref: ref, variant: "ghost", className: cn("h-auto p-0 hover:bg-transparent hover:text-inherit active:scale-100 [&_svg]:!size-auto", className), ...props })));
24
24
  Button.displayName = "TeamPrimitiveButton";
25
25
  function RoleIcon({ role }) {
@@ -219,7 +219,20 @@ function MembersCard({ appRoles }) {
219
219
  const totalMembers = membersData?.totalCount ?? 0;
220
220
  const pendingInvites = invitationsData?.invitations ?? [];
221
221
  const hasMultipleOrgs = (org.orgs?.length ?? 0) > 1;
222
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs(SettingsGroup, { title: "Organization", children: [_jsx(SettingsRow, { id: "organization", label: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(IconUsersGroup, { className: "size-4 text-muted-foreground" }), _jsx(OrgNameDisplay, { name: org.orgName ?? "", canEdit: isOwnerOrAdmin })] }), description: `${t("org.memberCount", { count: totalMembers })} · ${t("org.youAreRole", { role: org.role })}`, control: hasMultipleOrgs ? (_jsxs(Select, { value: org.orgId ?? "", onValueChange: (value) => switchOrg.mutate(value || null), disabled: switchOrg.isPending, children: [_jsx(SelectTrigger, { className: "h-auto w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs sm:w-auto", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: org.orgs.map((o) => (_jsx(SelectItem, { value: o.orgId, children: o.orgName }, o.orgId))) })] })) : undefined }), isOwnerOrAdmin && (_jsxs(_Fragment, { children: [_jsx(DomainSettingsSection, { domain: org.allowedDomain, ownerEmail: org.email }), _jsx(WorkspaceUrlSettingsSection, { workspaceUrl: org.workspaceUrl }), _jsx(AuthProviderSettingsSection, { requiredAuthProvider: org.requiredAuthProvider }), isOwner && _jsx(A2ASecretSection, { isSet: Boolean(org.a2aSecretSet) })] })), switchOrg.error && (_jsx("div", { className: "px-5 pb-4", children: _jsx(ErrorText, { error: switchOrg.error }) }))] }), _jsx(MembersTableCard, { members: members, totalMembers: totalMembers, pendingInvites: pendingInvites, isLoadingMembers: isLoadingMembers, isFetchingMembers: isFetchingMembers, currentUserEmail: org.email, currentUserRole: org.role ?? null, appRoles: appRoles, groups: groupsQuery.data ?? [], canManageGroups: isOwnerOrAdmin, memberOffset: memberOffset, hasNextPage: membersData?.hasMore === true, nextMemberOffset: membersData?.nextOffset ?? null, onMemberPageChange: setMemberOffset }), isOwnerOrAdmin && (_jsx(WorkspaceGroupsCard, { groups: groupsQuery.data ?? [] })), isOwner && _jsx(DangerZoneCard, { orgName: org.orgName ?? "" })] }));
222
+ return (_jsxs("div", { className: "space-y-6", children: [_jsxs(SettingsGroup, { title: "Organization", children: [_jsx(SettingsRow, { id: "organization", label: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(IconUsersGroup, { className: "size-4 text-muted-foreground" }), _jsx(OrgNameDisplay, { name: org.orgName ?? "", canEdit: isOwnerOrAdmin })] }), description: `${t("org.memberCount", { count: totalMembers })} · ${t("org.youAreRole", { role: org.role })}`, control: hasMultipleOrgs ? (_jsxs(Select, { value: org.orgId ?? "", onValueChange: (value) => switchOrg.mutate(value || null), disabled: switchOrg.isPending, children: [_jsx(SelectTrigger, { className: "h-auto w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs sm:w-auto", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: org.orgs.map((o) => (_jsx(SelectItem, { value: o.orgId, children: o.orgName }, o.orgId))) })] })) : undefined }), isOwnerOrAdmin && (_jsxs(_Fragment, { children: [_jsx(DomainSettingsSection, { domain: org.allowedDomain, ownerEmail: org.email }), _jsx(WorkspaceAppPrivacySettingsSection, { visibility: org.workspaceAppDefaultVisibility ?? "org" }), _jsx(WorkspaceUrlSettingsSection, { workspaceUrl: org.workspaceUrl }), _jsx(AuthProviderSettingsSection, { requiredAuthProvider: org.requiredAuthProvider }), isOwner && _jsx(A2ASecretSection, { isSet: Boolean(org.a2aSecretSet) })] })), switchOrg.error && (_jsx("div", { className: "px-5 pb-4", children: _jsx(ErrorText, { error: switchOrg.error }) }))] }), _jsx(MembersTableCard, { members: members, totalMembers: totalMembers, pendingInvites: pendingInvites, isLoadingMembers: isLoadingMembers, isFetchingMembers: isFetchingMembers, currentUserEmail: org.email, currentUserRole: org.role ?? null, appRoles: appRoles, groups: groupsQuery.data ?? [], canManageGroups: isOwnerOrAdmin, memberOffset: memberOffset, hasNextPage: membersData?.hasMore === true, nextMemberOffset: membersData?.nextOffset ?? null, onMemberPageChange: setMemberOffset }), isOwnerOrAdmin && (_jsx(WorkspaceGroupsCard, { groups: groupsQuery.data ?? [] })), isOwner && _jsx(DangerZoneCard, { orgName: org.orgName ?? "" })] }));
223
+ }
224
+ function WorkspaceAppPrivacySettingsSection({ visibility, }) {
225
+ const t = useT();
226
+ const setDefault = useSetWorkspaceAppDefaultVisibility();
227
+ return (_jsx(SettingsRow, { id: "workspace-app-default-visibility", label: t("org.workspaceAppsDefaultPrivacy", {
228
+ defaultValue: "New app privacy",
229
+ }), description: t("org.workspaceAppsDefaultPrivacyDescription", {
230
+ defaultValue: "Choose whether new workspace apps start private to their creator or visible to the organization.",
231
+ }), control: _jsxs(Select, { value: visibility, onValueChange: (value) => setDefault.mutate(value === "private" ? "private" : "org"), disabled: setDefault.isPending, children: [_jsx(SelectTrigger, { className: "h-auto w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-xs sm:w-auto", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "org", children: t("org.workspaceAppsOrganization", {
232
+ defaultValue: "Organization",
233
+ }) }), _jsx(SelectItem, { value: "private", children: t("org.workspaceAppsCreatorOnly", {
234
+ defaultValue: "Creator only",
235
+ }) })] })] }) }));
223
236
  }
224
237
  function MembersTableCard({ members, totalMembers, pendingInvites, isLoadingMembers, isFetchingMembers, currentUserEmail, currentUserRole, appRoles, groups, canManageGroups, memberOffset, hasNextPage, nextMemberOffset, onMemberPageChange, }) {
225
238
  const t = useT();
@@ -59,6 +59,8 @@ export declare function useSwitchOrg(): import("@tanstack/react-query").UseMutat
59
59
  export declare function useDeleteOrg(): import("@tanstack/react-query").UseMutationResult<any, Error, string, unknown>;
60
60
  export declare function useJoinByDomain(): import("@tanstack/react-query").UseMutationResult<any, Error, string, unknown>;
61
61
  export declare function useSetOrgDomain(): import("@tanstack/react-query").UseMutationResult<any, Error, string | null, unknown>;
62
+ export type WorkspaceAppDefaultVisibility = "private" | "org";
63
+ export declare function useSetWorkspaceAppDefaultVisibility(): import("@tanstack/react-query").UseMutationResult<any, Error, WorkspaceAppDefaultVisibility, unknown>;
62
64
  export declare function useSetOrgWorkspaceUrl(): import("@tanstack/react-query").UseMutationResult<any, Error, string | null, unknown>;
63
65
  export declare function useSetOrgAuthProvider(): import("@tanstack/react-query").UseMutationResult<any, Error, "google" | null, unknown>;
64
66
  export interface AppRoleAssignment {
@@ -247,6 +247,18 @@ export function useSetOrgDomain() {
247
247
  },
248
248
  });
249
249
  }
250
+ export function useSetWorkspaceAppDefaultVisibility() {
251
+ const qc = useQueryClient();
252
+ return useMutation({
253
+ mutationFn: (visibility) => apiFetch(`${ORG_BASE}/workspace-app-default-visibility`, {
254
+ method: "PUT",
255
+ body: JSON.stringify({ visibility }),
256
+ }),
257
+ onSuccess: async () => {
258
+ await qc.invalidateQueries({ queryKey: ["org-me"] });
259
+ },
260
+ });
261
+ }
250
262
  export function useSetOrgWorkspaceUrl() {
251
263
  const qc = useQueryClient();
252
264
  return useMutation({
@@ -1,5 +1,5 @@
1
- export { useOrg, useOrgMembers, useOrgInvitations, useCreateOrg, useUpdateOrg, useInviteMember, useBulkInviteMembers, useChangeMemberRole, useAcceptInvitation, useRemoveMember, useDeleteOrg, useSwitchOrg, useJoinByDomain, useSetOrgDomain, useSetOrgWorkspaceUrl, useRevealA2ASecret, useSetA2ASecret, useSyncA2ASecret, useOrgRole, useAppRoles, useAppRole, useSetAppMemberRole, } from "./hooks.js";
2
- export type { InviteRole, InviteVars, BulkInviteResult, SyncA2ASecretResult, UseOrgRoleResult, AppRoleAssignment, AppRolesInfo, } from "./hooks.js";
1
+ export { useOrg, useOrgMembers, useOrgInvitations, useCreateOrg, useUpdateOrg, useInviteMember, useBulkInviteMembers, useChangeMemberRole, useAcceptInvitation, useRemoveMember, useDeleteOrg, useSwitchOrg, useJoinByDomain, useSetOrgDomain, useSetWorkspaceAppDefaultVisibility, useSetOrgWorkspaceUrl, useRevealA2ASecret, useSetA2ASecret, useSyncA2ASecret, useOrgRole, useAppRoles, useAppRole, useSetAppMemberRole, } from "./hooks.js";
2
+ export type { InviteRole, InviteVars, BulkInviteResult, SyncA2ASecretResult, UseOrgRoleResult, AppRoleAssignment, AppRolesInfo, WorkspaceAppDefaultVisibility, } from "./hooks.js";
3
3
  export type { AppRolesDescriptor } from "../../org/app-roles.js";
4
4
  export { OrgSwitcher, type OrgSwitcherProps } from "./OrgSwitcher.js";
5
5
  export { InvitationBanner, type InvitationBannerProps, } from "./InvitationBanner.js";
@@ -1,5 +1,5 @@
1
1
  // Public client API for the org module.
2
- export { useOrg, useOrgMembers, useOrgInvitations, useCreateOrg, useUpdateOrg, useInviteMember, useBulkInviteMembers, useChangeMemberRole, useAcceptInvitation, useRemoveMember, useDeleteOrg, useSwitchOrg, useJoinByDomain, useSetOrgDomain, useSetOrgWorkspaceUrl, useRevealA2ASecret, useSetA2ASecret, useSyncA2ASecret, useOrgRole, useAppRoles, useAppRole, useSetAppMemberRole, } from "./hooks.js";
2
+ export { useOrg, useOrgMembers, useOrgInvitations, useCreateOrg, useUpdateOrg, useInviteMember, useBulkInviteMembers, useChangeMemberRole, useAcceptInvitation, useRemoveMember, useDeleteOrg, useSwitchOrg, useJoinByDomain, useSetOrgDomain, useSetWorkspaceAppDefaultVisibility, useSetOrgWorkspaceUrl, useRevealA2ASecret, useSetA2ASecret, useSyncA2ASecret, useOrgRole, useAppRoles, useAppRole, useSetAppMemberRole, } from "./hooks.js";
3
3
  export { OrgSwitcher } from "./OrgSwitcher.js";
4
4
  export { InvitationBanner, } from "./InvitationBanner.js";
5
5
  export { WorkspaceNotice } from "./WorkspaceNotice.js";