@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
@@ -66,7 +66,10 @@ import { UpdateBanner } from "./components/UpdateBanner";
66
66
  import { useMediaDevices } from "./hooks/useMediaDevices";
67
67
  import { useMeetingTranscription } from "./hooks/useMeetingTranscription";
68
68
  import { stopAllMicMeters } from "./hooks/useMicMeter";
69
- import { useWhisperSettings } from "./hooks/useWhisperSettings";
69
+ import {
70
+ useWhisperSettings,
71
+ type WhisperModelOption,
72
+ } from "./hooks/useWhisperSettings";
70
73
  import { startBubbleFramePump } from "./lib/bubble-pump";
71
74
  import { shouldKeepBubbleSession } from "./lib/bubble-session";
72
75
  import {
@@ -111,6 +114,10 @@ import {
111
114
  } from "./lib/recording-link";
112
115
  import { REWIND_AGENT_PROMPT } from "./lib/rewind-agent-prompt";
113
116
  import { getRewindStatusPresentation } from "./lib/rewind-status";
117
+ import {
118
+ initialDesktopSettingsTab,
119
+ type DesktopSettingsTab,
120
+ } from "./lib/settings-navigation";
114
121
  import {
115
122
  loadBool,
116
123
  loadString,
@@ -133,6 +140,7 @@ import {
133
140
  type VoiceProvider,
134
141
  type VoiceShortcutPreference,
135
142
  } from "./lib/voice-dictation";
143
+ import { whisperModelOptionLabel } from "./lib/whisper-model-picker";
136
144
  import {
137
145
  useFeatureConfig,
138
146
  type FeatureConfig,
@@ -168,7 +176,7 @@ type PopoverView =
168
176
  | "meetings"
169
177
  | "dictation";
170
178
 
171
- type SettingsTabId = "general" | "recording" | "meetings" | "dictation";
179
+ type SettingsTabId = DesktopSettingsTab;
172
180
 
173
181
  interface PopoverMeeting {
174
182
  id: string;
@@ -988,6 +996,14 @@ export function App() {
988
996
  const [shareLinkNotice, setShareLinkNotice] =
989
997
  useState<ShareLinkNotice | null>(null);
990
998
  const [popoverView, setPopoverView] = useState<PopoverView>("recorder");
999
+ const [initialSettingsTab, setInitialSettingsTab] =
1000
+ useState<SettingsTabId>("general");
1001
+
1002
+ function openSettings(tab: SettingsTabId = "general") {
1003
+ setInitialSettingsTab(tab);
1004
+ setPopoverView("settings");
1005
+ }
1006
+
991
1007
  const [rewindSettingsReturnView, setRewindSettingsReturnView] = useState<
992
1008
  "recorder" | "settings"
993
1009
  >("recorder");
@@ -3802,6 +3818,7 @@ export function App() {
3802
3818
  {pendingUploadBanner}
3803
3819
  {isRecording ? <ActiveRecordingBanner /> : null}
3804
3820
  <Setup
3821
+ initialSettingsTab={initialSettingsTab}
3805
3822
  recordingActive={isRecording || recordingFlowActive}
3806
3823
  initial={serverUrl}
3807
3824
  serverUrl={serverUrl}
@@ -3855,7 +3872,7 @@ export function App() {
3855
3872
  onOpenMeeting={(meetingId) =>
3856
3873
  openInBrowser(`/meetings/${encodeURIComponent(meetingId)}`)
3857
3874
  }
3858
- onOpenSettings={() => setPopoverView("settings")}
3875
+ onOpenSettings={() => openSettings()}
3859
3876
  onStartNotes={startMeetingNotes}
3860
3877
  onStartNotesAndJoin={startMeetingNotesAndJoin}
3861
3878
  onShowActiveMeeting={showActiveMeetingPill}
@@ -3877,7 +3894,7 @@ export function App() {
3877
3894
  voiceProvider={voiceProvider}
3878
3895
  onBack={() => setPopoverView("recorder")}
3879
3896
  onOpenDictate={() => openInBrowser("/dictate")}
3880
- onOpenSettings={() => setPopoverView("settings")}
3897
+ onOpenSettings={() => openSettings("dictation")}
3881
3898
  />
3882
3899
  </div>
3883
3900
  );
@@ -3932,7 +3949,7 @@ export function App() {
3932
3949
  </>
3933
3950
  )}
3934
3951
  <div className="footer">
3935
- <a className="footer-link" onClick={() => setPopoverView("settings")}>
3952
+ <a className="footer-link" onClick={() => openSettings()}>
3936
3953
  Settings
3937
3954
  </a>
3938
3955
  </div>
@@ -4154,7 +4171,7 @@ export function App() {
4154
4171
  <BottomButton
4155
4172
  icon="settings"
4156
4173
  label="Settings"
4157
- onClick={() => setPopoverView("settings")}
4174
+ onClick={() => openSettings()}
4158
4175
  />
4159
4176
  </div>
4160
4177
  </div>
@@ -5296,6 +5313,7 @@ function desktopUpdateStatusText(status: UpdateStatus): string {
5296
5313
 
5297
5314
  function Setup({
5298
5315
  surface = "settings",
5316
+ initialSettingsTab,
5299
5317
  promptRewindEnable = false,
5300
5318
  recordingActive = false,
5301
5319
  initial,
@@ -5324,6 +5342,7 @@ function Setup({
5324
5342
  onSignOut,
5325
5343
  }: {
5326
5344
  surface?: "settings" | "memory" | "rewind";
5345
+ initialSettingsTab?: SettingsTabId;
5327
5346
  promptRewindEnable?: boolean;
5328
5347
  recordingActive?: boolean;
5329
5348
  initial?: string | null;
@@ -5353,7 +5372,9 @@ function Setup({
5353
5372
  }) {
5354
5373
  const [url, setUrl] = useState(initial ?? DEFAULT_URL);
5355
5374
  const [readinessOpen, setReadinessOpen] = useState(false);
5356
- const [settingsTab, setSettingsTab] = useState<SettingsTabId>("general");
5375
+ const [settingsTab, setSettingsTab] = useState<SettingsTabId>(() =>
5376
+ initialDesktopSettingsTab(initialSettingsTab),
5377
+ );
5357
5378
  const [rewindSettingsOpen, setRewindSettingsOpen] = useState(false);
5358
5379
  const [rewindActivityOpen, setRewindActivityOpen] = useState(false);
5359
5380
  const [rewindMemoryOpen, setRewindMemoryOpen] = useState(false);
@@ -5361,6 +5382,12 @@ function Setup({
5361
5382
  const [rewindPrivacyOpen, setRewindPrivacyOpen] = useState(false);
5362
5383
  const [rewindAgentSetupOpen, setRewindAgentSetupOpen] = useState(false);
5363
5384
  const [rewindHandoffOpen, setRewindHandoffOpen] = useState(false);
5385
+
5386
+ useEffect(() => {
5387
+ if (surface !== "settings") return;
5388
+ setSettingsTab(initialDesktopSettingsTab(initialSettingsTab));
5389
+ }, [initialSettingsTab, surface]);
5390
+
5364
5391
  const featureConfig = useFeatureConfig();
5365
5392
  const updateStatus = useUpdateStatus();
5366
5393
  const voiceEnabled = featureConfig?.voiceEnabled !== false;
@@ -7491,22 +7518,15 @@ function Setup({
7491
7518
  "Choose the downloaded model used for offline transcription."
7492
7519
  }
7493
7520
  control={
7494
- <select
7495
- id="whisper-model"
7496
- className="setup-select"
7497
- value={whisperModelId}
7498
- onChange={(event) => whisper.setModelId(event.target.value)}
7521
+ <WhisperModelPicker
7522
+ models={whisperModels}
7523
+ modelId={whisperModelId}
7524
+ onChange={whisper.setModelId}
7499
7525
  disabled={
7500
7526
  whisperModels.length === 0 ||
7501
7527
  whisperStatus?.state === "downloading"
7502
7528
  }
7503
- >
7504
- {whisperModels.map((model) => (
7505
- <option key={model.id} value={model.id}>
7506
- {model.title} · {model.sizeMb} MB — {model.description}
7507
- </option>
7508
- ))}
7509
- </select>
7529
+ />
7510
7530
  }
7511
7531
  >
7512
7532
  <>
@@ -8061,6 +8081,83 @@ function SettingLabel({
8061
8081
  );
8062
8082
  }
8063
8083
 
8084
+ function WhisperModelPicker({
8085
+ models,
8086
+ modelId,
8087
+ onChange,
8088
+ disabled,
8089
+ }: {
8090
+ models: WhisperModelOption[];
8091
+ modelId: string;
8092
+ onChange: (modelId: string) => void;
8093
+ disabled: boolean;
8094
+ }) {
8095
+ const [open, setOpen] = useState(false);
8096
+ const selectedModel = models.find((model) => model.id === modelId);
8097
+
8098
+ return (
8099
+ <PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
8100
+ <PopoverPrimitive.Trigger asChild>
8101
+ <button
8102
+ type="button"
8103
+ className="whisper-model-picker-trigger"
8104
+ disabled={disabled}
8105
+ aria-haspopup="listbox"
8106
+ aria-expanded={open}
8107
+ >
8108
+ <span className="whisper-model-picker-trigger-label">
8109
+ {selectedModel
8110
+ ? whisperModelOptionLabel(selectedModel)
8111
+ : "No models available"}
8112
+ </span>
8113
+ <IconChevronDown size={15} stroke={1.8} aria-hidden="true" />
8114
+ </button>
8115
+ </PopoverPrimitive.Trigger>
8116
+ <PopoverPrimitive.Portal>
8117
+ <PopoverPrimitive.Content
8118
+ side="bottom"
8119
+ align="end"
8120
+ sideOffset={6}
8121
+ collisionPadding={12}
8122
+ className="whisper-model-picker"
8123
+ data-popover-overlay="true"
8124
+ >
8125
+ <div
8126
+ className="whisper-model-picker-list"
8127
+ role="listbox"
8128
+ aria-label="Whisper model"
8129
+ >
8130
+ {models.map((model) => {
8131
+ const selected = model.id === modelId;
8132
+ return (
8133
+ <button
8134
+ key={model.id}
8135
+ type="button"
8136
+ role="option"
8137
+ aria-selected={selected}
8138
+ className={`whisper-model-picker-option ${selected ? "is-selected" : ""}`}
8139
+ onClick={() => {
8140
+ onChange(model.id);
8141
+ setOpen(false);
8142
+ }}
8143
+ >
8144
+ <span className="whisper-model-picker-option-title">
8145
+ <span>{whisperModelOptionLabel(model)}</span>
8146
+ {selected ? <IconCheck size={15} stroke={2.1} /> : null}
8147
+ </span>
8148
+ <span className="whisper-model-picker-option-description">
8149
+ {model.description}
8150
+ </span>
8151
+ </button>
8152
+ );
8153
+ })}
8154
+ </div>
8155
+ </PopoverPrimitive.Content>
8156
+ </PopoverPrimitive.Portal>
8157
+ </PopoverPrimitive.Root>
8158
+ );
8159
+ }
8160
+
8064
8161
  function WhisperModelStatusRow({
8065
8162
  status,
8066
8163
  enabled,
@@ -83,6 +83,10 @@ import {
83
83
  type PauseTransitionQueue,
84
84
  } from "./pause-transition";
85
85
  import { reconcileProcessingBackup } from "./processing-backup-recovery";
86
+ import {
87
+ buildCreateRecordingRequestBody,
88
+ type NativeRecordingRequestOptions,
89
+ } from "./recording-request";
86
90
  import {
87
91
  guardRecordingStart,
88
92
  RECORDING_START_TIMEOUT_MS,
@@ -143,7 +147,6 @@ const STREAM_CHUNK_BYTES = 15 * GCS_CHUNK_ALIGN_BYTES; // 3.75 MiB
143
147
  // - "streaming" — server has a resumable session; flush aligned chunks live.
144
148
  // - "buffered" — per-blob chunks staged server-side, assembled on finalize.
145
149
  type UploadMode = "streaming" | "buffered";
146
- type StreamingUploadClient = "desktop-native";
147
150
  const CLOUD_CAPTURE_FRAME_RATE = 24;
148
151
  const CLOUD_CAPTURE_MAX_WIDTH = 1920;
149
152
  const CLOUD_CAPTURE_MAX_HEIGHT = 1080;
@@ -1538,13 +1541,7 @@ async function createServerRecording(
1538
1541
  hasCamera: boolean,
1539
1542
  hasAudio: boolean,
1540
1543
  titleContext?: CaptureTitleResult,
1541
- options?: {
1542
- mimeType?: string;
1543
- requestStreaming?: boolean;
1544
- streamingUploadClient?: StreamingUploadClient;
1545
- visibility?: "public" | "private";
1546
- signal?: AbortSignal;
1547
- },
1544
+ options?: NativeRecordingRequestOptions & { signal?: AbortSignal },
1548
1545
  ) {
1549
1546
  const url = `${serverUrl.replace(/\/+$/, "")}/_agent-native/actions/create-recording`;
1550
1547
  console.log("[clips-recorder] POST", url, {
@@ -1564,27 +1561,14 @@ async function createServerRecording(
1564
1561
  // cookies aren't needed.
1565
1562
  credentials: "include",
1566
1563
  signal: options?.signal,
1567
- body: JSON.stringify({
1568
- hasCamera,
1569
- hasAudio,
1570
- spaceIds: [],
1571
- visibility: options?.visibility ?? "public",
1572
- ...(options?.requestStreaming
1573
- ? {
1574
- requestStreaming: true,
1575
- mimeType: options.mimeType,
1576
- streamingUploadClient: options.streamingUploadClient,
1577
- }
1578
- : {}),
1579
- ...(titleContext
1580
- ? {
1581
- title: titleContext.title,
1582
- titleSource: titleContext.titleSource,
1583
- sourceAppName: titleContext.sourceAppName,
1584
- sourceWindowTitle: titleContext.sourceWindowTitle,
1585
- }
1586
- : {}),
1587
- }),
1564
+ body: JSON.stringify(
1565
+ buildCreateRecordingRequestBody(
1566
+ hasCamera,
1567
+ hasAudio,
1568
+ titleContext,
1569
+ options,
1570
+ ),
1571
+ ),
1588
1572
  });
1589
1573
  } catch (err) {
1590
1574
  const msg = err instanceof Error ? err.message : String(err);
@@ -0,0 +1,39 @@
1
+ import type { CaptureTitleResult } from "./recording-title";
2
+
3
+ export type NativeRecordingVisibility = "private" | "org" | "public";
4
+
5
+ export interface NativeRecordingRequestOptions {
6
+ mimeType?: string;
7
+ requestStreaming?: boolean;
8
+ streamingUploadClient?: "desktop-native";
9
+ visibility?: NativeRecordingVisibility;
10
+ }
11
+
12
+ export function buildCreateRecordingRequestBody(
13
+ hasCamera: boolean,
14
+ hasAudio: boolean,
15
+ titleContext?: CaptureTitleResult,
16
+ options?: NativeRecordingRequestOptions,
17
+ ): Record<string, unknown> {
18
+ return {
19
+ hasCamera,
20
+ hasAudio,
21
+ spaceIds: [],
22
+ ...(options?.visibility ? { visibility: options.visibility } : {}),
23
+ ...(options?.requestStreaming
24
+ ? {
25
+ requestStreaming: true,
26
+ mimeType: options.mimeType,
27
+ streamingUploadClient: options.streamingUploadClient,
28
+ }
29
+ : {}),
30
+ ...(titleContext
31
+ ? {
32
+ title: titleContext.title,
33
+ titleSource: titleContext.titleSource,
34
+ sourceAppName: titleContext.sourceAppName,
35
+ sourceWindowTitle: titleContext.sourceWindowTitle,
36
+ }
37
+ : {}),
38
+ };
39
+ }
@@ -0,0 +1,11 @@
1
+ export type DesktopSettingsTab =
2
+ | "general"
3
+ | "recording"
4
+ | "meetings"
5
+ | "dictation";
6
+
7
+ export function initialDesktopSettingsTab(
8
+ tab?: DesktopSettingsTab,
9
+ ): DesktopSettingsTab {
10
+ return tab ?? "general";
11
+ }
@@ -0,0 +1,7 @@
1
+ import type { WhisperModelOption } from "../hooks/useWhisperSettings";
2
+
3
+ export function whisperModelOptionLabel(
4
+ model: Pick<WhisperModelOption, "title" | "sizeMb">,
5
+ ): string {
6
+ return `${model.title} · ${model.sizeMb} MB`;
7
+ }
@@ -362,6 +362,10 @@ body[data-clips-route="recording-pill"] #root {
362
362
  width: min(100%, 300px);
363
363
  }
364
364
 
365
+ .settings-row-control > .whisper-model-picker-trigger {
366
+ width: min(100%, 300px);
367
+ }
368
+
365
369
  .settings-row-control > .setup-textarea {
366
370
  width: 100%;
367
371
  }
@@ -396,6 +400,119 @@ body[data-clips-route="recording-pill"] #root {
396
400
  white-space: nowrap;
397
401
  }
398
402
 
403
+ .whisper-model-picker-trigger {
404
+ display: flex;
405
+ align-items: center;
406
+ justify-content: space-between;
407
+ gap: 8px;
408
+ min-width: 0;
409
+ padding: 10px 12px;
410
+ border: 1px solid var(--border-strong);
411
+ border-radius: var(--radius-sm);
412
+ background: var(--bg);
413
+ color: var(--fg);
414
+ cursor: pointer;
415
+ font-size: 13px;
416
+ text-align: left;
417
+ outline: none;
418
+ transition:
419
+ border-color 120ms,
420
+ box-shadow 120ms;
421
+ }
422
+
423
+ .whisper-model-picker-trigger:hover:not(:disabled) {
424
+ border-color: var(--fg-muted);
425
+ }
426
+
427
+ .whisper-model-picker-trigger:focus-visible {
428
+ border-color: var(--brand);
429
+ box-shadow: 0 0 0 3px var(--brand-ring);
430
+ }
431
+
432
+ .whisper-model-picker-trigger:disabled {
433
+ opacity: 0.48;
434
+ background: var(--surface);
435
+ cursor: not-allowed;
436
+ }
437
+
438
+ .whisper-model-picker-trigger-label {
439
+ min-width: 0;
440
+ overflow: hidden;
441
+ text-overflow: ellipsis;
442
+ white-space: nowrap;
443
+ }
444
+
445
+ .whisper-model-picker {
446
+ z-index: 80;
447
+ width: min(420px, calc(100vw - 24px));
448
+ max-height: min(360px, calc(100vh - 24px));
449
+ overflow-x: hidden;
450
+ overflow-y: auto;
451
+ padding: 6px;
452
+ border: 1px solid var(--border);
453
+ border-radius: var(--radius);
454
+ background: var(--bg);
455
+ color: var(--fg);
456
+ box-shadow: var(--shadow-md);
457
+ outline: none;
458
+ }
459
+
460
+ .whisper-model-picker-list {
461
+ display: grid;
462
+ gap: 2px;
463
+ }
464
+
465
+ .whisper-model-picker-option {
466
+ display: grid;
467
+ gap: 4px;
468
+ width: 100%;
469
+ min-width: 0;
470
+ padding: 9px 10px;
471
+ border: 1px solid transparent;
472
+ border-radius: var(--radius-sm);
473
+ background: transparent;
474
+ color: var(--fg-muted);
475
+ cursor: pointer;
476
+ text-align: left;
477
+ }
478
+
479
+ .whisper-model-picker-option:hover,
480
+ .whisper-model-picker-option.is-selected {
481
+ background: var(--surface-hover);
482
+ color: var(--fg);
483
+ }
484
+
485
+ .whisper-model-picker-option.is-selected {
486
+ border-color: var(--border);
487
+ }
488
+
489
+ .whisper-model-picker-option:focus-visible {
490
+ outline: none;
491
+ box-shadow: 0 0 0 3px var(--brand-ring);
492
+ }
493
+
494
+ .whisper-model-picker-option-title {
495
+ display: flex;
496
+ align-items: center;
497
+ justify-content: space-between;
498
+ gap: 10px;
499
+ min-width: 0;
500
+ color: var(--fg);
501
+ font-size: 12px;
502
+ font-weight: 650;
503
+ }
504
+
505
+ .whisper-model-picker-option-title > span {
506
+ min-width: 0;
507
+ }
508
+
509
+ .whisper-model-picker-option-description {
510
+ color: var(--fg-muted);
511
+ font-size: 11px;
512
+ line-height: 1.4;
513
+ white-space: normal;
514
+ }
515
+
399
516
  .settings-url-input {
400
517
  width: 100%;
401
518
  }
@@ -3,6 +3,10 @@ import { organizations } from "@agent-native/core/org";
3
3
  import { registerShareableResource } from "@agent-native/core/sharing";
4
4
  import { eq } from "drizzle-orm";
5
5
 
6
+ import {
7
+ CLIPS_MEETING_AGENT_CONTEXT_ENDPOINT,
8
+ CLIPS_MEETING_AGENT_RESOURCE_KIND,
9
+ } from "../../shared/meeting-agent-access.js";
6
10
  import {
7
11
  absoluteUrl,
8
12
  recordingShareEmailExtras,
@@ -75,6 +79,12 @@ registerShareableResource({
75
79
  displayName: "Meeting",
76
80
  titleColumn: "title",
77
81
  getResourcePath: (meeting) => `/meetings/${meeting.id}`,
82
+ agentReadable: {
83
+ resourceKind: CLIPS_MEETING_AGENT_RESOURCE_KIND,
84
+ getContextPath: () => CLIPS_MEETING_AGENT_CONTEXT_ENDPOINT,
85
+ getPagePath: (meeting) =>
86
+ `/share/meeting/${encodeURIComponent(meeting.id)}`,
87
+ },
78
88
  getDb,
79
89
  });
80
90
 
@@ -216,6 +216,7 @@ export const recordings = table("recordings", {
216
216
 
217
217
  createdAt: text("created_at").notNull().default(now()),
218
218
  updatedAt: text("updated_at").notNull().default(now()),
219
+ mediaUpdatedAt: text("media_updated_at").notNull().default(now()),
219
220
  archivedAt: text("archived_at"),
220
221
  trashedAt: text("trashed_at"),
221
222
 
@@ -11,6 +11,7 @@ import { HTTPError, type H3Event } from "h3";
11
11
 
12
12
  import {
13
13
  CLIPS_USER_PREFS_KEY,
14
+ DEFAULT_CLIPS_RECORDING_VISIBILITY,
14
15
  type ClipsUserPrefs,
15
16
  } from "../../shared/clips-ai-prefs.js";
16
17
  import { getDb, schema } from "../db/index.js";
@@ -67,7 +68,8 @@ export type OrganizationAccessRole = "owner" | "admin" | "member";
67
68
 
68
69
  export type RecordingVisibility = "private" | "org" | "public";
69
70
 
70
- export const DEFAULT_RECORDING_VISIBILITY: RecordingVisibility = "public";
71
+ export const DEFAULT_RECORDING_VISIBILITY: RecordingVisibility =
72
+ DEFAULT_CLIPS_RECORDING_VISIBILITY;
71
73
 
72
74
  export function isRecordingVisibility(
73
75
  value: unknown,
@@ -0,0 +1,90 @@
1
+ import {
2
+ appStateGet,
3
+ deleteAppState,
4
+ writeAppState,
5
+ } from "@agent-native/core/application-state";
6
+
7
+ export const SEEKABLE_REPAIR_STATE_PREFIX = "recording-seekable-repair-";
8
+ export const SEEKABLE_REPAIR_PENDING_TTL_MS = 10 * 60 * 1000;
9
+
10
+ export type SeekableRepairMarker = {
11
+ recordingId: string;
12
+ status: "pending";
13
+ videoUrl: string;
14
+ startedAt: string;
15
+ expiresAt: string;
16
+ };
17
+
18
+ export function seekableRepairStateKey(recordingId: string): string {
19
+ return `${SEEKABLE_REPAIR_STATE_PREFIX}${recordingId}`;
20
+ }
21
+
22
+ export function parseSeekableRepairMarker(
23
+ value: unknown,
24
+ ): SeekableRepairMarker | null {
25
+ if (!value || typeof value !== "object") return null;
26
+ const state = value as Record<string, unknown>;
27
+ if (
28
+ typeof state.recordingId !== "string" ||
29
+ !state.recordingId ||
30
+ state.status !== "pending" ||
31
+ typeof state.videoUrl !== "string" ||
32
+ !state.videoUrl ||
33
+ typeof state.startedAt !== "string" ||
34
+ !Number.isFinite(Date.parse(state.startedAt)) ||
35
+ typeof state.expiresAt !== "string" ||
36
+ !Number.isFinite(Date.parse(state.expiresAt))
37
+ ) {
38
+ return null;
39
+ }
40
+ return state as SeekableRepairMarker;
41
+ }
42
+
43
+ export async function markSeekableRepairPending(params: {
44
+ recordingId: string;
45
+ videoUrl: string;
46
+ }): Promise<void> {
47
+ const startedAt = Date.now();
48
+ await writeAppState(seekableRepairStateKey(params.recordingId), {
49
+ recordingId: params.recordingId,
50
+ status: "pending",
51
+ videoUrl: params.videoUrl,
52
+ startedAt: new Date(startedAt).toISOString(),
53
+ expiresAt: new Date(
54
+ startedAt + SEEKABLE_REPAIR_PENDING_TTL_MS,
55
+ ).toISOString(),
56
+ });
57
+ }
58
+
59
+ export async function clearSeekableRepairPending(
60
+ recordingId: string,
61
+ ): Promise<void> {
62
+ await deleteAppState(seekableRepairStateKey(recordingId));
63
+ }
64
+
65
+ export async function isSeekableRepairPending(args: {
66
+ ownerEmail: string;
67
+ recordingId: string;
68
+ recordingStatus: string;
69
+ videoUrl?: string | null;
70
+ }): Promise<boolean> {
71
+ if (args.recordingStatus !== "ready") return false;
72
+
73
+ const value = await appStateGet(
74
+ args.ownerEmail,
75
+ seekableRepairStateKey(args.recordingId),
76
+ );
77
+ if (value === null) return false;
78
+ const marker = parseSeekableRepairMarker(value);
79
+ if (!marker) {
80
+ throw new Error(
81
+ `Malformed seekable repair marker for recording ${args.recordingId}`,
82
+ );
83
+ }
84
+ if (marker.recordingId !== args.recordingId) return false;
85
+ if (args.videoUrl !== undefined && marker.videoUrl !== args.videoUrl) {
86
+ return false;
87
+ }
88
+
89
+ return Date.parse(marker.expiresAt) > Date.now();
90
+ }
@@ -1154,6 +1154,11 @@ export const migrations = runMigrations(
1154
1154
  // re-derived by regex over the message.
1155
1155
  sql: `ALTER TABLE recording_transcripts ADD COLUMN failure_code TEXT`,
1156
1156
  },
1157
+ {
1158
+ version: 65,
1159
+ name: "recording-media-updated-at",
1160
+ sql: `ALTER TABLE recordings ADD COLUMN IF NOT EXISTS media_updated_at TEXT NOT NULL DEFAULT (datetime('now'))`,
1161
+ },
1157
1162
  ],
1158
1163
  { table: "clips_migrations" },
1159
1164
  );