@agent-native/core 0.162.0 → 0.163.1

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 (132) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +27 -2
  3. package/corpus/templates/analytics/server/plugins/collab.ts +1 -0
  4. package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +27 -2
  5. package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +27 -2
  6. package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +27 -2
  7. package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  8. package/corpus/templates/clips/app/lib/camera-composite.ts +15 -1
  9. package/corpus/templates/clips/changelog/2026-08-18-clips-no-longer-crashes-when-a-camera-bubble-starts-at-a-tin.md +6 -0
  10. package/corpus/templates/content/.agents/skills/sharing/SKILL.md +27 -2
  11. package/corpus/templates/content/actions/create-content-database.ts +1 -1
  12. package/corpus/templates/content/actions/create-document.ts +1 -1
  13. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +27 -2
  14. package/corpus/templates/design/.agents/skills/sharing/SKILL.md +27 -2
  15. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +6 -1
  16. package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +27 -2
  17. package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +27 -2
  18. package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +27 -2
  19. package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +27 -2
  20. package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +27 -2
  21. package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +27 -2
  22. package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +27 -2
  23. package/corpus/templates/slides/DEVELOPING.md +17 -0
  24. package/corpus/templates/slides/actions/get-deck-access-status.ts +67 -0
  25. package/corpus/templates/slides/actions/request-deck-access.ts +569 -0
  26. package/corpus/templates/slides/agent-native.json +3 -0
  27. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +3 -3
  28. package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +3 -3
  29. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -1
  30. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
  31. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +1 -1
  32. package/corpus/templates/slides/app/components/layout/Header.tsx +2 -0
  33. package/corpus/templates/slides/app/hooks/use-deck-access.ts +51 -0
  34. package/corpus/templates/slides/app/i18n/en-US.ts +12 -0
  35. package/corpus/templates/slides/app/lib/deck-editor-loading.ts +7 -0
  36. package/corpus/templates/slides/app/pages/DeckEditor.tsx +173 -33
  37. package/corpus/templates/slides/changelog/2026-08-18-private-deck-links-now-explain-access-and-let-viewers-notify.md +6 -0
  38. package/corpus/templates/slides/server/db/schema.ts +10 -0
  39. package/corpus/templates/slides/server/handlers/request-auth-context.ts +1 -0
  40. package/corpus/templates/slides/server/plugins/db.ts +17 -0
  41. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +27 -2
  42. package/dist/app-config/schema.d.ts +1 -0
  43. package/dist/app-config/workspace.d.ts +1 -0
  44. package/dist/app-config/workspace.js +8 -0
  45. package/dist/chat-threads/schema.d.ts +2 -2
  46. package/dist/client/analytics.d.ts +1 -0
  47. package/dist/client/analytics.js +19 -5
  48. package/dist/client/org/TeamPage.js +15 -2
  49. package/dist/client/org/hooks.d.ts +2 -0
  50. package/dist/client/org/hooks.js +12 -0
  51. package/dist/client/org/index.d.ts +2 -2
  52. package/dist/client/org/index.js +1 -1
  53. package/dist/client/sharing/ShareButton.js +43 -18
  54. package/dist/client/sharing/useShareButtonController.d.ts +12 -2
  55. package/dist/client/sharing/useShareButtonController.js +38 -8
  56. package/dist/collab/awareness.d.ts +2 -2
  57. package/dist/collab/routes.d.ts +1 -1
  58. package/dist/collab/storage.d.ts +2 -0
  59. package/dist/collab/storage.js +7 -0
  60. package/dist/config.js +11 -1
  61. package/dist/dashboard-storage/schema.d.ts +2 -2
  62. package/dist/data-programs/schema.d.ts +2 -2
  63. package/dist/deploy/build.d.ts +1 -1
  64. package/dist/deploy/build.js +89 -12
  65. package/dist/extensions/schema.d.ts +2 -2
  66. package/dist/extensions/store.js +3 -0
  67. package/dist/localization/core-messages/ar-SA.js +1 -0
  68. package/dist/localization/core-messages/de-DE.js +1 -0
  69. package/dist/localization/core-messages/en-US.d.ts +1 -0
  70. package/dist/localization/core-messages/en-US.js +1 -0
  71. package/dist/localization/core-messages/es-ES.js +1 -0
  72. package/dist/localization/core-messages/fr-FR.js +1 -0
  73. package/dist/localization/core-messages/hi-IN.js +1 -0
  74. package/dist/localization/core-messages/ja-JP.js +1 -0
  75. package/dist/localization/core-messages/ko-KR.js +1 -0
  76. package/dist/localization/core-messages/pt-BR.js +1 -0
  77. package/dist/localization/core-messages/zh-CN.js +1 -0
  78. package/dist/localization/core-messages/zh-TW.js +1 -0
  79. package/dist/localization/core-messages.d.ts +1 -0
  80. package/dist/localization/default-messages.d.ts +4 -0
  81. package/dist/localization/default-messages.js +4 -0
  82. package/dist/observability/routes.d.ts +1 -1
  83. package/dist/observability/tracking-identity.js +2 -0
  84. package/dist/org/handlers.d.ts +6 -1
  85. package/dist/org/handlers.js +43 -0
  86. package/dist/org/index.d.ts +3 -3
  87. package/dist/org/index.js +2 -2
  88. package/dist/org/migrations.js +33 -0
  89. package/dist/org/plugin.js +10 -1
  90. package/dist/org/schema.d.ts +315 -0
  91. package/dist/org/schema.js +20 -1
  92. package/dist/org/types.d.ts +3 -0
  93. package/dist/org/workspace-app-access.d.ts +10 -0
  94. package/dist/org/workspace-app-access.js +179 -0
  95. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  96. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  97. package/dist/secrets/routes.d.ts +9 -9
  98. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  99. package/dist/server/auth.js +14 -1
  100. package/dist/server/collab-plugin.d.ts +6 -0
  101. package/dist/server/collab-plugin.js +27 -6
  102. package/dist/server/deploy-environment.d.ts +1 -1
  103. package/dist/server/deploy-environment.js +40 -2
  104. package/dist/server/poll.js +6 -0
  105. package/dist/server/realtime-token.d.ts +1 -1
  106. package/dist/server/sentry-config.js +8 -4
  107. package/dist/server/sentry.js +4 -0
  108. package/dist/sharing/access.d.ts +2 -2
  109. package/dist/sharing/access.js +56 -3
  110. package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
  111. package/dist/sharing/actions/list-resource-shares.js +26 -1
  112. package/dist/sharing/actions/share-resource.d.ts +1 -1
  113. package/dist/sharing/actions/share-resource.js +23 -4
  114. package/dist/sharing/actions/unshare-resource.d.ts +1 -1
  115. package/dist/sharing/actions/unshare-resource.js +1 -1
  116. package/dist/sharing/registry.d.ts +12 -0
  117. package/dist/sharing/schema.d.ts +4 -4
  118. package/dist/sharing/schema.js +2 -2
  119. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  120. package/dist/templates/default/.agents/skills/sharing/SKILL.md +27 -2
  121. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
  122. package/dist/tracking/registry.js +8 -6
  123. package/dist/vite/client.js +5 -0
  124. package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +9 -1
  125. package/dist/workspace-connections/actions/list-workspace-user-groups.js +9 -4
  126. package/dist/workspace-connections/groups.d.ts +2 -0
  127. package/dist/workspace-connections/groups.js +16 -5
  128. package/docs/content/deployment.mdx +19 -5
  129. package/package.json +1 -1
  130. package/src/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  131. package/src/templates/default/.agents/skills/sharing/SKILL.md +27 -2
  132. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
@@ -3,6 +3,7 @@ import { llmConnectionTrackingProperties, } from "../shared/llm-connection.js";
3
3
  import { toPostHogExceptionProperties } from "../tracking/posthog-exception.js";
4
4
  import { getAnalyticsClientPlatform } from "./analytics-platform.js";
5
5
  import { getOrCreateAnalyticsAnonymousId, getOrCreateAnalyticsSessionId, } from "./analytics-session.js";
6
+ import { injectedAgentNativeConfig } from "./app-config.js";
6
7
  export { clearAnalyticsSessionId, setAnalyticsSessionId, } from "./analytics-session.js";
7
8
  import { fetchAgentEngineStatus, fetchAuthSessionStatus, } from "./client-status-requests.js";
8
9
  import { installErrorCapture, } from "./error-capture.js";
@@ -681,6 +682,16 @@ function getClientSentryDsn() {
681
682
  window.__AGENT_NATIVE_CONFIG__?.sentryDsn ||
682
683
  resolveClientSentryDsnFromKeyProject(env));
683
684
  }
685
+ function resolveClientDeploymentEnvironment() {
686
+ const env = import.meta.env ?? {};
687
+ return (window.__AGENT_NATIVE_CONFIG__?.deploymentEnvironment ||
688
+ injectedAgentNativeConfig().deployment?.environment ||
689
+ env.VITE_AGENT_NATIVE_DEPLOYMENT_ENVIRONMENT ||
690
+ env.VITE_SENTRY_ENVIRONMENT ||
691
+ window.__AGENT_NATIVE_CONFIG__?.sentryEnvironment ||
692
+ env.MODE ||
693
+ "production");
694
+ }
684
695
  function captureWithSentry(module, error, context) {
685
696
  return module.withScope((scope) => {
686
697
  if (context.tags) {
@@ -721,10 +732,12 @@ function ensureSentry(loadWithoutDsn = false) {
721
732
  }
722
733
  module.init({
723
734
  dsn,
724
- environment: window.__AGENT_NATIVE_CONFIG__?.sentryEnvironment ||
725
- import.meta.env?.MODE ||
726
- "production",
735
+ environment: resolveClientDeploymentEnvironment(),
727
736
  beforeSend(event) {
737
+ event.tags = {
738
+ ...event.tags,
739
+ deployment_environment: resolveClientDeploymentEnvironment(),
740
+ };
728
741
  if (shouldDropBrowserSentryNoise(event)) {
729
742
  return null;
730
743
  }
@@ -756,6 +769,7 @@ function ensureSentry(loadWithoutDsn = false) {
756
769
  },
757
770
  });
758
771
  module.setTag("runtime", "browser");
772
+ module.setTag("deployment_environment", resolveClientDeploymentEnvironment());
759
773
  _sentryInitialized = true;
760
774
  // Flush any user/tag that was set before init.
761
775
  if (_pendingSentryUser !== undefined) {
@@ -1169,8 +1183,7 @@ function maybeInstallErrorCapture(config) {
1169
1183
  send: sendExceptionEvent,
1170
1184
  getSessionContext: errorCaptureSessionContext,
1171
1185
  emitReplayEvent: emitExceptionToReplay,
1172
- environment: options.environment ||
1173
- import.meta.env?.MODE,
1186
+ environment: options.environment || resolveClientDeploymentEnvironment(),
1174
1187
  ...(options.release ? { release: options.release } : {}),
1175
1188
  ...(options.captureGlobalErrors !== undefined
1176
1189
  ? { captureGlobalErrors: options.captureGlobalErrors }
@@ -1410,6 +1423,7 @@ function resolveProps(name, params) {
1410
1423
  }
1411
1424
  const llmProps = llmConnectionTrackingProperties(_llmConnectionStatus);
1412
1425
  const enriched = { ...withTemplate };
1426
+ enriched.deployment_environment = resolveClientDeploymentEnvironment();
1413
1427
  for (const [key, value] of Object.entries(llmProps)) {
1414
1428
  if (enriched[key] === undefined)
1415
1429
  enriched[key] = value;
@@ -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";
@@ -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) {
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
- error?: undefined;
66
65
  states: {
67
66
  clientId: number;
68
67
  state: string;
69
68
  }[];
69
+ error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
- error?: undefined;
81
80
  users: {
82
81
  clientId: number;
83
82
  lastSeen: number;
84
83
  }[];
84
+ error?: undefined;
85
85
  }>>;
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- error: string;
30
29
  ok?: undefined;
30
+ error: string;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -18,6 +18,8 @@ export declare function trySaveYDocState(docId: string, state: Uint8Array, textS
18
18
  export declare function saveYDocState(docId: string, state: Uint8Array, textSnapshot: string): Promise<void>;
19
19
  /** Check if a document has collaborative state. */
20
20
  export declare function hasCollabState(docId: string): Promise<boolean>;
21
+ /** Load all existing document ids in one query for startup reconciliation. */
22
+ export declare function listCollabDocIds(): Promise<Set<string>>;
21
23
  /** Delete collaborative state for a document. */
22
24
  export declare function deleteCollabState(docId: string): Promise<void>;
23
25
  declare function uint8ArrayToBase64(arr: Uint8Array): string;
@@ -148,6 +148,13 @@ export async function hasCollabState(docId) {
148
148
  });
149
149
  return rows.length > 0;
150
150
  }
151
+ /** Load all existing document ids in one query for startup reconciliation. */
152
+ export async function listCollabDocIds() {
153
+ await ensureTable();
154
+ const client = getDbExec();
155
+ const { rows } = await client.execute("SELECT doc_id FROM _collab_docs");
156
+ return new Set(rows.flatMap((row) => typeof row.doc_id === "string" && row.doc_id ? [row.doc_id] : []));
157
+ }
151
158
  /** Delete collaborative state for a document. */
152
159
  export async function deleteCollabState(docId) {
153
160
  await ensureTable();
package/dist/config.js CHANGED
@@ -374,14 +374,24 @@ export function isAgentNativeDeploymentEnvironment(value) {
374
374
  * never parses process.env itself.
375
375
  */
376
376
  export function inferAgentNativeDeploymentEnvironment(env, mode) {
377
+ const explicit = env.AGENT_NATIVE_DEPLOYMENT_ENVIRONMENT?.trim().toLowerCase();
378
+ if (explicit && !isAgentNativeDeploymentEnvironment(explicit)) {
379
+ throw new Error('AGENT_NATIVE_DEPLOYMENT_ENVIRONMENT must be "local", "beta", "production", or "preview"');
380
+ }
381
+ if (isAgentNativeDeploymentEnvironment(explicit))
382
+ return explicit;
377
383
  const context = env.CONTEXT?.trim().toLowerCase();
378
384
  const branch = env.BRANCH?.trim().toLowerCase();
379
- if (branch === "production" || context === "production") {
385
+ const vercelEnv = env.VERCEL_ENV?.trim().toLowerCase();
386
+ if (branch === "production" ||
387
+ (context === "production" && branch !== "beta")) {
380
388
  return "production";
381
389
  }
382
390
  if (branch === "beta" || (context === "branch-deploy" && branch === "main")) {
383
391
  return "beta";
384
392
  }
393
+ if (vercelEnv === "preview")
394
+ return "preview";
385
395
  if (context === "deploy-preview" ||
386
396
  context === "branch-deploy" ||
387
397
  branch?.startsWith("deploy-preview")) {
@@ -464,14 +464,14 @@ export declare function createDashboardStorageSchema(options?: DashboardStorageS
464
464
  tableName: string;
465
465
  dataType: "string";
466
466
  columnType: "SQLiteText";
467
- data: "org" | "user";
467
+ data: "group" | "org" | "user";
468
468
  driverParam: string;
469
469
  notNull: true;
470
470
  hasDefault: false;
471
471
  isPrimaryKey: false;
472
472
  isAutoincrement: false;
473
473
  hasRuntimeDefault: false;
474
- enumValues: ["user", "org"];
474
+ enumValues: ["user", "group", "org"];
475
475
  baseColumn: never;
476
476
  identity: undefined;
477
477
  generated: undefined;
@@ -411,14 +411,14 @@ export declare const dataProgramShares: import("drizzle-orm/sqlite-core").SQLite
411
411
  tableName: string;
412
412
  dataType: "string";
413
413
  columnType: "SQLiteText";
414
- data: "org" | "user";
414
+ data: "group" | "org" | "user";
415
415
  driverParam: string;
416
416
  notNull: true;
417
417
  hasDefault: false;
418
418
  isPrimaryKey: false;
419
419
  isAutoincrement: false;
420
420
  hasRuntimeDefault: false;
421
- enumValues: ["user", "org"];
421
+ enumValues: ["user", "group", "org"];
422
422
  baseColumn: never;
423
423
  identity: undefined;
424
424
  generated: undefined;
@@ -337,4 +337,4 @@ export declare function resolveNitroBundledYjsEntry(): string;
337
337
  * small set above through the controlled post-build pass.
338
338
  */
339
339
  export declare function nitroNoExternalsForPreset(targetPreset: string): true | readonly string[];
340
- export declare function resolveNitroBuildReplacements(env?: NodeJS.ProcessEnv): Record<string, string>;
340
+ export declare function resolveNitroBuildReplacements(env?: NodeJS.ProcessEnv, deploymentEnvironment?: string): Record<string, string>;