@agent-native/core 0.154.5 → 0.155.0

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 (185) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/account-health/SKILL.md +76 -0
  3. package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +3 -0
  4. package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +2 -1
  5. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +6 -5
  6. package/corpus/templates/analytics/AGENTS.md +3 -0
  7. package/corpus/templates/analytics/actions/account-deep-dive.ts +3 -2
  8. package/corpus/templates/analytics/actions/bigquery.ts +1 -1
  9. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -1
  10. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
  11. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyCombobox.tsx +12 -4
  12. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyValueCombobox.tsx +5 -2
  13. package/corpus/templates/analytics/app/pages/adhoc/explorer/property-label.ts +32 -0
  14. package/corpus/templates/analytics/changelog/2026-08-13-account-health-readouts-verify-customer-scope-and-contract-metrics.md +7 -0
  15. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +43 -17
  16. package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +17 -0
  17. package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
  18. package/corpus/templates/calendar/AGENTS.md +2 -0
  19. package/corpus/templates/calendar/DEVELOPING.md +4 -4
  20. package/corpus/templates/calendar/actions/get-settings.ts +4 -11
  21. package/corpus/templates/calendar/actions/update-availability.ts +15 -4
  22. package/corpus/templates/calendar/actions/update-settings.ts +15 -4
  23. package/corpus/templates/calendar/app/components/booking/DatePicker.tsx +9 -3
  24. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +21 -19
  25. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +9 -6
  26. package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +25 -3
  27. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +10 -3
  28. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +9 -9
  29. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -3
  30. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +10 -4
  31. package/corpus/templates/calendar/app/i18n-data.ts +30 -0
  32. package/corpus/templates/calendar/app/lib/availability-schedule.ts +53 -0
  33. package/corpus/templates/calendar/app/lib/event-form-utils.ts +5 -0
  34. package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +90 -28
  35. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +135 -61
  36. package/corpus/templates/calendar/app/pages/BookingPage.tsx +2 -0
  37. package/corpus/templates/calendar/app/pages/CalendarView.tsx +30 -15
  38. package/corpus/templates/calendar/app/pages/Settings.tsx +60 -1
  39. package/corpus/templates/calendar/changelog/2026-08-13-find-a-time-now-closes-from-a-clear-header-action.md +6 -0
  40. package/corpus/templates/calendar/server/handlers/bookings.ts +2 -1
  41. package/corpus/templates/calendar/server/handlers/settings.ts +12 -16
  42. package/corpus/templates/calendar/shared/api.ts +1 -0
  43. package/corpus/templates/calendar/shared/availability-schedule.ts +64 -0
  44. package/corpus/templates/calendar/shared/calendar-week.ts +21 -0
  45. package/corpus/templates/calendar/shared/settings.ts +44 -0
  46. package/corpus/templates/clips/AGENTS.md +2 -0
  47. package/corpus/templates/clips/actions/add-comment.ts +5 -2
  48. package/corpus/templates/clips/actions/finalize-recording.ts +31 -1
  49. package/corpus/templates/clips/actions/get-recording-player-data.ts +9 -0
  50. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +21 -1
  51. package/corpus/templates/clips/actions/reply-to-comment.ts +5 -2
  52. package/corpus/templates/clips/actions/update-comment.ts +9 -2
  53. package/corpus/templates/clips/app/components/library/search-bar.tsx +26 -13
  54. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +135 -3
  55. package/corpus/templates/clips/app/components/player/comments-panel.tsx +5 -99
  56. package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +7 -3
  57. package/corpus/templates/clips/app/components/player/video-player.tsx +84 -24
  58. package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +11 -3
  59. package/corpus/templates/clips/app/i18n/en-US.ts +12 -0
  60. package/corpus/templates/clips/app/lib/public-meeting.ts +11 -3
  61. package/corpus/templates/clips/app/lib/search-focus.ts +15 -0
  62. package/corpus/templates/clips/app/root.tsx +2 -1
  63. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +94 -26
  64. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +24 -7
  65. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +8 -4
  66. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +14 -7
  67. package/corpus/templates/clips/app/routes/share.$shareId.tsx +8 -4
  68. package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +72 -26
  69. package/corpus/templates/clips/changelog/2026-08-13-inline-markdown-comments.md +6 -0
  70. package/corpus/templates/clips/changelog/2026-08-13-private-meeting-agent-links.md +6 -0
  71. package/corpus/templates/clips/desktop/src/app.tsx +116 -19
  72. package/corpus/templates/clips/desktop/src/lib/recorder.ts +13 -29
  73. package/corpus/templates/clips/desktop/src/lib/recording-request.ts +39 -0
  74. package/corpus/templates/clips/desktop/src/lib/settings-navigation.ts +11 -0
  75. package/corpus/templates/clips/desktop/src/lib/whisper-model-picker.ts +7 -0
  76. package/corpus/templates/clips/desktop/src/styles.css +117 -0
  77. package/corpus/templates/clips/server/db/index.ts +10 -0
  78. package/corpus/templates/clips/server/db/schema.ts +1 -0
  79. package/corpus/templates/clips/server/lib/recordings.ts +3 -1
  80. package/corpus/templates/clips/server/lib/seekable-media-state.ts +90 -0
  81. package/corpus/templates/clips/server/plugins/db.ts +5 -0
  82. package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +37 -4
  83. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +16 -5
  84. package/corpus/templates/clips/shared/clips-ai-prefs.ts +3 -0
  85. package/corpus/templates/clips/shared/meeting-agent-access.ts +5 -0
  86. package/corpus/templates/content/actions/add-comment.ts +2 -1
  87. package/corpus/templates/content/actions/update-comment.ts +1 -1
  88. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +24 -15
  89. package/corpus/templates/design/actions/generate-design.ts +10 -7
  90. package/corpus/templates/design/actions/present-design-variants.ts +35 -6
  91. package/corpus/templates/design/actions/run-design-audit.ts +33 -0
  92. package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-catches-a-screen-that-renders-behind-an.md +6 -0
  93. package/corpus/templates/design/shared/design-review.ts +1 -0
  94. package/corpus/templates/design/shared/html-integrity.ts +66 -0
  95. package/corpus/templates/dispatch/.env.example +5 -0
  96. package/corpus/templates/dispatch/AGENTS.md +24 -19
  97. package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
  98. package/corpus/templates/dispatch/server/lib/identity-sso.ts +21 -90
  99. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +3 -12
  100. package/corpus/templates/dispatch/shared/feature-flags.ts +4 -0
  101. package/corpus/templates/factory/AGENTS.md +5 -1
  102. package/corpus/templates/factory/README.md +23 -0
  103. package/corpus/templates/factory/actions/view-screen.ts +17 -0
  104. package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +239 -0
  105. package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +2 -0
  106. package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +130 -7
  107. package/corpus/templates/factory/app/i18n/en-US.ts +20 -0
  108. package/corpus/templates/factory/app/routes/factory.tsx +41 -0
  109. package/corpus/templates/factory/package.json +1 -0
  110. package/corpus/templates/factory/server/factory-graph/contracts.ts +2 -0
  111. package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -0
  112. package/corpus/templates/factory/server/plugins/dispatch-db.ts +1 -0
  113. package/corpus/templates/factory/server/plugins/setup-dispatch.ts +3 -0
  114. package/corpus/templates/mail/app/lib/native-mcp-exclusions.ts +3 -0
  115. package/corpus/templates/mail/vite.config.ts +7 -0
  116. package/corpus/templates/plan/actions/reply-to-plan-comment.ts +7 -2
  117. package/corpus/templates/plan/actions/resolve-plan-comment.ts +2 -2
  118. package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
  119. package/corpus/templates/plan/app/pages/PlansPage.tsx +29 -20
  120. package/corpus/templates/slides/actions/add-slide-comment.ts +1 -1
  121. package/corpus/templates/slides/actions/update-slide-comment.ts +1 -1
  122. package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +9 -6
  123. package/dist/agent/production-agent.js +5 -1
  124. package/dist/agent/thread-data-builder.js +17 -1
  125. package/dist/cli/skills-content/help.js +6 -4
  126. package/dist/cli/skills.js +17 -2
  127. package/dist/cli/workspacify.d.ts +5 -0
  128. package/dist/cli/workspacify.js +65 -8
  129. package/dist/client/AgentPanel.d.ts +5 -0
  130. package/dist/client/AgentPanel.js +20 -4
  131. package/dist/client/AssistantChat.d.ts +21 -0
  132. package/dist/client/AssistantChat.js +66 -68
  133. package/dist/client/active-run-state.d.ts +14 -1
  134. package/dist/client/active-run-state.js +0 -0
  135. package/dist/client/agent-chat-adapter.js +107 -5
  136. package/dist/client/analytics.js +9 -2
  137. package/dist/client/chat/tool-call-display.js +4 -4
  138. package/dist/client/chat-first/apps-rail.js +9 -4
  139. package/dist/client/chat-first/primary-nav.d.ts +2 -1
  140. package/dist/client/chat-first/primary-nav.js +3 -3
  141. package/dist/client/chat-first/types.d.ts +5 -1
  142. package/dist/client/markdown/InlineMarkdown.d.ts +23 -0
  143. package/dist/client/markdown/InlineMarkdown.js +65 -0
  144. package/dist/client/markdown/index.d.ts +1 -0
  145. package/dist/client/markdown/index.js +1 -0
  146. package/dist/client/review/ReviewThreadPanel.js +2 -1
  147. package/dist/client/run-stream-ownership.d.ts +39 -0
  148. package/dist/client/run-stream-ownership.js +65 -0
  149. package/dist/collab/awareness.d.ts +2 -2
  150. package/dist/collab/struct-routes.d.ts +1 -1
  151. package/dist/feature-flags/index.d.ts +1 -1
  152. package/dist/feature-flags/index.js +1 -1
  153. package/dist/feature-flags/store.d.ts +10 -0
  154. package/dist/feature-flags/store.js +112 -1
  155. package/dist/guards/no-env-credentials.js +4 -0
  156. package/dist/index.d.ts +1 -1
  157. package/dist/index.js +1 -1
  158. package/dist/integrations/adapters/slack.js +5 -1
  159. package/dist/localization/default-messages.d.ts +1 -0
  160. package/dist/localization/default-messages.js +1 -0
  161. package/dist/observability/routes.d.ts +6 -6
  162. package/dist/progress/routes.d.ts +1 -1
  163. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  164. package/dist/provider-api/actions/provider-api.d.ts +15 -15
  165. package/dist/resources/agents.js +40 -5
  166. package/dist/resources/handlers.d.ts +1 -1
  167. package/dist/resources/metadata.d.ts +10 -0
  168. package/dist/resources/metadata.js +7 -3
  169. package/dist/review/actions/create-review-comment.js +1 -1
  170. package/dist/review/actions/reply-review-comment.js +1 -1
  171. package/dist/review/actions/resolve-review-thread.js +1 -1
  172. package/dist/secrets/routes.d.ts +3 -3
  173. package/dist/server/action-routes.js +32 -6
  174. package/dist/server/agent-discovery.js +14 -3
  175. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  176. package/dist/server/core-routes-plugin.js +1 -1
  177. package/dist/server/identity-sso-migrations.d.ts +7 -0
  178. package/dist/server/identity-sso-migrations.js +36 -0
  179. package/dist/server/identity-sso-store.d.ts +3 -2
  180. package/dist/server/identity-sso-store.js +8 -3
  181. package/dist/server/realtime-token.d.ts +1 -1
  182. package/dist/server/release-migrations.js +4 -0
  183. package/dist/tracking/providers.js +13 -3
  184. package/dist/workspace-files/tool.js +1 -1
  185. package/package.json +3 -2
@@ -4,8 +4,15 @@ import {
4
4
  useActionQuery,
5
5
  } from "@agent-native/core/client/hooks";
6
6
  import { useT } from "@agent-native/core/client/i18n";
7
- import { IconLink, IconMail } from "@tabler/icons-react";
8
- import { useEffect, useMemo, useState, type ReactNode } from "react";
7
+ import { IconChevronDown, IconLink, IconMail } from "@tabler/icons-react";
8
+ import {
9
+ useCallback,
10
+ useEffect,
11
+ useMemo,
12
+ useRef,
13
+ useState,
14
+ type ReactNode,
15
+ } from "react";
9
16
  import { toast } from "sonner";
10
17
 
11
18
  import {
@@ -19,6 +26,12 @@ import {
19
26
  type SharesResponse,
20
27
  type Visibility,
21
28
  } from "@/components/sharing/share-ui";
29
+ import { Button } from "@/components/ui/button";
30
+ import {
31
+ Collapsible,
32
+ CollapsibleContent,
33
+ CollapsibleTrigger,
34
+ } from "@/components/ui/collapsible";
22
35
  import {
23
36
  Popover,
24
37
  PopoverContent,
@@ -157,6 +170,56 @@ function LinkTab({
157
170
  const visibility: Visibility =
158
171
  (data?.visibility as Visibility | null) ?? "private";
159
172
  const isPublic = visibility === "public";
173
+ const sharesLoaded = data?.visibility != null;
174
+ const createAgentLink = useActionMutation("create-agent-resource-link");
175
+ const createAgentLinkAsyncRef = useRef(createAgentLink.mutateAsync);
176
+ const agentLinkRequestIdRef = useRef(0);
177
+ const [agentLink, setAgentLink] = useState("");
178
+ const [agentLinkError, setAgentLinkError] = useState(false);
179
+ const [agentDetailsOpen, setAgentDetailsOpen] = useState(false);
180
+
181
+ useEffect(() => {
182
+ createAgentLinkAsyncRef.current = createAgentLink.mutateAsync;
183
+ });
184
+
185
+ const loadAgentLink = useCallback(async () => {
186
+ const requestId = agentLinkRequestIdRef.current + 1;
187
+ agentLinkRequestIdRef.current = requestId;
188
+ setAgentLink("");
189
+ setAgentLinkError(false);
190
+
191
+ try {
192
+ const result = (await createAgentLinkAsyncRef.current({
193
+ resourceType: "meeting",
194
+ resourceId: meetingId,
195
+ })) as { contextUrl?: string };
196
+ if (agentLinkRequestIdRef.current !== requestId) return;
197
+ if (result?.contextUrl) setAgentLink(result.contextUrl);
198
+ else setAgentLinkError(true);
199
+ } catch {
200
+ if (agentLinkRequestIdRef.current === requestId) setAgentLinkError(true);
201
+ }
202
+ }, [meetingId]);
203
+
204
+ useEffect(() => {
205
+ setAgentLink("");
206
+ setAgentLinkError(false);
207
+ if (!sharesLoaded || isPublic) return;
208
+ void loadAgentLink();
209
+
210
+ return () => {
211
+ agentLinkRequestIdRef.current += 1;
212
+ };
213
+ }, [isPublic, loadAgentLink, meetingId, sharesLoaded, visibility]);
214
+
215
+ useEffect(() => {
216
+ if (isPublic) setAgentDetailsOpen(false);
217
+ }, [isPublic]);
218
+
219
+ const agentShareDisabled =
220
+ sharesQuery.isLoading ||
221
+ !sharesLoaded ||
222
+ (!isPublic && (createAgentLink.isPending || !agentLink));
160
223
 
161
224
  useEffect(() => {
162
225
  setIncludeTranscript(shareTranscript);
@@ -217,7 +280,15 @@ function LinkTab({
217
280
  />
218
281
  </div>
219
282
 
220
- <CopyField label={t("clipsFinalRaw.shareLink")} value={shareUrl} />
283
+ <CopyField
284
+ label={
285
+ isPublic
286
+ ? t("clipsFinalRaw.shareLink")
287
+ : t("shareDialog.shareWithHumans")
288
+ }
289
+ value={shareUrl}
290
+ disabled={!sharesLoaded}
291
+ />
221
292
 
222
293
  {!isPublic && canManage ? (
223
294
  <MakePublicCard
@@ -229,6 +300,67 @@ function LinkTab({
229
300
  }
230
301
  />
231
302
  ) : null}
303
+
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
+ <>
338
+ <p className="text-xs text-muted-foreground">
339
+ {t("shareMeeting.agentLinkDescription")}
340
+ </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
+ </>
359
+ ) : null}
360
+ </div>
361
+ </CollapsibleContent>
362
+ </Collapsible>
363
+ ) : null}
232
364
  </div>
233
365
  );
234
366
  }
@@ -3,6 +3,7 @@ import {
3
3
  useAvatarUrl,
4
4
  } from "@agent-native/core/client/hooks";
5
5
  import { useT } from "@agent-native/core/client/i18n";
6
+ import { InlineMarkdown } from "@agent-native/core/client/markdown";
6
7
  import {
7
8
  IconSend,
8
9
  IconCheck,
@@ -895,7 +896,6 @@ function CommentCard({
895
896
  return updatedReactions;
896
897
  }
897
898
 
898
- const commentContent = linkifyCommentContent(comment.content);
899
899
  const avatarUrl = useAvatarUrl(comment.authorEmail);
900
900
 
901
901
  return (
@@ -940,9 +940,10 @@ function CommentCard({
940
940
  />
941
941
  ) : (
942
942
  <>
943
- <p className="text-sm text-foreground whitespace-pre-wrap break-words mt-0.5">
944
- {commentContent}
945
- </p>
943
+ <InlineMarkdown
944
+ content={comment.content}
945
+ className="mt-0.5 text-sm text-foreground"
946
+ />
946
947
 
947
948
  <div className="flex items-center gap-2 mt-1.5 text-xs text-muted-foreground">
948
949
  {canComment ? (
@@ -1070,101 +1071,6 @@ function CommentCard({
1070
1071
  );
1071
1072
  }
1072
1073
 
1073
- const COMMENT_URL_PATTERN = /\b(?:https?:\/\/|www\.)[^\s<]+/gi;
1074
- const ALWAYS_TRAILING_PUNCTUATION = new Set([
1075
- ".",
1076
- ",",
1077
- "!",
1078
- "?",
1079
- ";",
1080
- ":",
1081
- "'",
1082
- '"',
1083
- ]);
1084
- const PAIRED_TRAILING_PUNCTUATION: Record<string, string> = {
1085
- ")": "(",
1086
- "]": "[",
1087
- "}": "{",
1088
- };
1089
-
1090
- function trimTrailingUrlPunctuation(value: string): {
1091
- url: string;
1092
- trailing: string;
1093
- } {
1094
- let end = value.length;
1095
-
1096
- while (end > 0) {
1097
- const lastCharacter = value[end - 1];
1098
- if (ALWAYS_TRAILING_PUNCTUATION.has(lastCharacter)) {
1099
- end -= 1;
1100
- continue;
1101
- }
1102
-
1103
- const openingCharacter = PAIRED_TRAILING_PUNCTUATION[lastCharacter];
1104
- if (openingCharacter) {
1105
- const candidate = value.slice(0, end);
1106
- const openingCount = candidate.split(openingCharacter).length - 1;
1107
- const closingCount = candidate.split(lastCharacter).length - 1;
1108
- if (closingCount > openingCount) {
1109
- end -= 1;
1110
- continue;
1111
- }
1112
- }
1113
-
1114
- break;
1115
- }
1116
-
1117
- return {
1118
- url: value.slice(0, end),
1119
- trailing: value.slice(end),
1120
- };
1121
- }
1122
-
1123
- function linkifyCommentContent(content: string): React.ReactNode[] {
1124
- const result: React.ReactNode[] = [];
1125
- let lastIndex = 0;
1126
-
1127
- for (const match of content.matchAll(COMMENT_URL_PATTERN)) {
1128
- const matchIndex = match.index;
1129
- const matchedText = match[0];
1130
- const { url, trailing } = trimTrailingUrlPunctuation(matchedText);
1131
- const href = url.toLowerCase().startsWith("www.") ? `https://${url}` : url;
1132
-
1133
- let isValidUrl = false;
1134
- try {
1135
- isValidUrl = Boolean(new URL(href).hostname);
1136
- } catch {
1137
- // Leave malformed URL-like text unlinked.
1138
- }
1139
-
1140
- if (!isValidUrl) continue;
1141
-
1142
- if (matchIndex > lastIndex) {
1143
- result.push(content.slice(lastIndex, matchIndex));
1144
- }
1145
- result.push(
1146
- <a
1147
- key={`${matchIndex}-${url}`}
1148
- href={href}
1149
- target="_blank"
1150
- rel="noopener noreferrer"
1151
- className="text-primary hover:underline"
1152
- >
1153
- {url}
1154
- </a>,
1155
- );
1156
- if (trailing) result.push(trailing);
1157
-
1158
- lastIndex = matchIndex + matchedText.length;
1159
- }
1160
-
1161
- if (lastIndex < content.length) {
1162
- result.push(content.slice(lastIndex));
1163
- }
1164
-
1165
- return result;
1166
- }
1167
-
1168
1074
  function parseReactions(raw: string): Record<string, string[]> {
1169
1075
  try {
1170
1076
  const v = JSON.parse(raw ?? "{}");
@@ -1,4 +1,5 @@
1
1
  import { useAvatarUrl } from "@agent-native/core/client/hooks";
2
+ import { InlineMarkdown } from "@agent-native/core/client/markdown";
2
3
 
3
4
  import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
4
5
  import { cn } from "@/lib/utils";
@@ -100,9 +101,12 @@ export function PlaybackCommentOverlay({
100
101
  <p className="truncate text-xs font-semibold text-white/80">
101
102
  {author}
102
103
  </p>
103
- <p className="line-clamp-3 break-words text-sm leading-5 text-white">
104
- {comment.content}
105
- </p>
104
+ <InlineMarkdown
105
+ content={comment.content}
106
+ className="line-clamp-3 text-sm leading-5 text-primary-foreground"
107
+ linkClassName="text-primary-foreground underline decoration-primary-foreground/60 hover:decoration-primary-foreground"
108
+ codeClassName="bg-primary-foreground/15 text-primary-foreground"
109
+ />
106
110
  </div>
107
111
  </div>
108
112
  {rest.length > 0 && (
@@ -353,6 +353,7 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
353
353
  const [captionsOn, setCaptionsOn] = useState(false);
354
354
  const [hasPlaybackStarted, setHasPlaybackStarted] = useState(false);
355
355
  const [isFullscreen, setIsFullscreen] = useState(false);
356
+ const nativeFullscreenRef = useRef(false);
356
357
  const [isPip, setIsPip] = useState(false);
357
358
  const [canPlay, setCanPlay] = useState(false);
358
359
  const [isPlayPending, setIsPlayPending] = useState(false);
@@ -1362,33 +1363,83 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
1362
1363
  const el = containerRef.current;
1363
1364
  const video = videoRef.current as WebkitFullscreenVideo | null;
1364
1365
  if (!el) return;
1366
+ const hasNativeVideoFullscreen =
1367
+ typeof video?.webkitEnterFullscreen === "function";
1368
+ let nativeFullscreenAttempted = false;
1365
1369
  try {
1366
- if (!document.fullscreenElement) {
1367
- if (
1368
- video?.webkitDisplayingFullscreen ||
1369
- (isFullscreen && video?.webkitExitFullscreen)
1370
- ) {
1371
- video?.webkitExitFullscreen?.();
1372
- setIsFullscreen(false);
1373
- } else if (typeof el.requestFullscreen === "function") {
1374
- await el.requestFullscreen();
1375
- setIsFullscreen(true);
1376
- } else if (video?.webkitEnterFullscreen) {
1377
- // iPhone Safari exposes fullscreen on the video element, not the
1378
- // containing div used by the custom player controls.
1379
- video.webkitEnterFullscreen();
1380
- setIsFullscreen(true);
1381
- } else {
1382
- console.warn("[clips] Fullscreen unavailable");
1383
- }
1384
- } else {
1370
+ if (document.fullscreenElement) {
1385
1371
  await document.exitFullscreen();
1386
1372
  setIsFullscreen(false);
1373
+ return;
1374
+ }
1375
+
1376
+ if (nativeFullscreenRef.current || video?.webkitDisplayingFullscreen) {
1377
+ video?.webkitExitFullscreen?.();
1378
+ nativeFullscreenRef.current = false;
1379
+ setIsFullscreen(false);
1380
+ return;
1381
+ }
1382
+
1383
+ // iPhone Safari exposes fullscreen on the video element, not the
1384
+ // containing div used by the custom player controls. Older versions
1385
+ // can expose requestFullscreen on the div without supporting it.
1386
+ const documentFullscreenUnavailable = !document.fullscreenEnabled;
1387
+ if (
1388
+ hasNativeVideoFullscreen &&
1389
+ (documentFullscreenUnavailable ||
1390
+ typeof el.requestFullscreen !== "function")
1391
+ ) {
1392
+ nativeFullscreenAttempted = true;
1393
+ video.webkitEnterFullscreen?.();
1394
+ nativeFullscreenRef.current = true;
1395
+ setIsFullscreen(true);
1396
+ return;
1397
+ }
1398
+
1399
+ if (isFullscreen) {
1400
+ // Keep the control reversible when neither browser fullscreen API
1401
+ // is available and the player is using its fixed-viewport fallback.
1402
+ setIsFullscreen(false);
1403
+ return;
1404
+ }
1405
+
1406
+ if (typeof el.requestFullscreen === "function") {
1407
+ await el.requestFullscreen();
1408
+ if (document.fullscreenElement || !hasNativeVideoFullscreen) {
1409
+ setIsFullscreen(true);
1410
+ return;
1411
+ }
1412
+
1413
+ // A few mobile WebKit versions resolve the container request but do
1414
+ // not enter fullscreen. Retry against the video before falling back
1415
+ // to the in-app fixed viewport.
1416
+ nativeFullscreenAttempted = true;
1417
+ video.webkitEnterFullscreen?.();
1418
+ nativeFullscreenRef.current = true;
1419
+ setIsFullscreen(true);
1420
+ return;
1387
1421
  }
1422
+
1423
+ if (hasNativeVideoFullscreen) {
1424
+ nativeFullscreenAttempted = true;
1425
+ video.webkitEnterFullscreen?.();
1426
+ nativeFullscreenRef.current = true;
1427
+ setIsFullscreen(true);
1428
+ return;
1429
+ }
1430
+
1431
+ // Keep fullscreen usable in browsers that expose neither API.
1432
+ setIsFullscreen(true);
1388
1433
  } catch (err) {
1389
- if (video?.webkitEnterFullscreen && !document.fullscreenElement) {
1434
+ if (
1435
+ !nativeFullscreenAttempted &&
1436
+ hasNativeVideoFullscreen &&
1437
+ !document.fullscreenElement
1438
+ ) {
1390
1439
  try {
1391
- video.webkitEnterFullscreen();
1440
+ nativeFullscreenAttempted = true;
1441
+ video.webkitEnterFullscreen?.();
1442
+ nativeFullscreenRef.current = true;
1392
1443
  setIsFullscreen(true);
1393
1444
  return;
1394
1445
  } catch (fallbackErr) {
@@ -1396,14 +1447,21 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
1396
1447
  }
1397
1448
  }
1398
1449
  console.warn("[clips] Fullscreen failed", err);
1450
+ setIsFullscreen(true);
1399
1451
  }
1400
1452
  }
1401
1453
 
1402
1454
  useEffect(() => {
1403
1455
  const video = videoRef.current as WebkitFullscreenVideo | null;
1404
1456
  const onFs = () => setIsFullscreen(!!document.fullscreenElement);
1405
- const onNativeFsEnter = () => setIsFullscreen(true);
1406
- const onNativeFsExit = () => setIsFullscreen(false);
1457
+ const onNativeFsEnter = () => {
1458
+ nativeFullscreenRef.current = true;
1459
+ setIsFullscreen(true);
1460
+ };
1461
+ const onNativeFsExit = () => {
1462
+ nativeFullscreenRef.current = false;
1463
+ setIsFullscreen(false);
1464
+ };
1407
1465
  document.addEventListener("fullscreenchange", onFs);
1408
1466
  video?.addEventListener("webkitbeginfullscreen", onNativeFsEnter);
1409
1467
  video?.addEventListener("webkitendfullscreen", onNativeFsExit);
@@ -1456,7 +1514,9 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
1456
1514
  // width (see CenterPlaybackOverlay) so it isn't oversized inside
1457
1515
  // small embeds like the Slack unfurl iframe.
1458
1516
  "relative @container bg-black overflow-hidden select-none group",
1459
- theaterMode ? "fixed inset-0 z-40" : "rounded-xl",
1517
+ theaterMode || isFullscreen
1518
+ ? "fixed inset-0 z-40 h-dvh w-dvw"
1519
+ : "rounded-xl",
1460
1520
  className,
1461
1521
  )}
1462
1522
  onMouseMove={bumpControls}
@@ -1,4 +1,5 @@
1
1
  import { useFormatters, useT } from "@agent-native/core/client/i18n";
2
+ import { InlineMarkdown } from "@agent-native/core/client/markdown";
2
3
  import {
3
4
  IconMessage,
4
5
  IconMoodSmile,
@@ -87,9 +88,16 @@ export function NotificationsList({ items, onReply }: NotificationsListProps) {
87
88
  </span>
88
89
  </div>
89
90
  {item.preview ? (
90
- <div className="mt-1 text-sm text-muted-foreground line-clamp-2">
91
- {item.preview}
92
- </div>
91
+ item.kind === "comment" || item.kind === "mention" ? (
92
+ <InlineMarkdown
93
+ content={item.preview}
94
+ className="mt-1 line-clamp-2 text-sm text-muted-foreground"
95
+ />
96
+ ) : (
97
+ <div className="mt-1 line-clamp-2 text-sm text-muted-foreground">
98
+ {item.preview}
99
+ </div>
100
+ )
93
101
  ) : null}
94
102
  <div className="mt-1.5 flex items-center gap-3 text-xs">
95
103
  <Link
@@ -969,6 +969,8 @@ All notable user-facing changes to Clips are documented here. Open it any time f
969
969
  includeTranscriptDescription:
970
970
  "Anyone with access to this meeting can read the complete transcript.",
971
971
  transcriptUnavailable: "The transcript isn't ready yet.",
972
+ agentLinkDescription:
973
+ "This temporary link lets agents read these meeting notes without making them public. It expires after two hours.",
972
974
  transcript: "Transcript",
973
975
  copyTranscript: "Copy transcript",
974
976
  transcriptCopied: "Transcript copied",
@@ -1545,6 +1547,16 @@ All notable user-facing changes to Clips are documented here. Open it any time f
1545
1547
  calendarConnected: "Calendar connected",
1546
1548
  calendarDisconnected: "Calendar disconnected",
1547
1549
  calendarSettings: "Calendar settings",
1550
+ calendarAccountsButton: "Calendars",
1551
+ connectedAccounts: "Connected accounts",
1552
+ calendarConnectedLabel: "Connected",
1553
+ calendarNeedsReconnectLabel: "Needs reconnect",
1554
+ calendarDisconnectedLabel: "Disconnected",
1555
+ calendarStatusUnavailable: "Status unavailable",
1556
+ reconnectCalendar: "Reconnect calendar",
1557
+ addAnotherCalendarAccount: "Add another account",
1558
+ connectCalendar: "Connect calendar",
1559
+ disconnectCalendarAccount: "Disconnect an account",
1548
1560
  connectCalendarReminder: "Connect Google Calendar for meeting reminders.",
1549
1561
  disconnectGoogleCalendarTitle: "Disconnect Google Calendar?",
1550
1562
  title: "Meetings",
@@ -2,6 +2,8 @@ import { appBasePath } from "@agent-native/core/client/api-path";
2
2
 
3
3
  import type { TranscriptSegment } from "@/components/meetings/transcript-bubbles";
4
4
 
5
+ import { CLIPS_MEETING_AGENT_ACCESS_PARAM } from "../../shared/meeting-agent-access";
6
+
5
7
  export interface PublicMeetingBullet {
6
8
  text: string;
7
9
  }
@@ -59,9 +61,13 @@ export function publicMeetingUrl(
59
61
  meetingId: string,
60
62
  origin: string,
61
63
  basePath: string,
64
+ agentAccessToken?: string,
62
65
  ): string {
63
66
  const url = new URL(`${basePath}/api/public-meeting`, origin);
64
67
  url.searchParams.set("id", meetingId);
68
+ if (agentAccessToken) {
69
+ url.searchParams.set(CLIPS_MEETING_AGENT_ACCESS_PARAM, agentAccessToken);
70
+ }
65
71
  return url.toString();
66
72
  }
67
73
 
@@ -71,13 +77,15 @@ export async function fetchPublicMeeting(
71
77
  signal?: AbortSignal;
72
78
  origin?: string;
73
79
  basePath?: string;
80
+ agentAccessToken?: string;
74
81
  } = {},
75
82
  ): Promise<PublicMeetingResult> {
76
83
  const origin = options.origin ?? window.location.origin;
77
84
  const basePath = options.basePath ?? appBasePath();
78
- const response = await fetch(publicMeetingUrl(meetingId, origin, basePath), {
79
- signal: options.signal,
80
- });
85
+ const response = await fetch(
86
+ publicMeetingUrl(meetingId, origin, basePath, options.agentAccessToken),
87
+ { signal: options.signal },
88
+ );
81
89
  const data = (await response.json().catch(() => ({}))) as
82
90
  | PublicMeetingPayload
83
91
  | { error?: string };
@@ -0,0 +1,15 @@
1
+ export const SEARCH_FOCUS_PARAM = "focus";
2
+ export const SEARCH_FOCUS_VALUE = "search";
3
+ export const SEARCH_FOCUS_PATH = `/library?${SEARCH_FOCUS_PARAM}=${SEARCH_FOCUS_VALUE}`;
4
+
5
+ export function hasSearchFocusRequest(params: URLSearchParams): boolean {
6
+ return params.get(SEARCH_FOCUS_PARAM) === SEARCH_FOCUS_VALUE;
7
+ }
8
+
9
+ export function clearSearchFocusRequest(
10
+ params: URLSearchParams,
11
+ ): URLSearchParams {
12
+ const next = new URLSearchParams(params);
13
+ next.delete(SEARCH_FOCUS_PARAM);
14
+ return next;
15
+ }
@@ -54,6 +54,7 @@ import {
54
54
  import { Toaster } from "@/components/ui/sonner";
55
55
  import { AppToolkitProvider } from "@/components/ui/toolkit-provider";
56
56
  import { useNavigationState } from "@/hooks/use-navigation-state";
57
+ import { SEARCH_FOCUS_PATH } from "@/lib/search-focus";
57
58
 
58
59
  import { i18nCatalog, loadI18nMessages } from "./i18n";
59
60
 
@@ -374,7 +375,7 @@ function AppContent() {
374
375
  <IconHierarchy2 size={16} />
375
376
  {t("root.openAgent")}
376
377
  </CommandMenu.Item>
377
- <CommandMenu.Item onSelect={() => {}}>
378
+ <CommandMenu.Item onSelect={() => navigate(SEARCH_FOCUS_PATH)}>
378
379
  {t("root.commandSearch")}
379
380
  </CommandMenu.Item>
380
381
  </CommandMenu.Group>