@agent-native/core 0.157.0 → 0.157.2
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/server/jobs/analytics-bigquery-backfill.ts +19 -4
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +7 -2
- package/corpus/templates/assets/app/global.css +4 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1 -2
- package/corpus/templates/brain/app/routes/sources.tsx +1 -2
- package/corpus/templates/calendar/app/hooks/use-bookings.ts +12 -2
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +64 -2
- package/corpus/templates/calendar/server/handlers/bookings.ts +224 -93
- package/corpus/templates/calendar/server/plugins/auth.ts +0 -2
- package/corpus/templates/clips/actions/create-folder.ts +7 -1
- package/corpus/templates/clips/actions/create-meeting.ts +6 -2
- package/corpus/templates/clips/actions/create-recording.ts +5 -3
- package/corpus/templates/clips/actions/finalize-recording.ts +97 -65
- package/corpus/templates/clips/actions/import-loom-recording.ts +5 -3
- package/corpus/templates/clips/actions/request-recording-access.ts +305 -0
- package/corpus/templates/clips/actions/stitch-recordings.ts +5 -3
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +38 -63
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +75 -75
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +3 -21
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +13 -30
- package/corpus/templates/clips/app/i18n/en-US.ts +10 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -5
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +9 -16
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +135 -18
- package/corpus/templates/clips/changelog/2026-08-14-long-resumable-uploads-now-abort-cleanly-and-show-errors.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-14-standardized-share-popovers.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +11 -3
- package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +37 -8
- package/corpus/templates/clips/chrome-extension/src/github-preview.ts +3 -3
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +8 -25
- package/corpus/templates/clips/desktop/src/app.tsx +24 -0
- package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +42 -13
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +11 -18
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +1 -0
- package/corpus/templates/clips/desktop/src/shared/config.ts +1 -0
- package/corpus/templates/clips/desktop/src/styles.css +24 -9
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +8 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +154 -176
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +115 -31
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +7 -0
- package/corpus/templates/clips/server/db/index.ts +1 -0
- package/corpus/templates/clips/server/db/schema.ts +1 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +6 -1
- package/corpus/templates/clips/server/lib/emails.ts +23 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +55 -0
- package/corpus/templates/clips/server/lib/recordings.ts +2 -1
- package/corpus/templates/clips/server/lib/resumable-upload-cleanup.ts +49 -0
- package/corpus/templates/clips/server/lib/s3-upload-provider.ts +34 -7
- package/corpus/templates/clips/server/lib/upload-interruption.ts +15 -1
- package/corpus/templates/clips/server/lib/upload-lease.ts +94 -9
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +82 -7
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +71 -12
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +4 -3
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +133 -4
- package/corpus/templates/clips/server/routes/api/uploads/reap.post.ts +8 -1
- package/corpus/templates/clips/shared/recording-link.ts +4 -0
- package/corpus/templates/clips/shared/share-loader-response.ts +2 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +7 -16
- package/corpus/templates/factory/AGENTS.md +5 -1
- package/corpus/templates/factory/README.md +6 -2
- package/corpus/templates/factory/actions/view-screen.ts +15 -0
- package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +58 -189
- package/corpus/templates/factory/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +7 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +3 -1
- package/corpus/templates/factory/app/routes/agents.tsx +9 -0
- package/corpus/templates/mail/server/plugins/auth.ts +0 -2
- package/corpus/templates/plan/app/components/plan/LocalCodebasePicker.tsx +50 -14
- package/corpus/templates/plan/app/pages/PlansPage.tsx +9 -19
- package/corpus/templates/slides/app/components/editor/HistoryPanel.tsx +7 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +71 -22
- package/corpus/templates/slides/app/lib/editor-shortcuts.ts +46 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +13 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +12 -10
- package/dist/client/AssistantChat.js +25 -5
- package/dist/client/HighlightedCodeBlock.js +12 -2
- package/dist/client/agent-chat/index.d.ts +1 -0
- package/dist/client/agent-chat/index.js +1 -0
- package/dist/client/chat/markdown-renderer.js +16 -3
- package/dist/client/chat-first/apps-rail.js +1 -1
- package/dist/client/components/ui/message-scroller.js +1 -1
- package/dist/client/settings/AccountSettingsCard.js +48 -19
- package/dist/client/sharing/ShareButton.d.ts +1 -2
- package/dist/client/sharing/ShareButton.js +5 -41
- package/dist/client/sharing/ShareDialog.js +5 -4
- package/dist/client/use-chat-threads.js +10 -14
- 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/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/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +0 -2
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.js +18 -0
- package/dist/server/onboarding-html.d.ts +1 -0
- package/dist/server/onboarding-html.js +2 -88
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/shared/markdown-block-split.d.ts +16 -0
- package/dist/shared/markdown-block-split.js +81 -5
- package/dist/styles/agent-native.css +13 -0
- package/dist/voice/voice-cleanup-prompt.js +1 -0
- package/docs/content/cloneable-saas.mdx +24 -26
- package/package.json +3 -3
- package/corpus/templates/clips/shared/mic-audio-cleanup.ts +0 -350
|
@@ -144,7 +144,7 @@ export default defineAction({
|
|
|
144
144
|
description:
|
|
145
145
|
"Import a public Loom share URL, or a direct link to a video file, into Clips as a playable recording. Loom links create the recording immediately and download/reupload Loom's public MP4 plus import Loom's public transcript in the background, since Loom's CDN plus a reupload can take longer than a single request should block on. Other direct video links (e.g. an MP4/WebM/MOV hosted by another screen recorder) are downloaded and reuploaded synchronously without transcript metadata — use request-transcript afterward. If storage is not connected, creates a waiting recording that can be retried after storage setup.",
|
|
146
146
|
schema: ImportLoomRecordingSchema,
|
|
147
|
-
run: async (args) => {
|
|
147
|
+
run: async (args, actionContext) => {
|
|
148
148
|
const loomId = extractLoomVideoId(args.url);
|
|
149
149
|
const isLoom = Boolean(loomId);
|
|
150
150
|
const loomShareUrl = isLoom ? normalizeLoomShareUrl(args.url) : null;
|
|
@@ -214,8 +214,10 @@ export default defineAction({
|
|
|
214
214
|
const { organizationId } = await requireOrganizationAccess(
|
|
215
215
|
existingRecording?.organizationId ?? args.organizationId,
|
|
216
216
|
);
|
|
217
|
-
const defaultVisibility =
|
|
218
|
-
|
|
217
|
+
const defaultVisibility = await getDefaultRecordingVisibility(
|
|
218
|
+
organizationId,
|
|
219
|
+
actionContext?.userEmail ?? ownerEmail,
|
|
220
|
+
);
|
|
219
221
|
|
|
220
222
|
const now = new Date().toISOString();
|
|
221
223
|
const id = existingRecording?.id ?? nanoid();
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { defineAction } from "@agent-native/core";
|
|
4
|
+
import { notify } from "@agent-native/core/notifications";
|
|
5
|
+
import {
|
|
6
|
+
emailStrong,
|
|
7
|
+
getAppProductionUrl,
|
|
8
|
+
isEmailConfigured,
|
|
9
|
+
renderEmail,
|
|
10
|
+
sendEmail,
|
|
11
|
+
verifyScopedAgentAccessToken,
|
|
12
|
+
withConfiguredAppBasePath,
|
|
13
|
+
} from "@agent-native/core/server";
|
|
14
|
+
import {
|
|
15
|
+
getRequestUserEmail,
|
|
16
|
+
getRequestOrgId,
|
|
17
|
+
getRequestUserName,
|
|
18
|
+
} from "@agent-native/core/server/request-context";
|
|
19
|
+
import { resolveAccess } from "@agent-native/core/sharing";
|
|
20
|
+
import { and, eq } from "drizzle-orm";
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
|
|
23
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
24
|
+
import { normalizeOwnerEmail } from "../server/lib/recordings.js";
|
|
25
|
+
import {
|
|
26
|
+
CLIPS_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
27
|
+
recordingSharePath,
|
|
28
|
+
} from "../shared/recording-link.js";
|
|
29
|
+
|
|
30
|
+
export const CLIPS_ACCESS_REQUEST_EMAIL_ID = "clips.access-request";
|
|
31
|
+
|
|
32
|
+
function accessRequestEventId(
|
|
33
|
+
recordingId: string,
|
|
34
|
+
requesterEmail: string,
|
|
35
|
+
): string {
|
|
36
|
+
return (
|
|
37
|
+
"access-request-" +
|
|
38
|
+
createHash("sha256")
|
|
39
|
+
.update(recordingId)
|
|
40
|
+
.update("\0")
|
|
41
|
+
.update(requesterEmail)
|
|
42
|
+
.digest("hex")
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function httpError(message: string, statusCode: number): Error {
|
|
47
|
+
return Object.assign(new Error(message), { statusCode });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function displayNameForEmail(email: string): string {
|
|
51
|
+
const local = email.replace(/@.*/, "");
|
|
52
|
+
const parts = local
|
|
53
|
+
.split(/[._+-]+/)
|
|
54
|
+
.map((part) => part.trim())
|
|
55
|
+
.filter(Boolean);
|
|
56
|
+
if (parts.length === 0) return email;
|
|
57
|
+
return parts
|
|
58
|
+
.slice(0, 2)
|
|
59
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
60
|
+
.join(" ");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function absoluteRecordingUrl(recordingId: string): string {
|
|
64
|
+
return `${withConfiguredAppBasePath(getAppProductionUrl()).replace(/\/+$/, "")}${recordingSharePath(recordingId)}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function renderRecordingAccessRequestEmail(input: {
|
|
68
|
+
requesterName: string;
|
|
69
|
+
requesterEmail: string;
|
|
70
|
+
recordingTitle: string;
|
|
71
|
+
url: string;
|
|
72
|
+
}) {
|
|
73
|
+
const subject = `${input.requesterName} requested access to "${input.recordingTitle}"`;
|
|
74
|
+
return {
|
|
75
|
+
subject,
|
|
76
|
+
...renderEmail({
|
|
77
|
+
brandName: "Clips",
|
|
78
|
+
preheader: subject,
|
|
79
|
+
heading: "Access request",
|
|
80
|
+
paragraphs: [
|
|
81
|
+
`${emailStrong(input.requesterName)} (${emailStrong(input.requesterEmail)}) requested access to ${emailStrong(input.recordingTitle)}.`,
|
|
82
|
+
"Open the Clip and use Share to grant access if this request should be approved.",
|
|
83
|
+
],
|
|
84
|
+
cta: { label: "Open Clip", url: input.url },
|
|
85
|
+
footer: "You received this because you own this Clip.",
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function notifyOwner(input: {
|
|
91
|
+
recordingId: string;
|
|
92
|
+
recordingTitle: string;
|
|
93
|
+
ownerEmail: string | null;
|
|
94
|
+
requesterEmail: string;
|
|
95
|
+
requesterName: string;
|
|
96
|
+
}): Promise<boolean> {
|
|
97
|
+
if (!input.ownerEmail || input.ownerEmail === input.requesterEmail) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (!(await isEmailConfigured())) return false;
|
|
101
|
+
|
|
102
|
+
await sendEmail({
|
|
103
|
+
...renderRecordingAccessRequestEmail({
|
|
104
|
+
requesterName: input.requesterName,
|
|
105
|
+
requesterEmail: input.requesterEmail,
|
|
106
|
+
recordingTitle: input.recordingTitle,
|
|
107
|
+
url: absoluteRecordingUrl(input.recordingId),
|
|
108
|
+
}),
|
|
109
|
+
to: input.ownerEmail,
|
|
110
|
+
replyTo: input.requesterEmail,
|
|
111
|
+
templateId: CLIPS_ACCESS_REQUEST_EMAIL_ID,
|
|
112
|
+
});
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default defineAction({
|
|
117
|
+
description:
|
|
118
|
+
"Request access to a private Clips recording. Records the request and notifies the owner in-app and by email when configured.",
|
|
119
|
+
schema: z.object({
|
|
120
|
+
recordingId: z
|
|
121
|
+
.string()
|
|
122
|
+
.min(1)
|
|
123
|
+
.describe("Recording ID to request access to."),
|
|
124
|
+
accessRequestToken: z
|
|
125
|
+
.string()
|
|
126
|
+
.trim()
|
|
127
|
+
.min(1)
|
|
128
|
+
.optional()
|
|
129
|
+
.describe("Short-lived capability from the private share page."),
|
|
130
|
+
}),
|
|
131
|
+
agentTool: false,
|
|
132
|
+
run: async ({ recordingId, accessRequestToken }) => {
|
|
133
|
+
const requesterEmail = getRequestUserEmail();
|
|
134
|
+
if (!requesterEmail) {
|
|
135
|
+
throw httpError("Sign in to request access to this clip.", 401);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const normalizedRequesterEmail = normalizeOwnerEmail(requesterEmail);
|
|
139
|
+
const access = await resolveAccess("recording", recordingId, {
|
|
140
|
+
userEmail: normalizedRequesterEmail,
|
|
141
|
+
orgId: getRequestOrgId() ?? undefined,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (access) {
|
|
145
|
+
const recording = access.resource as {
|
|
146
|
+
trashedAt?: string | null;
|
|
147
|
+
};
|
|
148
|
+
if (recording.trashedAt) {
|
|
149
|
+
throw httpError(`Recording ${recordingId} not found`, 404);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
ok: true as const,
|
|
153
|
+
alreadyHasAccess: true,
|
|
154
|
+
alreadyRequested: false,
|
|
155
|
+
notifiedOwner: false,
|
|
156
|
+
message: "You already have access to this clip.",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
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
|
+
const db = getDb();
|
|
171
|
+
const [recording] = await db
|
|
172
|
+
.select({
|
|
173
|
+
id: schema.recordings.id,
|
|
174
|
+
title: schema.recordings.title,
|
|
175
|
+
ownerEmail: schema.recordings.ownerEmail,
|
|
176
|
+
visibility: schema.recordings.visibility,
|
|
177
|
+
trashedAt: schema.recordings.trashedAt,
|
|
178
|
+
})
|
|
179
|
+
.from(schema.recordings)
|
|
180
|
+
.where(eq(schema.recordings.id, recordingId))
|
|
181
|
+
.limit(1);
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
!recording ||
|
|
185
|
+
recording.trashedAt ||
|
|
186
|
+
recording.visibility === "public"
|
|
187
|
+
) {
|
|
188
|
+
throw httpError(`Recording ${recordingId} not found`, 404);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const previousRequests = await db
|
|
192
|
+
.select({ payload: schema.recordingEvents.payload })
|
|
193
|
+
.from(schema.recordingEvents)
|
|
194
|
+
.where(
|
|
195
|
+
and(
|
|
196
|
+
eq(schema.recordingEvents.recordingId, recordingId),
|
|
197
|
+
eq(schema.recordingEvents.kind, "access-request"),
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
const alreadyRequested = previousRequests.some((event) => {
|
|
201
|
+
try {
|
|
202
|
+
const payload = JSON.parse(event.payload) as {
|
|
203
|
+
requesterEmail?: string;
|
|
204
|
+
};
|
|
205
|
+
return (
|
|
206
|
+
typeof payload.requesterEmail === "string" &&
|
|
207
|
+
normalizeOwnerEmail(payload.requesterEmail) ===
|
|
208
|
+
normalizedRequesterEmail
|
|
209
|
+
);
|
|
210
|
+
} catch {
|
|
211
|
+
// coercion-ok: malformed historical event payload cannot represent a matching requester.
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (alreadyRequested) {
|
|
217
|
+
return {
|
|
218
|
+
ok: true as const,
|
|
219
|
+
alreadyHasAccess: false,
|
|
220
|
+
alreadyRequested: true,
|
|
221
|
+
notifiedOwner: false,
|
|
222
|
+
message: "Your access request is already with the clip owner.",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const requesterName =
|
|
227
|
+
getRequestUserName()?.trim() ||
|
|
228
|
+
displayNameForEmail(normalizedRequesterEmail);
|
|
229
|
+
const requestedAt = new Date().toISOString();
|
|
230
|
+
const [insertedRequest] = await db
|
|
231
|
+
.insert(schema.recordingEvents)
|
|
232
|
+
.values({
|
|
233
|
+
id: accessRequestEventId(recordingId, normalizedRequesterEmail),
|
|
234
|
+
recordingId,
|
|
235
|
+
viewerId: null,
|
|
236
|
+
kind: "access-request",
|
|
237
|
+
timestampMs: 0,
|
|
238
|
+
payload: JSON.stringify({
|
|
239
|
+
requesterEmail: normalizedRequesterEmail,
|
|
240
|
+
requesterName,
|
|
241
|
+
requestedAt,
|
|
242
|
+
}),
|
|
243
|
+
createdAt: requestedAt,
|
|
244
|
+
})
|
|
245
|
+
.onConflictDoNothing()
|
|
246
|
+
.returning({ id: schema.recordingEvents.id });
|
|
247
|
+
|
|
248
|
+
// Historical requests use random IDs, so the read above still handles
|
|
249
|
+
// them. New requests use a deterministic primary key so concurrent
|
|
250
|
+
// callers have one database-backed winner before notifications are sent.
|
|
251
|
+
if (!insertedRequest) {
|
|
252
|
+
return {
|
|
253
|
+
ok: true as const,
|
|
254
|
+
alreadyHasAccess: false,
|
|
255
|
+
alreadyRequested: true,
|
|
256
|
+
notifiedOwner: false,
|
|
257
|
+
message: "Your access request is already with the clip owner.",
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const ownerEmail = recording.ownerEmail
|
|
262
|
+
? normalizeOwnerEmail(recording.ownerEmail)
|
|
263
|
+
: null;
|
|
264
|
+
let notifiedOwner = false;
|
|
265
|
+
try {
|
|
266
|
+
if (ownerEmail) {
|
|
267
|
+
await notify(
|
|
268
|
+
{
|
|
269
|
+
severity: "info",
|
|
270
|
+
title: "Clip access requested",
|
|
271
|
+
body: `${requesterName} requested access to “${recording.title}”.`,
|
|
272
|
+
metadata: {
|
|
273
|
+
recordingId,
|
|
274
|
+
requesterEmail: normalizedRequesterEmail,
|
|
275
|
+
url: absoluteRecordingUrl(recordingId),
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{ owner: ownerEmail },
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
notifiedOwner = await notifyOwner({
|
|
282
|
+
recordingId,
|
|
283
|
+
recordingTitle: recording.title,
|
|
284
|
+
ownerEmail,
|
|
285
|
+
requesterEmail: normalizedRequesterEmail,
|
|
286
|
+
requesterName,
|
|
287
|
+
});
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.warn(
|
|
290
|
+
"[recording-access] access request notification failed:",
|
|
291
|
+
error,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
ok: true as const,
|
|
297
|
+
alreadyHasAccess: false,
|
|
298
|
+
alreadyRequested: false,
|
|
299
|
+
notifiedOwner,
|
|
300
|
+
message: notifiedOwner
|
|
301
|
+
? "Access request sent to the clip owner."
|
|
302
|
+
: "Access request recorded for the clip owner.",
|
|
303
|
+
};
|
|
304
|
+
},
|
|
305
|
+
});
|
|
@@ -84,7 +84,7 @@ export default defineAction({
|
|
|
84
84
|
height: z.coerce.number().int().optional(),
|
|
85
85
|
folderId: z.string().nullish(),
|
|
86
86
|
}),
|
|
87
|
-
run: async (args) => {
|
|
87
|
+
run: async (args, actionContext) => {
|
|
88
88
|
const db = getDb();
|
|
89
89
|
const ownerEmail = getCurrentOwnerEmail();
|
|
90
90
|
const videoUrl = args.videoUrl?.trim() || null;
|
|
@@ -134,8 +134,10 @@ export default defineAction({
|
|
|
134
134
|
assertNativeRecordingMedia(source);
|
|
135
135
|
}
|
|
136
136
|
const organizationId = ordered[0].organizationId;
|
|
137
|
-
const defaultVisibility =
|
|
138
|
-
|
|
137
|
+
const defaultVisibility = await getDefaultRecordingVisibility(
|
|
138
|
+
organizationId,
|
|
139
|
+
actionContext?.userEmail ?? ownerEmail,
|
|
140
|
+
);
|
|
139
141
|
|
|
140
142
|
const totalDuration =
|
|
141
143
|
args.durationMs ??
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
useActionQuery,
|
|
5
5
|
} from "@agent-native/core/client/hooks";
|
|
6
6
|
import { useT } from "@agent-native/core/client/i18n";
|
|
7
|
-
import {
|
|
7
|
+
import { ShareAgentsSection } from "@agent-native/toolkit/sharing";
|
|
8
|
+
import { IconLink, IconMail } from "@tabler/icons-react";
|
|
8
9
|
import {
|
|
9
10
|
useCallback,
|
|
10
11
|
useEffect,
|
|
@@ -27,11 +28,6 @@ import {
|
|
|
27
28
|
type Visibility,
|
|
28
29
|
} from "@/components/sharing/share-ui";
|
|
29
30
|
import { Button } from "@/components/ui/button";
|
|
30
|
-
import {
|
|
31
|
-
Collapsible,
|
|
32
|
-
CollapsibleContent,
|
|
33
|
-
CollapsibleTrigger,
|
|
34
|
-
} from "@/components/ui/collapsible";
|
|
35
31
|
import {
|
|
36
32
|
Popover,
|
|
37
33
|
PopoverContent,
|
|
@@ -220,6 +216,7 @@ function LinkTab({
|
|
|
220
216
|
sharesQuery.isLoading ||
|
|
221
217
|
!sharesLoaded ||
|
|
222
218
|
(!isPublic && (createAgentLink.isPending || !agentLink));
|
|
219
|
+
const visibleAgentLink = isPublic ? shareUrl : agentLink;
|
|
223
220
|
|
|
224
221
|
useEffect(() => {
|
|
225
222
|
setIncludeTranscript(shareTranscript);
|
|
@@ -301,66 +298,44 @@ function LinkTab({
|
|
|
301
298
|
/>
|
|
302
299
|
) : null}
|
|
303
300
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
<IconChevronDown
|
|
324
|
-
aria-hidden="true"
|
|
325
|
-
className={`size-4 shrink-0 text-muted-foreground transition-transform ${agentDetailsOpen ? "rotate-180" : ""}`}
|
|
326
|
-
/>
|
|
327
|
-
</button>
|
|
328
|
-
</CollapsibleTrigger>
|
|
329
|
-
<CollapsibleContent className="clips-collapsible-content border-t border-border px-3 py-3">
|
|
330
|
-
<div className="space-y-2">
|
|
331
|
-
<CopyField
|
|
332
|
-
label={t("shareDialog.shareWithAgents")}
|
|
333
|
-
value={agentLink}
|
|
334
|
-
disabled={agentShareDisabled}
|
|
335
|
-
/>
|
|
336
|
-
{sharesLoaded ? (
|
|
337
|
-
<>
|
|
301
|
+
<ShareAgentsSection
|
|
302
|
+
label={t("shareDialog.shareWithAgents")}
|
|
303
|
+
open={agentDetailsOpen}
|
|
304
|
+
onOpenChange={setAgentDetailsOpen}
|
|
305
|
+
contentClassName="clips-collapsible-content"
|
|
306
|
+
>
|
|
307
|
+
<div className="space-y-2">
|
|
308
|
+
<CopyField
|
|
309
|
+
label={t("shareDialog.shareLink")}
|
|
310
|
+
value={visibleAgentLink}
|
|
311
|
+
disabled={agentShareDisabled}
|
|
312
|
+
/>
|
|
313
|
+
{sharesLoaded ? (
|
|
314
|
+
<>
|
|
315
|
+
<p className="text-xs text-muted-foreground">
|
|
316
|
+
{t("shareMeeting.agentLinkDescription")}
|
|
317
|
+
</p>
|
|
318
|
+
{agentLinkError ? (
|
|
319
|
+
<div className="flex items-center justify-between gap-2 rounded-md border border-border bg-muted/30 px-3 py-2">
|
|
338
320
|
<p className="text-xs text-muted-foreground">
|
|
339
|
-
{t("
|
|
321
|
+
{t("shareDialog.agentLinkUnavailable")}
|
|
340
322
|
</p>
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
disabled={createAgentLink.isPending}
|
|
353
|
-
>
|
|
354
|
-
{t("shareDialog.retryAgentLink")}
|
|
355
|
-
</Button>
|
|
356
|
-
</div>
|
|
357
|
-
) : null}
|
|
358
|
-
</>
|
|
323
|
+
<Button
|
|
324
|
+
type="button"
|
|
325
|
+
variant="ghost"
|
|
326
|
+
size="sm"
|
|
327
|
+
className="h-7"
|
|
328
|
+
onClick={() => void loadAgentLink()}
|
|
329
|
+
disabled={createAgentLink.isPending}
|
|
330
|
+
>
|
|
331
|
+
{t("shareDialog.retryAgentLink")}
|
|
332
|
+
</Button>
|
|
333
|
+
</div>
|
|
359
334
|
) : null}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
</
|
|
363
|
-
|
|
335
|
+
</>
|
|
336
|
+
) : null}
|
|
337
|
+
</div>
|
|
338
|
+
</ShareAgentsSection>
|
|
364
339
|
</div>
|
|
365
340
|
);
|
|
366
341
|
}
|