@agent-native/core 0.157.0 → 0.157.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +219 -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 +272 -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 +19 -0
- 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/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/progress/routes.d.ts +1 -1
- 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/package.json +2 -2
- 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,272 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { notify } from "@agent-native/core/notifications";
|
|
3
|
+
import {
|
|
4
|
+
emailStrong,
|
|
5
|
+
getAppProductionUrl,
|
|
6
|
+
isEmailConfigured,
|
|
7
|
+
renderEmail,
|
|
8
|
+
sendEmail,
|
|
9
|
+
verifyScopedAgentAccessToken,
|
|
10
|
+
withConfiguredAppBasePath,
|
|
11
|
+
} from "@agent-native/core/server";
|
|
12
|
+
import {
|
|
13
|
+
getRequestUserEmail,
|
|
14
|
+
getRequestOrgId,
|
|
15
|
+
getRequestUserName,
|
|
16
|
+
} from "@agent-native/core/server/request-context";
|
|
17
|
+
import { resolveAccess } from "@agent-native/core/sharing";
|
|
18
|
+
import { and, eq } from "drizzle-orm";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
|
|
21
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
22
|
+
import { nanoid, normalizeOwnerEmail } from "../server/lib/recordings.js";
|
|
23
|
+
import {
|
|
24
|
+
CLIPS_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
25
|
+
recordingSharePath,
|
|
26
|
+
} from "../shared/recording-link.js";
|
|
27
|
+
|
|
28
|
+
export const CLIPS_ACCESS_REQUEST_EMAIL_ID = "clips.access-request";
|
|
29
|
+
|
|
30
|
+
function httpError(message: string, statusCode: number): Error {
|
|
31
|
+
return Object.assign(new Error(message), { statusCode });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function displayNameForEmail(email: string): string {
|
|
35
|
+
const local = email.replace(/@.*/, "");
|
|
36
|
+
const parts = local
|
|
37
|
+
.split(/[._+-]+/)
|
|
38
|
+
.map((part) => part.trim())
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
if (parts.length === 0) return email;
|
|
41
|
+
return parts
|
|
42
|
+
.slice(0, 2)
|
|
43
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
44
|
+
.join(" ");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function absoluteRecordingUrl(recordingId: string): string {
|
|
48
|
+
return `${withConfiguredAppBasePath(getAppProductionUrl()).replace(/\/+$/, "")}${recordingSharePath(recordingId)}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function renderRecordingAccessRequestEmail(input: {
|
|
52
|
+
requesterName: string;
|
|
53
|
+
requesterEmail: string;
|
|
54
|
+
recordingTitle: string;
|
|
55
|
+
url: string;
|
|
56
|
+
}) {
|
|
57
|
+
const subject = `${input.requesterName} requested access to "${input.recordingTitle}"`;
|
|
58
|
+
return {
|
|
59
|
+
subject,
|
|
60
|
+
...renderEmail({
|
|
61
|
+
brandName: "Clips",
|
|
62
|
+
preheader: subject,
|
|
63
|
+
heading: "Access request",
|
|
64
|
+
paragraphs: [
|
|
65
|
+
`${emailStrong(input.requesterName)} (${emailStrong(input.requesterEmail)}) requested access to ${emailStrong(input.recordingTitle)}.`,
|
|
66
|
+
"Open the Clip and use Share to grant access if this request should be approved.",
|
|
67
|
+
],
|
|
68
|
+
cta: { label: "Open Clip", url: input.url },
|
|
69
|
+
footer: "You received this because you own this Clip.",
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function notifyOwner(input: {
|
|
75
|
+
recordingId: string;
|
|
76
|
+
recordingTitle: string;
|
|
77
|
+
ownerEmail: string | null;
|
|
78
|
+
requesterEmail: string;
|
|
79
|
+
requesterName: string;
|
|
80
|
+
}): Promise<boolean> {
|
|
81
|
+
if (!input.ownerEmail || input.ownerEmail === input.requesterEmail) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (!(await isEmailConfigured())) return false;
|
|
85
|
+
|
|
86
|
+
await sendEmail({
|
|
87
|
+
...renderRecordingAccessRequestEmail({
|
|
88
|
+
requesterName: input.requesterName,
|
|
89
|
+
requesterEmail: input.requesterEmail,
|
|
90
|
+
recordingTitle: input.recordingTitle,
|
|
91
|
+
url: absoluteRecordingUrl(input.recordingId),
|
|
92
|
+
}),
|
|
93
|
+
to: input.ownerEmail,
|
|
94
|
+
replyTo: input.requesterEmail,
|
|
95
|
+
templateId: CLIPS_ACCESS_REQUEST_EMAIL_ID,
|
|
96
|
+
});
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default defineAction({
|
|
101
|
+
description:
|
|
102
|
+
"Request access to a private Clips recording. Records the request and notifies the owner in-app and by email when configured.",
|
|
103
|
+
schema: z.object({
|
|
104
|
+
recordingId: z
|
|
105
|
+
.string()
|
|
106
|
+
.min(1)
|
|
107
|
+
.describe("Recording ID to request access to."),
|
|
108
|
+
accessRequestToken: z
|
|
109
|
+
.string()
|
|
110
|
+
.trim()
|
|
111
|
+
.min(1)
|
|
112
|
+
.optional()
|
|
113
|
+
.describe("Short-lived capability from the private share page."),
|
|
114
|
+
}),
|
|
115
|
+
agentTool: false,
|
|
116
|
+
run: async ({ recordingId, accessRequestToken }) => {
|
|
117
|
+
const requesterEmail = getRequestUserEmail();
|
|
118
|
+
if (!requesterEmail) {
|
|
119
|
+
throw httpError("Sign in to request access to this clip.", 401);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const normalizedRequesterEmail = normalizeOwnerEmail(requesterEmail);
|
|
123
|
+
const access = await resolveAccess("recording", recordingId, {
|
|
124
|
+
userEmail: normalizedRequesterEmail,
|
|
125
|
+
orgId: getRequestOrgId() ?? undefined,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (access) {
|
|
129
|
+
const recording = access.resource as {
|
|
130
|
+
trashedAt?: string | null;
|
|
131
|
+
};
|
|
132
|
+
if (recording.trashedAt) {
|
|
133
|
+
throw httpError(`Recording ${recordingId} not found`, 404);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
ok: true as const,
|
|
137
|
+
alreadyHasAccess: true,
|
|
138
|
+
alreadyRequested: false,
|
|
139
|
+
notifiedOwner: false,
|
|
140
|
+
message: "You already have access to this clip.",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
!accessRequestToken ||
|
|
146
|
+
!verifyScopedAgentAccessToken(accessRequestToken, {
|
|
147
|
+
resourceKind: CLIPS_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
148
|
+
resourceId: recordingId,
|
|
149
|
+
}).ok
|
|
150
|
+
) {
|
|
151
|
+
throw httpError(`Recording ${recordingId} not found`, 404);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const db = getDb();
|
|
155
|
+
const [recording] = await db
|
|
156
|
+
.select({
|
|
157
|
+
id: schema.recordings.id,
|
|
158
|
+
title: schema.recordings.title,
|
|
159
|
+
ownerEmail: schema.recordings.ownerEmail,
|
|
160
|
+
visibility: schema.recordings.visibility,
|
|
161
|
+
trashedAt: schema.recordings.trashedAt,
|
|
162
|
+
})
|
|
163
|
+
.from(schema.recordings)
|
|
164
|
+
.where(eq(schema.recordings.id, recordingId))
|
|
165
|
+
.limit(1);
|
|
166
|
+
|
|
167
|
+
if (
|
|
168
|
+
!recording ||
|
|
169
|
+
recording.trashedAt ||
|
|
170
|
+
recording.visibility === "public"
|
|
171
|
+
) {
|
|
172
|
+
throw httpError(`Recording ${recordingId} not found`, 404);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const previousRequests = await db
|
|
176
|
+
.select({ payload: schema.recordingEvents.payload })
|
|
177
|
+
.from(schema.recordingEvents)
|
|
178
|
+
.where(
|
|
179
|
+
and(
|
|
180
|
+
eq(schema.recordingEvents.recordingId, recordingId),
|
|
181
|
+
eq(schema.recordingEvents.kind, "access-request"),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
const alreadyRequested = previousRequests.some((event) => {
|
|
185
|
+
try {
|
|
186
|
+
const payload = JSON.parse(event.payload) as {
|
|
187
|
+
requesterEmail?: string;
|
|
188
|
+
};
|
|
189
|
+
return (
|
|
190
|
+
typeof payload.requesterEmail === "string" &&
|
|
191
|
+
normalizeOwnerEmail(payload.requesterEmail) ===
|
|
192
|
+
normalizedRequesterEmail
|
|
193
|
+
);
|
|
194
|
+
} catch {
|
|
195
|
+
// coercion-ok: malformed historical event payload cannot represent a matching requester.
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (alreadyRequested) {
|
|
201
|
+
return {
|
|
202
|
+
ok: true as const,
|
|
203
|
+
alreadyHasAccess: false,
|
|
204
|
+
alreadyRequested: true,
|
|
205
|
+
notifiedOwner: false,
|
|
206
|
+
message: "Your access request is already with the clip owner.",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const requesterName =
|
|
211
|
+
getRequestUserName()?.trim() ||
|
|
212
|
+
displayNameForEmail(normalizedRequesterEmail);
|
|
213
|
+
const requestedAt = new Date().toISOString();
|
|
214
|
+
await db.insert(schema.recordingEvents).values({
|
|
215
|
+
id: nanoid(),
|
|
216
|
+
recordingId,
|
|
217
|
+
viewerId: null,
|
|
218
|
+
kind: "access-request",
|
|
219
|
+
timestampMs: 0,
|
|
220
|
+
payload: JSON.stringify({
|
|
221
|
+
requesterEmail: normalizedRequesterEmail,
|
|
222
|
+
requesterName,
|
|
223
|
+
requestedAt,
|
|
224
|
+
}),
|
|
225
|
+
createdAt: requestedAt,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const ownerEmail = recording.ownerEmail
|
|
229
|
+
? normalizeOwnerEmail(recording.ownerEmail)
|
|
230
|
+
: null;
|
|
231
|
+
let notifiedOwner = false;
|
|
232
|
+
try {
|
|
233
|
+
if (ownerEmail) {
|
|
234
|
+
await notify(
|
|
235
|
+
{
|
|
236
|
+
severity: "info",
|
|
237
|
+
title: "Clip access requested",
|
|
238
|
+
body: `${requesterName} requested access to “${recording.title}”.`,
|
|
239
|
+
metadata: {
|
|
240
|
+
recordingId,
|
|
241
|
+
requesterEmail: normalizedRequesterEmail,
|
|
242
|
+
url: absoluteRecordingUrl(recordingId),
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{ owner: ownerEmail },
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
notifiedOwner = await notifyOwner({
|
|
249
|
+
recordingId,
|
|
250
|
+
recordingTitle: recording.title,
|
|
251
|
+
ownerEmail,
|
|
252
|
+
requesterEmail: normalizedRequesterEmail,
|
|
253
|
+
requesterName,
|
|
254
|
+
});
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.warn(
|
|
257
|
+
"[recording-access] access request notification failed:",
|
|
258
|
+
error,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
ok: true as const,
|
|
264
|
+
alreadyHasAccess: false,
|
|
265
|
+
alreadyRequested: false,
|
|
266
|
+
notifiedOwner,
|
|
267
|
+
message: notifiedOwner
|
|
268
|
+
? "Access request sent to the clip owner."
|
|
269
|
+
: "Access request recorded for the clip owner.",
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
});
|
|
@@ -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
|
}
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
useSession,
|
|
7
7
|
} from "@agent-native/core/client/hooks";
|
|
8
8
|
import { useT } from "@agent-native/core/client/i18n";
|
|
9
|
+
import { ShareAgentsSection } from "@agent-native/toolkit/sharing";
|
|
9
10
|
import {
|
|
10
|
-
IconChevronDown,
|
|
11
11
|
IconCode,
|
|
12
12
|
IconExternalLink,
|
|
13
13
|
IconLink,
|
|
@@ -35,11 +35,6 @@ import {
|
|
|
35
35
|
type Visibility,
|
|
36
36
|
} from "@/components/sharing/share-ui";
|
|
37
37
|
import { Button } from "@/components/ui/button";
|
|
38
|
-
import {
|
|
39
|
-
Collapsible,
|
|
40
|
-
CollapsibleContent,
|
|
41
|
-
CollapsibleTrigger,
|
|
42
|
-
} from "@/components/ui/collapsible";
|
|
43
38
|
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
|
44
39
|
import { Input } from "@/components/ui/input";
|
|
45
40
|
import { Label } from "@/components/ui/label";
|
|
@@ -52,6 +47,7 @@ import { Switch } from "@/components/ui/switch";
|
|
|
52
47
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
|
53
48
|
import { cn } from "@/lib/utils";
|
|
54
49
|
|
|
50
|
+
import { buildAgentApiUrls } from "../../../shared/agent-context";
|
|
55
51
|
import { buildEmailPreviewMarkup } from "../../../shared/email-preview";
|
|
56
52
|
import { isLoomEmbedUrl } from "../../../shared/loom";
|
|
57
53
|
import { withShareAttribution } from "../../../shared/share-attribution";
|
|
@@ -62,6 +58,14 @@ function absoluteAppUrl(path: string): string {
|
|
|
62
58
|
return new URL(appPath(path), window.location.origin).toString();
|
|
63
59
|
}
|
|
64
60
|
|
|
61
|
+
function absolutePublicAgentContextUrl(recordingId: string): string {
|
|
62
|
+
if (typeof window === "undefined") return "";
|
|
63
|
+
return buildAgentApiUrls(recordingId, {
|
|
64
|
+
origin: window.location.origin,
|
|
65
|
+
basePath: appPath("/").replace(/\/$/, ""),
|
|
66
|
+
}).contextUrl;
|
|
67
|
+
}
|
|
68
|
+
|
|
65
69
|
export interface ShareRecordingPopoverProps {
|
|
66
70
|
recordingId: string;
|
|
67
71
|
recordingTitle?: string;
|
|
@@ -375,6 +379,14 @@ function LinkTab({
|
|
|
375
379
|
const sharesLoaded = visibility !== null;
|
|
376
380
|
const visibilityPending = isPending || sharesQuery.isLoading;
|
|
377
381
|
const isLoomRecording = isLoomRecordingProp || isLoomEmbedUrl(videoUrl);
|
|
382
|
+
const needsScopedAgentContext = !isPublic || hasPassword !== false;
|
|
383
|
+
const publicAgentContextUrl = useMemo(
|
|
384
|
+
() =>
|
|
385
|
+
isPublic && hasPassword === false
|
|
386
|
+
? absolutePublicAgentContextUrl(recordingId)
|
|
387
|
+
: "",
|
|
388
|
+
[hasPassword, isPublic, recordingId],
|
|
389
|
+
);
|
|
378
390
|
const emailPreviewThumbnailUrl = useMemo(() => {
|
|
379
391
|
if (!isPublic || hasPassword !== false || !sharesLoaded) return null;
|
|
380
392
|
const variant = preferredThumbnailVariant({
|
|
@@ -438,10 +450,10 @@ function LinkTab({
|
|
|
438
450
|
try {
|
|
439
451
|
const result = (await createAgentLinkAsyncRef.current({
|
|
440
452
|
recordingId,
|
|
441
|
-
})) as {
|
|
453
|
+
})) as { contextUrl?: string };
|
|
442
454
|
if (agentLinkRequestIdRef.current !== requestId) return;
|
|
443
|
-
if (result?.
|
|
444
|
-
setAgentContextUrl(result.
|
|
455
|
+
if (result?.contextUrl) {
|
|
456
|
+
setAgentContextUrl(result.contextUrl);
|
|
445
457
|
} else {
|
|
446
458
|
setAgentLinkError(true);
|
|
447
459
|
}
|
|
@@ -457,20 +469,30 @@ function LinkTab({
|
|
|
457
469
|
setAgentLinkError(false);
|
|
458
470
|
if (!sharesLoaded) return;
|
|
459
471
|
|
|
460
|
-
if (
|
|
472
|
+
if (needsScopedAgentContext) {
|
|
461
473
|
void loadAgentContextUrl();
|
|
462
474
|
}
|
|
463
475
|
|
|
464
476
|
return () => {
|
|
465
477
|
agentLinkRequestIdRef.current += 1;
|
|
466
478
|
};
|
|
467
|
-
}, [
|
|
479
|
+
}, [
|
|
480
|
+
loadAgentContextUrl,
|
|
481
|
+
needsScopedAgentContext,
|
|
482
|
+
recordingId,
|
|
483
|
+
sharesLoaded,
|
|
484
|
+
visibility,
|
|
485
|
+
]);
|
|
468
486
|
|
|
469
|
-
const agentLink = isPublic
|
|
487
|
+
const agentLink = isPublic
|
|
488
|
+
? publicAgentContextUrl || agentContextUrl
|
|
489
|
+
: agentContextUrl;
|
|
470
490
|
const agentShareDisabled =
|
|
471
491
|
visibilityPending ||
|
|
472
492
|
!sharesLoaded ||
|
|
473
|
-
|
|
493
|
+
!agentLink ||
|
|
494
|
+
(needsScopedAgentContext &&
|
|
495
|
+
(isPending || createAgentLink.isPending || !agentContextUrl));
|
|
474
496
|
const agentPrompt = agentLink
|
|
475
497
|
? t("shareDialog.agentPrompt", { agentContextUrl: agentLink })
|
|
476
498
|
: "";
|
|
@@ -529,71 +551,49 @@ function LinkTab({
|
|
|
529
551
|
disabled={visibilityPending || !sharesLoaded}
|
|
530
552
|
/>
|
|
531
553
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
<IconChevronDown
|
|
552
|
-
aria-hidden="true"
|
|
553
|
-
className={`size-4 shrink-0 text-muted-foreground transition-transform ${agentDetailsOpen ? "rotate-180" : ""}`}
|
|
554
|
-
/>
|
|
555
|
-
</button>
|
|
556
|
-
</CollapsibleTrigger>
|
|
557
|
-
<CollapsibleContent className="clips-collapsible-content border-t border-border px-3 py-3">
|
|
558
|
-
<div className="space-y-2">
|
|
559
|
-
<CopyField
|
|
560
|
-
label={t("shareDialog.shareWithAgents")}
|
|
561
|
-
value={agentLink}
|
|
562
|
-
disabled={agentShareDisabled}
|
|
563
|
-
/>
|
|
564
|
-
{sharesLoaded ? (
|
|
565
|
-
<>
|
|
554
|
+
<ShareAgentsSection
|
|
555
|
+
label={t("shareDialog.shareWithAgents")}
|
|
556
|
+
open={agentDetailsOpen}
|
|
557
|
+
onOpenChange={setAgentDetailsOpen}
|
|
558
|
+
contentClassName="clips-collapsible-content"
|
|
559
|
+
>
|
|
560
|
+
<div className="space-y-2">
|
|
561
|
+
<CopyField
|
|
562
|
+
label={t("shareDialog.shareLink")}
|
|
563
|
+
value={agentLink}
|
|
564
|
+
disabled={agentShareDisabled}
|
|
565
|
+
/>
|
|
566
|
+
{sharesLoaded ? (
|
|
567
|
+
<>
|
|
568
|
+
<p className="text-xs text-muted-foreground">
|
|
569
|
+
{t("shareDialog.agentTokenDescription")}
|
|
570
|
+
</p>
|
|
571
|
+
{agentLinkError ? (
|
|
572
|
+
<div className="flex items-center justify-between gap-2 rounded-md border border-border bg-muted/30 px-3 py-2">
|
|
566
573
|
<p className="text-xs text-muted-foreground">
|
|
567
|
-
{t("shareDialog.
|
|
574
|
+
{t("shareDialog.agentLinkUnavailable")}
|
|
568
575
|
</p>
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
disabled={createAgentLink.isPending}
|
|
581
|
-
>
|
|
582
|
-
{t("shareDialog.retryAgentLink")}
|
|
583
|
-
</Button>
|
|
584
|
-
</div>
|
|
585
|
-
) : null}
|
|
586
|
-
<CopyField
|
|
587
|
-
label={t("shareDialog.copyAgentPrompt")}
|
|
588
|
-
value={agentPrompt}
|
|
589
|
-
disabled={agentShareDisabled}
|
|
590
|
-
/>
|
|
591
|
-
</>
|
|
576
|
+
<Button
|
|
577
|
+
type="button"
|
|
578
|
+
variant="ghost"
|
|
579
|
+
size="sm"
|
|
580
|
+
className="h-7"
|
|
581
|
+
onClick={() => void loadAgentContextUrl()}
|
|
582
|
+
disabled={createAgentLink.isPending}
|
|
583
|
+
>
|
|
584
|
+
{t("shareDialog.retryAgentLink")}
|
|
585
|
+
</Button>
|
|
586
|
+
</div>
|
|
592
587
|
) : null}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
588
|
+
<CopyField
|
|
589
|
+
label={t("shareDialog.copyAgentPrompt")}
|
|
590
|
+
value={agentPrompt}
|
|
591
|
+
disabled={agentShareDisabled}
|
|
592
|
+
/>
|
|
593
|
+
</>
|
|
594
|
+
) : null}
|
|
595
|
+
</div>
|
|
596
|
+
</ShareAgentsSection>
|
|
597
597
|
|
|
598
598
|
{showMakePublic ? (
|
|
599
599
|
<MakePublicCard
|