@agent-native/core 0.157.2 → 0.157.4
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/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +40 -1
- package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +3 -1
- package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +7 -7
- package/corpus/templates/clips/actions/approve-recording-access-request.ts +147 -0
- package/corpus/templates/clips/actions/request-recording-access.ts +121 -23
- package/corpus/templates/clips/actions/view-screen.ts +1 -1
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +34 -53
- package/corpus/templates/clips/app/components/player/request-access-dialog.tsx +104 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +36 -45
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +3 -6
- package/corpus/templates/clips/app/i18n/en-US.ts +26 -1
- package/corpus/templates/clips/app/lib/public-meeting.ts +1 -1
- package/corpus/templates/clips/app/routes/access-request.approve.tsx +250 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +9 -32
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +123 -37
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +41 -1
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +1 -0
- package/corpus/templates/clips/server/lib/emails.ts +40 -11
- package/corpus/templates/clips/shared/recording-link.ts +32 -1
- package/corpus/templates/clips/shared/share-attribution.ts +13 -0
- package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/content/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/content/actions/add-database-item.ts +9 -0
- package/corpus/templates/content/actions/describe-content-database.ts +92 -0
- package/corpus/templates/content/actions/list-content-databases.ts +213 -72
- package/corpus/templates/content/actions/set-document-property.ts +9 -0
- package/corpus/templates/content/actions/submit-content-database-form.ts +9 -0
- package/corpus/templates/content/actions/update-document.ts +9 -0
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +22 -10
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +2 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +245 -9
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +192 -134
- package/corpus/templates/content/app/components/editor/database/settings.tsx +7 -0
- package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +61 -45
- package/corpus/templates/content/app/components/editor/extensions/ImageNode.ts +6 -0
- package/corpus/templates/content/app/components/editor/image-upload.ts +137 -5
- package/corpus/templates/content/app/global.css +8 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +91 -11
- package/corpus/templates/content/changelog/2026-08-12-image-uploads-now-confirm-the-image-can-be-displayed-before-.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-images-fill-the-page-width-by-default-and-remain-visible-whe.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-images-inserted-from-the-slash-command-stay-saved-after-uplo.md +6 -0
- package/corpus/templates/content/changelog/2026-08-14-svg-files-can-be-selected-reliably-from-the-image-picker.md +6 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +7 -1
- package/corpus/templates/content/server/plugins/agent-chat.ts +3 -0
- package/corpus/templates/content/shared/api.ts +9 -0
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/design/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/factory/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -1
- package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +25 -30
- package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +21 -0
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +2 -1
- package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +124 -180
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +1 -0
- package/corpus/templates/slides/app/global.css +99 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/chat-threads/schema.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +318 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +41 -3
- package/dist/client/AssistantChat.d.ts +9 -1
- package/dist/client/AssistantChat.js +26 -13
- package/dist/client/MultiTabAssistantChat.js +18 -5
- package/dist/client/agent-chat.d.ts +8 -0
- package/dist/client/agent-chat.js +15 -0
- package/dist/client/app-chat-sidebar.d.ts +23 -0
- package/dist/client/app-chat-sidebar.js +53 -0
- package/dist/client/app-providers.js +31 -2
- package/dist/client/frame-protocol.d.ts +20 -2
- package/dist/client/hooks/index.d.ts +1 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/sharing/AgentShareSection.d.ts +8 -0
- package/dist/client/sharing/AgentShareSection.js +50 -0
- package/dist/client/sharing/ShareButton.js +2 -1
- package/dist/client/sharing/ShareDialog.d.ts +2 -2
- package/dist/client/sharing/ShareDialog.js +21 -29
- package/dist/client/sharing/index.d.ts +1 -0
- package/dist/client/sharing/index.js +1 -0
- package/dist/client/sharing/useShareButtonController.d.ts +1 -0
- package/dist/client/sharing/useShareDialogController.d.ts +3 -0
- package/dist/client/sharing/useShareDialogController.js +2 -1
- package/dist/client/theme.d.ts +21 -0
- package/dist/client/theme.js +60 -0
- package/dist/client/ui/index.d.ts +1 -1
- package/dist/client/ui/index.js +1 -1
- package/dist/client/use-session.js +23 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/dashboard-storage/schema.d.ts +1 -1
- package/dist/data-programs/schema.d.ts +1 -1
- package/dist/extensions/schema.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +6 -0
- package/dist/localization/core-messages/de-DE.js +6 -0
- package/dist/localization/core-messages/en-US.d.ts +6 -0
- package/dist/localization/core-messages/en-US.js +6 -0
- package/dist/localization/core-messages/es-ES.js +6 -0
- package/dist/localization/core-messages/fr-FR.js +6 -0
- package/dist/localization/core-messages/hi-IN.js +6 -0
- package/dist/localization/core-messages/ja-JP.js +6 -0
- package/dist/localization/core-messages/ko-KR.js +6 -0
- package/dist/localization/core-messages/pt-BR.js +6 -0
- package/dist/localization/core-messages/zh-CN.js +6 -0
- package/dist/localization/core-messages/zh-TW.js +6 -0
- package/dist/localization/core-messages.d.ts +6 -0
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
- package/dist/provider-api/actions/provider-api.d.ts +14 -14
- 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 +8 -5
- package/dist/server/identity-sso-store.d.ts +6 -1
- package/dist/server/identity-sso-store.js +57 -14
- package/dist/server/identity-sso.d.ts +7 -8
- package/dist/server/identity-sso.js +15 -12
- package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
- package/dist/sharing/actions/list-resource-shares.js +1 -0
- package/dist/sharing/schema.d.ts +1 -1
- package/dist/sharing/schema.js +4 -1
- package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/default/.agents/skills/sharing/SKILL.md +21 -0
- package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +21 -0
- package/docs/content/cross-app-sso.mdx +24 -17
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/chat/.agents/skills/sharing/SKILL.md +21 -0
- package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/default/.agents/skills/sharing/SKILL.md +21 -0
- package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +21 -0
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isEmailConfigured,
|
|
9
9
|
renderEmail,
|
|
10
10
|
sendEmail,
|
|
11
|
+
signScopedAgentAccessToken,
|
|
11
12
|
verifyScopedAgentAccessToken,
|
|
12
13
|
withConfiguredAppBasePath,
|
|
13
14
|
} from "@agent-native/core/server";
|
|
@@ -23,12 +24,53 @@ import { z } from "zod";
|
|
|
23
24
|
import { getDb, schema } from "../server/db/index.js";
|
|
24
25
|
import { normalizeOwnerEmail } from "../server/lib/recordings.js";
|
|
25
26
|
import {
|
|
27
|
+
CLIPS_ACCESS_APPROVAL_TOKEN_PREFIX,
|
|
28
|
+
CLIPS_ACCESS_APPROVAL_TOKEN_TTL_SECONDS,
|
|
26
29
|
CLIPS_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
30
|
+
recordingAccessApprovalPath,
|
|
27
31
|
recordingSharePath,
|
|
28
32
|
} from "../shared/recording-link.js";
|
|
29
33
|
|
|
30
34
|
export const CLIPS_ACCESS_REQUEST_EMAIL_ID = "clips.access-request";
|
|
31
35
|
|
|
36
|
+
const ANONYMOUS_ACCESS_REQUEST_WINDOW_MS = 10 * 60 * 1000;
|
|
37
|
+
const ANONYMOUS_ACCESS_REQUEST_MAX = 5;
|
|
38
|
+
const ANONYMOUS_ACCESS_REQUEST_MAX_BUCKETS = 5000;
|
|
39
|
+
const anonymousAccessRequestBuckets = new Map<
|
|
40
|
+
string,
|
|
41
|
+
{ count: number; resetAt: number }
|
|
42
|
+
>();
|
|
43
|
+
|
|
44
|
+
export function __resetAnonymousAccessRequestRateLimitForTests(): void {
|
|
45
|
+
anonymousAccessRequestBuckets.clear();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function allowAnonymousAccessRequest(recordingId: string): boolean {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
for (const [key, bucket] of anonymousAccessRequestBuckets) {
|
|
51
|
+
if (bucket.resetAt <= now) anonymousAccessRequestBuckets.delete(key);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let bucket = anonymousAccessRequestBuckets.get(recordingId);
|
|
55
|
+
if (!bucket) {
|
|
56
|
+
if (
|
|
57
|
+
anonymousAccessRequestBuckets.size >= ANONYMOUS_ACCESS_REQUEST_MAX_BUCKETS
|
|
58
|
+
) {
|
|
59
|
+
const oldestKey = anonymousAccessRequestBuckets.keys().next().value;
|
|
60
|
+
if (oldestKey) anonymousAccessRequestBuckets.delete(oldestKey);
|
|
61
|
+
}
|
|
62
|
+
bucket = {
|
|
63
|
+
count: 0,
|
|
64
|
+
resetAt: now + ANONYMOUS_ACCESS_REQUEST_WINDOW_MS,
|
|
65
|
+
};
|
|
66
|
+
anonymousAccessRequestBuckets.set(recordingId, bucket);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (bucket.count >= ANONYMOUS_ACCESS_REQUEST_MAX) return false;
|
|
70
|
+
bucket.count += 1;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
32
74
|
function accessRequestEventId(
|
|
33
75
|
recordingId: string,
|
|
34
76
|
requesterEmail: string,
|
|
@@ -64,25 +106,38 @@ function absoluteRecordingUrl(recordingId: string): string {
|
|
|
64
106
|
return `${withConfiguredAppBasePath(getAppProductionUrl()).replace(/\/+$/, "")}${recordingSharePath(recordingId)}`;
|
|
65
107
|
}
|
|
66
108
|
|
|
109
|
+
function absoluteAccessApprovalUrl(
|
|
110
|
+
recordingId: string,
|
|
111
|
+
approvalToken: string,
|
|
112
|
+
): string {
|
|
113
|
+
return `${withConfiguredAppBasePath(getAppProductionUrl()).replace(/\/+$/, "")}${recordingAccessApprovalPath(recordingId, approvalToken)}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
67
116
|
export function renderRecordingAccessRequestEmail(input: {
|
|
68
117
|
requesterName: string;
|
|
69
118
|
requesterEmail: string;
|
|
70
119
|
recordingTitle: string;
|
|
71
120
|
url: string;
|
|
121
|
+
allowAccessUrl: string;
|
|
72
122
|
}) {
|
|
73
|
-
const subject =
|
|
123
|
+
const subject = `Access request for "${input.recordingTitle}"`;
|
|
74
124
|
return {
|
|
75
125
|
subject,
|
|
76
126
|
...renderEmail({
|
|
77
127
|
brandName: "Clips",
|
|
78
128
|
preheader: subject,
|
|
79
|
-
heading: "Access
|
|
129
|
+
heading: "Access requested",
|
|
80
130
|
paragraphs: [
|
|
81
|
-
`${emailStrong(input.requesterName)} (${emailStrong(input.requesterEmail)}) requested access to ${emailStrong(input.recordingTitle)}.`,
|
|
82
|
-
"
|
|
131
|
+
`${emailStrong(input.requesterName)} (${emailStrong(input.requesterEmail)}) requested viewer access to ${emailStrong(input.recordingTitle)}.`,
|
|
132
|
+
"Select Allow access to add them to this Clip's standard sharing list. You can also open the Clip to review sharing first.",
|
|
83
133
|
],
|
|
84
|
-
cta: { label: "
|
|
85
|
-
|
|
134
|
+
cta: { label: "Allow access", url: input.allowAccessUrl },
|
|
135
|
+
secondaryCta: { label: "Open Clip", url: input.url },
|
|
136
|
+
closingParagraphs: [
|
|
137
|
+
"This approval link expires in 7 days and requires you to be signed in as a Clip owner or admin.",
|
|
138
|
+
],
|
|
139
|
+
footer:
|
|
140
|
+
"You received this because you own this Clip. If you do not recognize the requester, you can ignore this email.",
|
|
86
141
|
}),
|
|
87
142
|
};
|
|
88
143
|
}
|
|
@@ -99,12 +154,23 @@ async function notifyOwner(input: {
|
|
|
99
154
|
}
|
|
100
155
|
if (!(await isEmailConfigured())) return false;
|
|
101
156
|
|
|
157
|
+
const approvalToken = signScopedAgentAccessToken({
|
|
158
|
+
resourceKind: CLIPS_ACCESS_APPROVAL_TOKEN_PREFIX,
|
|
159
|
+
resourceId: input.recordingId,
|
|
160
|
+
viewerEmail: input.requesterEmail,
|
|
161
|
+
ttlSeconds: CLIPS_ACCESS_APPROVAL_TOKEN_TTL_SECONDS,
|
|
162
|
+
});
|
|
163
|
+
|
|
102
164
|
await sendEmail({
|
|
103
165
|
...renderRecordingAccessRequestEmail({
|
|
104
166
|
requesterName: input.requesterName,
|
|
105
167
|
requesterEmail: input.requesterEmail,
|
|
106
168
|
recordingTitle: input.recordingTitle,
|
|
107
169
|
url: absoluteRecordingUrl(input.recordingId),
|
|
170
|
+
allowAccessUrl: absoluteAccessApprovalUrl(
|
|
171
|
+
input.recordingId,
|
|
172
|
+
approvalToken,
|
|
173
|
+
),
|
|
108
174
|
}),
|
|
109
175
|
to: input.ownerEmail,
|
|
110
176
|
replyTo: input.requesterEmail,
|
|
@@ -115,7 +181,7 @@ async function notifyOwner(input: {
|
|
|
115
181
|
|
|
116
182
|
export default defineAction({
|
|
117
183
|
description:
|
|
118
|
-
"Request access to a private Clips recording. Records the request and notifies the owner in-app and by email when configured.",
|
|
184
|
+
"Request access to a private Clips recording. Signed-in viewers use their account email; anonymous viewers may provide an email address. Records the request and notifies the owner in-app and by email when configured.",
|
|
119
185
|
schema: z.object({
|
|
120
186
|
recordingId: z
|
|
121
187
|
.string()
|
|
@@ -127,18 +193,53 @@ export default defineAction({
|
|
|
127
193
|
.min(1)
|
|
128
194
|
.optional()
|
|
129
195
|
.describe("Short-lived capability from the private share page."),
|
|
196
|
+
requesterEmail: z
|
|
197
|
+
.string()
|
|
198
|
+
.trim()
|
|
199
|
+
.email()
|
|
200
|
+
.optional()
|
|
201
|
+
.describe(
|
|
202
|
+
"Email address to request access for when the viewer is not signed in.",
|
|
203
|
+
),
|
|
130
204
|
}),
|
|
131
205
|
agentTool: false,
|
|
132
|
-
run: async ({ recordingId, accessRequestToken }) => {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
206
|
+
run: async ({ recordingId, accessRequestToken, requesterEmail }) => {
|
|
207
|
+
const sessionEmail = getRequestUserEmail();
|
|
208
|
+
const token = accessRequestToken
|
|
209
|
+
? verifyScopedAgentAccessToken(accessRequestToken, {
|
|
210
|
+
resourceKind: CLIPS_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
211
|
+
resourceId: recordingId,
|
|
212
|
+
})
|
|
213
|
+
: { ok: false as const, reason: "missing" };
|
|
214
|
+
if (!token.ok) {
|
|
215
|
+
throw httpError(`Recording ${recordingId} not found`, 404);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const normalizedRequesterEmail = sessionEmail
|
|
219
|
+
? normalizeOwnerEmail(sessionEmail)
|
|
220
|
+
: requesterEmail
|
|
221
|
+
? normalizeOwnerEmail(requesterEmail)
|
|
222
|
+
: null;
|
|
223
|
+
if (!normalizedRequesterEmail) {
|
|
224
|
+
throw httpError(
|
|
225
|
+
"Sign in or provide an email address to request access to this clip.",
|
|
226
|
+
401,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const tokenViewerEmail = token.viewerEmail
|
|
231
|
+
? normalizeOwnerEmail(token.viewerEmail)
|
|
232
|
+
: null;
|
|
233
|
+
if (tokenViewerEmail && tokenViewerEmail !== normalizedRequesterEmail) {
|
|
234
|
+
throw httpError(
|
|
235
|
+
"This request is tied to a different email. Sign in with the email that opened the link.",
|
|
236
|
+
403,
|
|
237
|
+
);
|
|
136
238
|
}
|
|
137
239
|
|
|
138
|
-
const normalizedRequesterEmail = normalizeOwnerEmail(requesterEmail);
|
|
139
240
|
const access = await resolveAccess("recording", recordingId, {
|
|
140
241
|
userEmail: normalizedRequesterEmail,
|
|
141
|
-
orgId: getRequestOrgId() ?? undefined,
|
|
242
|
+
orgId: sessionEmail ? (getRequestOrgId() ?? undefined) : undefined,
|
|
142
243
|
});
|
|
143
244
|
|
|
144
245
|
if (access) {
|
|
@@ -157,16 +258,6 @@ export default defineAction({
|
|
|
157
258
|
};
|
|
158
259
|
}
|
|
159
260
|
|
|
160
|
-
if (
|
|
161
|
-
!accessRequestToken ||
|
|
162
|
-
!verifyScopedAgentAccessToken(accessRequestToken, {
|
|
163
|
-
resourceKind: CLIPS_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
164
|
-
resourceId: recordingId,
|
|
165
|
-
}).ok
|
|
166
|
-
) {
|
|
167
|
-
throw httpError(`Recording ${recordingId} not found`, 404);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
261
|
const db = getDb();
|
|
171
262
|
const [recording] = await db
|
|
172
263
|
.select({
|
|
@@ -223,6 +314,13 @@ export default defineAction({
|
|
|
223
314
|
};
|
|
224
315
|
}
|
|
225
316
|
|
|
317
|
+
if (!sessionEmail && !allowAnonymousAccessRequest(recordingId)) {
|
|
318
|
+
throw httpError(
|
|
319
|
+
"Too many anonymous access requests for this clip. Try again later.",
|
|
320
|
+
429,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
226
324
|
const requesterName =
|
|
227
325
|
getRequestUserName()?.trim() ||
|
|
228
326
|
displayNameForEmail(normalizedRequesterEmail);
|
|
@@ -425,7 +425,7 @@ async function fetchMeetingDetail(meetingId: string) {
|
|
|
425
425
|
source: meeting.source,
|
|
426
426
|
userNotesMd: meeting.userNotesMd,
|
|
427
427
|
transcriptStatus: meeting.transcriptStatus,
|
|
428
|
-
shareTranscript:
|
|
428
|
+
shareTranscript: meeting.shareTranscript === true,
|
|
429
429
|
summaryMd: meeting.summaryMd,
|
|
430
430
|
bullets: safeJsonArray<{ text: string }>(meeting.bulletsJson),
|
|
431
431
|
},
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { appPath } from "@agent-native/core/client/api-path";
|
|
2
2
|
import {
|
|
3
|
-
useActionMutation,
|
|
4
3
|
useActionQuery,
|
|
4
|
+
useActionMutation,
|
|
5
5
|
} from "@agent-native/core/client/hooks";
|
|
6
6
|
import { useT } from "@agent-native/core/client/i18n";
|
|
7
7
|
import { ShareAgentsSection } from "@agent-native/toolkit/sharing";
|
|
8
|
-
import { IconLink, IconMail } from "@tabler/icons-react";
|
|
9
8
|
import {
|
|
10
9
|
useCallback,
|
|
11
10
|
useEffect,
|
|
@@ -34,7 +33,6 @@ import {
|
|
|
34
33
|
PopoverTrigger,
|
|
35
34
|
} from "@/components/ui/popover";
|
|
36
35
|
import { Switch } from "@/components/ui/switch";
|
|
37
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
38
36
|
|
|
39
37
|
export interface ShareMeetingPopoverProps {
|
|
40
38
|
meetingId: string;
|
|
@@ -90,49 +88,16 @@ function ShareMeetingContent({
|
|
|
90
88
|
const canManage = data?.role === "owner" || data?.role === "admin";
|
|
91
89
|
|
|
92
90
|
return (
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
</TabsTrigger>
|
|
104
|
-
</TabsList>
|
|
105
|
-
|
|
106
|
-
<TabsContent value="link" className="mt-3">
|
|
107
|
-
<LinkTab
|
|
108
|
-
meetingId={meetingId}
|
|
109
|
-
shareUrl={shareUrl}
|
|
110
|
-
sharesQuery={sharesQuery}
|
|
111
|
-
canManage={canManage}
|
|
112
|
-
shareTranscript={shareTranscript}
|
|
113
|
-
transcriptReady={transcriptReady}
|
|
114
|
-
/>
|
|
115
|
-
</TabsContent>
|
|
116
|
-
|
|
117
|
-
<TabsContent value="invite" className="mt-3">
|
|
118
|
-
<SharePeopleTab
|
|
119
|
-
resourceType="meeting"
|
|
120
|
-
resourceId={meetingId}
|
|
121
|
-
sharesQuery={sharesQuery}
|
|
122
|
-
canManage={canManage}
|
|
123
|
-
onError={(err, action) =>
|
|
124
|
-
toast.error(
|
|
125
|
-
err instanceof Error
|
|
126
|
-
? err.message
|
|
127
|
-
: action === "invite"
|
|
128
|
-
? t("clipsFinalRaw.inviteFailed")
|
|
129
|
-
: t("clipsFinalRaw.removePersonFailed"),
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
/>
|
|
133
|
-
</TabsContent>
|
|
134
|
-
</Tabs>
|
|
135
|
-
</>
|
|
91
|
+
<div className="min-w-0 px-4 py-3">
|
|
92
|
+
<LinkTab
|
|
93
|
+
meetingId={meetingId}
|
|
94
|
+
shareUrl={shareUrl}
|
|
95
|
+
sharesQuery={sharesQuery}
|
|
96
|
+
canManage={canManage}
|
|
97
|
+
shareTranscript={shareTranscript}
|
|
98
|
+
transcriptReady={transcriptReady}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
136
101
|
);
|
|
137
102
|
}
|
|
138
103
|
|
|
@@ -242,6 +207,16 @@ function LinkTab({
|
|
|
242
207
|
|
|
243
208
|
return (
|
|
244
209
|
<div className="space-y-3">
|
|
210
|
+
<CopyField
|
|
211
|
+
label={
|
|
212
|
+
isPublic
|
|
213
|
+
? t("clipsFinalRaw.shareLink")
|
|
214
|
+
: t("shareDialog.shareWithHumans")
|
|
215
|
+
}
|
|
216
|
+
value={shareUrl}
|
|
217
|
+
disabled={!sharesLoaded}
|
|
218
|
+
/>
|
|
219
|
+
|
|
245
220
|
<GeneralAccessSelect
|
|
246
221
|
visibility={visibility}
|
|
247
222
|
canManage={canManage}
|
|
@@ -277,14 +252,20 @@ function LinkTab({
|
|
|
277
252
|
/>
|
|
278
253
|
</div>
|
|
279
254
|
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
255
|
+
<SharePeopleTab
|
|
256
|
+
resourceType="meeting"
|
|
257
|
+
resourceId={meetingId}
|
|
258
|
+
sharesQuery={sharesQuery}
|
|
259
|
+
canManage={canManage}
|
|
260
|
+
onError={(err, action) =>
|
|
261
|
+
toast.error(
|
|
262
|
+
err instanceof Error
|
|
263
|
+
? err.message
|
|
264
|
+
: action === "invite"
|
|
265
|
+
? t("clipsFinalRaw.inviteFailed")
|
|
266
|
+
: t("clipsFinalRaw.removePersonFailed"),
|
|
267
|
+
)
|
|
285
268
|
}
|
|
286
|
-
value={shareUrl}
|
|
287
|
-
disabled={!sharesLoaded}
|
|
288
269
|
/>
|
|
289
270
|
|
|
290
271
|
{!isPublic && canManage ? (
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { IconLogin2 } from "@tabler/icons-react";
|
|
3
|
+
import type { FormEvent } from "react";
|
|
4
|
+
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
} from "@/components/ui/dialog";
|
|
13
|
+
import { Input } from "@/components/ui/input";
|
|
14
|
+
import { Label } from "@/components/ui/label";
|
|
15
|
+
import { Separator } from "@/components/ui/separator";
|
|
16
|
+
|
|
17
|
+
export interface RequestAccessDialogProps {
|
|
18
|
+
open: boolean;
|
|
19
|
+
onOpenChange: (open: boolean) => void;
|
|
20
|
+
signInHref: string;
|
|
21
|
+
email: string;
|
|
22
|
+
onEmailChange: (email: string) => void;
|
|
23
|
+
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
|
24
|
+
isSubmitting: boolean;
|
|
25
|
+
error?: string | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function RequestAccessDialog({
|
|
29
|
+
open,
|
|
30
|
+
onOpenChange,
|
|
31
|
+
signInHref,
|
|
32
|
+
email,
|
|
33
|
+
onEmailChange,
|
|
34
|
+
onSubmit,
|
|
35
|
+
isSubmitting,
|
|
36
|
+
error,
|
|
37
|
+
}: RequestAccessDialogProps) {
|
|
38
|
+
const t = useT();
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
42
|
+
<DialogContent className="sm:max-w-md">
|
|
43
|
+
<DialogHeader>
|
|
44
|
+
<DialogTitle>{t("sharePage.requestAccessDialogTitle")}</DialogTitle>
|
|
45
|
+
<DialogDescription>
|
|
46
|
+
{t("sharePage.requestAccessDialogDescription")}
|
|
47
|
+
</DialogDescription>
|
|
48
|
+
</DialogHeader>
|
|
49
|
+
|
|
50
|
+
<div className="grid gap-4">
|
|
51
|
+
<Button asChild className="w-full gap-2">
|
|
52
|
+
<a href={signInHref}>
|
|
53
|
+
<IconLogin2
|
|
54
|
+
className="size-4 rtl:-scale-x-100"
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
/>
|
|
57
|
+
{t("sharePage.requestAccessSignIn")}
|
|
58
|
+
</a>
|
|
59
|
+
</Button>
|
|
60
|
+
|
|
61
|
+
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
|
62
|
+
<Separator className="flex-1" />
|
|
63
|
+
<span>{t("sharePage.requestAccessOr")}</span>
|
|
64
|
+
<Separator className="flex-1" />
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<form className="grid gap-3" onSubmit={onSubmit}>
|
|
68
|
+
<div className="grid gap-2">
|
|
69
|
+
<Label htmlFor="private-clip-request-email">
|
|
70
|
+
{t("sharePage.requestAccessEmailLabel")}
|
|
71
|
+
</Label>
|
|
72
|
+
<Input
|
|
73
|
+
id="private-clip-request-email"
|
|
74
|
+
type="email"
|
|
75
|
+
inputMode="email"
|
|
76
|
+
autoComplete="email"
|
|
77
|
+
required
|
|
78
|
+
value={email}
|
|
79
|
+
onChange={(event) => onEmailChange(event.target.value)}
|
|
80
|
+
placeholder={t("sharePage.requestAccessEmailPlaceholder")}
|
|
81
|
+
aria-invalid={Boolean(error)}
|
|
82
|
+
/>
|
|
83
|
+
<p className="text-xs text-muted-foreground">
|
|
84
|
+
{t("sharePage.requestAccessEmailHint")}
|
|
85
|
+
</p>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{error ? (
|
|
89
|
+
<p className="text-sm text-destructive" role="alert">
|
|
90
|
+
{error}
|
|
91
|
+
</p>
|
|
92
|
+
) : null}
|
|
93
|
+
|
|
94
|
+
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
|
95
|
+
{isSubmitting
|
|
96
|
+
? t("sharePage.requestingAccess")
|
|
97
|
+
: t("sharePage.requestAccessWithEmail")}
|
|
98
|
+
</Button>
|
|
99
|
+
</form>
|
|
100
|
+
</div>
|
|
101
|
+
</DialogContent>
|
|
102
|
+
</Dialog>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -7,12 +7,7 @@ import {
|
|
|
7
7
|
} from "@agent-native/core/client/hooks";
|
|
8
8
|
import { useT } from "@agent-native/core/client/i18n";
|
|
9
9
|
import { ShareAgentsSection } from "@agent-native/toolkit/sharing";
|
|
10
|
-
import {
|
|
11
|
-
IconCode,
|
|
12
|
-
IconExternalLink,
|
|
13
|
-
IconLink,
|
|
14
|
-
IconMail,
|
|
15
|
-
} from "@tabler/icons-react";
|
|
10
|
+
import { IconExternalLink } from "@tabler/icons-react";
|
|
16
11
|
import {
|
|
17
12
|
useCallback,
|
|
18
13
|
useEffect,
|
|
@@ -240,7 +235,7 @@ function ShareRecordingContent({
|
|
|
240
235
|
// editor keep it regardless of visibility since they can flip to public
|
|
241
236
|
// from inside it.
|
|
242
237
|
const canEmbed = canViewShares || visibility === "public";
|
|
243
|
-
const tabCount = 1 + (
|
|
238
|
+
const tabCount = 1 + (canEmbed ? 1 : 0);
|
|
244
239
|
|
|
245
240
|
// Attribution `via` must be a stable non-PII id, never an email. The only
|
|
246
241
|
// owner id available client-side is the *current* session's userId, which is
|
|
@@ -266,21 +261,19 @@ function ShareRecordingContent({
|
|
|
266
261
|
>
|
|
267
262
|
{tabCount > 1 ? (
|
|
268
263
|
<TabsList
|
|
269
|
-
className={`grid w-full ${tabCount === 3 ? "grid-cols-3" : "grid-cols-2"}`}
|
|
264
|
+
className={`grid w-full rounded-xl bg-muted/70 p-1 ${tabCount === 3 ? "grid-cols-3" : "grid-cols-2"}`}
|
|
270
265
|
>
|
|
271
|
-
<TabsTrigger
|
|
272
|
-
|
|
266
|
+
<TabsTrigger
|
|
267
|
+
value="link"
|
|
268
|
+
className="h-10 rounded-lg px-3 text-sm font-medium text-muted-foreground transition-colors data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm data-[state=active]:ring-1 data-[state=active]:ring-border"
|
|
269
|
+
>
|
|
273
270
|
{t("shareDialog.link")}
|
|
274
271
|
</TabsTrigger>
|
|
275
|
-
{canViewShares ? (
|
|
276
|
-
<TabsTrigger value="invite" className="gap-1.5">
|
|
277
|
-
<IconMail size={14} />
|
|
278
|
-
{t("shareDialog.invite")}
|
|
279
|
-
</TabsTrigger>
|
|
280
|
-
) : null}
|
|
281
272
|
{canEmbed ? (
|
|
282
|
-
<TabsTrigger
|
|
283
|
-
|
|
273
|
+
<TabsTrigger
|
|
274
|
+
value="embed"
|
|
275
|
+
className="h-10 rounded-lg px-3 text-sm font-medium text-muted-foreground transition-colors data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm data-[state=active]:ring-1 data-[state=active]:ring-border"
|
|
276
|
+
>
|
|
284
277
|
{t("shareDialog.embed")}
|
|
285
278
|
</TabsTrigger>
|
|
286
279
|
) : null}
|
|
@@ -304,24 +297,6 @@ function ShareRecordingContent({
|
|
|
304
297
|
/>
|
|
305
298
|
</TabsContent>
|
|
306
299
|
|
|
307
|
-
{canViewShares ? (
|
|
308
|
-
<TabsContent value="invite" className="mt-3">
|
|
309
|
-
<SharePeopleTab
|
|
310
|
-
resourceType="recording"
|
|
311
|
-
resourceId={recordingId}
|
|
312
|
-
resourceUrl={absoluteAppUrl(`/r/${recordingId}`)}
|
|
313
|
-
sharesQuery={sharesQuery}
|
|
314
|
-
canManage={canManage}
|
|
315
|
-
roleCopy={{
|
|
316
|
-
commenter: {
|
|
317
|
-
label: t("shareUi.recordingCommenter.label"),
|
|
318
|
-
description: t("shareUi.recordingCommenter.description"),
|
|
319
|
-
},
|
|
320
|
-
}}
|
|
321
|
-
/>
|
|
322
|
-
</TabsContent>
|
|
323
|
-
) : null}
|
|
324
|
-
|
|
325
300
|
{canEmbed ? (
|
|
326
301
|
<TabsContent value="embed" className="mt-3">
|
|
327
302
|
<ClipsEmbedConfigurator
|
|
@@ -523,6 +498,16 @@ function LinkTab({
|
|
|
523
498
|
|
|
524
499
|
return (
|
|
525
500
|
<div className="space-y-4">
|
|
501
|
+
<CopyField
|
|
502
|
+
label={
|
|
503
|
+
isPublic
|
|
504
|
+
? t("shareDialog.shareLink")
|
|
505
|
+
: t("shareDialog.shareWithHumans")
|
|
506
|
+
}
|
|
507
|
+
value={shareUrl}
|
|
508
|
+
disabled={visibilityPending || !sharesLoaded}
|
|
509
|
+
/>
|
|
510
|
+
|
|
526
511
|
{canViewShares ? (
|
|
527
512
|
visibility ? (
|
|
528
513
|
<GeneralAccessSelect
|
|
@@ -541,15 +526,21 @@ function LinkTab({
|
|
|
541
526
|
)
|
|
542
527
|
) : null}
|
|
543
528
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
529
|
+
{canViewShares ? (
|
|
530
|
+
<SharePeopleTab
|
|
531
|
+
resourceType="recording"
|
|
532
|
+
resourceId={recordingId}
|
|
533
|
+
resourceUrl={absoluteAppUrl(`/r/${recordingId}`)}
|
|
534
|
+
sharesQuery={sharesQuery}
|
|
535
|
+
canManage={canManage}
|
|
536
|
+
roleCopy={{
|
|
537
|
+
commenter: {
|
|
538
|
+
label: t("shareUi.recordingCommenter.label"),
|
|
539
|
+
description: t("shareUi.recordingCommenter.description"),
|
|
540
|
+
},
|
|
541
|
+
}}
|
|
542
|
+
/>
|
|
543
|
+
) : null}
|
|
553
544
|
|
|
554
545
|
<ShareAgentsSection
|
|
555
546
|
label={t("shareDialog.shareWithAgents")}
|
|
@@ -81,8 +81,8 @@ export const ROLE_OPTIONS: Array<{ value: Role; label: string }> = [
|
|
|
81
81
|
{ value: "admin", label: "Admin" },
|
|
82
82
|
];
|
|
83
83
|
|
|
84
|
-
export function copyToClipboard(value: string):
|
|
85
|
-
|
|
84
|
+
export function copyToClipboard(value: string): Promise<boolean> {
|
|
85
|
+
return writeClipboardText(value);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// ---------------------------------------------------------------------------
|
|
@@ -288,10 +288,7 @@ export function CopyField({
|
|
|
288
288
|
disabled={disabled}
|
|
289
289
|
copyLabel={t("shareUi.copy")}
|
|
290
290
|
copiedLabel={t("recordRoute.linkCopied")}
|
|
291
|
-
onCopy={
|
|
292
|
-
copyToClipboard(nextValue);
|
|
293
|
-
return true;
|
|
294
|
-
}}
|
|
291
|
+
onCopy={copyToClipboard}
|
|
295
292
|
/>
|
|
296
293
|
);
|
|
297
294
|
}
|
|
@@ -260,12 +260,37 @@ const messages = {
|
|
|
260
260
|
privateClipMessage:
|
|
261
261
|
"This clip is private. Request access and the owner will be notified.",
|
|
262
262
|
privateClipSignedOutMessage:
|
|
263
|
-
"This clip is private. Sign in to request access
|
|
263
|
+
"This clip is private. Sign in or enter your email to request access.",
|
|
264
264
|
requestAccess: "Request access",
|
|
265
|
+
requestAccessDialogTitle: "Request access",
|
|
266
|
+
requestAccessDialogDescription:
|
|
267
|
+
"Sign in, or enter the email address the owner should share this clip with.",
|
|
268
|
+
requestAccessSignIn: "Sign in or sign up",
|
|
269
|
+
requestAccessOr: "or",
|
|
270
|
+
requestAccessEmailLabel: "Email address",
|
|
271
|
+
requestAccessEmailPlaceholder: "you@example.com",
|
|
272
|
+
requestAccessEmailHint:
|
|
273
|
+
"After access is granted, sign in with this email to view the clip.",
|
|
274
|
+
requestAccessWithEmail: "Request with email",
|
|
275
|
+
requestAccessEmailRequired: "Enter a valid email address.",
|
|
265
276
|
requestingAccess: "Requesting access...",
|
|
266
277
|
accessRequested: "Access requested",
|
|
267
278
|
accessRequestSent: "The clip owner was notified.",
|
|
279
|
+
accessRequestSentWithEmail:
|
|
280
|
+
"The clip owner was asked to share this clip with {{email}}.",
|
|
268
281
|
accessRequestFailed: "Couldn't request access. Try again.",
|
|
282
|
+
accessApprovalTitle: "Access granted",
|
|
283
|
+
accessApprovalAlreadyTitle: "Access already granted",
|
|
284
|
+
accessApprovalMessage: "{{email}} can now view this clip.",
|
|
285
|
+
accessApprovalAlreadyMessage: "{{email}} already has access to this clip.",
|
|
286
|
+
accessApprovalErrorTitle: "Couldn't grant access",
|
|
287
|
+
accessApprovalInvalid: "This access request is invalid or expired.",
|
|
288
|
+
accessApprovalSignInTitle: "Sign in to allow access",
|
|
289
|
+
accessApprovalSignInMessage:
|
|
290
|
+
"Sign in as the clip owner or admin to approve this request.",
|
|
291
|
+
accessApprovalOpenClip: "Open clip",
|
|
292
|
+
accessApprovalSignIn: "Sign in",
|
|
293
|
+
accessApprovalLoading: "Granting access...",
|
|
269
294
|
clipUnavailable: "Clip unavailable",
|
|
270
295
|
clipUnavailableMessage:
|
|
271
296
|
"This recording isn't public, or the link is invalid. If it's your clip, sign in to check access.",
|
|
@@ -39,7 +39,7 @@ export interface PublicMeeting {
|
|
|
39
39
|
actualStart: string | null;
|
|
40
40
|
actualEnd: string | null;
|
|
41
41
|
transcriptStatus: string | null;
|
|
42
|
-
transcript
|
|
42
|
+
transcript: PublicMeetingTranscript | null;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export interface PublicMeetingPayload {
|