@agent-native/core 0.162.0 → 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 (102) 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/assets/.agents/skills/sharing/SKILL.md +27 -2
  4. package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +27 -2
  5. package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +27 -2
  6. package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  7. package/corpus/templates/clips/app/lib/camera-composite.ts +15 -1
  8. package/corpus/templates/clips/changelog/2026-08-18-clips-no-longer-crashes-when-a-camera-bubble-starts-at-a-tin.md +6 -0
  9. package/corpus/templates/content/.agents/skills/sharing/SKILL.md +27 -2
  10. package/corpus/templates/content/actions/create-content-database.ts +1 -1
  11. package/corpus/templates/content/actions/create-document.ts +1 -1
  12. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +27 -2
  13. package/corpus/templates/design/.agents/skills/sharing/SKILL.md +27 -2
  14. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +6 -1
  15. package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +27 -2
  16. package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +27 -2
  17. package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +27 -2
  18. package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +27 -2
  19. package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +27 -2
  20. package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +27 -2
  21. package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +27 -2
  22. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +3 -3
  23. package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +3 -3
  24. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -1
  25. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
  26. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +1 -1
  27. package/corpus/templates/slides/app/pages/DeckEditor.tsx +1 -1
  28. package/corpus/templates/slides/server/handlers/request-auth-context.ts +1 -0
  29. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +27 -2
  30. package/dist/app-config/schema.d.ts +1 -0
  31. package/dist/app-config/workspace.d.ts +1 -0
  32. package/dist/app-config/workspace.js +8 -0
  33. package/dist/chat-threads/schema.d.ts +2 -2
  34. package/dist/client/org/TeamPage.js +15 -2
  35. package/dist/client/org/hooks.d.ts +2 -0
  36. package/dist/client/org/hooks.js +12 -0
  37. package/dist/client/org/index.d.ts +2 -2
  38. package/dist/client/org/index.js +1 -1
  39. package/dist/client/sharing/ShareButton.js +43 -18
  40. package/dist/client/sharing/useShareButtonController.d.ts +12 -2
  41. package/dist/client/sharing/useShareButtonController.js +38 -8
  42. package/dist/collab/awareness.d.ts +2 -2
  43. package/dist/dashboard-storage/schema.d.ts +2 -2
  44. package/dist/data-programs/schema.d.ts +2 -2
  45. package/dist/extensions/schema.d.ts +2 -2
  46. package/dist/extensions/store.js +3 -0
  47. package/dist/localization/core-messages/ar-SA.js +1 -0
  48. package/dist/localization/core-messages/de-DE.js +1 -0
  49. package/dist/localization/core-messages/en-US.d.ts +1 -0
  50. package/dist/localization/core-messages/en-US.js +1 -0
  51. package/dist/localization/core-messages/es-ES.js +1 -0
  52. package/dist/localization/core-messages/fr-FR.js +1 -0
  53. package/dist/localization/core-messages/hi-IN.js +1 -0
  54. package/dist/localization/core-messages/ja-JP.js +1 -0
  55. package/dist/localization/core-messages/ko-KR.js +1 -0
  56. package/dist/localization/core-messages/pt-BR.js +1 -0
  57. package/dist/localization/core-messages/zh-CN.js +1 -0
  58. package/dist/localization/core-messages/zh-TW.js +1 -0
  59. package/dist/localization/core-messages.d.ts +1 -0
  60. package/dist/localization/default-messages.d.ts +4 -0
  61. package/dist/localization/default-messages.js +4 -0
  62. package/dist/org/handlers.d.ts +6 -1
  63. package/dist/org/handlers.js +43 -0
  64. package/dist/org/index.d.ts +3 -3
  65. package/dist/org/index.js +2 -2
  66. package/dist/org/migrations.js +33 -0
  67. package/dist/org/plugin.js +10 -1
  68. package/dist/org/schema.d.ts +315 -0
  69. package/dist/org/schema.js +20 -1
  70. package/dist/org/types.d.ts +3 -0
  71. package/dist/org/workspace-app-access.d.ts +10 -0
  72. package/dist/org/workspace-app-access.js +179 -0
  73. package/dist/progress/routes.d.ts +1 -1
  74. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  75. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  76. package/dist/resources/handlers.d.ts +1 -1
  77. package/dist/secrets/routes.d.ts +9 -9
  78. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  79. package/dist/server/auth.js +14 -1
  80. package/dist/server/poll.js +6 -0
  81. package/dist/sharing/access.d.ts +2 -2
  82. package/dist/sharing/access.js +56 -3
  83. package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
  84. package/dist/sharing/actions/list-resource-shares.js +26 -1
  85. package/dist/sharing/actions/share-resource.d.ts +1 -1
  86. package/dist/sharing/actions/share-resource.js +23 -4
  87. package/dist/sharing/actions/unshare-resource.d.ts +1 -1
  88. package/dist/sharing/actions/unshare-resource.js +1 -1
  89. package/dist/sharing/registry.d.ts +12 -0
  90. package/dist/sharing/schema.d.ts +4 -4
  91. package/dist/sharing/schema.js +2 -2
  92. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  93. package/dist/templates/default/.agents/skills/sharing/SKILL.md +27 -2
  94. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
  95. package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +9 -1
  96. package/dist/workspace-connections/actions/list-workspace-user-groups.js +9 -4
  97. package/dist/workspace-connections/groups.d.ts +2 -0
  98. package/dist/workspace-connections/groups.js +16 -5
  99. package/package.json +1 -1
  100. package/src/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  101. package/src/templates/default/.agents/skills/sharing/SKILL.md +27 -2
  102. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
@@ -18,6 +18,11 @@ Any resource a user **creates** (dashboards, documents, forms, decks, compositio
18
18
 
19
19
  This is the framework-level primitive. Every ownable resource gets it for free — same API, same UI, same skill.
20
20
 
21
+ Workspace apps are the intentional organization-scoped exception: their default
22
+ visibility is `org`, configurable by an organization owner/admin. The creator is
23
+ stored as the app owner, and the creator or an organization owner/admin can
24
+ change the app's visibility or manage its share grants.
25
+
21
26
  ## Concepts
22
27
 
23
28
  ### Three visibility levels
@@ -26,7 +31,8 @@ This is the framework-level primitive. Every ownable resource gets it for free
26
31
  - **`org`** — owner + explicit grants + anyone in the same org (read-only).
27
32
  - **`public`** — owner + explicit grants + **anyone with the link** (read-only). Public docs do NOT appear in other users' list/sidebar/search results — `accessFilter` omits them by default. They're reachable by id (`resolveAccess` admits them) so direct links and SSR routes like `/p/:id` keep working. If a list endpoint legitimately needs cross-user public discovery (a template gallery, etc.), pass `accessFilter(table, shares, ctx, minRole, { includePublic: true })`.
28
33
 
29
- Visibility is coarse. Explicit share grants are fine-grained (per user or per org).
34
+ Visibility is coarse. Explicit share grants are fine-grained (per user, reusable
35
+ organization group, or per org).
30
36
 
31
37
  ### Roles on a share grant
32
38
 
@@ -101,6 +107,7 @@ registerShareableResource({
101
107
  // ...
102
108
  allowPublic: false, // hides "Public" in the share dialog and rejects it server-side
103
109
  requireOrgMemberForUserShares: true, // user shares must target an org member or pending invitee
110
+ supportsGroupShares: true, // enable reusable organization groups for this resource
104
111
  });
105
112
  ```
106
113
 
@@ -111,6 +118,21 @@ Use both for resources that execute code or expose privileged data with the *vie
111
118
 
112
119
  Defaults match historical behaviour: `allowPublic: true`, `requireOrgMemberForUserShares: false`. Resources that don't set the flags work as before.
113
120
 
121
+ ### Reusable organization groups
122
+
123
+ The workspace organization model stores reusable groups in
124
+ `workspace_user_groups`. Organization owners/admins manage the groups and their
125
+ members through the existing group actions. Set `supportsGroupShares: true` on a
126
+ resource registration to enable `principalType: "group"` in the generic share
127
+ actions and the shared popover's autocomplete. Group ids are always checked
128
+ against the resource's organization, and membership is evaluated at access time
129
+ so group changes apply to existing shares.
130
+
131
+ Workspace apps opt into this flag and use the organization default visibility
132
+ setting. The app creator is the durable `owner_email`, while organization
133
+ owners/admins receive admin access through the registration's
134
+ `canManageAccess` callback.
135
+
114
136
  ## Filter list/read queries
115
137
 
116
138
  ```ts
@@ -197,6 +219,9 @@ All app share popovers should use the same compact surface contract:
197
219
  - Add `ShareAgentsSection` only when the resource has a real agent-readable
198
220
  link or prompt. Keep it collapsed by default and supply domain-specific
199
221
  content through the shared section shell.
222
+ - When `supportsGroupShares` is enabled, keep the existing people input as the
223
+ only entry point and include organization groups in its autocomplete. Do not
224
+ add a separate group-management surface to each resource.
200
225
  - App-specific tabs or controls may remain when they represent a real domain
201
226
  action (for example, an embed-code editor), but they should retain the same
202
227
  trigger, copy-row, access, and spacing language.
@@ -211,7 +236,7 @@ The framework auto-mounts these actions in every template — no per-template bo
211
236
 
212
237
  | Action | Args | Purpose |
213
238
  | -------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- |
214
- | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
239
+ | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user, group, or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
215
240
  | `unshare-resource` | `resourceType, resourceId, principalType, principalId` | Revoke access. |
216
241
  | `list-resource-shares` | `resourceType, resourceId` | Current visibility + all share grants. |
217
242
  | `set-resource-visibility` | `resourceType, resourceId, visibility` | Change to `private` / `org` / `public`. |
@@ -43,6 +43,7 @@ export declare const appConfigSchema: z.ZodObject<{
43
43
  isWorkspace: z.ZodOptional<z.ZodBoolean>;
44
44
  appsJson: z.ZodOptional<z.ZodString>;
45
45
  gatewayUrl: z.ZodOptional<z.ZodString>;
46
+ orgDirectoryUrl: z.ZodOptional<z.ZodString>;
46
47
  oauthOrigin: z.ZodOptional<z.ZodString>;
47
48
  }, z.core.$strip>>;
48
49
  }, z.core.$strip>;
@@ -19,5 +19,6 @@ export declare const workspaceConfig: z.ZodObject<{
19
19
  isWorkspace: z.ZodOptional<z.ZodBoolean>;
20
20
  appsJson: z.ZodOptional<z.ZodString>;
21
21
  gatewayUrl: z.ZodOptional<z.ZodString>;
22
+ orgDirectoryUrl: z.ZodOptional<z.ZodString>;
22
23
  oauthOrigin: z.ZodOptional<z.ZodString>;
23
24
  }, z.core.$strip>;
@@ -42,6 +42,14 @@ export const workspaceConfig = z.object({
42
42
  env: ["WORKSPACE_GATEWAY_URL", "VITE_WORKSPACE_GATEWAY_URL"],
43
43
  doc: "URL of the workspace gateway fronting this app.",
44
44
  }),
45
+ orgDirectoryUrl: z
46
+ .string()
47
+ .min(1)
48
+ .optional()
49
+ .meta({
50
+ env: ["AGENT_NATIVE_ORG_DIRECTORY_URL"],
51
+ doc: "URL of the authoritative organization Dispatch directory.",
52
+ }),
45
53
  oauthOrigin: z
46
54
  .string()
47
55
  .min(1)
@@ -403,14 +403,14 @@ export declare const chatThreadShares: import("drizzle-orm/sqlite-core").SQLiteT
403
403
  tableName: string;
404
404
  dataType: "string";
405
405
  columnType: "SQLiteText";
406
- data: "org" | "user";
406
+ data: "group" | "org" | "user";
407
407
  driverParam: string;
408
408
  notNull: true;
409
409
  hasDefault: false;
410
410
  isPrimaryKey: false;
411
411
  isAutoincrement: false;
412
412
  hasRuntimeDefault: false;
413
- enumValues: ["user", "org"];
413
+ enumValues: ["user", "group", "org"];
414
414
  baseColumn: never;
415
415
  identity: undefined;
416
416
  generated: undefined;
@@ -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
  }>>;
@@ -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;
@@ -316,14 +316,14 @@ export declare const extensionShares: import("drizzle-orm/sqlite-core").SQLiteTa
316
316
  tableName: string;
317
317
  dataType: "string";
318
318
  columnType: "SQLiteText";
319
- data: "org" | "user";
319
+ data: "group" | "org" | "user";
320
320
  driverParam: string;
321
321
  notNull: true;
322
322
  hasDefault: false;
323
323
  isPrimaryKey: false;
324
324
  isAutoincrement: false;
325
325
  hasRuntimeDefault: false;
326
- enumValues: ["user", "org"];
326
+ enumValues: ["user", "group", "org"];
327
327
  baseColumn: never;
328
328
  identity: undefined;
329
329
  generated: undefined;