@agent-native/core 0.155.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.
Files changed (192) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +19 -4
  3. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +7 -2
  4. package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +3 -1
  5. package/corpus/templates/assets/app/global.css +4 -0
  6. package/corpus/templates/assets/app/i18n-data.ts +21 -1
  7. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +18 -15
  8. package/corpus/templates/brain/app/routes/sources.tsx +1 -2
  9. package/corpus/templates/calendar/app/hooks/use-bookings.ts +12 -2
  10. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +64 -2
  11. package/corpus/templates/calendar/server/handlers/bookings.ts +219 -93
  12. package/corpus/templates/calendar/server/plugins/auth.ts +0 -2
  13. package/corpus/templates/clips/actions/create-folder.ts +7 -1
  14. package/corpus/templates/clips/actions/create-meeting.ts +6 -2
  15. package/corpus/templates/clips/actions/create-recording.ts +5 -3
  16. package/corpus/templates/clips/actions/finalize-recording.ts +97 -65
  17. package/corpus/templates/clips/actions/import-loom-recording.ts +5 -3
  18. package/corpus/templates/clips/actions/request-recording-access.ts +272 -0
  19. package/corpus/templates/clips/actions/stitch-recordings.ts +5 -3
  20. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +38 -63
  21. package/corpus/templates/clips/app/components/player/share-dialog.tsx +177 -134
  22. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +3 -21
  23. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +13 -30
  24. package/corpus/templates/clips/app/i18n/en-US.ts +10 -0
  25. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -5
  26. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +24 -17
  27. package/corpus/templates/clips/app/routes/share.$shareId.tsx +151 -20
  28. package/corpus/templates/clips/changelog/2026-08-14-long-resumable-uploads-now-abort-cleanly-and-show-errors.md +6 -0
  29. package/corpus/templates/clips/changelog/2026-08-14-standardized-share-popovers.md +6 -0
  30. package/corpus/templates/clips/chrome-extension/public/manifest.json +11 -3
  31. package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +37 -8
  32. package/corpus/templates/clips/chrome-extension/src/github-preview.ts +3 -3
  33. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +8 -25
  34. package/corpus/templates/clips/desktop/src/app.tsx +24 -0
  35. package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +42 -13
  36. package/corpus/templates/clips/desktop/src/lib/recorder.ts +11 -18
  37. package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +1 -0
  38. package/corpus/templates/clips/desktop/src/shared/config.ts +1 -0
  39. package/corpus/templates/clips/desktop/src/styles.css +24 -9
  40. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -0
  41. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +8 -0
  42. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +154 -176
  43. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +115 -31
  44. package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +7 -0
  45. package/corpus/templates/clips/server/db/index.ts +1 -0
  46. package/corpus/templates/clips/server/db/schema.ts +1 -0
  47. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +6 -1
  48. package/corpus/templates/clips/server/lib/emails.ts +23 -0
  49. package/corpus/templates/clips/server/lib/public-agent-context.ts +55 -0
  50. package/corpus/templates/clips/server/lib/recordings.ts +2 -1
  51. package/corpus/templates/clips/server/lib/resumable-upload-cleanup.ts +49 -0
  52. package/corpus/templates/clips/server/lib/s3-upload-provider.ts +34 -7
  53. package/corpus/templates/clips/server/lib/upload-interruption.ts +15 -1
  54. package/corpus/templates/clips/server/lib/upload-lease.ts +94 -9
  55. package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +82 -7
  56. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
  57. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +71 -12
  58. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +4 -3
  59. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +19 -0
  60. package/corpus/templates/clips/server/routes/api/uploads/reap.post.ts +8 -1
  61. package/corpus/templates/clips/shared/recording-link.ts +4 -0
  62. package/corpus/templates/clips/shared/share-loader-response.ts +2 -1
  63. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +7 -16
  64. package/corpus/templates/factory/AGENTS.md +5 -1
  65. package/corpus/templates/factory/README.md +6 -2
  66. package/corpus/templates/factory/actions/view-screen.ts +15 -0
  67. package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +58 -189
  68. package/corpus/templates/factory/app/components/layout/Header.tsx +1 -0
  69. package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
  70. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +7 -0
  71. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -0
  72. package/corpus/templates/factory/app/i18n/en-US.ts +3 -1
  73. package/corpus/templates/factory/app/routes/agents.tsx +9 -0
  74. package/corpus/templates/mail/server/plugins/auth.ts +0 -2
  75. package/corpus/templates/plan/app/components/plan/LocalCodebasePicker.tsx +50 -14
  76. package/corpus/templates/plan/app/pages/PlansPage.tsx +9 -19
  77. package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +98 -29
  78. package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +45 -91
  79. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +94 -2
  80. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +17 -29
  81. package/corpus/templates/slides/app/components/editor/HistoryPanel.tsx +7 -1
  82. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +1 -1
  83. package/corpus/templates/slides/app/context/DeckContext.tsx +108 -19
  84. package/corpus/templates/slides/app/global.css +7 -40
  85. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  86. package/corpus/templates/slides/app/lib/editor-shortcuts.ts +46 -0
  87. package/corpus/templates/slides/app/pages/DeckEditor.tsx +143 -45
  88. package/dist/agent/index.d.ts +1 -1
  89. package/dist/agent/production-agent.d.ts +30 -0
  90. package/dist/agent/production-agent.js +100 -8
  91. package/dist/agent/types.d.ts +9 -0
  92. package/dist/client/AgentPanel.d.ts +13 -1
  93. package/dist/client/AgentPanel.js +22 -17
  94. package/dist/client/AssistantChat.d.ts +15 -1
  95. package/dist/client/AssistantChat.js +135 -48
  96. package/dist/client/HighlightedCodeBlock.js +12 -2
  97. package/dist/client/MultiTabAssistantChat.js +44 -27
  98. package/dist/client/agent-chat/index.d.ts +1 -0
  99. package/dist/client/agent-chat/index.js +1 -0
  100. package/dist/client/chat/markdown-renderer.js +20 -5
  101. package/dist/client/chat/message-components.d.ts +2 -2
  102. package/dist/client/chat/message-components.js +69 -35
  103. package/dist/client/chat/run-recovery.d.ts +4 -1
  104. package/dist/client/chat/run-recovery.js +62 -30
  105. package/dist/client/chat/tool-call-display.d.ts +12 -3
  106. package/dist/client/chat/tool-call-display.js +79 -40
  107. package/dist/client/chat/widgets/DataChartRenderer.js +3 -1
  108. package/dist/client/chat/widgets/DataChartWidget.js +8 -2
  109. package/dist/client/chat/widgets/DataInsightsWidget.js +3 -1
  110. package/dist/client/chat/widgets/DataTableWidget.js +23 -9
  111. package/dist/client/chat/widgets/builtin-tool-renderers.js +3 -1
  112. package/dist/client/chat-first/apps-rail.js +1 -1
  113. package/dist/client/components/ui/message-scroller.d.ts +1 -1
  114. package/dist/client/components/ui/message-scroller.js +7 -3
  115. package/dist/client/composer/runtime-adapters.js +3 -2
  116. package/dist/client/context-xray/ContextMeter.js +5 -1
  117. package/dist/client/context-xray/ContextXRayPanel.js +5 -5
  118. package/dist/client/error-format.d.ts +4 -0
  119. package/dist/client/error-format.js +93 -5
  120. package/dist/client/i18n.js +83 -17
  121. package/dist/client/observability/ThumbsFeedback.js +10 -8
  122. package/dist/client/settings/AccountSettingsCard.js +48 -19
  123. package/dist/client/sharing/ShareButton.d.ts +1 -2
  124. package/dist/client/sharing/ShareButton.js +151 -81
  125. package/dist/client/sharing/ShareDialog.js +5 -4
  126. package/dist/client/use-chat-threads.js +10 -14
  127. package/dist/coding-tools/run-code.js +2 -1
  128. package/dist/coding-tools/sandbox/background.d.ts +1 -0
  129. package/dist/coding-tools/sandbox/background.js +8 -1
  130. package/dist/coding-tools/sandbox/executions-store.d.ts +2 -0
  131. package/dist/coding-tools/sandbox/executions-store.js +41 -3
  132. package/dist/collab/routes.d.ts +1 -1
  133. package/dist/collab/struct-routes.d.ts +1 -1
  134. package/dist/index.d.ts +1 -1
  135. package/dist/localization/core-messages/ar-SA.d.ts +3 -0
  136. package/dist/localization/core-messages/ar-SA.js +541 -0
  137. package/dist/localization/core-messages/de-DE.d.ts +3 -0
  138. package/dist/localization/core-messages/de-DE.js +513 -0
  139. package/dist/localization/core-messages/en-US.d.ts +513 -0
  140. package/dist/localization/core-messages/en-US.js +513 -0
  141. package/dist/localization/core-messages/es-ES.d.ts +3 -0
  142. package/dist/localization/core-messages/es-ES.js +520 -0
  143. package/dist/localization/core-messages/fr-FR.d.ts +3 -0
  144. package/dist/localization/core-messages/fr-FR.js +520 -0
  145. package/dist/localization/core-messages/hi-IN.d.ts +3 -0
  146. package/dist/localization/core-messages/hi-IN.js +513 -0
  147. package/dist/localization/core-messages/ja-JP.d.ts +3 -0
  148. package/dist/localization/core-messages/ja-JP.js +506 -0
  149. package/dist/localization/core-messages/ko-KR.d.ts +3 -0
  150. package/dist/localization/core-messages/ko-KR.js +506 -0
  151. package/dist/localization/core-messages/pt-BR.d.ts +3 -0
  152. package/dist/localization/core-messages/pt-BR.js +520 -0
  153. package/dist/localization/core-messages/zh-CN.d.ts +3 -0
  154. package/dist/localization/core-messages/zh-CN.js +506 -0
  155. package/dist/localization/core-messages/zh-TW.d.ts +3 -0
  156. package/dist/localization/core-messages/zh-TW.js +506 -0
  157. package/dist/localization/core-messages.d.ts +524 -0
  158. package/dist/localization/core-messages.js +124 -0
  159. package/dist/localization/default-messages.d.ts +3 -0
  160. package/dist/localization/default-messages.js +3 -0
  161. package/dist/notifications/routes.d.ts +1 -1
  162. package/dist/observability/routes.d.ts +5 -5
  163. package/dist/progress/routes.d.ts +1 -1
  164. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  165. package/dist/provider-api/actions/provider-api.d.ts +15 -15
  166. package/dist/resources/handlers.d.ts +1 -1
  167. package/dist/secrets/routes.d.ts +9 -9
  168. package/dist/server/agent-chat/plugin-options.d.ts +7 -2
  169. package/dist/server/agent-chat-plugin.d.ts +10 -0
  170. package/dist/server/agent-chat-plugin.js +144 -41
  171. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  172. package/dist/server/agent-run-context.d.ts +7 -1
  173. package/dist/server/agent-run-context.js +9 -2
  174. package/dist/server/agent-teams-run-queue.d.ts +3 -0
  175. package/dist/server/agent-teams.js +38 -9
  176. package/dist/server/auth-marketing.d.ts +1 -0
  177. package/dist/server/auth-marketing.js +0 -2
  178. package/dist/server/auth.d.ts +1 -0
  179. package/dist/server/auth.js +18 -0
  180. package/dist/server/index.d.ts +1 -1
  181. package/dist/server/onboarding-html.d.ts +1 -0
  182. package/dist/server/onboarding-html.js +2 -88
  183. package/dist/server/realtime-token.d.ts +1 -1
  184. package/dist/server/request-context.d.ts +7 -0
  185. package/dist/server/request-context.js +15 -0
  186. package/dist/shared/markdown-block-split.d.ts +16 -0
  187. package/dist/shared/markdown-block-split.js +81 -5
  188. package/dist/styles/agent-native.css +13 -0
  189. package/dist/voice/voice-cleanup-prompt.js +1 -0
  190. package/docs/content/agent-surfaces.mdx +30 -0
  191. package/package.json +2 -2
  192. 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
- await getDefaultRecordingVisibility(organizationId);
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
- await getDefaultRecordingVisibility(organizationId);
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 { IconChevronDown, IconLink, IconMail } from "@tabler/icons-react";
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
- {!isPublic ? (
305
- <Collapsible
306
- open={agentDetailsOpen}
307
- onOpenChange={setAgentDetailsOpen}
308
- className="overflow-hidden rounded-md border border-border"
309
- >
310
- <CollapsibleTrigger asChild>
311
- <button
312
- type="button"
313
- className="flex min-h-10 w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm font-medium transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
314
- >
315
- <span className="flex min-w-0 items-center gap-2 truncate">
316
- <IconLink
317
- aria-hidden="true"
318
- className="size-3.5 shrink-0 text-muted-foreground"
319
- strokeWidth={1.8}
320
- />
321
- {t("shareDialog.shareWithAgents")}
322
- </span>
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("shareMeeting.agentLinkDescription")}
321
+ {t("shareDialog.agentLinkUnavailable")}
340
322
  </p>
341
- {agentLinkError ? (
342
- <div className="flex items-center justify-between gap-2 rounded-md border border-border bg-muted/30 px-3 py-2">
343
- <p className="text-xs text-muted-foreground">
344
- {t("shareDialog.agentLinkUnavailable")}
345
- </p>
346
- <Button
347
- type="button"
348
- variant="ghost"
349
- size="sm"
350
- className="h-7"
351
- onClick={() => void loadAgentLink()}
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
- </div>
361
- </CollapsibleContent>
362
- </Collapsible>
363
- ) : null}
335
+ </>
336
+ ) : null}
337
+ </div>
338
+ </ShareAgentsSection>
364
339
  </div>
365
340
  );
366
341
  }