@agent-native/core 0.154.5 → 0.157.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 (263) 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/assets/app/components/asset/AssetPreviewDialog.tsx +3 -1
  19. package/corpus/templates/assets/app/i18n-data.ts +21 -1
  20. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +17 -13
  21. package/corpus/templates/calendar/AGENTS.md +2 -0
  22. package/corpus/templates/calendar/DEVELOPING.md +4 -4
  23. package/corpus/templates/calendar/actions/get-settings.ts +4 -11
  24. package/corpus/templates/calendar/actions/update-availability.ts +15 -4
  25. package/corpus/templates/calendar/actions/update-settings.ts +15 -4
  26. package/corpus/templates/calendar/app/components/booking/DatePicker.tsx +9 -3
  27. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +21 -19
  28. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +9 -6
  29. package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +25 -3
  30. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +10 -3
  31. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +9 -9
  32. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -3
  33. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +10 -4
  34. package/corpus/templates/calendar/app/i18n-data.ts +30 -0
  35. package/corpus/templates/calendar/app/lib/availability-schedule.ts +53 -0
  36. package/corpus/templates/calendar/app/lib/event-form-utils.ts +5 -0
  37. package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +90 -28
  38. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +135 -61
  39. package/corpus/templates/calendar/app/pages/BookingPage.tsx +2 -0
  40. package/corpus/templates/calendar/app/pages/CalendarView.tsx +30 -15
  41. package/corpus/templates/calendar/app/pages/Settings.tsx +60 -1
  42. package/corpus/templates/calendar/changelog/2026-08-13-find-a-time-now-closes-from-a-clear-header-action.md +6 -0
  43. package/corpus/templates/calendar/server/handlers/bookings.ts +2 -1
  44. package/corpus/templates/calendar/server/handlers/settings.ts +12 -16
  45. package/corpus/templates/calendar/shared/api.ts +1 -0
  46. package/corpus/templates/calendar/shared/availability-schedule.ts +64 -0
  47. package/corpus/templates/calendar/shared/calendar-week.ts +21 -0
  48. package/corpus/templates/calendar/shared/settings.ts +44 -0
  49. package/corpus/templates/clips/AGENTS.md +2 -0
  50. package/corpus/templates/clips/actions/add-comment.ts +5 -2
  51. package/corpus/templates/clips/actions/finalize-recording.ts +31 -1
  52. package/corpus/templates/clips/actions/get-recording-player-data.ts +9 -0
  53. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +21 -1
  54. package/corpus/templates/clips/actions/reply-to-comment.ts +5 -2
  55. package/corpus/templates/clips/actions/update-comment.ts +9 -2
  56. package/corpus/templates/clips/app/components/library/search-bar.tsx +26 -13
  57. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +135 -3
  58. package/corpus/templates/clips/app/components/player/comments-panel.tsx +5 -99
  59. package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +7 -3
  60. package/corpus/templates/clips/app/components/player/share-dialog.tsx +102 -59
  61. package/corpus/templates/clips/app/components/player/video-player.tsx +84 -24
  62. package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +11 -3
  63. package/corpus/templates/clips/app/i18n/en-US.ts +12 -0
  64. package/corpus/templates/clips/app/lib/public-meeting.ts +11 -3
  65. package/corpus/templates/clips/app/lib/search-focus.ts +15 -0
  66. package/corpus/templates/clips/app/root.tsx +2 -1
  67. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +94 -26
  68. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +24 -7
  69. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +8 -4
  70. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +29 -8
  71. package/corpus/templates/clips/app/routes/share.$shareId.tsx +24 -6
  72. package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +72 -26
  73. package/corpus/templates/clips/changelog/2026-08-13-inline-markdown-comments.md +6 -0
  74. package/corpus/templates/clips/changelog/2026-08-13-private-meeting-agent-links.md +6 -0
  75. package/corpus/templates/clips/desktop/src/app.tsx +116 -19
  76. package/corpus/templates/clips/desktop/src/lib/recorder.ts +13 -29
  77. package/corpus/templates/clips/desktop/src/lib/recording-request.ts +39 -0
  78. package/corpus/templates/clips/desktop/src/lib/settings-navigation.ts +11 -0
  79. package/corpus/templates/clips/desktop/src/lib/whisper-model-picker.ts +7 -0
  80. package/corpus/templates/clips/desktop/src/styles.css +117 -0
  81. package/corpus/templates/clips/server/db/index.ts +10 -0
  82. package/corpus/templates/clips/server/db/schema.ts +1 -0
  83. package/corpus/templates/clips/server/lib/recordings.ts +3 -1
  84. package/corpus/templates/clips/server/lib/seekable-media-state.ts +90 -0
  85. package/corpus/templates/clips/server/plugins/db.ts +5 -0
  86. package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +37 -4
  87. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +16 -5
  88. package/corpus/templates/clips/shared/clips-ai-prefs.ts +3 -0
  89. package/corpus/templates/clips/shared/meeting-agent-access.ts +5 -0
  90. package/corpus/templates/content/actions/add-comment.ts +2 -1
  91. package/corpus/templates/content/actions/update-comment.ts +1 -1
  92. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +24 -15
  93. package/corpus/templates/design/actions/generate-design.ts +10 -7
  94. package/corpus/templates/design/actions/present-design-variants.ts +35 -6
  95. package/corpus/templates/design/actions/run-design-audit.ts +33 -0
  96. package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-catches-a-screen-that-renders-behind-an.md +6 -0
  97. package/corpus/templates/design/shared/design-review.ts +1 -0
  98. package/corpus/templates/design/shared/html-integrity.ts +66 -0
  99. package/corpus/templates/dispatch/.env.example +5 -0
  100. package/corpus/templates/dispatch/AGENTS.md +24 -19
  101. package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
  102. package/corpus/templates/dispatch/server/lib/identity-sso.ts +21 -90
  103. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +3 -12
  104. package/corpus/templates/dispatch/shared/feature-flags.ts +4 -0
  105. package/corpus/templates/factory/AGENTS.md +5 -1
  106. package/corpus/templates/factory/README.md +23 -0
  107. package/corpus/templates/factory/actions/view-screen.ts +17 -0
  108. package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +239 -0
  109. package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +2 -0
  110. package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +130 -7
  111. package/corpus/templates/factory/app/i18n/en-US.ts +20 -0
  112. package/corpus/templates/factory/app/routes/factory.tsx +41 -0
  113. package/corpus/templates/factory/package.json +1 -0
  114. package/corpus/templates/factory/server/factory-graph/contracts.ts +2 -0
  115. package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -0
  116. package/corpus/templates/factory/server/plugins/dispatch-db.ts +1 -0
  117. package/corpus/templates/factory/server/plugins/setup-dispatch.ts +3 -0
  118. package/corpus/templates/mail/app/lib/native-mcp-exclusions.ts +3 -0
  119. package/corpus/templates/mail/vite.config.ts +7 -0
  120. package/corpus/templates/plan/actions/reply-to-plan-comment.ts +7 -2
  121. package/corpus/templates/plan/actions/resolve-plan-comment.ts +2 -2
  122. package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
  123. package/corpus/templates/plan/app/pages/PlansPage.tsx +29 -20
  124. package/corpus/templates/slides/actions/add-slide-comment.ts +1 -1
  125. package/corpus/templates/slides/actions/update-slide-comment.ts +1 -1
  126. package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +9 -6
  127. package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +98 -29
  128. package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +45 -91
  129. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +94 -2
  130. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +17 -29
  131. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +1 -1
  132. package/corpus/templates/slides/app/context/DeckContext.tsx +42 -2
  133. package/corpus/templates/slides/app/global.css +7 -40
  134. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  135. package/corpus/templates/slides/app/pages/DeckEditor.tsx +130 -45
  136. package/dist/agent/index.d.ts +1 -1
  137. package/dist/agent/production-agent.d.ts +30 -0
  138. package/dist/agent/production-agent.js +105 -9
  139. package/dist/agent/thread-data-builder.js +17 -1
  140. package/dist/agent/types.d.ts +9 -0
  141. package/dist/cli/skills-content/help.js +6 -4
  142. package/dist/cli/skills.js +17 -2
  143. package/dist/cli/workspacify.d.ts +5 -0
  144. package/dist/cli/workspacify.js +65 -8
  145. package/dist/client/AgentPanel.d.ts +5 -0
  146. package/dist/client/AgentPanel.js +32 -13
  147. package/dist/client/AssistantChat.d.ts +36 -1
  148. package/dist/client/AssistantChat.js +175 -110
  149. package/dist/client/MultiTabAssistantChat.js +44 -27
  150. package/dist/client/active-run-state.d.ts +14 -1
  151. package/dist/client/active-run-state.js +0 -0
  152. package/dist/client/agent-chat-adapter.js +107 -5
  153. package/dist/client/analytics.js +9 -2
  154. package/dist/client/chat/markdown-renderer.js +4 -2
  155. package/dist/client/chat/message-components.d.ts +2 -2
  156. package/dist/client/chat/message-components.js +69 -35
  157. package/dist/client/chat/run-recovery.d.ts +4 -1
  158. package/dist/client/chat/run-recovery.js +62 -30
  159. package/dist/client/chat/tool-call-display.d.ts +12 -3
  160. package/dist/client/chat/tool-call-display.js +79 -40
  161. package/dist/client/chat/widgets/DataChartRenderer.js +3 -1
  162. package/dist/client/chat/widgets/DataChartWidget.js +8 -2
  163. package/dist/client/chat/widgets/DataInsightsWidget.js +3 -1
  164. package/dist/client/chat/widgets/DataTableWidget.js +23 -9
  165. package/dist/client/chat/widgets/builtin-tool-renderers.js +3 -1
  166. package/dist/client/chat-first/apps-rail.js +9 -4
  167. package/dist/client/chat-first/primary-nav.d.ts +2 -1
  168. package/dist/client/chat-first/primary-nav.js +3 -3
  169. package/dist/client/chat-first/types.d.ts +5 -1
  170. package/dist/client/components/ui/message-scroller.d.ts +1 -1
  171. package/dist/client/components/ui/message-scroller.js +7 -3
  172. package/dist/client/composer/runtime-adapters.js +3 -2
  173. package/dist/client/context-xray/ContextMeter.js +5 -1
  174. package/dist/client/context-xray/ContextXRayPanel.js +5 -5
  175. package/dist/client/error-format.d.ts +4 -0
  176. package/dist/client/error-format.js +93 -5
  177. package/dist/client/i18n.js +83 -17
  178. package/dist/client/markdown/InlineMarkdown.d.ts +23 -0
  179. package/dist/client/markdown/InlineMarkdown.js +65 -0
  180. package/dist/client/markdown/index.d.ts +1 -0
  181. package/dist/client/markdown/index.js +1 -0
  182. package/dist/client/observability/ThumbsFeedback.js +10 -8
  183. package/dist/client/review/ReviewThreadPanel.js +2 -1
  184. package/dist/client/run-stream-ownership.d.ts +39 -0
  185. package/dist/client/run-stream-ownership.js +65 -0
  186. package/dist/client/sharing/ShareButton.js +160 -54
  187. package/dist/coding-tools/run-code.js +2 -1
  188. package/dist/coding-tools/sandbox/background.d.ts +1 -0
  189. package/dist/coding-tools/sandbox/background.js +8 -1
  190. package/dist/coding-tools/sandbox/executions-store.d.ts +2 -0
  191. package/dist/coding-tools/sandbox/executions-store.js +41 -3
  192. package/dist/collab/awareness.d.ts +2 -2
  193. package/dist/collab/routes.d.ts +1 -1
  194. package/dist/collab/struct-routes.d.ts +1 -1
  195. package/dist/feature-flags/index.d.ts +1 -1
  196. package/dist/feature-flags/index.js +1 -1
  197. package/dist/feature-flags/store.d.ts +10 -0
  198. package/dist/feature-flags/store.js +112 -1
  199. package/dist/guards/no-env-credentials.js +4 -0
  200. package/dist/index.d.ts +2 -2
  201. package/dist/index.js +1 -1
  202. package/dist/integrations/adapters/slack.js +5 -1
  203. package/dist/localization/core-messages/ar-SA.d.ts +3 -0
  204. package/dist/localization/core-messages/ar-SA.js +541 -0
  205. package/dist/localization/core-messages/de-DE.d.ts +3 -0
  206. package/dist/localization/core-messages/de-DE.js +513 -0
  207. package/dist/localization/core-messages/en-US.d.ts +513 -0
  208. package/dist/localization/core-messages/en-US.js +513 -0
  209. package/dist/localization/core-messages/es-ES.d.ts +3 -0
  210. package/dist/localization/core-messages/es-ES.js +520 -0
  211. package/dist/localization/core-messages/fr-FR.d.ts +3 -0
  212. package/dist/localization/core-messages/fr-FR.js +520 -0
  213. package/dist/localization/core-messages/hi-IN.d.ts +3 -0
  214. package/dist/localization/core-messages/hi-IN.js +513 -0
  215. package/dist/localization/core-messages/ja-JP.d.ts +3 -0
  216. package/dist/localization/core-messages/ja-JP.js +506 -0
  217. package/dist/localization/core-messages/ko-KR.d.ts +3 -0
  218. package/dist/localization/core-messages/ko-KR.js +506 -0
  219. package/dist/localization/core-messages/pt-BR.d.ts +3 -0
  220. package/dist/localization/core-messages/pt-BR.js +520 -0
  221. package/dist/localization/core-messages/zh-CN.d.ts +3 -0
  222. package/dist/localization/core-messages/zh-CN.js +506 -0
  223. package/dist/localization/core-messages/zh-TW.d.ts +3 -0
  224. package/dist/localization/core-messages/zh-TW.js +506 -0
  225. package/dist/localization/core-messages.d.ts +524 -0
  226. package/dist/localization/core-messages.js +124 -0
  227. package/dist/localization/default-messages.d.ts +3 -0
  228. package/dist/localization/default-messages.js +3 -0
  229. package/dist/notifications/routes.d.ts +1 -1
  230. package/dist/observability/routes.d.ts +2 -2
  231. package/dist/progress/routes.d.ts +1 -1
  232. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  233. package/dist/provider-api/actions/provider-api.d.ts +15 -15
  234. package/dist/resources/agents.js +40 -5
  235. package/dist/resources/metadata.d.ts +10 -0
  236. package/dist/resources/metadata.js +7 -3
  237. package/dist/review/actions/create-review-comment.js +1 -1
  238. package/dist/review/actions/reply-review-comment.js +1 -1
  239. package/dist/review/actions/resolve-review-thread.js +1 -1
  240. package/dist/secrets/routes.d.ts +6 -6
  241. package/dist/server/action-routes.js +32 -6
  242. package/dist/server/agent-chat/plugin-options.d.ts +7 -2
  243. package/dist/server/agent-chat-plugin.d.ts +10 -0
  244. package/dist/server/agent-chat-plugin.js +144 -41
  245. package/dist/server/agent-discovery.js +14 -3
  246. package/dist/server/agent-run-context.d.ts +7 -1
  247. package/dist/server/agent-run-context.js +9 -2
  248. package/dist/server/agent-teams-run-queue.d.ts +3 -0
  249. package/dist/server/agent-teams.js +38 -9
  250. package/dist/server/core-routes-plugin.js +1 -1
  251. package/dist/server/identity-sso-migrations.d.ts +7 -0
  252. package/dist/server/identity-sso-migrations.js +36 -0
  253. package/dist/server/identity-sso-store.d.ts +3 -2
  254. package/dist/server/identity-sso-store.js +8 -3
  255. package/dist/server/index.d.ts +1 -1
  256. package/dist/server/realtime-token.d.ts +1 -1
  257. package/dist/server/release-migrations.js +4 -0
  258. package/dist/server/request-context.d.ts +7 -0
  259. package/dist/server/request-context.js +15 -0
  260. package/dist/tracking/providers.js +13 -3
  261. package/dist/workspace-files/tool.js +1 -1
  262. package/docs/content/agent-surfaces.mdx +30 -0
  263. package/package.json +3 -2
@@ -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 && (
@@ -72,6 +72,13 @@ export interface ShareRecordingPopoverProps {
72
72
  animatedThumbnailUrl?: string | null;
73
73
  isLoomRecording?: boolean;
74
74
  hasPassword?: boolean;
75
+ /**
76
+ * Restricts the dialog to a bare copy-link control for viewers who can
77
+ * reshare a public/org clip's link but have no edit access: it skips
78
+ * `list-resource-shares` (which returns every individually-shared
79
+ * principal's email to any reader) and hides the Invite tab entirely.
80
+ */
81
+ viewerReshareOnly?: boolean;
75
82
  /** Trigger element rendered as the popover anchor (usually the Share button). */
76
83
  children: ReactNode;
77
84
  open?: boolean;
@@ -103,6 +110,7 @@ export function ShareRecordingPopover({
103
110
  animatedThumbnailUrl,
104
111
  isLoomRecording = false,
105
112
  hasPassword,
113
+ viewerReshareOnly = false,
106
114
  children,
107
115
  open,
108
116
  onOpenChange,
@@ -125,6 +133,7 @@ export function ShareRecordingPopover({
125
133
  animatedThumbnailUrl={animatedThumbnailUrl}
126
134
  isLoomRecording={isLoomRecording}
127
135
  hasPassword={hasPassword}
136
+ viewerReshareOnly={viewerReshareOnly}
128
137
  />
129
138
  </PopoverContent>
130
139
  </Popover>
@@ -148,6 +157,7 @@ export function ShareRecordingDialog({
148
157
  open,
149
158
  onOpenChange,
150
159
  hasPassword,
160
+ viewerReshareOnly = false,
151
161
  }: ShareRecordingDialogProps) {
152
162
  const t = useT();
153
163
  return (
@@ -168,6 +178,7 @@ export function ShareRecordingDialog({
168
178
  animatedThumbnailUrl={animatedThumbnailUrl}
169
179
  isLoomRecording={isLoomRecording}
170
180
  hasPassword={hasPassword}
181
+ viewerReshareOnly={viewerReshareOnly}
171
182
  reserveCloseButton
172
183
  />
173
184
  </DialogContent>
@@ -186,6 +197,7 @@ function ShareRecordingContent({
186
197
  isLoomRecording = false,
187
198
  reserveCloseButton = false,
188
199
  hasPassword,
200
+ viewerReshareOnly = false,
189
201
  }: {
190
202
  recordingId: string;
191
203
  recordingTitle?: string;
@@ -197,20 +209,34 @@ function ShareRecordingContent({
197
209
  isLoomRecording?: boolean;
198
210
  reserveCloseButton?: boolean;
199
211
  hasPassword?: boolean;
212
+ viewerReshareOnly?: boolean;
200
213
  }) {
201
214
  const t = useT();
202
- const sharesQuery = useActionQuery<SharesResponse>("list-resource-shares", {
203
- resourceType: "recording",
204
- resourceId: recordingId,
205
- });
215
+ const sharesQuery = useActionQuery<SharesResponse>(
216
+ "list-resource-shares",
217
+ { resourceType: "recording", resourceId: recordingId },
218
+ { enabled: !viewerReshareOnly },
219
+ );
206
220
 
207
- const data = sharesQuery.data;
221
+ const data = viewerReshareOnly ? undefined : sharesQuery.data;
208
222
  const role = data?.role ?? initialRole;
209
223
  const canManage = role === "owner" || role === "admin";
224
+ // Editors could always see (read-only) who a clip is shared with; only
225
+ // gate the Invite tab's mutation controls behind canManage. Commenters are
226
+ // grouped with plain viewers here -- neither can manage shares.
227
+ const canViewShares =
228
+ role === "owner" || role === "admin" || role === "editor";
210
229
  const visibility =
211
230
  (data?.visibility as Visibility | null | undefined) ??
212
231
  initialVisibility ??
213
232
  null;
233
+ // A plain viewer/commenter can't produce a working embed for a non-public
234
+ // clip (they have no way to make it public), so don't dangle the tab in
235
+ // front of them only to show an "ask the owner" dead end. Owner/admin/
236
+ // editor keep it regardless of visibility since they can flip to public
237
+ // from inside it.
238
+ const canEmbed = canViewShares || visibility === "public";
239
+ const tabCount = 1 + (canViewShares ? 1 : 0) + (canEmbed ? 1 : 0);
214
240
 
215
241
  // Attribution `via` must be a stable non-PII id, never an email. The only
216
242
  // owner id available client-side is the *current* session's userId, which is
@@ -234,20 +260,28 @@ function ShareRecordingContent({
234
260
  defaultValue="link"
235
261
  className={cn("min-w-0 px-4 py-3", reserveCloseButton && "pe-12")}
236
262
  >
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>
263
+ {tabCount > 1 ? (
264
+ <TabsList
265
+ className={`grid w-full ${tabCount === 3 ? "grid-cols-3" : "grid-cols-2"}`}
266
+ >
267
+ <TabsTrigger value="link" className="gap-1.5">
268
+ <IconLink size={14} />
269
+ {t("shareDialog.link")}
270
+ </TabsTrigger>
271
+ {canViewShares ? (
272
+ <TabsTrigger value="invite" className="gap-1.5">
273
+ <IconMail size={14} />
274
+ {t("shareDialog.invite")}
275
+ </TabsTrigger>
276
+ ) : null}
277
+ {canEmbed ? (
278
+ <TabsTrigger value="embed" className="gap-1.5">
279
+ <IconCode size={14} />
280
+ {t("shareDialog.embed")}
281
+ </TabsTrigger>
282
+ ) : null}
283
+ </TabsList>
284
+ ) : null}
251
285
 
252
286
  <TabsContent value="link" className="mt-3">
253
287
  <LinkTab
@@ -262,34 +296,39 @@ function ShareRecordingContent({
262
296
  animatedThumbnailUrl={animatedThumbnailUrl}
263
297
  isLoomRecording={isLoomRecording}
264
298
  hasPassword={hasPassword}
299
+ canViewShares={canViewShares}
265
300
  />
266
301
  </TabsContent>
267
302
 
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>
303
+ {canViewShares ? (
304
+ <TabsContent value="invite" className="mt-3">
305
+ <SharePeopleTab
306
+ resourceType="recording"
307
+ resourceId={recordingId}
308
+ resourceUrl={absoluteAppUrl(`/r/${recordingId}`)}
309
+ sharesQuery={sharesQuery}
310
+ canManage={canManage}
311
+ roleCopy={{
312
+ commenter: {
313
+ label: t("shareUi.recordingCommenter.label"),
314
+ description: t("shareUi.recordingCommenter.description"),
315
+ },
316
+ }}
317
+ />
318
+ </TabsContent>
319
+ ) : null}
320
+
321
+ {canEmbed ? (
322
+ <TabsContent value="embed" className="mt-3">
323
+ <ClipsEmbedConfigurator
324
+ recordingId={recordingId}
325
+ sharesQuery={sharesQuery}
326
+ visibility={visibility}
327
+ canManage={canManage}
328
+ ownerViaId={ownerViaId}
329
+ />
330
+ </TabsContent>
331
+ ) : null}
293
332
  </Tabs>
294
333
  </>
295
334
  );
@@ -311,6 +350,7 @@ function LinkTab({
311
350
  animatedThumbnailUrl,
312
351
  isLoomRecording: isLoomRecordingProp,
313
352
  hasPassword,
353
+ canViewShares,
314
354
  }: {
315
355
  recordingId: string;
316
356
  recordingTitle?: string;
@@ -323,6 +363,7 @@ function LinkTab({
323
363
  animatedThumbnailUrl?: string | null;
324
364
  isLoomRecording?: boolean;
325
365
  hasPassword?: boolean;
366
+ canViewShares: boolean;
326
367
  }) {
327
368
  const t = useT();
328
369
  const { setResourceVisibility, isPending } = useResourceVisibilityMutation(
@@ -460,21 +501,23 @@ function LinkTab({
460
501
 
461
502
  return (
462
503
  <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
- )}
504
+ {canViewShares ? (
505
+ visibility ? (
506
+ <GeneralAccessSelect
507
+ visibility={visibility}
508
+ canManage={canManage}
509
+ isPending={visibilityPending}
510
+ onChange={(next) => setResourceVisibility(next)}
511
+ publicDescription={t("shareDialog.publicDescription")}
512
+ showDescription={false}
513
+ />
514
+ ) : (
515
+ <div className="space-y-2" aria-hidden>
516
+ <div className="h-3 w-24 animate-pulse rounded bg-muted" />
517
+ <div className="h-12 w-full animate-pulse rounded bg-muted" />
518
+ </div>
519
+ )
520
+ ) : null}
478
521
 
479
522
  <CopyField
480
523
  label={
@@ -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
+ }