@agent-native/core 0.161.23 → 0.163.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +18 -2
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/calendar/server/handlers/google-auth.ts +1 -8
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +88 -35
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +36 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +4 -3
- package/corpus/templates/clips/app/lib/camera-composite.ts +15 -1
- package/corpus/templates/clips/changelog/2026-08-18-clips-no-longer-crashes-when-a-camera-bubble-starts-at-a-tin.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +30 -40
- package/corpus/templates/clips/desktop/src/components/MicOffConfirmation.tsx +47 -0
- package/corpus/templates/clips/desktop/src/styles.css +110 -0
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +114 -7
- package/corpus/templates/clips/shared/recording-capture.ts +18 -13
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/content/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/content/actions/create-content-database.ts +1 -1
- package/corpus/templates/content/actions/create-document.ts +1 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -5
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +0 -3
- package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +0 -4
- package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +4 -1
- package/corpus/templates/content/app/hooks/use-notion.ts +17 -15
- package/corpus/templates/crm/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/design/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/design/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +6 -1
- package/corpus/templates/dispatch/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/factory/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/macros/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +18 -3
- package/corpus/templates/mail/server/handlers/google-auth.ts +1 -8
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +4 -0
- package/corpus/templates/slides/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +27 -2
- package/corpus/templates/slides/actions/_await-fit-check.ts +30 -5
- package/corpus/templates/slides/actions/add-slide.ts +6 -2
- package/corpus/templates/slides/actions/create-deck.ts +2 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +3 -3
- package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +3 -3
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -1
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +1 -1
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +35 -2
- package/corpus/templates/slides/app/routes/p.$id.tsx +3 -1
- package/corpus/templates/slides/changelog/2026-08-18-generated-slides-preserve-speaker-notes.md +6 -0
- package/corpus/templates/slides/server/handlers/assets.ts +23 -12
- package/corpus/templates/slides/server/handlers/design-system-decode-job-status.ts +15 -6
- package/corpus/templates/slides/server/handlers/design-system-upload-start.ts +15 -6
- package/corpus/templates/slides/server/handlers/index-design-system-sources.ts +34 -19
- package/corpus/templates/slides/server/handlers/request-auth-context.ts +1 -0
- package/corpus/templates/slides/server/handlers/share.ts +1 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/server/routes/api/deck-agent-context.json.get.ts +1 -1
- package/corpus/templates/slides/server/routes/api/exports/[filename].get.ts +9 -3
- package/corpus/templates/slides/server/routes/api/exports/google-slides.post.ts +12 -5
- package/corpus/templates/slides/server/routes/api/exports/html.post.ts +9 -7
- package/corpus/templates/slides/server/routes/api/exports/pptx.post.ts +9 -7
- package/corpus/templates/slides/shared/api.ts +7 -1
- package/corpus/templates/tasks/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/agent/engine/translate-anthropic.js +27 -2
- package/dist/agent/production-agent.d.ts +2 -2
- package/dist/agent/production-agent.js +16 -8
- package/dist/agent/thread-data-builder.d.ts +1 -0
- package/dist/agent/thread-data-builder.js +6 -4
- package/dist/agent/tool-approval-store.d.ts +10 -1
- package/dist/agent/tool-approval-store.js +19 -2
- package/dist/agent/types.d.ts +9 -0
- package/dist/app-config/schema.d.ts +1 -0
- package/dist/app-config/workspace.d.ts +1 -0
- package/dist/app-config/workspace.js +8 -0
- package/dist/chat-threads/schema.d.ts +2 -2
- package/dist/client/AssistantChat.js +14 -6
- package/dist/client/EnvironmentBadge.d.ts +15 -0
- package/dist/client/EnvironmentBadge.js +79 -0
- package/dist/client/MultiTabAssistantChat.js +73 -17
- package/dist/client/agent-chat-adapter.js +11 -1
- package/dist/client/api-path.js +14 -1
- package/dist/client/app-providers.js +2 -1
- package/dist/client/chat/tool-call-display.d.ts +12 -2
- package/dist/client/chat/tool-call-display.js +17 -7
- package/dist/client/chat/widgets/WorkspaceFileWidget.js +1 -1
- package/dist/client/chat/widgets/builtin-tool-renderers.d.ts +6 -0
- package/dist/client/chat/widgets/builtin-tool-renderers.js +19 -0
- package/dist/client/onboarding/FirstRunOnboarding.js +46 -32
- package/dist/client/onboarding/use-onboarding.d.ts +8 -1
- package/dist/client/onboarding/use-onboarding.js +22 -7
- package/dist/client/org/TeamPage.js +15 -2
- package/dist/client/org/hooks.d.ts +2 -0
- package/dist/client/org/hooks.js +12 -0
- package/dist/client/org/index.d.ts +2 -2
- package/dist/client/org/index.js +1 -1
- package/dist/client/session-replay.js +58 -15
- package/dist/client/settings/SettingsTabsPage.js +19 -6
- package/dist/client/sharing/ShareButton.js +43 -18
- package/dist/client/sharing/useShareButtonController.d.ts +12 -2
- package/dist/client/sharing/useShareButtonController.js +38 -8
- package/dist/client/sse-event-processor.d.ts +7 -0
- package/dist/client/sse-event-processor.js +4 -1
- package/dist/client/ui/index.d.ts +1 -0
- package/dist/client/ui/index.js +1 -0
- package/dist/client/use-action.js +26 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/config.d.ts +15 -0
- package/dist/config.js +53 -0
- package/dist/dashboard-storage/schema.d.ts +2 -2
- package/dist/data-programs/schema.d.ts +2 -2
- package/dist/extensions/fetch-tool.js +8 -2
- package/dist/extensions/schema.d.ts +2 -2
- package/dist/extensions/store.js +3 -0
- package/dist/localization/core-messages/ar-SA.js +1 -0
- package/dist/localization/core-messages/de-DE.js +1 -0
- package/dist/localization/core-messages/en-US.d.ts +1 -0
- package/dist/localization/core-messages/en-US.js +1 -0
- package/dist/localization/core-messages/es-ES.js +1 -0
- package/dist/localization/core-messages/fr-FR.js +1 -0
- package/dist/localization/core-messages/hi-IN.js +1 -0
- package/dist/localization/core-messages/ja-JP.js +1 -0
- package/dist/localization/core-messages/ko-KR.js +1 -0
- package/dist/localization/core-messages/pt-BR.js +1 -0
- package/dist/localization/core-messages/zh-CN.js +1 -0
- package/dist/localization/core-messages/zh-TW.js +1 -0
- package/dist/localization/core-messages.d.ts +1 -0
- package/dist/localization/default-messages.d.ts +4 -0
- package/dist/localization/default-messages.js +4 -0
- package/dist/mcp/embed-app.js +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/org/handlers.d.ts +6 -1
- package/dist/org/handlers.js +43 -0
- package/dist/org/index.d.ts +3 -3
- package/dist/org/index.js +2 -2
- package/dist/org/migrations.js +33 -0
- package/dist/org/plugin.js +10 -1
- package/dist/org/schema.d.ts +315 -0
- package/dist/org/schema.js +20 -1
- package/dist/org/types.d.ts +3 -0
- package/dist/org/workspace-app-access.d.ts +10 -0
- package/dist/org/workspace-app-access.js +179 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/provider-api/index.d.ts +10 -0
- package/dist/provider-api/index.js +45 -9
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +24 -4
- package/dist/server/poll.js +13 -3
- package/dist/server/sentry.d.ts +1 -1
- package/dist/sharing/access.d.ts +2 -2
- package/dist/sharing/access.js +56 -3
- package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
- package/dist/sharing/actions/list-resource-shares.js +26 -1
- package/dist/sharing/actions/share-resource.d.ts +1 -1
- package/dist/sharing/actions/share-resource.js +23 -4
- package/dist/sharing/actions/unshare-resource.d.ts +1 -1
- package/dist/sharing/actions/unshare-resource.js +1 -1
- package/dist/sharing/registry.d.ts +12 -0
- package/dist/sharing/schema.d.ts +4 -4
- package/dist/sharing/schema.js +2 -2
- package/dist/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/default/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
- package/dist/vite/client.d.ts +5 -0
- package/dist/vite/client.js +64 -2
- package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +9 -1
- package/dist/workspace-connections/actions/list-workspace-user-groups.js +9 -4
- package/dist/workspace-connections/groups.d.ts +2 -0
- package/dist/workspace-connections/groups.js +16 -5
- package/dist/workspace-files/actions.d.ts +1 -8
- package/dist/workspace-files/actions.js +5 -21
- package/dist/workspace-files/index.d.ts +1 -1
- package/dist/workspace-files/index.js +1 -1
- package/dist/workspace-files/store.d.ts +18 -0
- package/dist/workspace-files/store.js +24 -3
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/default/.agents/skills/sharing/SKILL.md +27 -2
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
|
@@ -1040,6 +1040,31 @@ function isDefinitiveReplayUploadClientError(status) {
|
|
|
1040
1040
|
return status === 400 || status === 409 || status === 413 || status === 422;
|
|
1041
1041
|
}
|
|
1042
1042
|
const MAX_TRANSIENT_REPLAY_CLIENT_FAILURES = 3;
|
|
1043
|
+
// A hung upload never settles, so `state.flushing` stays set, every later flush
|
|
1044
|
+
// early-returns while still queueing rrweb events, and the queue grows until the
|
|
1045
|
+
// session ends. Time the request out so the failure is observable and the lock
|
|
1046
|
+
// is released.
|
|
1047
|
+
const REPLAY_UPLOAD_TIMEOUT_MS = 15_000;
|
|
1048
|
+
function startReplayUploadTimeout() {
|
|
1049
|
+
// Use an explicit controller/timer pair instead of relying only on
|
|
1050
|
+
// AbortSignal.timeout: the controller is available in older supported
|
|
1051
|
+
// browsers, and the timer can always be cleared when a normal upload
|
|
1052
|
+
// settles so completed uploads do not leave timeout work behind.
|
|
1053
|
+
const controller = typeof AbortController === "undefined" ? undefined : new AbortController();
|
|
1054
|
+
let rejectTimeout;
|
|
1055
|
+
const promise = new Promise((_, reject) => {
|
|
1056
|
+
rejectTimeout = reject;
|
|
1057
|
+
});
|
|
1058
|
+
const timer = setTimeout(() => {
|
|
1059
|
+
controller?.abort();
|
|
1060
|
+
rejectTimeout(new Error("Session replay upload timed out"));
|
|
1061
|
+
}, REPLAY_UPLOAD_TIMEOUT_MS);
|
|
1062
|
+
return {
|
|
1063
|
+
signal: controller?.signal,
|
|
1064
|
+
promise,
|
|
1065
|
+
done: () => clearTimeout(timer),
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1043
1068
|
function isTransientReplayUploadClientError(status) {
|
|
1044
1069
|
return status === 401 || status === 403 || status === 404;
|
|
1045
1070
|
}
|
|
@@ -1048,12 +1073,21 @@ async function sendReplayUpload(options, body, callbacks = {}) {
|
|
|
1048
1073
|
const canUseKeepalive = canUseReplayKeepalive(body);
|
|
1049
1074
|
if (canUseKeepalive)
|
|
1050
1075
|
callbacks.beforeKeepaliveUpload?.();
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1076
|
+
const timeout = startReplayUploadTimeout();
|
|
1077
|
+
let response;
|
|
1078
|
+
try {
|
|
1079
|
+
const request = fetch(options.endpoint, {
|
|
1080
|
+
method: "POST",
|
|
1081
|
+
body,
|
|
1082
|
+
keepalive: canUseKeepalive,
|
|
1083
|
+
headers: { "Content-Type": "text/plain;charset=UTF-8" },
|
|
1084
|
+
signal: timeout.signal,
|
|
1085
|
+
});
|
|
1086
|
+
response = await Promise.race([request, timeout.promise]);
|
|
1087
|
+
}
|
|
1088
|
+
finally {
|
|
1089
|
+
timeout.done();
|
|
1090
|
+
}
|
|
1057
1091
|
if (!response.ok) {
|
|
1058
1092
|
throw new ReplayUploadHttpError(response.status);
|
|
1059
1093
|
}
|
|
@@ -1063,15 +1097,24 @@ async function sendReplayUpload(options, body, callbacks = {}) {
|
|
|
1063
1097
|
const canUseKeepalive = canUseReplayKeepalive(upload.body);
|
|
1064
1098
|
if (canUseKeepalive)
|
|
1065
1099
|
callbacks.beforeKeepaliveUpload?.();
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1100
|
+
const timeout = startReplayUploadTimeout();
|
|
1101
|
+
let response;
|
|
1102
|
+
try {
|
|
1103
|
+
const request = fetch(options.endpoint, {
|
|
1104
|
+
method: "POST",
|
|
1105
|
+
body: upload.body,
|
|
1106
|
+
keepalive: canUseKeepalive,
|
|
1107
|
+
headers: {
|
|
1108
|
+
...upload.headers,
|
|
1109
|
+
"X-Agent-Native-Analytics-Key": options.publicKey,
|
|
1110
|
+
},
|
|
1111
|
+
signal: timeout.signal,
|
|
1112
|
+
});
|
|
1113
|
+
response = await Promise.race([request, timeout.promise]);
|
|
1114
|
+
}
|
|
1115
|
+
finally {
|
|
1116
|
+
timeout.done();
|
|
1117
|
+
}
|
|
1075
1118
|
if (!response.ok) {
|
|
1076
1119
|
throw new ReplayUploadHttpError(response.status);
|
|
1077
1120
|
}
|
|
@@ -3,6 +3,7 @@ import { Tabs, useDesignSystem } from "@agent-native/toolkit/design-system";
|
|
|
3
3
|
import { IconArrowUpRight, IconHistory, IconSearch, IconSettings, IconUserCircle, IconUsers, IconX, } from "@tabler/icons-react";
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
5
5
|
import { Link } from "react-router";
|
|
6
|
+
import { appBasePath, appPath } from "../../client/api-path.js";
|
|
6
7
|
import { buildSettingsRoute } from "../../navigation/index.js";
|
|
7
8
|
import { cn } from "../utils.js";
|
|
8
9
|
function normalizeTabId(value) {
|
|
@@ -70,12 +71,13 @@ function resolveTabId(tabs, value) {
|
|
|
70
71
|
function activeTabFromLocation(tabs, defaultTab) {
|
|
71
72
|
if (typeof window === "undefined")
|
|
72
73
|
return defaultTab;
|
|
74
|
+
const pathname = appLocalPathname();
|
|
73
75
|
const settingsPrefix = "/settings";
|
|
74
|
-
if (
|
|
76
|
+
if (pathname === settingsPrefix) {
|
|
75
77
|
return resolveTabId(tabs, window.location.hash) ?? defaultTab;
|
|
76
78
|
}
|
|
77
|
-
if (
|
|
78
|
-
const segments =
|
|
79
|
+
if (pathname.startsWith(`${settingsPrefix}/`)) {
|
|
80
|
+
const segments = pathname
|
|
79
81
|
.slice(`${settingsPrefix}/`.length)
|
|
80
82
|
.split("/")
|
|
81
83
|
.filter(Boolean)
|
|
@@ -95,6 +97,17 @@ function activeTabFromLocation(tabs, defaultTab) {
|
|
|
95
97
|
}
|
|
96
98
|
return resolveTabId(tabs, window.location.hash) ?? defaultTab;
|
|
97
99
|
}
|
|
100
|
+
function appLocalPathname() {
|
|
101
|
+
if (typeof window === "undefined")
|
|
102
|
+
return "/";
|
|
103
|
+
const pathname = window.location.pathname;
|
|
104
|
+
const basePath = appBasePath();
|
|
105
|
+
if (basePath &&
|
|
106
|
+
(pathname === basePath || pathname.startsWith(`${basePath}/`))) {
|
|
107
|
+
return pathname.slice(basePath.length) || "/";
|
|
108
|
+
}
|
|
109
|
+
return pathname;
|
|
110
|
+
}
|
|
98
111
|
function buildSettingsEntryRoute(tabId, section) {
|
|
99
112
|
const normalizedSection = section?.replace(/^#/, "").trim();
|
|
100
113
|
if (!normalizedSection || normalizedSection === tabId) {
|
|
@@ -112,7 +125,7 @@ function updateRouteForTab(tabId, section) {
|
|
|
112
125
|
if (typeof window === "undefined")
|
|
113
126
|
return;
|
|
114
127
|
const route = buildSettingsEntryRoute(tabId, section);
|
|
115
|
-
window.history.pushState(null, "", `${route}${window.location.search}`);
|
|
128
|
+
window.history.pushState(null, "", `${appPath(route)}${window.location.search}`);
|
|
116
129
|
}
|
|
117
130
|
function isEditableElement(element) {
|
|
118
131
|
if (!(element instanceof HTMLElement))
|
|
@@ -233,7 +246,7 @@ export function SettingsTabsPage({ general, account, team, whatsNew, extraTabs =
|
|
|
233
246
|
const fromPath = activeTabFromLocation(tabs, fallbackTab);
|
|
234
247
|
if (fromPath)
|
|
235
248
|
setInternalTab(fromPath);
|
|
236
|
-
if (
|
|
249
|
+
if (appLocalPathname().startsWith("/settings/"))
|
|
237
250
|
return;
|
|
238
251
|
const hashValue = window.location.hash.replace(/^#/, "");
|
|
239
252
|
const fromHash = resolveTabId(tabs, hashValue);
|
|
@@ -254,7 +267,7 @@ export function SettingsTabsPage({ general, account, team, whatsNew, extraTabs =
|
|
|
254
267
|
if (!isControlled)
|
|
255
268
|
return;
|
|
256
269
|
const syncControlledLocation = () => {
|
|
257
|
-
const fromPath =
|
|
270
|
+
const fromPath = appLocalPathname().startsWith("/settings/")
|
|
258
271
|
? activeTabFromLocation(tabs, defaultTab)
|
|
259
272
|
: null;
|
|
260
273
|
const hashValue = window.location.hash.replace(/^#/, "");
|
|
@@ -131,7 +131,7 @@ export function ShareButton(props) {
|
|
|
131
131
|
function SharePanel(props) {
|
|
132
132
|
const t = useT();
|
|
133
133
|
const { controller } = props;
|
|
134
|
-
const { inviteEmail, setInviteEmail: onInviteEmailChange, activeShareTab, handleShareTabChange, data, policy, visibility, canManage, role, setRole, notifyPeople, setNotifyPeople, shareMessage, setShareMessage, messageOpen, setMessageOpen, shareError, setShareError, suggestionsOpen, setSuggestionsOpen, inFlight, memberSearch, memberSuggestions, knownMembers, shares, handleVisibility, handleHideInSearch, handleAdd, handleChangeRole, handleRemove, } = controller;
|
|
134
|
+
const { inviteEmail, setInviteEmail: onInviteEmailChange, activeShareTab, handleShareTabChange, data, policy, visibility, canManage, role, setRole, notifyPeople, setNotifyPeople, shareMessage, setShareMessage, messageOpen, setMessageOpen, shareError, setShareError, suggestionsOpen, setSuggestionsOpen, inFlight, memberSearch, memberSuggestions, groupSuggestions, selectGroup, knownMembers, shares, handleVisibility, handleHideInSearch, handleAdd, handleChangeRole, handleRemove, } = controller;
|
|
135
135
|
const hasInviteEmail = inviteEmail.trim().length > 0;
|
|
136
136
|
const isLoading = data === undefined;
|
|
137
137
|
const meta = visibilityMeta(visibility, t, props.visibilityCopy);
|
|
@@ -155,8 +155,13 @@ function SharePanel(props) {
|
|
|
155
155
|
onInviteEmailChange(next);
|
|
156
156
|
if (shareError)
|
|
157
157
|
setShareError(null);
|
|
158
|
-
},
|
|
159
|
-
|
|
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:
|
|
169
|
-
|
|
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,
|
|
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
|
|
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
|
|
247
|
-
|
|
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 &&
|
|
294
|
+
if (open && activeSuggestion) {
|
|
277
295
|
event.preventDefault();
|
|
278
|
-
|
|
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: "
|
|
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((
|
|
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: () =>
|
|
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: [
|
|
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
|
|
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
|
|
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-${
|
|
195
|
-
principalType
|
|
196
|
-
principalId
|
|
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
|
|
214
|
-
principalId
|
|
238
|
+
principalType,
|
|
239
|
+
principalId,
|
|
215
240
|
role,
|
|
216
|
-
notify: notifyPeople,
|
|
241
|
+
notify: principalType === "user" && notifyPeople,
|
|
217
242
|
resourceUrl: getNotificationUrl(options.shareUrl),
|
|
218
243
|
...(message ? { message } : {}),
|
|
219
244
|
}, {
|
|
@@ -228,6 +253,7 @@ export function useShareButtonController(options) {
|
|
|
228
253
|
setPendingAdds((previous) => previous.filter((item) => item.id !== optimistic.id));
|
|
229
254
|
clearInFlight(key);
|
|
230
255
|
setInviteEmail(trimmed);
|
|
256
|
+
setSelectedGroup(selectedGroup);
|
|
231
257
|
setShareMessage((current) => current || message);
|
|
232
258
|
setMessageOpen((current) => current || Boolean(message));
|
|
233
259
|
setShareError(extractShareErrorMessage(error));
|
|
@@ -244,6 +270,7 @@ export function useShareButtonController(options) {
|
|
|
244
270
|
options.resourceType,
|
|
245
271
|
options.shareUrl,
|
|
246
272
|
role,
|
|
273
|
+
selectedGroup,
|
|
247
274
|
shareMessage,
|
|
248
275
|
share,
|
|
249
276
|
queryClient,
|
|
@@ -368,7 +395,7 @@ export function useShareButtonController(options) {
|
|
|
368
395
|
activeShareTab,
|
|
369
396
|
handleShareTabChange,
|
|
370
397
|
inviteEmail,
|
|
371
|
-
setInviteEmail,
|
|
398
|
+
setInviteEmail: setInviteEmailValue,
|
|
372
399
|
sharesQuery,
|
|
373
400
|
visibilityOverride,
|
|
374
401
|
handleVisibilityChange,
|
|
@@ -399,6 +426,9 @@ export function useShareButtonController(options) {
|
|
|
399
426
|
handleAdd,
|
|
400
427
|
handleChangeRole,
|
|
401
428
|
handleRemove,
|
|
429
|
+
groupSuggestions,
|
|
430
|
+
selectedGroup,
|
|
431
|
+
selectGroup,
|
|
402
432
|
};
|
|
403
433
|
}
|
|
404
434
|
function keyOf(share) {
|
|
@@ -39,10 +39,15 @@ export type ContentPart = {
|
|
|
39
39
|
* call (opt-in `needsApproval` actions). The action did NOT run; the UI
|
|
40
40
|
* renders an Approve/Deny affordance. `approvalKey` is echoed back in
|
|
41
41
|
* `approvedToolCalls` to approve, `dismissed` records a local Deny.
|
|
42
|
+
* `askId` identifies THIS gate hit; it changes when a failed resume
|
|
43
|
+
* re-emits `approval_required` for the same call, which is how the UI
|
|
44
|
+
* tells that apart from the same ask simply re-rendering (see
|
|
45
|
+
* `ApprovalAffordance` in chat/tool-call-display.tsx).
|
|
42
46
|
*/
|
|
43
47
|
approval?: {
|
|
44
48
|
approvalKey: string;
|
|
45
49
|
dismissed?: boolean;
|
|
50
|
+
askId?: string;
|
|
46
51
|
};
|
|
47
52
|
/**
|
|
48
53
|
* Structured metadata from the coding-tools executor side-channel.
|
|
@@ -70,6 +75,8 @@ export interface SSEEvent {
|
|
|
70
75
|
approvalKey?: string;
|
|
71
76
|
/** Model-side tool-call id for `approval_required` (mirrors AgentChatEvent). */
|
|
72
77
|
toolCallId?: string;
|
|
78
|
+
/** Identifies this `approval_required` gate hit (mirrors AgentChatEvent). */
|
|
79
|
+
askId?: string;
|
|
73
80
|
error?: string;
|
|
74
81
|
seq?: number;
|
|
75
82
|
agent?: string;
|
|
@@ -1182,7 +1182,10 @@ export function processEvent(ev, content, toolCallCounter, tabId, state) {
|
|
|
1182
1182
|
if (idx >= 0) {
|
|
1183
1183
|
const part = content[idx];
|
|
1184
1184
|
if (part.type === "tool-call") {
|
|
1185
|
-
part.approval = {
|
|
1185
|
+
part.approval = {
|
|
1186
|
+
approvalKey,
|
|
1187
|
+
...(ev.askId ? { askId: ev.askId } : {}),
|
|
1188
|
+
};
|
|
1186
1189
|
}
|
|
1187
1190
|
}
|
|
1188
1191
|
}
|
|
@@ -12,6 +12,7 @@ export { ErrorBoundary } from "../ErrorBoundary.js";
|
|
|
12
12
|
export { ClientOnly } from "../ClientOnly.js";
|
|
13
13
|
export { DefaultSpinner } from "../DefaultSpinner.js";
|
|
14
14
|
export { RuntimeConfigNotice } from "../RuntimeConfigNotice.js";
|
|
15
|
+
export { EnvironmentBadge, buildEnvironmentUrl, isBuilderIoEmployee, resolveEnvironmentChannel, resolveEnvironmentTargets, type EnvironmentBadgeTargets, } from "../EnvironmentBadge.js";
|
|
15
16
|
export { RouteTransitionIndicator, ROUTE_TRANSITION_INDICATOR_DELAY_MS, } from "../RouteTransitionIndicator.js";
|
|
16
17
|
export { applyEmbeddedThemeUpdate, buildEmbeddedThemeUpdate, EMBEDDED_THEME_CHANGE_EVENT, EMBEDDED_THEME_UPDATE_MESSAGE, getThemeInitScript, parseEmbeddedThemeUpdate, themeInitScript, type EmbeddedThemeUpdate, type NormalizedEmbeddedThemeUpdate, type ResolvedTheme, type ThemePreference, } from "../theme.js";
|
|
17
18
|
export { APPEARANCE_PRESETS, applyAppearance, getStoredAppearance, useAppearance, useAppearanceSync, type AppearancePresetId, } from "../appearance.js";
|
package/dist/client/ui/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export { ErrorBoundary } from "../ErrorBoundary.js";
|
|
|
12
12
|
export { ClientOnly } from "../ClientOnly.js";
|
|
13
13
|
export { DefaultSpinner } from "../DefaultSpinner.js";
|
|
14
14
|
export { RuntimeConfigNotice } from "../RuntimeConfigNotice.js";
|
|
15
|
+
export { EnvironmentBadge, buildEnvironmentUrl, isBuilderIoEmployee, resolveEnvironmentChannel, resolveEnvironmentTargets, } from "../EnvironmentBadge.js";
|
|
15
16
|
export { RouteTransitionIndicator, ROUTE_TRANSITION_INDICATOR_DELAY_MS, } from "../RouteTransitionIndicator.js";
|
|
16
17
|
export { applyEmbeddedThemeUpdate, buildEmbeddedThemeUpdate, EMBEDDED_THEME_CHANGE_EVENT, EMBEDDED_THEME_UPDATE_MESSAGE, getThemeInitScript, parseEmbeddedThemeUpdate, themeInitScript, } from "../theme.js";
|
|
17
18
|
export { APPEARANCE_PRESETS, applyAppearance, getStoredAppearance, useAppearance, useAppearanceSync, } from "../appearance.js";
|
|
@@ -52,6 +52,11 @@ function isActionTimeout(error) {
|
|
|
52
52
|
typeof error === "object" &&
|
|
53
53
|
error.timedOut === true);
|
|
54
54
|
}
|
|
55
|
+
function isActionMethodMismatch(error) {
|
|
56
|
+
return (!!error &&
|
|
57
|
+
typeof error === "object" &&
|
|
58
|
+
error.code === "action_method_mismatch");
|
|
59
|
+
}
|
|
55
60
|
/** @internal exported for tests */
|
|
56
61
|
export function defaultActionQueryRetry(failureCount, error) {
|
|
57
62
|
if (isAuthFailure(error))
|
|
@@ -60,6 +65,9 @@ export function defaultActionQueryRetry(failureCount, error) {
|
|
|
60
65
|
// silently retrying would multiply that wait. Surface it instead.
|
|
61
66
|
if (isActionTimeout(error))
|
|
62
67
|
return false;
|
|
68
|
+
// Wrong verb is deterministic — retrying sends the same wrong verb again.
|
|
69
|
+
if (isActionMethodMismatch(error))
|
|
70
|
+
return false;
|
|
63
71
|
if (isBrowserResourceExhaustion(error))
|
|
64
72
|
return false;
|
|
65
73
|
// Network-level failures never carry an HTTP `status` (actionFetch only
|
|
@@ -338,6 +346,24 @@ async function performActionFetch(name, method, params, options) {
|
|
|
338
346
|
(raw && raw.slice(0, 200)) ||
|
|
339
347
|
res.statusText ||
|
|
340
348
|
`HTTP ${res.status}`;
|
|
349
|
+
// mountActionRoutes only ever returns 405 for one reason: the verb this
|
|
350
|
+
// call sent doesn't match the action's declared `http.method`. The client
|
|
351
|
+
// has no way to look that method up ahead of time (defineAction lives in
|
|
352
|
+
// server-only modules), so callers routinely default to POST and hit this
|
|
353
|
+
// blind. Name the action and the required method instead of leaving a
|
|
354
|
+
// bare 405 for the caller to reverse-engineer from a "Use X" string.
|
|
355
|
+
if (res.status === 405) {
|
|
356
|
+
const requiredMethod = /\bUse (GET|POST|PUT|DELETE)\b/.exec(message)?.[1];
|
|
357
|
+
const error = new Error(`Action ${name} was called with ${method}, but it declares ` +
|
|
358
|
+
`http: { method: "${requiredMethod ?? "?"}" }. Pass { method: "${requiredMethod ?? "..."}" } ` +
|
|
359
|
+
`to this call (or use the hook that defaults to it) to match the action's declared method.`);
|
|
360
|
+
error.status = 405;
|
|
361
|
+
error.code = "action_method_mismatch";
|
|
362
|
+
error.sentMethod = method;
|
|
363
|
+
if (requiredMethod)
|
|
364
|
+
error.requiredMethod = requiredMethod;
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
341
367
|
const error = new Error(`Action ${name} failed: ${message}`);
|
|
342
368
|
error.status = res.status;
|
|
343
369
|
throw error;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|