@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
@@ -1040,6 +1040,31 @@ function isDefinitiveReplayUploadClientError(status) {
1040
1040
  return status === 400 || status === 409 || status === 413 || status === 422;
1041
1041
  }
1042
1042
  const MAX_TRANSIENT_REPLAY_CLIENT_FAILURES = 3;
1043
+ // A hung upload never settles, so `state.flushing` stays set, every later flush
1044
+ // early-returns while still queueing rrweb events, and the queue grows until the
1045
+ // session ends. Time the request out so the failure is observable and the lock
1046
+ // is released.
1047
+ const REPLAY_UPLOAD_TIMEOUT_MS = 15_000;
1048
+ function startReplayUploadTimeout() {
1049
+ // Use an explicit controller/timer pair instead of relying only on
1050
+ // AbortSignal.timeout: the controller is available in older supported
1051
+ // browsers, and the timer can always be cleared when a normal upload
1052
+ // settles so completed uploads do not leave timeout work behind.
1053
+ const controller = typeof AbortController === "undefined" ? undefined : new AbortController();
1054
+ let rejectTimeout;
1055
+ const promise = new Promise((_, reject) => {
1056
+ rejectTimeout = reject;
1057
+ });
1058
+ const timer = setTimeout(() => {
1059
+ controller?.abort();
1060
+ rejectTimeout(new Error("Session replay upload timed out"));
1061
+ }, REPLAY_UPLOAD_TIMEOUT_MS);
1062
+ return {
1063
+ signal: controller?.signal,
1064
+ promise,
1065
+ done: () => clearTimeout(timer),
1066
+ };
1067
+ }
1043
1068
  function isTransientReplayUploadClientError(status) {
1044
1069
  return status === 401 || status === 403 || status === 404;
1045
1070
  }
@@ -1048,12 +1073,21 @@ async function sendReplayUpload(options, body, callbacks = {}) {
1048
1073
  const canUseKeepalive = canUseReplayKeepalive(body);
1049
1074
  if (canUseKeepalive)
1050
1075
  callbacks.beforeKeepaliveUpload?.();
1051
- const response = await fetch(options.endpoint, {
1052
- method: "POST",
1053
- body,
1054
- keepalive: canUseKeepalive,
1055
- headers: { "Content-Type": "text/plain;charset=UTF-8" },
1056
- });
1076
+ const timeout = startReplayUploadTimeout();
1077
+ let response;
1078
+ try {
1079
+ const request = fetch(options.endpoint, {
1080
+ method: "POST",
1081
+ body,
1082
+ keepalive: canUseKeepalive,
1083
+ headers: { "Content-Type": "text/plain;charset=UTF-8" },
1084
+ signal: timeout.signal,
1085
+ });
1086
+ response = await Promise.race([request, timeout.promise]);
1087
+ }
1088
+ finally {
1089
+ timeout.done();
1090
+ }
1057
1091
  if (!response.ok) {
1058
1092
  throw new ReplayUploadHttpError(response.status);
1059
1093
  }
@@ -1063,15 +1097,24 @@ async function sendReplayUpload(options, body, callbacks = {}) {
1063
1097
  const canUseKeepalive = canUseReplayKeepalive(upload.body);
1064
1098
  if (canUseKeepalive)
1065
1099
  callbacks.beforeKeepaliveUpload?.();
1066
- const response = await fetch(options.endpoint, {
1067
- method: "POST",
1068
- body: upload.body,
1069
- keepalive: canUseKeepalive,
1070
- headers: {
1071
- ...upload.headers,
1072
- "X-Agent-Native-Analytics-Key": options.publicKey,
1073
- },
1074
- });
1100
+ const timeout = startReplayUploadTimeout();
1101
+ let response;
1102
+ try {
1103
+ const request = fetch(options.endpoint, {
1104
+ method: "POST",
1105
+ body: upload.body,
1106
+ keepalive: canUseKeepalive,
1107
+ headers: {
1108
+ ...upload.headers,
1109
+ "X-Agent-Native-Analytics-Key": options.publicKey,
1110
+ },
1111
+ signal: timeout.signal,
1112
+ });
1113
+ response = await Promise.race([request, timeout.promise]);
1114
+ }
1115
+ finally {
1116
+ timeout.done();
1117
+ }
1075
1118
  if (!response.ok) {
1076
1119
  throw new ReplayUploadHttpError(response.status);
1077
1120
  }
@@ -3,6 +3,7 @@ import { Tabs, useDesignSystem } from "@agent-native/toolkit/design-system";
3
3
  import { IconArrowUpRight, IconHistory, IconSearch, IconSettings, IconUserCircle, IconUsers, IconX, } from "@tabler/icons-react";
4
4
  import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
5
5
  import { Link } from "react-router";
6
+ import { appBasePath, appPath } from "../../client/api-path.js";
6
7
  import { buildSettingsRoute } from "../../navigation/index.js";
7
8
  import { cn } from "../utils.js";
8
9
  function normalizeTabId(value) {
@@ -70,12 +71,13 @@ function resolveTabId(tabs, value) {
70
71
  function activeTabFromLocation(tabs, defaultTab) {
71
72
  if (typeof window === "undefined")
72
73
  return defaultTab;
74
+ const pathname = appLocalPathname();
73
75
  const settingsPrefix = "/settings";
74
- if (window.location.pathname === settingsPrefix) {
76
+ if (pathname === settingsPrefix) {
75
77
  return resolveTabId(tabs, window.location.hash) ?? defaultTab;
76
78
  }
77
- if (window.location.pathname.startsWith(`${settingsPrefix}/`)) {
78
- const segments = window.location.pathname
79
+ if (pathname.startsWith(`${settingsPrefix}/`)) {
80
+ const segments = pathname
79
81
  .slice(`${settingsPrefix}/`.length)
80
82
  .split("/")
81
83
  .filter(Boolean)
@@ -95,6 +97,17 @@ function activeTabFromLocation(tabs, defaultTab) {
95
97
  }
96
98
  return resolveTabId(tabs, window.location.hash) ?? defaultTab;
97
99
  }
100
+ function appLocalPathname() {
101
+ if (typeof window === "undefined")
102
+ return "/";
103
+ const pathname = window.location.pathname;
104
+ const basePath = appBasePath();
105
+ if (basePath &&
106
+ (pathname === basePath || pathname.startsWith(`${basePath}/`))) {
107
+ return pathname.slice(basePath.length) || "/";
108
+ }
109
+ return pathname;
110
+ }
98
111
  function buildSettingsEntryRoute(tabId, section) {
99
112
  const normalizedSection = section?.replace(/^#/, "").trim();
100
113
  if (!normalizedSection || normalizedSection === tabId) {
@@ -112,7 +125,7 @@ function updateRouteForTab(tabId, section) {
112
125
  if (typeof window === "undefined")
113
126
  return;
114
127
  const route = buildSettingsEntryRoute(tabId, section);
115
- window.history.pushState(null, "", `${route}${window.location.search}`);
128
+ window.history.pushState(null, "", `${appPath(route)}${window.location.search}`);
116
129
  }
117
130
  function isEditableElement(element) {
118
131
  if (!(element instanceof HTMLElement))
@@ -233,7 +246,7 @@ export function SettingsTabsPage({ general, account, team, whatsNew, extraTabs =
233
246
  const fromPath = activeTabFromLocation(tabs, fallbackTab);
234
247
  if (fromPath)
235
248
  setInternalTab(fromPath);
236
- if (window.location.pathname.startsWith("/settings/"))
249
+ if (appLocalPathname().startsWith("/settings/"))
237
250
  return;
238
251
  const hashValue = window.location.hash.replace(/^#/, "");
239
252
  const fromHash = resolveTabId(tabs, hashValue);
@@ -254,7 +267,7 @@ export function SettingsTabsPage({ general, account, team, whatsNew, extraTabs =
254
267
  if (!isControlled)
255
268
  return;
256
269
  const syncControlledLocation = () => {
257
- const fromPath = window.location.pathname.startsWith("/settings/")
270
+ const fromPath = appLocalPathname().startsWith("/settings/")
258
271
  ? activeTabFromLocation(tabs, defaultTab)
259
272
  : null;
260
273
  const hashValue = window.location.hash.replace(/^#/, "");
@@ -131,7 +131,7 @@ export function ShareButton(props) {
131
131
  function SharePanel(props) {
132
132
  const t = useT();
133
133
  const { controller } = props;
134
- const { inviteEmail, setInviteEmail: onInviteEmailChange, activeShareTab, handleShareTabChange, data, policy, visibility, canManage, role, setRole, notifyPeople, setNotifyPeople, shareMessage, setShareMessage, messageOpen, setMessageOpen, shareError, setShareError, suggestionsOpen, setSuggestionsOpen, inFlight, memberSearch, memberSuggestions, knownMembers, shares, handleVisibility, handleHideInSearch, handleAdd, handleChangeRole, handleRemove, } = controller;
134
+ const { inviteEmail, setInviteEmail: onInviteEmailChange, activeShareTab, handleShareTabChange, data, policy, visibility, canManage, role, setRole, notifyPeople, setNotifyPeople, shareMessage, setShareMessage, messageOpen, setMessageOpen, shareError, setShareError, suggestionsOpen, setSuggestionsOpen, inFlight, memberSearch, memberSuggestions, groupSuggestions, selectGroup, knownMembers, shares, handleVisibility, handleHideInSearch, handleAdd, handleChangeRole, handleRemove, } = controller;
135
135
  const hasInviteEmail = inviteEmail.trim().length > 0;
136
136
  const isLoading = data === undefined;
137
137
  const meta = visibilityMeta(visibility, t, props.visibilityCopy);
@@ -155,8 +155,13 @@ function SharePanel(props) {
155
155
  onInviteEmailChange(next);
156
156
  if (shareError)
157
157
  setShareError(null);
158
- }, onSelectMember: (member) => {
159
- onInviteEmailChange(member.email);
158
+ }, onSelectSuggestion: (suggestion) => {
159
+ if (suggestion.principalType === "group") {
160
+ selectGroup(suggestion);
161
+ }
162
+ else {
163
+ onInviteEmailChange(suggestion.email);
164
+ }
160
165
  setSuggestionsOpen(false);
161
166
  if (shareError)
162
167
  setShareError(null);
@@ -165,8 +170,21 @@ function SharePanel(props) {
165
170
  defaultValue: "Add people from your organization",
166
171
  })
167
172
  : t("agentChat.share.addPeopleEmail", {
168
- defaultValue: "Add people by email",
169
- }), suggestions: memberSuggestions, search: memberSearch }), _jsx(RoleSelect, { value: role, onChange: setRole, roleCopy: props.roleCopy, allowedRoles: props.allowedRoles }), _jsx("button", { type: "button", onClick: handleAdd, disabled: !hasInviteEmail, className: BUTTON_PRIMARY_SM, children: t("agentChat.share.add", { defaultValue: "Add" }) })] }), shareError ? (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, hasInviteEmail ? (_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-xs text-muted-foreground", children: [_jsxs("label", { className: "inline-flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: notifyPeople, onChange: (e) => setNotifyPeople(e.target.checked), className: "h-4 w-4 rounded border-input accent-primary" }), t("agentChat.share.notifyPeople", {
173
+ defaultValue: policy.supportsGroupShares
174
+ ? "Add people or groups"
175
+ : "Add people by email",
176
+ }), suggestions: [
177
+ ...memberSuggestions.map((member) => ({
178
+ ...member,
179
+ principalType: "user",
180
+ })),
181
+ ...(policy.supportsGroupShares
182
+ ? groupSuggestions.map((group) => ({
183
+ ...group,
184
+ principalType: "group",
185
+ }))
186
+ : []),
187
+ ], search: memberSearch }), _jsx(RoleSelect, { value: role, onChange: setRole, roleCopy: props.roleCopy, allowedRoles: props.allowedRoles }), _jsx("button", { type: "button", onClick: handleAdd, disabled: !hasInviteEmail, className: BUTTON_PRIMARY_SM, children: t("agentChat.share.add", { defaultValue: "Add" }) })] }), shareError ? (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, hasInviteEmail ? (_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-xs text-muted-foreground", children: [_jsxs("label", { className: "inline-flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: notifyPeople, onChange: (e) => setNotifyPeople(e.target.checked), className: "h-4 w-4 rounded border-input accent-primary" }), t("agentChat.share.notifyPeople", {
170
188
  defaultValue: "Notify people",
171
189
  })] }), notifyPeople ? (_jsx("button", { type: "button", "aria-expanded": messageOpen, onClick: () => setMessageOpen(!messageOpen), className: "rounded-sm px-1 py-0.5 font-medium text-foreground underline decoration-border underline-offset-2 transition-colors hover:bg-accent hover:text-accent-foreground", children: messageOpen
172
190
  ? t("agentChat.share.hideMessage", {
@@ -178,7 +196,7 @@ function SharePanel(props) {
178
196
  defaultValue: "Message",
179
197
  }), placeholder: t("agentChat.share.messagePlaceholder", {
180
198
  defaultValue: "Add a short note (optional)",
181
- }), value: shareMessage, onChange: (event) => setShareMessage(event.target.value), maxLength: 500, rows: 3, className: "w-full resize-y rounded-md border border-input bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" }) })) : null] })) : null, _jsxs("ul", { className: "flex list-none flex-col gap-1 p-0 m-0", children: [data?.ownerEmail ? (_jsxs("li", { className: "flex items-center gap-3 px-1 py-1.5 text-sm", children: [_jsx(Avatar, { label: displayName(data.ownerEmail, knownMembers, t) }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: displayName(data.ownerEmail, knownMembers, t) }), _jsx("span", { className: "text-xs text-muted-foreground", children: t("agentChat.share.owner", { defaultValue: "Owner" }) })] })) : null, shares.map((s) => (_jsxs("li", { className: cn("flex items-center gap-3 px-1 py-1.5 text-sm", inFlight.has(keyOf(s)) && "opacity-60"), children: [_jsx(Avatar, { label: principalLabel(s, knownMembers, t), org: s.principalType === "org" }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: principalLabel(s, knownMembers, t) }), canManage ? (_jsx(RoleSelect, { value: s.role, onChange: (r) => handleChangeRole(s, r), disabled: inFlight.has(keyOf(s)), plain: true, roleCopy: props.roleCopy, allowedRoles: props.allowedRoles })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: roleMeta(s.role, t, props.roleCopy).label })), canManage ? (_jsx("button", { type: "button", "aria-label": t("agentChat.share.remove", {
199
+ }), value: shareMessage, onChange: (event) => setShareMessage(event.target.value), maxLength: 500, rows: 3, className: "w-full resize-y rounded-md border border-input bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" }) })) : null] })) : null, _jsxs("ul", { className: "flex list-none flex-col gap-1 p-0 m-0", children: [data?.ownerEmail ? (_jsxs("li", { className: "flex items-center gap-3 px-1 py-1.5 text-sm", children: [_jsx(Avatar, { label: displayName(data.ownerEmail, knownMembers, t) }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: displayName(data.ownerEmail, knownMembers, t) }), _jsx("span", { className: "text-xs text-muted-foreground", children: t("agentChat.share.owner", { defaultValue: "Owner" }) })] })) : null, shares.map((s) => (_jsxs("li", { className: cn("flex items-center gap-3 px-1 py-1.5 text-sm", inFlight.has(keyOf(s)) && "opacity-60"), children: [_jsx(Avatar, { label: principalLabel(s, knownMembers, t), org: s.principalType === "org", group: s.principalType === "group" }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: principalLabel(s, knownMembers, t) }), canManage ? (_jsx(RoleSelect, { value: s.role, onChange: (r) => handleChangeRole(s, r), disabled: inFlight.has(keyOf(s)), plain: true, roleCopy: props.roleCopy, allowedRoles: props.allowedRoles })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: roleMeta(s.role, t, props.roleCopy).label })), canManage ? (_jsx("button", { type: "button", "aria-label": t("agentChat.share.remove", {
182
200
  defaultValue: "Remove",
183
201
  }), onClick: () => handleRemove(s), disabled: inFlight.has(keyOf(s)), className: BUTTON_GHOST_ICON, children: _jsx(IconTrash, { size: 14 }) })) : null] }, keyOf(s)))), !shares.length && !data?.ownerEmail ? (_jsx("li", { className: "px-1 py-1.5 text-sm text-muted-foreground", children: t("agentChat.share.noAccess", {
184
202
  defaultValue: "No one has access yet.",
@@ -219,13 +237,13 @@ function AdvancedAccessPopover({ control, canManage, onToggle, }) {
219
237
  defaultValue: "People with the link can still open this.",
220
238
  }) })] })] })] }) })] }));
221
239
  }
222
- function MemberAutocomplete({ value, open, onOpenChange, onValueChange, onSelectMember, onSubmit, placeholder, suggestions, search, }) {
240
+ function MemberAutocomplete({ value, open, onOpenChange, onValueChange, onSelectSuggestion, onSubmit, placeholder, suggestions, search, }) {
223
241
  const t = useT();
224
242
  const rawListboxId = useId();
225
243
  const listboxId = rawListboxId.replace(/:/g, "");
226
244
  const inputRef = useRef(null);
227
245
  const [activeIndex, setActiveIndex] = useState(-1);
228
- const activeMember = activeIndex >= 0 && activeIndex < suggestions.length
246
+ const activeSuggestion = activeIndex >= 0 && activeIndex < suggestions.length
229
247
  ? suggestions[activeIndex]
230
248
  : null;
231
249
  useEffect(() => {
@@ -243,8 +261,8 @@ function MemberAutocomplete({ value, open, onOpenChange, onValueChange, onSelect
243
261
  .getElementById(optionId(listboxId, activeIndex))
244
262
  ?.scrollIntoView({ block: "nearest" });
245
263
  }, [activeIndex, listboxId]);
246
- const chooseMember = (member) => {
247
- onSelectMember(member);
264
+ const chooseSuggestion = (suggestion) => {
265
+ onSelectSuggestion(suggestion);
248
266
  onOpenChange(false);
249
267
  inputRef.current?.focus();
250
268
  };
@@ -273,9 +291,9 @@ function MemberAutocomplete({ value, open, onOpenChange, onValueChange, onSelect
273
291
  return;
274
292
  }
275
293
  if (event.key === "Enter") {
276
- if (open && activeMember) {
294
+ if (open && activeSuggestion) {
277
295
  event.preventDefault();
278
- chooseMember(activeMember);
296
+ chooseSuggestion(activeSuggestion);
279
297
  return;
280
298
  }
281
299
  if (value.trim()) {
@@ -299,7 +317,7 @@ function MemberAutocomplete({ value, open, onOpenChange, onValueChange, onSelect
299
317
  search.loadMore();
300
318
  }
301
319
  };
302
- return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverAnchor, { asChild: true, children: _jsxs("div", { className: "relative flex-1 min-w-0", children: [_jsx(IconSearch, { "aria-hidden": true, size: 15, strokeWidth: 1.8, className: "pointer-events-none absolute start-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" }), _jsx("input", { ref: inputRef, type: "email", role: "combobox", "aria-autocomplete": "list", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-activedescendant": activeIndex >= 0 ? optionId(listboxId, activeIndex) : undefined, placeholder: placeholder, value: value, onChange: (event) => {
320
+ return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverAnchor, { asChild: true, children: _jsxs("div", { className: "relative flex-1 min-w-0", children: [_jsx(IconSearch, { "aria-hidden": true, size: 15, strokeWidth: 1.8, className: "pointer-events-none absolute start-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" }), _jsx("input", { ref: inputRef, type: "text", role: "combobox", "aria-autocomplete": "list", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-activedescendant": activeIndex >= 0 ? optionId(listboxId, activeIndex) : undefined, placeholder: placeholder, value: value, onChange: (event) => {
303
321
  onValueChange(event.target.value);
304
322
  onOpenChange(true);
305
323
  }, onFocus: () => onOpenChange(true), onBlur: () => {
@@ -308,11 +326,15 @@ function MemberAutocomplete({ value, open, onOpenChange, onValueChange, onSelect
308
326
  onOpenChange(false);
309
327
  }
310
328
  }, 0);
311
- }, onKeyDown: handleKeyDown, autoComplete: "off", className: "h-9 w-full min-w-0 rounded-md border border-input bg-card ps-8 pe-8 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" }), search.isLoading ? (_jsx(IconLoader2, { "aria-hidden": true, size: 15, strokeWidth: 1.8, className: "pointer-events-none absolute end-2.5 top-1/2 -translate-y-1/2 animate-spin text-muted-foreground" })) : null] }) }), _jsx(PopoverContent, { align: "start", sideOffset: 4, "data-agent-native-share-overlay": "", onOpenAutoFocus: (event) => event.preventDefault(), className: cn(SHARE_NESTED_OVERLAY_Z, "w-[var(--radix-popper-anchor-width)] min-w-[18rem] rounded-md p-1 shadow-lg", SHARE_POPOVER_SURFACE), children: _jsxs("div", { id: listboxId, role: "listbox", className: "max-h-56 overflow-y-auto overflow-x-hidden", onScroll: handleScroll, children: [suggestions.map((member, index) => {
329
+ }, onKeyDown: handleKeyDown, autoComplete: "off", className: "h-9 w-full min-w-0 rounded-md border border-input bg-card ps-8 pe-8 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" }), search.isLoading ? (_jsx(IconLoader2, { "aria-hidden": true, size: 15, strokeWidth: 1.8, className: "pointer-events-none absolute end-2.5 top-1/2 -translate-y-1/2 animate-spin text-muted-foreground" })) : null] }) }), _jsx(PopoverContent, { align: "start", sideOffset: 4, "data-agent-native-share-overlay": "", onOpenAutoFocus: (event) => event.preventDefault(), className: cn(SHARE_NESTED_OVERLAY_Z, "w-[var(--radix-popper-anchor-width)] min-w-[18rem] rounded-md p-1 shadow-lg", SHARE_POPOVER_SURFACE), children: _jsxs("div", { id: listboxId, role: "listbox", className: "max-h-56 overflow-y-auto overflow-x-hidden", onScroll: handleScroll, children: [suggestions.map((suggestion, index) => {
312
330
  const active = index === activeIndex;
313
- return (_jsxs("div", { id: optionId(listboxId, index), role: "option", "aria-selected": active, onMouseDown: (event) => event.preventDefault(), onMouseEnter: () => setActiveIndex(index), onClick: () => chooseMember(member), className: cn("flex cursor-pointer select-none flex-col rounded-sm px-3 py-2 text-sm outline-none", active
331
+ return (_jsxs("div", { id: optionId(listboxId, index), role: "option", "aria-selected": active, onMouseDown: (event) => event.preventDefault(), onMouseEnter: () => setActiveIndex(index), onClick: () => chooseSuggestion(suggestion), className: cn("flex cursor-pointer select-none flex-col rounded-sm px-3 py-2 text-sm outline-none", active
314
332
  ? "bg-accent text-accent-foreground"
315
- : "text-foreground hover:bg-accent hover:text-accent-foreground"), children: [_jsx("span", { className: "truncate font-medium", children: member.name?.trim() || member.email }), member.name?.trim() ? (_jsx("span", { className: "truncate text-xs text-muted-foreground", children: member.email })) : null] }, member.email));
333
+ : "text-foreground hover:bg-accent hover:text-accent-foreground"), children: [_jsxs("span", { className: "flex items-center gap-2 truncate font-medium", children: [suggestion.principalType === "group" ? (_jsx(IconUsersGroup, { "aria-hidden": true, size: 14, strokeWidth: 1.8, className: "shrink-0 text-muted-foreground" })) : null, _jsx("span", { className: "truncate", children: suggestion.principalType === "group"
334
+ ? suggestion.name
335
+ : suggestion.name?.trim() || suggestion.email })] }), suggestion.principalType === "group" ? (_jsx("span", { className: "truncate text-xs text-muted-foreground", children: t("agentChat.share.userGroup", {
336
+ defaultValue: "User group",
337
+ }) })) : suggestion.name?.trim() ? (_jsx("span", { className: "truncate text-xs text-muted-foreground", children: suggestion.email })) : null] }, `${suggestion.principalType}:${suggestion.principalType === "group" ? suggestion.id : suggestion.email}`));
316
338
  }), search.isLoading && suggestions.length === 0 ? (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: t("agentChat.share.searching", {
317
339
  defaultValue: "Searching...",
318
340
  }) })) : null, search.error ? (_jsx("div", { className: "px-3 py-3 text-sm text-muted-foreground", children: t("agentChat.share.loadPeopleFailed", {
@@ -375,8 +397,8 @@ function VisibilitySelect(props) {
375
397
  .description,
376
398
  })) }) }) }) })] }));
377
399
  }
378
- function Avatar({ label, org }) {
379
- return (_jsx("span", { "aria-hidden": true, className: "inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-muted text-[11px] font-semibold text-muted-foreground", children: org ? _jsx(IconUsersGroup, { size: 14, strokeWidth: 1.75 }) : initials(label) }));
400
+ function Avatar({ label, org, group, }) {
401
+ return (_jsx("span", { "aria-hidden": true, className: "inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-muted text-[11px] font-semibold text-muted-foreground", children: org || group ? (_jsx(IconUsersGroup, { size: 14, strokeWidth: 1.75 })) : (initials(label)) }));
380
402
  }
381
403
  function keyOf(s) {
382
404
  return `${s.principalType}:${s.principalId}`;
@@ -393,6 +415,9 @@ function principalLabel(share, members, t) {
393
415
  return t("agentChat.share.organization", {
394
416
  defaultValue: "Organization",
395
417
  });
418
+ if (share.principalType === "group")
419
+ return (serverLabel ||
420
+ t("agentChat.share.userGroup", { defaultValue: "User group" }));
396
421
  return displayName(share.principalId, members, t);
397
422
  }
398
423
  function displayName(emailOrId, members, t) {
@@ -1,13 +1,18 @@
1
- import type { useActionQuery } from "../use-action.js";
1
+ import { useActionQuery } from "../use-action.js";
2
2
  export type ShareButtonVisibility = "private" | "org" | "public";
3
3
  export type ShareButtonRole = "viewer" | "commenter" | "editor" | "admin";
4
4
  export interface ShareButtonShare {
5
5
  id: string;
6
- principalType: "user" | "org";
6
+ principalType: "user" | "group" | "org";
7
7
  principalId: string;
8
8
  displayName?: string | null;
9
9
  role: ShareButtonRole;
10
10
  }
11
+ export interface ShareButtonGroup {
12
+ id: string;
13
+ name: string;
14
+ memberEmails?: string[];
15
+ }
11
16
  export interface ShareButtonSharesResponse {
12
17
  ownerEmail: string | null;
13
18
  orgId: string | null;
@@ -18,6 +23,7 @@ export interface ShareButtonSharesResponse {
18
23
  policy?: {
19
24
  allowPublic: boolean;
20
25
  requireOrgMemberForUserShares: boolean;
26
+ supportsGroupShares?: boolean;
21
27
  };
22
28
  }
23
29
  export interface ShareButtonOrgMember {
@@ -65,6 +71,7 @@ export interface ShareButtonController {
65
71
  policy: {
66
72
  allowPublic: boolean;
67
73
  requireOrgMemberForUserShares: boolean;
74
+ supportsGroupShares?: boolean;
68
75
  };
69
76
  visibility: ShareButtonVisibility;
70
77
  triggerVisibility: ShareButtonVisibility | null;
@@ -84,7 +91,10 @@ export interface ShareButtonController {
84
91
  inFlight: Set<string>;
85
92
  memberSearch: ShareButtonOrgMemberSearch;
86
93
  memberSuggestions: ShareButtonOrgMember[];
94
+ groupSuggestions: ShareButtonGroup[];
87
95
  knownMembers: ShareButtonOrgMember[];
96
+ selectedGroup: ShareButtonGroup | null;
97
+ selectGroup: (group: ShareButtonGroup) => void;
88
98
  shares: ShareButtonShare[];
89
99
  handleVisibility: (visibility: ShareButtonVisibility) => void;
90
100
  handleHideInSearch: () => void;
@@ -1,8 +1,10 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useActionQuery } from "../use-action.js";
2
3
  import { DEFAULT_MEMBER_SEARCH_DEBOUNCE_MS, DEFAULT_MEMBER_SUGGESTION_LIMIT, extractShareErrorMessage, optimisticallyUpdateShareCache, rollbackShareCache, useShareOrgMemberSearch, useShareMutationGuard, useShareMutations, useShareQuery, } from "./share-controller-helpers.js";
3
4
  export function useShareButtonController(options) {
4
5
  const [open, setOpen] = useState(false);
5
6
  const [inviteEmail, setInviteEmail] = useState("");
7
+ const [selectedGroup, setSelectedGroup] = useState(null);
6
8
  const shareTabDefaultValue = options.shareTabs?.defaultValue ?? "share";
7
9
  const [activeShareTab, setActiveShareTab] = useState(shareTabDefaultValue);
8
10
  const [visibilityOverride, setVisibilityOverride] = useState(null);
@@ -25,6 +27,7 @@ export function useShareButtonController(options) {
25
27
  }, [options, shareTabDefaultValue, sharesQuery, visibilityOverride]);
26
28
  useEffect(() => {
27
29
  setInviteEmail("");
30
+ setSelectedGroup(null);
28
31
  setShareMessage("");
29
32
  setMessageOpen(false);
30
33
  }, [options.resourceId, options.resourceType]);
@@ -141,6 +144,10 @@ export function useShareButtonController(options) {
141
144
  limit: DEFAULT_MEMBER_SUGGESTION_LIMIT,
142
145
  debounceMs: DEFAULT_MEMBER_SEARCH_DEBOUNCE_MS,
143
146
  });
147
+ const groupsQuery = useActionQuery("list-workspace-user-groups", {}, {
148
+ enabled: canManage && policy.supportsGroupShares === true && suggestionsOpen,
149
+ staleTime: 30_000,
150
+ });
144
151
  const serverShares = data?.shares ?? [];
145
152
  const shares = [
146
153
  ...serverShares
@@ -160,7 +167,21 @@ export function useShareButtonController(options) {
160
167
  }
161
168
  }
162
169
  const memberSuggestions = memberSearch.members.filter((member) => !excludedMemberEmails.has(member.email.toLowerCase()));
170
+ const excludedGroupIds = new Set(shares
171
+ .filter((share) => share.principalType === "group")
172
+ .map((share) => share.principalId));
173
+ const groupSuggestions = (Array.isArray(groupsQuery.data) ? groupsQuery.data : []).filter((group) => !excludedGroupIds.has(group.id) &&
174
+ (!inviteEmail.trim() ||
175
+ group.name.toLowerCase().includes(inviteEmail.trim().toLowerCase())));
163
176
  const knownMembers = memberSearch.members;
177
+ const setInviteEmailValue = useCallback((email) => {
178
+ setSelectedGroup(null);
179
+ setInviteEmail(email);
180
+ }, []);
181
+ const selectGroup = useCallback((group) => {
182
+ setSelectedGroup(group);
183
+ setInviteEmail(group.name);
184
+ }, []);
164
185
  const handleVisibility = useCallback((next) => {
165
186
  if (next === visibility)
166
187
  return;
@@ -189,11 +210,14 @@ export function useShareButtonController(options) {
189
210
  const trimmed = inviteEmail.trim();
190
211
  if (!trimmed || !canManage)
191
212
  return;
192
- const message = notifyPeople ? shareMessage.trim() : "";
213
+ const principalType = selectedGroup ? "group" : "user";
214
+ const principalId = selectedGroup?.id ?? trimmed;
215
+ const message = principalType === "user" && notifyPeople ? shareMessage.trim() : "";
193
216
  const optimistic = {
194
- id: `pending-${trimmed}`,
195
- principalType: "user",
196
- principalId: trimmed,
217
+ id: `pending-${principalType}-${principalId}`,
218
+ principalType,
219
+ principalId,
220
+ ...(selectedGroup ? { displayName: selectedGroup.name } : {}),
197
221
  role,
198
222
  };
199
223
  const key = keyOf(optimistic);
@@ -202,6 +226,7 @@ export function useShareButtonController(options) {
202
226
  setShareError(null);
203
227
  setPendingAdds((previous) => [...previous, optimistic]);
204
228
  setInviteEmail("");
229
+ setSelectedGroup(null);
205
230
  setShareMessage("");
206
231
  setMessageOpen(false);
207
232
  setSuggestionsOpen(false);
@@ -210,10 +235,10 @@ export function useShareButtonController(options) {
210
235
  share.mutate({
211
236
  resourceType: options.resourceType,
212
237
  resourceId: options.resourceId,
213
- principalType: "user",
214
- principalId: trimmed,
238
+ principalType,
239
+ principalId,
215
240
  role,
216
- notify: notifyPeople,
241
+ notify: principalType === "user" && notifyPeople,
217
242
  resourceUrl: getNotificationUrl(options.shareUrl),
218
243
  ...(message ? { message } : {}),
219
244
  }, {
@@ -228,6 +253,7 @@ export function useShareButtonController(options) {
228
253
  setPendingAdds((previous) => previous.filter((item) => item.id !== optimistic.id));
229
254
  clearInFlight(key);
230
255
  setInviteEmail(trimmed);
256
+ setSelectedGroup(selectedGroup);
231
257
  setShareMessage((current) => current || message);
232
258
  setMessageOpen((current) => current || Boolean(message));
233
259
  setShareError(extractShareErrorMessage(error));
@@ -244,6 +270,7 @@ export function useShareButtonController(options) {
244
270
  options.resourceType,
245
271
  options.shareUrl,
246
272
  role,
273
+ selectedGroup,
247
274
  shareMessage,
248
275
  share,
249
276
  queryClient,
@@ -368,7 +395,7 @@ export function useShareButtonController(options) {
368
395
  activeShareTab,
369
396
  handleShareTabChange,
370
397
  inviteEmail,
371
- setInviteEmail,
398
+ setInviteEmail: setInviteEmailValue,
372
399
  sharesQuery,
373
400
  visibilityOverride,
374
401
  handleVisibilityChange,
@@ -399,6 +426,9 @@ export function useShareButtonController(options) {
399
426
  handleAdd,
400
427
  handleChangeRole,
401
428
  handleRemove,
429
+ groupSuggestions,
430
+ selectedGroup,
431
+ selectGroup,
402
432
  };
403
433
  }
404
434
  function keyOf(share) {
@@ -39,10 +39,15 @@ export type ContentPart = {
39
39
  * call (opt-in `needsApproval` actions). The action did NOT run; the UI
40
40
  * renders an Approve/Deny affordance. `approvalKey` is echoed back in
41
41
  * `approvedToolCalls` to approve, `dismissed` records a local Deny.
42
+ * `askId` identifies THIS gate hit; it changes when a failed resume
43
+ * re-emits `approval_required` for the same call, which is how the UI
44
+ * tells that apart from the same ask simply re-rendering (see
45
+ * `ApprovalAffordance` in chat/tool-call-display.tsx).
42
46
  */
43
47
  approval?: {
44
48
  approvalKey: string;
45
49
  dismissed?: boolean;
50
+ askId?: string;
46
51
  };
47
52
  /**
48
53
  * Structured metadata from the coding-tools executor side-channel.
@@ -70,6 +75,8 @@ export interface SSEEvent {
70
75
  approvalKey?: string;
71
76
  /** Model-side tool-call id for `approval_required` (mirrors AgentChatEvent). */
72
77
  toolCallId?: string;
78
+ /** Identifies this `approval_required` gate hit (mirrors AgentChatEvent). */
79
+ askId?: string;
73
80
  error?: string;
74
81
  seq?: number;
75
82
  agent?: string;
@@ -1182,7 +1182,10 @@ export function processEvent(ev, content, toolCallCounter, tabId, state) {
1182
1182
  if (idx >= 0) {
1183
1183
  const part = content[idx];
1184
1184
  if (part.type === "tool-call") {
1185
- part.approval = { approvalKey };
1185
+ part.approval = {
1186
+ approvalKey,
1187
+ ...(ev.askId ? { askId: ev.askId } : {}),
1188
+ };
1186
1189
  }
1187
1190
  }
1188
1191
  }
@@ -12,6 +12,7 @@ export { ErrorBoundary } from "../ErrorBoundary.js";
12
12
  export { ClientOnly } from "../ClientOnly.js";
13
13
  export { DefaultSpinner } from "../DefaultSpinner.js";
14
14
  export { RuntimeConfigNotice } from "../RuntimeConfigNotice.js";
15
+ export { EnvironmentBadge, buildEnvironmentUrl, isBuilderIoEmployee, resolveEnvironmentChannel, resolveEnvironmentTargets, type EnvironmentBadgeTargets, } from "../EnvironmentBadge.js";
15
16
  export { RouteTransitionIndicator, ROUTE_TRANSITION_INDICATOR_DELAY_MS, } from "../RouteTransitionIndicator.js";
16
17
  export { applyEmbeddedThemeUpdate, buildEmbeddedThemeUpdate, EMBEDDED_THEME_CHANGE_EVENT, EMBEDDED_THEME_UPDATE_MESSAGE, getThemeInitScript, parseEmbeddedThemeUpdate, themeInitScript, type EmbeddedThemeUpdate, type NormalizedEmbeddedThemeUpdate, type ResolvedTheme, type ThemePreference, } from "../theme.js";
17
18
  export { APPEARANCE_PRESETS, applyAppearance, getStoredAppearance, useAppearance, useAppearanceSync, type AppearancePresetId, } from "../appearance.js";
@@ -12,6 +12,7 @@ export { ErrorBoundary } from "../ErrorBoundary.js";
12
12
  export { ClientOnly } from "../ClientOnly.js";
13
13
  export { DefaultSpinner } from "../DefaultSpinner.js";
14
14
  export { RuntimeConfigNotice } from "../RuntimeConfigNotice.js";
15
+ export { EnvironmentBadge, buildEnvironmentUrl, isBuilderIoEmployee, resolveEnvironmentChannel, resolveEnvironmentTargets, } from "../EnvironmentBadge.js";
15
16
  export { RouteTransitionIndicator, ROUTE_TRANSITION_INDICATOR_DELAY_MS, } from "../RouteTransitionIndicator.js";
16
17
  export { applyEmbeddedThemeUpdate, buildEmbeddedThemeUpdate, EMBEDDED_THEME_CHANGE_EVENT, EMBEDDED_THEME_UPDATE_MESSAGE, getThemeInitScript, parseEmbeddedThemeUpdate, themeInitScript, } from "../theme.js";
17
18
  export { APPEARANCE_PRESETS, applyAppearance, getStoredAppearance, useAppearance, useAppearanceSync, } from "../appearance.js";
@@ -52,6 +52,11 @@ function isActionTimeout(error) {
52
52
  typeof error === "object" &&
53
53
  error.timedOut === true);
54
54
  }
55
+ function isActionMethodMismatch(error) {
56
+ return (!!error &&
57
+ typeof error === "object" &&
58
+ error.code === "action_method_mismatch");
59
+ }
55
60
  /** @internal exported for tests */
56
61
  export function defaultActionQueryRetry(failureCount, error) {
57
62
  if (isAuthFailure(error))
@@ -60,6 +65,9 @@ export function defaultActionQueryRetry(failureCount, error) {
60
65
  // silently retrying would multiply that wait. Surface it instead.
61
66
  if (isActionTimeout(error))
62
67
  return false;
68
+ // Wrong verb is deterministic — retrying sends the same wrong verb again.
69
+ if (isActionMethodMismatch(error))
70
+ return false;
63
71
  if (isBrowserResourceExhaustion(error))
64
72
  return false;
65
73
  // Network-level failures never carry an HTTP `status` (actionFetch only
@@ -338,6 +346,24 @@ async function performActionFetch(name, method, params, options) {
338
346
  (raw && raw.slice(0, 200)) ||
339
347
  res.statusText ||
340
348
  `HTTP ${res.status}`;
349
+ // mountActionRoutes only ever returns 405 for one reason: the verb this
350
+ // call sent doesn't match the action's declared `http.method`. The client
351
+ // has no way to look that method up ahead of time (defineAction lives in
352
+ // server-only modules), so callers routinely default to POST and hit this
353
+ // blind. Name the action and the required method instead of leaving a
354
+ // bare 405 for the caller to reverse-engineer from a "Use X" string.
355
+ if (res.status === 405) {
356
+ const requiredMethod = /\bUse (GET|POST|PUT|DELETE)\b/.exec(message)?.[1];
357
+ const error = new Error(`Action ${name} was called with ${method}, but it declares ` +
358
+ `http: { method: "${requiredMethod ?? "?"}" }. Pass { method: "${requiredMethod ?? "..."}" } ` +
359
+ `to this call (or use the hook that defaults to it) to match the action's declared method.`);
360
+ error.status = 405;
361
+ error.code = "action_method_mismatch";
362
+ error.sentMethod = method;
363
+ if (requiredMethod)
364
+ error.requiredMethod = requiredMethod;
365
+ throw error;
366
+ }
341
367
  const error = new Error(`Action ${name} failed: ${message}`);
342
368
  error.status = res.status;
343
369
  throw error;
@@ -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;