@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
@@ -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;
@@ -72,6 +76,13 @@ export interface ShareRecordingPopoverProps {
72
76
  animatedThumbnailUrl?: string | null;
73
77
  isLoomRecording?: boolean;
74
78
  hasPassword?: boolean;
79
+ /**
80
+ * Restricts the dialog to a bare copy-link control for viewers who can
81
+ * reshare a public/org clip's link but have no edit access: it skips
82
+ * `list-resource-shares` (which returns every individually-shared
83
+ * principal's email to any reader) and hides the Invite tab entirely.
84
+ */
85
+ viewerReshareOnly?: boolean;
75
86
  /** Trigger element rendered as the popover anchor (usually the Share button). */
76
87
  children: ReactNode;
77
88
  open?: boolean;
@@ -103,6 +114,7 @@ export function ShareRecordingPopover({
103
114
  animatedThumbnailUrl,
104
115
  isLoomRecording = false,
105
116
  hasPassword,
117
+ viewerReshareOnly = false,
106
118
  children,
107
119
  open,
108
120
  onOpenChange,
@@ -125,6 +137,7 @@ export function ShareRecordingPopover({
125
137
  animatedThumbnailUrl={animatedThumbnailUrl}
126
138
  isLoomRecording={isLoomRecording}
127
139
  hasPassword={hasPassword}
140
+ viewerReshareOnly={viewerReshareOnly}
128
141
  />
129
142
  </PopoverContent>
130
143
  </Popover>
@@ -148,6 +161,7 @@ export function ShareRecordingDialog({
148
161
  open,
149
162
  onOpenChange,
150
163
  hasPassword,
164
+ viewerReshareOnly = false,
151
165
  }: ShareRecordingDialogProps) {
152
166
  const t = useT();
153
167
  return (
@@ -168,6 +182,7 @@ export function ShareRecordingDialog({
168
182
  animatedThumbnailUrl={animatedThumbnailUrl}
169
183
  isLoomRecording={isLoomRecording}
170
184
  hasPassword={hasPassword}
185
+ viewerReshareOnly={viewerReshareOnly}
171
186
  reserveCloseButton
172
187
  />
173
188
  </DialogContent>
@@ -186,6 +201,7 @@ function ShareRecordingContent({
186
201
  isLoomRecording = false,
187
202
  reserveCloseButton = false,
188
203
  hasPassword,
204
+ viewerReshareOnly = false,
189
205
  }: {
190
206
  recordingId: string;
191
207
  recordingTitle?: string;
@@ -197,20 +213,34 @@ function ShareRecordingContent({
197
213
  isLoomRecording?: boolean;
198
214
  reserveCloseButton?: boolean;
199
215
  hasPassword?: boolean;
216
+ viewerReshareOnly?: boolean;
200
217
  }) {
201
218
  const t = useT();
202
- const sharesQuery = useActionQuery<SharesResponse>("list-resource-shares", {
203
- resourceType: "recording",
204
- resourceId: recordingId,
205
- });
219
+ const sharesQuery = useActionQuery<SharesResponse>(
220
+ "list-resource-shares",
221
+ { resourceType: "recording", resourceId: recordingId },
222
+ { enabled: !viewerReshareOnly },
223
+ );
206
224
 
207
- const data = sharesQuery.data;
225
+ const data = viewerReshareOnly ? undefined : sharesQuery.data;
208
226
  const role = data?.role ?? initialRole;
209
227
  const canManage = role === "owner" || role === "admin";
228
+ // Editors could always see (read-only) who a clip is shared with; only
229
+ // gate the Invite tab's mutation controls behind canManage. Commenters are
230
+ // grouped with plain viewers here -- neither can manage shares.
231
+ const canViewShares =
232
+ role === "owner" || role === "admin" || role === "editor";
210
233
  const visibility =
211
234
  (data?.visibility as Visibility | null | undefined) ??
212
235
  initialVisibility ??
213
236
  null;
237
+ // A plain viewer/commenter can't produce a working embed for a non-public
238
+ // clip (they have no way to make it public), so don't dangle the tab in
239
+ // front of them only to show an "ask the owner" dead end. Owner/admin/
240
+ // editor keep it regardless of visibility since they can flip to public
241
+ // from inside it.
242
+ const canEmbed = canViewShares || visibility === "public";
243
+ const tabCount = 1 + (canViewShares ? 1 : 0) + (canEmbed ? 1 : 0);
214
244
 
215
245
  // Attribution `via` must be a stable non-PII id, never an email. The only
216
246
  // owner id available client-side is the *current* session's userId, which is
@@ -234,20 +264,28 @@ function ShareRecordingContent({
234
264
  defaultValue="link"
235
265
  className={cn("min-w-0 px-4 py-3", reserveCloseButton && "pe-12")}
236
266
  >
237
- <TabsList className="grid w-full grid-cols-3">
238
- <TabsTrigger value="link" className="gap-1.5">
239
- <IconLink size={14} />
240
- {t("shareDialog.link")}
241
- </TabsTrigger>
242
- <TabsTrigger value="invite" className="gap-1.5">
243
- <IconMail size={14} />
244
- {t("shareDialog.invite")}
245
- </TabsTrigger>
246
- <TabsTrigger value="embed" className="gap-1.5">
247
- <IconCode size={14} />
248
- {t("shareDialog.embed")}
249
- </TabsTrigger>
250
- </TabsList>
267
+ {tabCount > 1 ? (
268
+ <TabsList
269
+ className={`grid w-full ${tabCount === 3 ? "grid-cols-3" : "grid-cols-2"}`}
270
+ >
271
+ <TabsTrigger value="link" className="gap-1.5">
272
+ <IconLink size={14} />
273
+ {t("shareDialog.link")}
274
+ </TabsTrigger>
275
+ {canViewShares ? (
276
+ <TabsTrigger value="invite" className="gap-1.5">
277
+ <IconMail size={14} />
278
+ {t("shareDialog.invite")}
279
+ </TabsTrigger>
280
+ ) : null}
281
+ {canEmbed ? (
282
+ <TabsTrigger value="embed" className="gap-1.5">
283
+ <IconCode size={14} />
284
+ {t("shareDialog.embed")}
285
+ </TabsTrigger>
286
+ ) : null}
287
+ </TabsList>
288
+ ) : null}
251
289
 
252
290
  <TabsContent value="link" className="mt-3">
253
291
  <LinkTab
@@ -262,34 +300,39 @@ function ShareRecordingContent({
262
300
  animatedThumbnailUrl={animatedThumbnailUrl}
263
301
  isLoomRecording={isLoomRecording}
264
302
  hasPassword={hasPassword}
303
+ canViewShares={canViewShares}
265
304
  />
266
305
  </TabsContent>
267
306
 
268
- <TabsContent value="invite" className="mt-3">
269
- <SharePeopleTab
270
- resourceType="recording"
271
- resourceId={recordingId}
272
- resourceUrl={absoluteAppUrl(`/r/${recordingId}`)}
273
- sharesQuery={sharesQuery}
274
- canManage={canManage}
275
- roleCopy={{
276
- commenter: {
277
- label: t("shareUi.recordingCommenter.label"),
278
- description: t("shareUi.recordingCommenter.description"),
279
- },
280
- }}
281
- />
282
- </TabsContent>
283
-
284
- <TabsContent value="embed" className="mt-3">
285
- <ClipsEmbedConfigurator
286
- recordingId={recordingId}
287
- sharesQuery={sharesQuery}
288
- visibility={visibility}
289
- canManage={canManage}
290
- ownerViaId={ownerViaId}
291
- />
292
- </TabsContent>
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
+ {canEmbed ? (
326
+ <TabsContent value="embed" className="mt-3">
327
+ <ClipsEmbedConfigurator
328
+ recordingId={recordingId}
329
+ sharesQuery={sharesQuery}
330
+ visibility={visibility}
331
+ canManage={canManage}
332
+ ownerViaId={ownerViaId}
333
+ />
334
+ </TabsContent>
335
+ ) : null}
293
336
  </Tabs>
294
337
  </>
295
338
  );
@@ -311,6 +354,7 @@ function LinkTab({
311
354
  animatedThumbnailUrl,
312
355
  isLoomRecording: isLoomRecordingProp,
313
356
  hasPassword,
357
+ canViewShares,
314
358
  }: {
315
359
  recordingId: string;
316
360
  recordingTitle?: string;
@@ -323,6 +367,7 @@ function LinkTab({
323
367
  animatedThumbnailUrl?: string | null;
324
368
  isLoomRecording?: boolean;
325
369
  hasPassword?: boolean;
370
+ canViewShares: boolean;
326
371
  }) {
327
372
  const t = useT();
328
373
  const { setResourceVisibility, isPending } = useResourceVisibilityMutation(
@@ -334,6 +379,14 @@ function LinkTab({
334
379
  const sharesLoaded = visibility !== null;
335
380
  const visibilityPending = isPending || sharesQuery.isLoading;
336
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
+ );
337
390
  const emailPreviewThumbnailUrl = useMemo(() => {
338
391
  if (!isPublic || hasPassword !== false || !sharesLoaded) return null;
339
392
  const variant = preferredThumbnailVariant({
@@ -397,10 +450,10 @@ function LinkTab({
397
450
  try {
398
451
  const result = (await createAgentLinkAsyncRef.current({
399
452
  recordingId,
400
- })) as { url?: string };
453
+ })) as { contextUrl?: string };
401
454
  if (agentLinkRequestIdRef.current !== requestId) return;
402
- if (result?.url) {
403
- setAgentContextUrl(result.url);
455
+ if (result?.contextUrl) {
456
+ setAgentContextUrl(result.contextUrl);
404
457
  } else {
405
458
  setAgentLinkError(true);
406
459
  }
@@ -416,20 +469,30 @@ function LinkTab({
416
469
  setAgentLinkError(false);
417
470
  if (!sharesLoaded) return;
418
471
 
419
- if (!isPublic) {
472
+ if (needsScopedAgentContext) {
420
473
  void loadAgentContextUrl();
421
474
  }
422
475
 
423
476
  return () => {
424
477
  agentLinkRequestIdRef.current += 1;
425
478
  };
426
- }, [isPublic, loadAgentContextUrl, recordingId, sharesLoaded, visibility]);
479
+ }, [
480
+ loadAgentContextUrl,
481
+ needsScopedAgentContext,
482
+ recordingId,
483
+ sharesLoaded,
484
+ visibility,
485
+ ]);
427
486
 
428
- const agentLink = isPublic ? shareUrl : agentContextUrl;
487
+ const agentLink = isPublic
488
+ ? publicAgentContextUrl || agentContextUrl
489
+ : agentContextUrl;
429
490
  const agentShareDisabled =
430
491
  visibilityPending ||
431
492
  !sharesLoaded ||
432
- (!isPublic && (isPending || createAgentLink.isPending || !agentContextUrl));
493
+ !agentLink ||
494
+ (needsScopedAgentContext &&
495
+ (isPending || createAgentLink.isPending || !agentContextUrl));
433
496
  const agentPrompt = agentLink
434
497
  ? t("shareDialog.agentPrompt", { agentContextUrl: agentLink })
435
498
  : "";
@@ -460,21 +523,23 @@ function LinkTab({
460
523
 
461
524
  return (
462
525
  <div className="space-y-4">
463
- {visibility ? (
464
- <GeneralAccessSelect
465
- visibility={visibility}
466
- canManage={canManage}
467
- isPending={visibilityPending}
468
- onChange={(next) => setResourceVisibility(next)}
469
- publicDescription={t("shareDialog.publicDescription")}
470
- showDescription={false}
471
- />
472
- ) : (
473
- <div className="space-y-2" aria-hidden>
474
- <div className="h-3 w-24 animate-pulse rounded bg-muted" />
475
- <div className="h-12 w-full animate-pulse rounded bg-muted" />
476
- </div>
477
- )}
526
+ {canViewShares ? (
527
+ visibility ? (
528
+ <GeneralAccessSelect
529
+ visibility={visibility}
530
+ canManage={canManage}
531
+ isPending={visibilityPending}
532
+ onChange={(next) => setResourceVisibility(next)}
533
+ publicDescription={t("shareDialog.publicDescription")}
534
+ showDescription={false}
535
+ />
536
+ ) : (
537
+ <div className="space-y-2" aria-hidden>
538
+ <div className="h-3 w-24 animate-pulse rounded bg-muted" />
539
+ <div className="h-12 w-full animate-pulse rounded bg-muted" />
540
+ </div>
541
+ )
542
+ ) : null}
478
543
 
479
544
  <CopyField
480
545
  label={
@@ -486,71 +551,49 @@ function LinkTab({
486
551
  disabled={visibilityPending || !sharesLoaded}
487
552
  />
488
553
 
489
- {!isPublic ? (
490
- <Collapsible
491
- open={agentDetailsOpen}
492
- onOpenChange={setAgentDetailsOpen}
493
- className="overflow-hidden rounded-md border border-border"
494
- >
495
- <CollapsibleTrigger asChild>
496
- <button
497
- type="button"
498
- 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"
499
- >
500
- <span className="flex min-w-0 items-center gap-2 truncate">
501
- <IconLink
502
- aria-hidden="true"
503
- className="size-3.5 shrink-0 text-muted-foreground"
504
- strokeWidth={1.8}
505
- />
506
- {t("shareDialog.shareWithAgents")}
507
- </span>
508
- <IconChevronDown
509
- aria-hidden="true"
510
- className={`size-4 shrink-0 text-muted-foreground transition-transform ${agentDetailsOpen ? "rotate-180" : ""}`}
511
- />
512
- </button>
513
- </CollapsibleTrigger>
514
- <CollapsibleContent className="clips-collapsible-content border-t border-border px-3 py-3">
515
- <div className="space-y-2">
516
- <CopyField
517
- label={t("shareDialog.shareWithAgents")}
518
- value={agentLink}
519
- disabled={agentShareDisabled}
520
- />
521
- {sharesLoaded ? (
522
- <>
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">
523
573
  <p className="text-xs text-muted-foreground">
524
- {t("shareDialog.agentTokenDescription")}
574
+ {t("shareDialog.agentLinkUnavailable")}
525
575
  </p>
526
- {agentLinkError ? (
527
- <div className="flex items-center justify-between gap-2 rounded-md border border-border bg-muted/30 px-3 py-2">
528
- <p className="text-xs text-muted-foreground">
529
- {t("shareDialog.agentLinkUnavailable")}
530
- </p>
531
- <Button
532
- type="button"
533
- variant="ghost"
534
- size="sm"
535
- className="h-7"
536
- onClick={() => void loadAgentContextUrl()}
537
- disabled={createAgentLink.isPending}
538
- >
539
- {t("shareDialog.retryAgentLink")}
540
- </Button>
541
- </div>
542
- ) : null}
543
- <CopyField
544
- label={t("shareDialog.copyAgentPrompt")}
545
- value={agentPrompt}
546
- disabled={agentShareDisabled}
547
- />
548
- </>
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>
549
587
  ) : null}
550
- </div>
551
- </CollapsibleContent>
552
- </Collapsible>
553
- ) : null}
588
+ <CopyField
589
+ label={t("shareDialog.copyAgentPrompt")}
590
+ value={agentPrompt}
591
+ disabled={agentShareDisabled}
592
+ />
593
+ </>
594
+ ) : null}
595
+ </div>
596
+ </ShareAgentsSection>
554
597
 
555
598
  {showMakePublic ? (
556
599
  <MakePublicCard
@@ -8,10 +8,6 @@ import {
8
8
  isLikelyPhoneMicLabel,
9
9
  type AudioInputFallback,
10
10
  } from "@shared/media-device-selection";
11
- import {
12
- createMicAudioCleanup,
13
- type MicAudioCleanupHandle,
14
- } from "@shared/mic-audio-cleanup";
15
11
  import {
16
12
  SCREEN_CAPTURE_FRAME_RATE,
17
13
  screenCaptureDisplayOptions,
@@ -509,7 +505,6 @@ export class RecorderEngine {
509
505
  private cameraComposite: CameraCompositeHandle | null = null;
510
506
  private audioMixCtx: AudioContext | null = null;
511
507
  private audioMixSources: MediaStreamAudioSourceNode[] = [];
512
- private micAudioCleanup: MicAudioCleanupHandle[] = [];
513
508
  private recorder: MediaRecorder | null = null;
514
509
  private mimeType: string = "video/webm";
515
510
 
@@ -1360,7 +1355,6 @@ export class RecorderEngine {
1360
1355
  // Full stream teardown happens in the finally block below.
1361
1356
  this.cameraLive = false;
1362
1357
  this.audioMixSources = [];
1363
- this.stopMicAudioCleanup();
1364
1358
  this.audioMixCtx?.close().catch(() => {});
1365
1359
  this.audioMixCtx = null;
1366
1360
  for (const s of [this.cameraStream, this.rawCameraStream, this.micStream]) {
@@ -1828,26 +1822,15 @@ export class RecorderEngine {
1828
1822
  this.audioMixSources = [];
1829
1823
  const dest = ctx.createMediaStreamDestination();
1830
1824
  for (const input of audioInputs) {
1831
- let track = input.track;
1832
- if (input.isMicrophone) {
1833
- const cleanup = createMicAudioCleanup(new MediaStream([track]), {
1834
- audioContext: ctx,
1835
- });
1836
- this.micAudioCleanup.push(cleanup);
1837
- track = cleanup.stream.getAudioTracks()[0] ?? track;
1838
- }
1839
- const source = ctx.createMediaStreamSource(new MediaStream([track]));
1825
+ const source = ctx.createMediaStreamSource(
1826
+ new MediaStream([input.track]),
1827
+ );
1840
1828
  source.connect(dest);
1841
1829
  this.audioMixSources.push(source);
1842
1830
  }
1843
1831
  return dest.stream.getAudioTracks()[0];
1844
1832
  }
1845
1833
 
1846
- private stopMicAudioCleanup(): void {
1847
- for (const cleanup of this.micAudioCleanup) cleanup.stop();
1848
- this.micAudioCleanup = [];
1849
- }
1850
-
1851
1834
  private buildCombinedStream(): MediaStream {
1852
1835
  if (this.opts.mode === "screen") {
1853
1836
  return this.buildDisplayRecordingStream();
@@ -2382,7 +2365,6 @@ export class RecorderEngine {
2382
2365
  // aren't mistaken for a disconnect.
2383
2366
  this.cameraLive = false;
2384
2367
  this.audioMixSources = [];
2385
- this.stopMicAudioCleanup();
2386
2368
  this.audioMixCtx?.close().catch(() => {});
2387
2369
  this.audioMixCtx = null;
2388
2370
  this.cameraComposite?.cleanup();
@@ -4,9 +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 { ShareCopyRow } from "@agent-native/toolkit/sharing";
7
8
  import {
8
- IconCheck,
9
- IconLink,
10
9
  IconLock,
11
10
  IconSend2,
12
11
  IconTrash,
@@ -281,35 +280,19 @@ export function CopyField({
281
280
  description?: string;
282
281
  }) {
283
282
  const t = useT();
284
- const [copied, setCopied] = useState(false);
285
- const copy = () => {
286
- if (disabled) return;
287
- copyToClipboard(value);
288
- setCopied(true);
289
- setTimeout(() => setCopied(false), 1400);
290
- };
291
283
  return (
292
- <div className="flex items-center justify-between gap-3">
293
- <div className="min-w-0">
294
- {label ? <div className="text-sm font-medium">{label}</div> : null}
295
- {description ? (
296
- <div className="text-xs text-muted-foreground">{description}</div>
297
- ) : null}
298
- </div>
299
- <div aria-live="polite" className="shrink-0">
300
- <Button
301
- type="button"
302
- variant="outline"
303
- size="sm"
304
- onClick={copy}
305
- disabled={disabled}
306
- className="h-9"
307
- >
308
- {copied ? <IconCheck size={14} /> : <IconLink size={14} />}
309
- {copied ? t("recordRoute.linkCopied") : t("shareUi.copy")}
310
- </Button>
311
- </div>
312
- </div>
284
+ <ShareCopyRow
285
+ label={label}
286
+ value={value}
287
+ description={description}
288
+ disabled={disabled}
289
+ copyLabel={t("shareUi.copy")}
290
+ copiedLabel={t("recordRoute.linkCopied")}
291
+ onCopy={(nextValue) => {
292
+ copyToClipboard(nextValue);
293
+ return true;
294
+ }}
295
+ />
313
296
  );
314
297
  }
315
298
 
@@ -256,6 +256,16 @@ const messages = {
256
256
  passwordProtected: "This clip is password-protected",
257
257
  linkExpired: "Link expired",
258
258
  linkExpiredMessage: "The creator set an expiry on this share link.",
259
+ privateClip: "Private clip",
260
+ privateClipMessage:
261
+ "This clip is private. Request access and the owner will be notified.",
262
+ privateClipSignedOutMessage:
263
+ "This clip is private. Sign in to request access from the owner.",
264
+ requestAccess: "Request access",
265
+ requestingAccess: "Requesting access...",
266
+ accessRequested: "Access requested",
267
+ accessRequestSent: "The clip owner was notified.",
268
+ accessRequestFailed: "Couldn't request access. Try again.",
259
269
  clipUnavailable: "Clip unavailable",
260
270
  clipUnavailableMessage:
261
271
  "This recording isn't public, or the link is invalid. If it's your clip, sign in to check access.",
@@ -5,6 +5,7 @@ import {
5
5
  useActionQuery,
6
6
  } from "@agent-native/core/client/hooks";
7
7
  import { useT } from "@agent-native/core/client/i18n";
8
+ import { ShareTrigger } from "@agent-native/toolkit/sharing";
8
9
  import {
9
10
  IconArrowLeft,
10
11
  IconCheck,
@@ -18,7 +19,6 @@ import {
18
19
  IconNotes,
19
20
  IconPlayerStop,
20
21
  IconRefresh,
21
- IconShare3,
22
22
  IconTrash,
23
23
  IconUsers,
24
24
  } from "@tabler/icons-react";
@@ -711,10 +711,10 @@ export default function MeetingDetailRoute() {
711
711
  Boolean(data?.transcript?.fullText?.trim()))
712
712
  }
713
713
  >
714
- <Button size="sm" className="shrink-0 gap-1.5">
715
- <IconShare3 className="h-4 w-4" />
716
- {t("meetingDetail.share")}
717
- </Button>
714
+ <ShareTrigger
715
+ label={t("meetingDetail.share")}
716
+ className="shrink-0"
717
+ />
718
718
  </ShareMeetingPopover>
719
719
  {canEdit && (
720
720
  <AlertDialog open={deleteOpen} onOpenChange={setDeleteOpen}>