@agent-native/core 0.114.5 → 0.114.7

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 (210) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/mcp-config-writers.ts +6 -4
  5. package/corpus/core/src/cli/mcp.ts +124 -0
  6. package/corpus/core/src/cli/skills-content/help.ts +4 -3
  7. package/corpus/core/src/cli/skills-content/index.ts +1 -0
  8. package/corpus/core/src/cli/skills-content/rewind-skill.ts +52 -0
  9. package/corpus/core/src/cli/skills.ts +115 -13
  10. package/corpus/core/src/client/resources/ResourceTree.tsx +11 -5
  11. package/corpus/core/src/client/resources/ResourcesPanel.tsx +141 -18
  12. package/corpus/core/src/mcp/screen-memory-stdio.ts +1360 -104
  13. package/corpus/core/src/mcp-client/index.ts +9 -0
  14. package/corpus/core/src/mcp-client/screen-memory-local.ts +847 -13
  15. package/corpus/templates/clips/actions/apply-rewind-extension.ts +195 -0
  16. package/corpus/templates/clips/actions/delete-agent-recording-if-unpromoted.ts +64 -0
  17. package/corpus/templates/clips/actions/get-rewind-extension-request.ts +22 -0
  18. package/corpus/templates/clips/actions/get-screen-memory-status.ts +18 -2
  19. package/corpus/templates/clips/actions/list-rewind-extension-requests.ts +52 -0
  20. package/corpus/templates/clips/actions/make-recording-private-for-rewind.ts +55 -0
  21. package/corpus/templates/clips/actions/query-screen-memory-context.ts +4 -4
  22. package/corpus/templates/clips/actions/request-rewind-extension.ts +81 -0
  23. package/corpus/templates/clips/actions/update-rewind-extension-request.ts +47 -0
  24. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +25 -0
  25. package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +36 -0
  26. package/corpus/templates/clips/app/components/editor/rewind-extension-dialog.tsx +338 -0
  27. package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +1 -16
  28. package/corpus/templates/clips/app/components/editor/timeline.tsx +26 -0
  29. package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +1 -2
  30. package/corpus/templates/clips/app/i18n/en-US.ts +18 -0
  31. package/corpus/templates/clips/app/lib/ffmpeg-export.ts +19 -12
  32. package/corpus/templates/clips/app/lib/rewind-visibility.ts +31 -0
  33. package/corpus/templates/clips/app/lib/timestamp-mapping.ts +7 -0
  34. package/corpus/templates/clips/app/lib/upload-file-client.ts +17 -0
  35. package/corpus/templates/clips/changelog/2026-07-14-rewind-keeps-recent-screen-context-local-and-turns-it-into-clips.md +6 -0
  36. package/corpus/templates/clips/changelog/2026-07-17-active-clips-keep-rewind-stable-and-settings-clear.md +12 -0
  37. package/corpus/templates/clips/changelog/2026-07-17-clicking-the-clips-icon-during-a-recording-now-opens-the-app.md +6 -0
  38. package/corpus/templates/clips/changelog/2026-07-17-default-mic-now-uses-the-mac-default-and-transcription-shares-one-capture.md +6 -0
  39. package/corpus/templates/clips/changelog/2026-07-17-full-screen-clips-finalize-even-when-screencapturekit-stop-hangs.md +6 -0
  40. package/corpus/templates/clips/changelog/2026-07-17-normal-full-screen-clips-no-longer-stop-immediately-after-th.md +6 -0
  41. package/corpus/templates/clips/changelog/2026-07-17-rewind-can-hand-one-bounded-private-clip-to-your-agent.md +12 -0
  42. package/corpus/templates/clips/changelog/2026-07-17-rewind-home-can-copy-a-local-agent-prompt.md +6 -0
  43. package/corpus/templates/clips/changelog/2026-07-17-rewind-now-has-a-calm-home-status-focused-first-run-consent-.md +6 -0
  44. package/corpus/templates/clips/changelog/2026-07-17-rewind-settings-are-smaller-clearer-and-agent-first.md +6 -0
  45. package/corpus/templates/clips/changelog/2026-07-17-the-full-clips-interface-stays-available-while-recording.md +6 -0
  46. package/corpus/templates/clips/changelog/2026-07-18-rewind-organizes-recent-work-into-local-searchable-chapters.md +8 -0
  47. package/corpus/templates/clips/changelog/2026-07-18-searchable-rewind-chapters.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-19-rewind-backed-recordings-now-start-immediately-after-the-cou.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-20-opening-zoom-meeting-launches-native-app.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-20-rewind-can-set-up-your-local-agent-once-so-future-requests-w.md +6 -0
  51. package/corpus/templates/clips/desktop/package.json +1 -0
  52. package/corpus/templates/clips/desktop/scripts/sign-macos-local.ts +12 -1
  53. package/corpus/templates/clips/desktop/src/app.tsx +2478 -223
  54. package/corpus/templates/clips/desktop/src/components/ReadinessPanel.tsx +21 -1
  55. package/corpus/templates/clips/desktop/src/components/Switch.tsx +3 -0
  56. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +156 -24
  57. package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +6 -7
  58. package/corpus/templates/clips/desktop/src/lib/open-meeting-join-url.ts +22 -0
  59. package/corpus/templates/clips/desktop/src/lib/recorder.ts +1763 -1160
  60. package/corpus/templates/clips/desktop/src/lib/rewind-agent-prompt.ts +9 -0
  61. package/corpus/templates/clips/desktop/src/lib/rewind-recording-start.ts +20 -0
  62. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  63. package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +14 -14
  64. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +2 -3
  65. package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +4 -4
  66. package/corpus/templates/clips/desktop/src/overlays/preparing.tsx +13 -0
  67. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +27 -2
  68. package/corpus/templates/clips/desktop/src/shared/config.ts +16 -0
  69. package/corpus/templates/clips/desktop/src/styles.css +956 -134
  70. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +5 -0
  71. package/corpus/templates/clips/desktop/src-tauri/build.rs +69 -1
  72. package/corpus/templates/clips/desktop/src-tauri/src/accessibility.rs +386 -12
  73. package/corpus/templates/clips/desktop/src-tauri/src/capture_audio_bus.rs +382 -0
  74. package/corpus/templates/clips/desktop/src-tauri/src/capture_graph.rs +777 -0
  75. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +249 -22
  76. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +148 -1
  77. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +58 -4
  78. package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +13 -1
  79. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +1505 -51
  80. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +124 -0
  81. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1006 -95
  82. package/corpus/templates/clips/desktop/src-tauri/src/rewind_capture_suspension.rs +165 -0
  83. package/corpus/templates/clips/desktop/src-tauri/src/rewind_chapters.rs +2434 -0
  84. package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +1650 -0
  85. package/corpus/templates/clips/desktop/src-tauri/src/rewind_egress.rs +643 -0
  86. package/corpus/templates/clips/desktop/src-tauri/src/rewind_local_ask.rs +803 -0
  87. package/corpus/templates/clips/desktop/src-tauri/src/rewind_meeting_history.rs +595 -0
  88. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +2900 -91
  89. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_ocr.rs +360 -0
  90. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_transcript.rs +408 -0
  91. package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +114 -33
  92. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +2 -2
  93. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +13 -3
  94. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +4 -1
  95. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +7 -13
  96. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +198 -11
  97. package/corpus/templates/clips/desktop/src-tauri/tauri.alpha.conf.json +17 -0
  98. package/corpus/templates/clips/shared/meeting-join-url.ts +0 -5
  99. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +9 -1
  100. package/corpus/templates/content/AGENTS.md +4 -1
  101. package/corpus/templates/content/actions/_content-spaces.ts +6 -1
  102. package/corpus/templates/content/actions/_database-row-batch.ts +15 -2
  103. package/corpus/templates/content/actions/_database-utils.ts +83 -15
  104. package/corpus/templates/content/actions/add-database-item.ts +3 -0
  105. package/corpus/templates/content/actions/connect-local-folder-source.ts +2 -1
  106. package/corpus/templates/content/actions/delete-content-database.ts +12 -9
  107. package/corpus/templates/content/actions/delete-database-items.ts +3 -2
  108. package/corpus/templates/content/actions/delete-document.ts +233 -6
  109. package/corpus/templates/content/actions/duplicate-database-item.ts +1 -0
  110. package/corpus/templates/content/actions/get-document.ts +4 -1
  111. package/corpus/templates/content/actions/list-content-databases.ts +1 -0
  112. package/corpus/templates/content/actions/list-documents.ts +1 -0
  113. package/corpus/templates/content/actions/list-trashed-content-databases.ts +8 -2
  114. package/corpus/templates/content/actions/list-trashed-documents.ts +50 -0
  115. package/corpus/templates/content/actions/permanently-delete-document.ts +28 -0
  116. package/corpus/templates/content/actions/pull-document.ts +15 -2
  117. package/corpus/templates/content/actions/restore-content-database.ts +48 -11
  118. package/corpus/templates/content/actions/restore-document.ts +28 -0
  119. package/corpus/templates/content/actions/search-documents.ts +2 -1
  120. package/corpus/templates/content/actions/view-screen.ts +2 -1
  121. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +149 -55
  122. package/corpus/templates/content/app/components/editor/database/GalleryView.tsx +7 -2
  123. package/corpus/templates/content/app/components/editor/database/ListView.tsx +7 -2
  124. package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +3 -0
  125. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +26 -5
  126. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +243 -163
  127. package/corpus/templates/content/app/components/sidebar/WorkspaceSourceMenu.tsx +154 -0
  128. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +11 -0
  129. package/corpus/templates/content/app/hooks/use-content-database.ts +6 -0
  130. package/corpus/templates/content/app/hooks/use-documents.ts +57 -0
  131. package/corpus/templates/content/app/i18n-data.ts +83 -2
  132. package/corpus/templates/content/app/routes/_app.local-files.tsx +8 -1
  133. package/corpus/templates/content/changelog/2026-07-19-pages-now-move-to-a-reversible-trash-and-the-organization-pi.md +6 -0
  134. package/corpus/templates/content/changelog/2026-07-20-trash-actions-now-preserve-independent-archived-pages-enforc.md +6 -0
  135. package/corpus/templates/content/changelog/2026-07-20-workspace-toggles-now-stay-independently-open-or-closed-and-.md +6 -0
  136. package/corpus/templates/content/changelog/2026-07-20-workspaces-can-be-added-from-a-blank-workspace-or-a-connecte.md +6 -0
  137. package/corpus/templates/content/parity/matrix.md +1 -1
  138. package/corpus/templates/content/parity/matrix.ts +3 -0
  139. package/corpus/templates/content/server/db/schema.ts +2 -0
  140. package/corpus/templates/content/server/lib/public-documents.ts +8 -2
  141. package/corpus/templates/content/server/plugins/db.ts +52 -0
  142. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +2 -2
  143. package/corpus/templates/content/shared/api.ts +10 -0
  144. package/corpus/toolkit/CHANGELOG.md +6 -0
  145. package/corpus/toolkit/package.json +1 -1
  146. package/corpus/toolkit/src/ui/progress.tsx +3 -2
  147. package/dist/cli/mcp-config-writers.d.ts +2 -2
  148. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  149. package/dist/cli/mcp-config-writers.js +6 -6
  150. package/dist/cli/mcp-config-writers.js.map +1 -1
  151. package/dist/cli/mcp.d.ts +14 -0
  152. package/dist/cli/mcp.d.ts.map +1 -1
  153. package/dist/cli/mcp.js +79 -0
  154. package/dist/cli/mcp.js.map +1 -1
  155. package/dist/cli/skills-content/help.d.ts +1 -1
  156. package/dist/cli/skills-content/help.d.ts.map +1 -1
  157. package/dist/cli/skills-content/help.js +4 -3
  158. package/dist/cli/skills-content/help.js.map +1 -1
  159. package/dist/cli/skills-content/index.d.ts +1 -0
  160. package/dist/cli/skills-content/index.d.ts.map +1 -1
  161. package/dist/cli/skills-content/index.js +1 -0
  162. package/dist/cli/skills-content/index.js.map +1 -1
  163. package/dist/cli/skills-content/rewind-skill.d.ts +2 -0
  164. package/dist/cli/skills-content/rewind-skill.d.ts.map +1 -0
  165. package/dist/cli/skills-content/rewind-skill.js +53 -0
  166. package/dist/cli/skills-content/rewind-skill.js.map +1 -0
  167. package/dist/cli/skills.d.ts +6 -0
  168. package/dist/cli/skills.d.ts.map +1 -1
  169. package/dist/cli/skills.js +90 -14
  170. package/dist/cli/skills.js.map +1 -1
  171. package/dist/client/resources/ResourceTree.d.ts +3 -1
  172. package/dist/client/resources/ResourceTree.d.ts.map +1 -1
  173. package/dist/client/resources/ResourceTree.js +4 -4
  174. package/dist/client/resources/ResourceTree.js.map +1 -1
  175. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  176. package/dist/client/resources/ResourcesPanel.js +55 -6
  177. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  178. package/dist/collab/routes.d.ts +1 -1
  179. package/dist/collab/struct-routes.d.ts +1 -1
  180. package/dist/mcp/screen-memory-stdio.d.ts +365 -0
  181. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  182. package/dist/mcp/screen-memory-stdio.js +1014 -91
  183. package/dist/mcp/screen-memory-stdio.js.map +1 -1
  184. package/dist/mcp-client/index.d.ts +1 -1
  185. package/dist/mcp-client/index.d.ts.map +1 -1
  186. package/dist/mcp-client/index.js +1 -1
  187. package/dist/mcp-client/index.js.map +1 -1
  188. package/dist/mcp-client/screen-memory-local.d.ts +90 -0
  189. package/dist/mcp-client/screen-memory-local.d.ts.map +1 -1
  190. package/dist/mcp-client/screen-memory-local.js +590 -12
  191. package/dist/mcp-client/screen-memory-local.js.map +1 -1
  192. package/dist/notifications/routes.d.ts +2 -2
  193. package/dist/observability/routes.d.ts +5 -5
  194. package/dist/progress/routes.d.ts +1 -1
  195. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  196. package/dist/resources/handlers.d.ts +1 -1
  197. package/dist/secrets/routes.d.ts +9 -9
  198. package/dist/server/transcribe-voice.d.ts +1 -1
  199. package/package.json +2 -2
  200. package/src/cli/mcp-config-writers.ts +6 -4
  201. package/src/cli/mcp.ts +124 -0
  202. package/src/cli/skills-content/help.ts +4 -3
  203. package/src/cli/skills-content/index.ts +1 -0
  204. package/src/cli/skills-content/rewind-skill.ts +52 -0
  205. package/src/cli/skills.ts +115 -13
  206. package/src/client/resources/ResourceTree.tsx +11 -5
  207. package/src/client/resources/ResourcesPanel.tsx +141 -18
  208. package/src/mcp/screen-memory-stdio.ts +1360 -104
  209. package/src/mcp-client/index.ts +9 -0
  210. package/src/mcp-client/screen-memory-local.ts +847 -13
@@ -157,6 +157,26 @@ export function ReadinessPanel({
157
157
  }
158
158
  }, []);
159
159
 
160
+ const requestOrOpenPermission = useCallback(
161
+ async (pane: MacosPrivacyPane) => {
162
+ if (mac && pane === "screen") {
163
+ try {
164
+ const granted = await invoke<boolean>(
165
+ "system_audio_request_permission",
166
+ );
167
+ await checkStatuses();
168
+ if (granted) return;
169
+ } catch {
170
+ // Fall through to the dedicated privacy pane. The request API may
171
+ // be unavailable on an older macOS build or the user may already
172
+ // have denied the prompt once.
173
+ }
174
+ }
175
+ onOpenPermission(pane);
176
+ },
177
+ [checkStatuses, mac, onOpenPermission],
178
+ );
179
+
160
180
  useEffect(() => {
161
181
  if (open && !statuses && mac) checkStatuses();
162
182
  }, [open, statuses, mac, checkStatuses]);
@@ -219,7 +239,7 @@ export function ReadinessPanel({
219
239
  <button
220
240
  type="button"
221
241
  className="readiness-open-button"
222
- onClick={() => onOpenPermission(item.pane)}
242
+ onClick={() => void requestOrOpenPermission(item.pane)}
223
243
  >
224
244
  Open
225
245
  </button>
@@ -2,10 +2,12 @@ export function Switch({
2
2
  on,
3
3
  onChange,
4
4
  label,
5
+ disabled = false,
5
6
  }: {
6
7
  on: boolean;
7
8
  onChange: (v: boolean) => void;
8
9
  label: string;
10
+ disabled?: boolean;
9
11
  }) {
10
12
  return (
11
13
  <button
@@ -14,6 +16,7 @@ export function Switch({
14
16
  role="switch"
15
17
  aria-checked={on}
16
18
  aria-label={label}
19
+ disabled={disabled}
17
20
  onClick={() => onChange(!on)}
18
21
  >
19
22
  <span className="switch-thumb" aria-hidden />
@@ -23,6 +23,8 @@ export interface MeetingTranscriptionPayload {
23
23
  meetingId: string;
24
24
  joinUrl?: string | null;
25
25
  reason?: "user" | "calendar-auto" | string;
26
+ scheduledStart?: string | null;
27
+ includeFromMeetingStart?: boolean;
26
28
  }
27
29
 
28
30
  interface MeetingTranscriptionSession {
@@ -35,6 +37,9 @@ interface MeetingTranscriptionSession {
35
37
  stopping: boolean;
36
38
  paused: boolean;
37
39
  engine: TranscriptionEngine;
40
+ /** Offset local live-engine timestamps onto the scheduled meeting timeline. */
41
+ liveTimelineOffsetMs: number;
42
+ historyInFlight: Promise<void> | null;
38
43
  // Single-flight flush bookkeeping (M3): `flushInFlight` is the promise of
39
44
  // the currently-running save-browser-transcript call (or null). `flushSeq`
40
45
  // is bumped every time flushTranscript is invoked; `dirtySeq` records the
@@ -175,6 +180,9 @@ export function useMeetingTranscription({
175
180
  }
176
181
  });
177
182
  await invoke("silence_detector_stop").catch(() => {});
183
+ if (reason !== "app-quit") {
184
+ await session.historyInFlight?.catch(() => {});
185
+ }
178
186
  // Final flush waits for any in-flight flush first (flushTranscript's
179
187
  // single-flight coalescing) then sends the definitive snapshot.
180
188
  await flushTranscript().catch((err) => {
@@ -257,7 +265,29 @@ export function useMeetingTranscription({
257
265
  await stopTranscription("replaced");
258
266
  }
259
267
 
268
+ let historyPreparedRef: {
269
+ current: {
270
+ token: string;
271
+ scheduledStart: string;
272
+ capturedUntil: string;
273
+ } | null;
274
+ } = { current: null };
260
275
  try {
276
+ if (payload.includeFromMeetingStart) {
277
+ if (payload.reason !== "user" || !payload.scheduledStart) {
278
+ throw new Error(
279
+ "Include from meeting start is only available when you manually start a scheduled meeting.",
280
+ );
281
+ }
282
+ historyPreparedRef.current = await invoke<{
283
+ token: string;
284
+ scheduledStart: string;
285
+ capturedUntil: string;
286
+ }>("rewind_meeting_history_prepare", {
287
+ scheduledStart: payload.scheduledStart,
288
+ });
289
+ }
290
+
261
291
  const result = await callClipsAction<{
262
292
  meetingId?: string;
263
293
  scheduledEnd?: string | null;
@@ -286,19 +316,13 @@ export function useMeetingTranscription({
286
316
  stopping: false,
287
317
  paused: false,
288
318
  engine: "whisper",
319
+ liveTimelineOffsetMs: 0,
320
+ historyInFlight: null,
289
321
  flushInFlight: null,
290
322
  flushSeq: 0,
291
323
  dirtySeq: 0,
292
324
  };
293
325
  sessionRef.current = session;
294
- await invoke("set_recording_state", { active: true }).catch(() => {});
295
- await invoke("set_meeting_active", {
296
- active: true,
297
- meetingId: resolvedMeetingId,
298
- }).catch(() => {});
299
- emit("meetings:transcription-started", {
300
- meetingId: resolvedMeetingId,
301
- }).catch(() => {});
302
326
 
303
327
  const scheduleFlush = () => {
304
328
  if (session.flushTimer) window.clearTimeout(session.flushTimer);
@@ -325,7 +349,23 @@ export function useMeetingTranscription({
325
349
  addUnlisten(
326
350
  onFinalTranscript((event) => {
327
351
  if (sessionRef.current !== session) return;
328
- if (appendFinalTranscript(event, session.lines, session.segments)) {
352
+ const timelineEvent = session.liveTimelineOffsetMs
353
+ ? {
354
+ ...event,
355
+ segments: event.segments.map((segment) => ({
356
+ ...segment,
357
+ startMs: segment.startMs + session.liveTimelineOffsetMs,
358
+ endMs: segment.endMs + session.liveTimelineOffsetMs,
359
+ })),
360
+ }
361
+ : event;
362
+ if (
363
+ appendFinalTranscript(
364
+ timelineEvent,
365
+ session.lines,
366
+ session.segments,
367
+ )
368
+ ) {
329
369
  scheduleFlush();
330
370
  }
331
371
  }),
@@ -471,24 +511,15 @@ export function useMeetingTranscription({
471
511
  }),
472
512
  );
473
513
 
474
- // Pill init set synchronously before the pill mounts so pill-ready
475
- // re-emit has meetingId available immediately.
514
+ // Prepare the pill payload before live audio starts, but don't show a
515
+ // recording indicator or publish an active meeting until the engine
516
+ // has actually acquired its audio source. This keeps "Recording"
517
+ // truthful when model/capture startup fails.
476
518
  pendingPillInitRef.current = {
477
519
  meetingId: resolvedMeetingId,
478
520
  initialNotes: "",
479
521
  };
480
522
 
481
- await invoke("recording_pill_show", {
482
- meetingId: resolvedMeetingId,
483
- mode: "meeting",
484
- });
485
-
486
- // Immediate emit covers the reused-window case (pill already mounted).
487
- emit("clips:pill-context", {
488
- meetingId: resolvedMeetingId,
489
- mode: "meeting",
490
- }).catch(() => {});
491
-
492
523
  callClipsAction<{
493
524
  meeting?: { userNotesMd?: string };
494
525
  transcript?: { segmentsJson?: string | null } | null;
@@ -503,6 +534,11 @@ export function useMeetingTranscription({
503
534
  pendingPillInitRef.current = {
504
535
  meetingId: resolvedMeetingId,
505
536
  initialNotes,
537
+ preloadedLines: session.segments.map((segment) => ({
538
+ text: segment.text,
539
+ source: segment.source,
540
+ startMs: segment.startMs,
541
+ })),
506
542
  };
507
543
  emit("clips:meeting-notes-init", {
508
544
  meetingId: resolvedMeetingId,
@@ -534,9 +570,9 @@ export function useMeetingTranscription({
534
570
  ...preloadedSegments,
535
571
  ...session.segments,
536
572
  ];
537
- const preloadedLines = segs.map((s) => ({
573
+ const preloadedLines = session.segments.map((s) => ({
538
574
  text: s.text,
539
- source: (s.source ?? "mic") as "mic" | "system",
575
+ source: s.source,
540
576
  startMs: s.startMs,
541
577
  }));
542
578
  // Store in ref so clips:pill-ready can re-emit if the
@@ -560,6 +596,15 @@ export function useMeetingTranscription({
560
596
  })
561
597
  .catch(() => {});
562
598
 
599
+ // The local index may need a moment after the fragment fence. Anchor
600
+ // the live engine where it actually begins, not at the earlier click,
601
+ // so every stored segment remains on one honest meeting timeline.
602
+ if (payload.includeFromMeetingStart && payload.scheduledStart) {
603
+ session.liveTimelineOffsetMs = Math.max(
604
+ 0,
605
+ Date.now() - Date.parse(payload.scheduledStart),
606
+ );
607
+ }
563
608
  session.engine = await startTranscriptionEngine({
564
609
  mic: { deviceId: selectedMicId, label: selectedMicLabel },
565
610
  // macOS 15+ uses ScreenCaptureKit's independent microphone output.
@@ -568,6 +613,81 @@ export function useMeetingTranscription({
568
613
  voiceProcessing: false,
569
614
  });
570
615
 
616
+ await invoke("set_recording_state", { active: true }).catch(() => {});
617
+ await invoke("set_meeting_active", {
618
+ active: true,
619
+ meetingId: resolvedMeetingId,
620
+ }).catch(() => {});
621
+ await invoke("recording_pill_show", {
622
+ meetingId: resolvedMeetingId,
623
+ mode: "meeting",
624
+ });
625
+ // Immediate emit covers the reused-window case (pill already mounted).
626
+ emit("clips:pill-context", {
627
+ meetingId: resolvedMeetingId,
628
+ mode: "meeting",
629
+ }).catch(() => {});
630
+ emit("meetings:transcription-started", {
631
+ meetingId: resolvedMeetingId,
632
+ }).catch(() => {});
633
+
634
+ // Indexing the fenced local fragment can take tens of seconds. It runs
635
+ // after live capture is active, then prepends its bounded rows into the
636
+ // same canonical session. A local-index failure is visible but never
637
+ // tears down notes that are already recording.
638
+ if (historyPreparedRef.current) {
639
+ const prepared = historyPreparedRef.current;
640
+ const historyPromise = invoke<{
641
+ segments: SourcedTranscriptSegment[];
642
+ }>("rewind_meeting_history_collect", { token: prepared.token })
643
+ .then((history) => {
644
+ if (sessionRef.current !== session) return;
645
+ const historyLines = history.segments.map(
646
+ (segment) => `${speakerFor(segment.source)}: ${segment.text}`,
647
+ );
648
+ session.lines = [...historyLines, ...session.lines];
649
+ session.segments = [...history.segments, ...session.segments];
650
+ const preloadedLines = session.segments.map((segment) => ({
651
+ text: segment.text,
652
+ source: segment.source,
653
+ startMs: segment.startMs,
654
+ }));
655
+ if (pendingPillInitRef.current?.meetingId === resolvedMeetingId) {
656
+ pendingPillInitRef.current = {
657
+ ...pendingPillInitRef.current,
658
+ preloadedLines,
659
+ };
660
+ }
661
+ emit("clips:transcript-preload", {
662
+ lines: preloadedLines,
663
+ }).catch(() => {});
664
+ flushTranscript().catch((err) => {
665
+ console.warn(
666
+ "[clips-popover] earlier meeting transcript save failed:",
667
+ err,
668
+ );
669
+ });
670
+ })
671
+ .catch((error) => {
672
+ const message =
673
+ typeof error === "string"
674
+ ? error
675
+ : error instanceof Error
676
+ ? error.message
677
+ : "Earlier local meeting audio could not be included.";
678
+ emit("meetings:history-error", {
679
+ meetingId: resolvedMeetingId,
680
+ error: message,
681
+ }).catch(() => {});
682
+ })
683
+ .finally(() => {
684
+ if (session.historyInFlight === historyPromise) {
685
+ session.historyInFlight = null;
686
+ }
687
+ });
688
+ session.historyInFlight = historyPromise;
689
+ }
690
+
571
691
  await invoke("silence_detector_start", {
572
692
  config: silenceDetectorConfig,
573
693
  }).catch(() => {});
@@ -580,7 +700,19 @@ export function useMeetingTranscription({
580
700
 
581
701
  emit("meetings:hide-notification", { meetingId }).catch(() => {});
582
702
  } catch (err) {
703
+ if (historyPreparedRef.current) {
704
+ invoke("rewind_meeting_history_cancel", {
705
+ token: historyPreparedRef.current.token,
706
+ }).catch(() => {});
707
+ }
708
+ const failedSession = sessionRef.current;
583
709
  sessionRef.current = null;
710
+ if (failedSession?.meetingId) {
711
+ await callClipsAction("stop-meeting-recording", {
712
+ meetingId: failedSession.meetingId,
713
+ }).catch(() => {});
714
+ }
715
+ pendingPillInitRef.current = null;
584
716
  await invoke("recording_pill_hide").catch(() => {});
585
717
  await invoke("set_recording_state", { active: false }).catch(() => {});
586
718
  await invoke("set_meeting_active", { active: false }).catch(() => {});
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Hosted native start sequencing: overlap Whisper start, create-recording, and
3
- * deferred SCK warm so Skip no longer waits serially on Whisper then warm.
4
- * Begin/attach must still wait for this promise (transcription settled).
2
+ * Hosted native start sequencing: create the recording, warm its one physical
3
+ * SCK capture (which registers the shared audio producer), then attach Whisper
4
+ * as a PCM subscriber. The countdown still hides this work, while the ordering
5
+ * prevents a second microphone capture from muting both consumers.
5
6
  */
6
7
  export function planNativeFullscreenWarmOverlap<
7
8
  TRecording extends { id: string },
@@ -10,12 +11,10 @@ export function planNativeFullscreenWarmOverlap<
10
11
  startTranscription: () => Promise<unknown>;
11
12
  warmMic: (recordingId: string) => Promise<unknown>;
12
13
  }): Promise<TRecording> {
13
- const transcriptionPromise = input.startTranscription();
14
14
  return (async () => {
15
15
  const created = await input.createRecording();
16
- // Deferred-output warm may overlap remaining Whisper startup; frames are
17
- // not written until begin attaches the recording output.
18
- await Promise.all([transcriptionPromise, input.warmMic(created.id)]);
16
+ await input.warmMic(created.id);
17
+ await input.startTranscription();
19
18
  return created;
20
19
  })();
21
20
  }
@@ -0,0 +1,22 @@
1
+ import { open as openExternal } from "@tauri-apps/plugin-shell";
2
+
3
+ import { resolveDesktopMeetingJoinUrl } from "./meeting-join-url";
4
+
5
+ type OpenExternal = (url: string) => Promise<void>;
6
+
7
+ export async function openMeetingJoinUrl(
8
+ joinUrl: string,
9
+ open: OpenExternal = openExternal,
10
+ ): Promise<void> {
11
+ const nativeJoinUrl = resolveDesktopMeetingJoinUrl(joinUrl);
12
+ if (nativeJoinUrl === joinUrl) {
13
+ await open(joinUrl);
14
+ return;
15
+ }
16
+
17
+ try {
18
+ await open(nativeJoinUrl);
19
+ } catch {
20
+ await open(joinUrl);
21
+ }
22
+ }