@agent-native/core 0.114.6 → 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 (141) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +6 -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/mcp/screen-memory-stdio.ts +1360 -104
  11. package/corpus/core/src/mcp-client/index.ts +9 -0
  12. package/corpus/core/src/mcp-client/screen-memory-local.ts +847 -13
  13. package/corpus/templates/clips/actions/apply-rewind-extension.ts +195 -0
  14. package/corpus/templates/clips/actions/delete-agent-recording-if-unpromoted.ts +64 -0
  15. package/corpus/templates/clips/actions/get-rewind-extension-request.ts +22 -0
  16. package/corpus/templates/clips/actions/get-screen-memory-status.ts +18 -2
  17. package/corpus/templates/clips/actions/list-rewind-extension-requests.ts +52 -0
  18. package/corpus/templates/clips/actions/make-recording-private-for-rewind.ts +55 -0
  19. package/corpus/templates/clips/actions/query-screen-memory-context.ts +4 -4
  20. package/corpus/templates/clips/actions/request-rewind-extension.ts +81 -0
  21. package/corpus/templates/clips/actions/update-rewind-extension-request.ts +47 -0
  22. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +25 -0
  23. package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +36 -0
  24. package/corpus/templates/clips/app/components/editor/rewind-extension-dialog.tsx +338 -0
  25. package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +1 -16
  26. package/corpus/templates/clips/app/components/editor/timeline.tsx +26 -0
  27. package/corpus/templates/clips/app/i18n/en-US.ts +18 -0
  28. package/corpus/templates/clips/app/lib/ffmpeg-export.ts +19 -12
  29. package/corpus/templates/clips/app/lib/rewind-visibility.ts +31 -0
  30. package/corpus/templates/clips/app/lib/timestamp-mapping.ts +7 -0
  31. package/corpus/templates/clips/app/lib/upload-file-client.ts +17 -0
  32. package/corpus/templates/clips/changelog/2026-07-14-rewind-keeps-recent-screen-context-local-and-turns-it-into-clips.md +6 -0
  33. package/corpus/templates/clips/changelog/2026-07-17-active-clips-keep-rewind-stable-and-settings-clear.md +12 -0
  34. package/corpus/templates/clips/changelog/2026-07-17-clicking-the-clips-icon-during-a-recording-now-opens-the-app.md +6 -0
  35. package/corpus/templates/clips/changelog/2026-07-17-default-mic-now-uses-the-mac-default-and-transcription-shares-one-capture.md +6 -0
  36. package/corpus/templates/clips/changelog/2026-07-17-full-screen-clips-finalize-even-when-screencapturekit-stop-hangs.md +6 -0
  37. package/corpus/templates/clips/changelog/2026-07-17-normal-full-screen-clips-no-longer-stop-immediately-after-th.md +6 -0
  38. package/corpus/templates/clips/changelog/2026-07-17-rewind-can-hand-one-bounded-private-clip-to-your-agent.md +12 -0
  39. package/corpus/templates/clips/changelog/2026-07-17-rewind-home-can-copy-a-local-agent-prompt.md +6 -0
  40. package/corpus/templates/clips/changelog/2026-07-17-rewind-now-has-a-calm-home-status-focused-first-run-consent-.md +6 -0
  41. package/corpus/templates/clips/changelog/2026-07-17-rewind-settings-are-smaller-clearer-and-agent-first.md +6 -0
  42. package/corpus/templates/clips/changelog/2026-07-17-the-full-clips-interface-stays-available-while-recording.md +6 -0
  43. package/corpus/templates/clips/changelog/2026-07-18-rewind-organizes-recent-work-into-local-searchable-chapters.md +8 -0
  44. package/corpus/templates/clips/changelog/2026-07-18-searchable-rewind-chapters.md +6 -0
  45. package/corpus/templates/clips/changelog/2026-07-19-rewind-backed-recordings-now-start-immediately-after-the-cou.md +6 -0
  46. package/corpus/templates/clips/changelog/2026-07-20-rewind-can-set-up-your-local-agent-once-so-future-requests-w.md +6 -0
  47. package/corpus/templates/clips/desktop/package.json +1 -0
  48. package/corpus/templates/clips/desktop/scripts/sign-macos-local.ts +12 -1
  49. package/corpus/templates/clips/desktop/src/app.tsx +2498 -239
  50. package/corpus/templates/clips/desktop/src/components/ReadinessPanel.tsx +21 -1
  51. package/corpus/templates/clips/desktop/src/components/Switch.tsx +3 -0
  52. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +156 -24
  53. package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +6 -7
  54. package/corpus/templates/clips/desktop/src/lib/recorder.ts +1763 -1160
  55. package/corpus/templates/clips/desktop/src/lib/rewind-agent-prompt.ts +9 -0
  56. package/corpus/templates/clips/desktop/src/lib/rewind-recording-start.ts +20 -0
  57. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  58. package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +14 -14
  59. package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +4 -4
  60. package/corpus/templates/clips/desktop/src/overlays/preparing.tsx +13 -0
  61. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +27 -2
  62. package/corpus/templates/clips/desktop/src/shared/config.ts +16 -0
  63. package/corpus/templates/clips/desktop/src/styles.css +956 -134
  64. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +5 -0
  65. package/corpus/templates/clips/desktop/src-tauri/build.rs +69 -1
  66. package/corpus/templates/clips/desktop/src-tauri/src/accessibility.rs +386 -12
  67. package/corpus/templates/clips/desktop/src-tauri/src/capture_audio_bus.rs +382 -0
  68. package/corpus/templates/clips/desktop/src-tauri/src/capture_graph.rs +777 -0
  69. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +249 -22
  70. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +148 -1
  71. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +58 -4
  72. package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +13 -1
  73. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +1505 -51
  74. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +124 -0
  75. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1006 -95
  76. package/corpus/templates/clips/desktop/src-tauri/src/rewind_capture_suspension.rs +165 -0
  77. package/corpus/templates/clips/desktop/src-tauri/src/rewind_chapters.rs +2434 -0
  78. package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +1650 -0
  79. package/corpus/templates/clips/desktop/src-tauri/src/rewind_egress.rs +643 -0
  80. package/corpus/templates/clips/desktop/src-tauri/src/rewind_local_ask.rs +803 -0
  81. package/corpus/templates/clips/desktop/src-tauri/src/rewind_meeting_history.rs +595 -0
  82. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +2900 -91
  83. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_ocr.rs +360 -0
  84. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_transcript.rs +408 -0
  85. package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +114 -33
  86. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +2 -2
  87. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +13 -3
  88. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +4 -1
  89. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +7 -13
  90. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +198 -11
  91. package/corpus/templates/clips/desktop/src-tauri/tauri.alpha.conf.json +17 -0
  92. package/dist/cli/mcp-config-writers.d.ts +2 -2
  93. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  94. package/dist/cli/mcp-config-writers.js +6 -6
  95. package/dist/cli/mcp-config-writers.js.map +1 -1
  96. package/dist/cli/mcp.d.ts +14 -0
  97. package/dist/cli/mcp.d.ts.map +1 -1
  98. package/dist/cli/mcp.js +79 -0
  99. package/dist/cli/mcp.js.map +1 -1
  100. package/dist/cli/skills-content/help.d.ts +1 -1
  101. package/dist/cli/skills-content/help.d.ts.map +1 -1
  102. package/dist/cli/skills-content/help.js +4 -3
  103. package/dist/cli/skills-content/help.js.map +1 -1
  104. package/dist/cli/skills-content/index.d.ts +1 -0
  105. package/dist/cli/skills-content/index.d.ts.map +1 -1
  106. package/dist/cli/skills-content/index.js +1 -0
  107. package/dist/cli/skills-content/index.js.map +1 -1
  108. package/dist/cli/skills-content/rewind-skill.d.ts +2 -0
  109. package/dist/cli/skills-content/rewind-skill.d.ts.map +1 -0
  110. package/dist/cli/skills-content/rewind-skill.js +53 -0
  111. package/dist/cli/skills-content/rewind-skill.js.map +1 -0
  112. package/dist/cli/skills.d.ts +6 -0
  113. package/dist/cli/skills.d.ts.map +1 -1
  114. package/dist/cli/skills.js +90 -14
  115. package/dist/cli/skills.js.map +1 -1
  116. package/dist/mcp/screen-memory-stdio.d.ts +365 -0
  117. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  118. package/dist/mcp/screen-memory-stdio.js +1014 -91
  119. package/dist/mcp/screen-memory-stdio.js.map +1 -1
  120. package/dist/mcp-client/index.d.ts +1 -1
  121. package/dist/mcp-client/index.d.ts.map +1 -1
  122. package/dist/mcp-client/index.js +1 -1
  123. package/dist/mcp-client/index.js.map +1 -1
  124. package/dist/mcp-client/screen-memory-local.d.ts +90 -0
  125. package/dist/mcp-client/screen-memory-local.d.ts.map +1 -1
  126. package/dist/mcp-client/screen-memory-local.js +590 -12
  127. package/dist/mcp-client/screen-memory-local.js.map +1 -1
  128. package/dist/notifications/routes.d.ts +3 -3
  129. package/dist/observability/routes.d.ts +1 -1
  130. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  131. package/dist/resources/handlers.d.ts +1 -1
  132. package/package.json +1 -1
  133. package/src/cli/mcp-config-writers.ts +6 -4
  134. package/src/cli/mcp.ts +124 -0
  135. package/src/cli/skills-content/help.ts +4 -3
  136. package/src/cli/skills-content/index.ts +1 -0
  137. package/src/cli/skills-content/rewind-skill.ts +52 -0
  138. package/src/cli/skills.ts +115 -13
  139. package/src/mcp/screen-memory-stdio.ts +1360 -104
  140. package/src/mcp-client/index.ts +9 -0
  141. package/src/mcp-client/screen-memory-local.ts +847 -13
@@ -15,10 +15,10 @@ const POPOVER_DEFAULT_HEIGHT_LOGICAL: f64 = 520.0;
15
15
  //
16
16
  // Clips-owned recording chrome (toolbar / countdown / finalizing /
17
17
  // recording-pill) gets `NSWindow.sharingType = NSWindowSharingNone` so it does
18
- // not leak into the recorded video. The main popover is different: users expect
19
- // to screenshot it for feedback, so it stays shareable during normal idle /
20
- // settings use and is flipped to NSWindowSharingNone only while it is parked as
21
- // the hidden recording controller.
18
+ // not leak into the recorded video. The main popover follows the same user
19
+ // preference: private by default, shareable only when "Show Clips in screen
20
+ // captures" is enabled. Reopening it during a recording must not silently
21
+ // override that choice.
22
22
  // Recording-time exclusion has two effects on macOS: screen pickers don't list
23
23
  // excluded windows, and full-screen captures omit them from the compositor
24
24
  // output. This is the same mechanism Loom, 1Password, and CleanShot use to keep
@@ -183,12 +183,9 @@ pub fn set_capture_included(_window: &WebviewWindow) {
183
183
  /// immediately on anything currently on screen. Called from
184
184
  /// `set_feature_config` when the toggle flips.
185
185
  ///
186
- /// The popover is intentionally skipped its sharing-type is dynamic
187
- /// (`set_capture_included` while shown, `set_capture_excluded` while
188
- /// parked at 2x2 px during recording). Both of those helpers now consult
189
- /// the toggle, so the next `show_popover` / `park_popover_offscreen` call
190
- /// picks up the new setting. Rewriting it here would clobber the parked
191
- /// state mid-recording.
186
+ /// The popover follows the same preference as ordinary Clips chrome. Applying
187
+ /// it here makes the toggle take effect immediately whether the popover is
188
+ /// visible or parked as the 2x2 recording controller.
192
189
  ///
193
190
  /// Region-guide overlays are private recorder aids, not Clips chrome demos, so
194
191
  /// they stay excluded even when the debug toggle makes the rest visible.
@@ -198,9 +195,6 @@ pub fn reapply_capture_exclusion_to_overlays(app: &tauri::AppHandle) {
198
195
  let visible = crate::config::show_in_screen_capture(app);
199
196
  let windows = app.webview_windows();
200
197
  for (label, window) in &windows {
201
- if label.as_str() == "popover" {
202
- continue;
203
- }
204
198
  // The meeting reminder is a notification, not Clips recording
205
199
  // chrome — keep it visible in captures regardless of the debug
206
200
  // toggle so it never gets re-excluded on a config change.
@@ -16,6 +16,36 @@
16
16
  //!
17
17
  use tauri::AppHandle;
18
18
 
19
+ /// One timestamped segment produced by bounded, offline-file transcription.
20
+ /// Timestamps are relative to the supplied audio, never to wall-clock time.
21
+ #[derive(Debug, Clone, PartialEq, Eq)]
22
+ pub(crate) struct OfflineTranscriptSegment {
23
+ pub start_ms: i64,
24
+ pub end_ms: i64,
25
+ pub text: String,
26
+ }
27
+
28
+ /// Transcribe already-decoded 16 kHz mono samples with the process-wide
29
+ /// Whisper context. This is the reusable file-transcription entry point: the
30
+ /// caller owns container decoding and the samples are never persisted here.
31
+ ///
32
+ /// Blocking work -- call from a dedicated worker thread, not the async runtime.
33
+ pub(crate) fn transcribe_offline_file_samples(
34
+ app: &AppHandle,
35
+ samples: &[f32],
36
+ language: Option<&str>,
37
+ ) -> Result<Vec<OfflineTranscriptSegment>, String> {
38
+ #[cfg(target_os = "macos")]
39
+ {
40
+ macos::transcribe_offline_file_samples(app, samples, language)
41
+ }
42
+ #[cfg(not(target_os = "macos"))]
43
+ {
44
+ let _ = (app, samples, language);
45
+ Err("local Whisper file transcription is supported on macOS only".to_owned())
46
+ }
47
+ }
48
+
19
49
  #[tauri::command]
20
50
  pub async fn whisper_transcription_start(
21
51
  app: AppHandle,
@@ -112,6 +142,9 @@ mod macos {
112
142
  use tauri::{AppHandle, Emitter};
113
143
  use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters};
114
144
 
145
+ use crate::capture_audio_bus::{
146
+ try_subscribe, AudioSources, AudioSubscription, SubscriptionAttempt,
147
+ };
115
148
  use crate::native_speech::macos::{
116
149
  start_raw_mic_capture, MicVoiceProcessingMode, RawMicCapture,
117
150
  };
@@ -121,6 +154,8 @@ mod macos {
121
154
  };
122
155
  use crate::whisper_model::{ensure_model, model_file};
123
156
 
157
+ const MEETING_AUDIO_OWNER: &str = "meeting-whisper";
158
+
124
159
  /// One transcript segment with real timestamps from whisper, already
125
160
  /// offset onto the meeting timeline (ms since capture start).
126
161
  #[derive(Serialize, Clone)]
@@ -185,6 +220,31 @@ mod macos {
185
220
  Ok(())
186
221
  }
187
222
 
223
+ pub(super) fn transcribe_offline_file_samples(
224
+ app: &AppHandle,
225
+ samples: &[f32],
226
+ language: Option<&str>,
227
+ ) -> Result<Vec<super::OfflineTranscriptSegment>, String> {
228
+ const CHUNK_SAMPLES: usize = 25 * 16_000;
229
+
230
+ let ctx = context(app)?;
231
+ let mut state = ctx
232
+ .create_state()
233
+ .map_err(|e| format!("whisper state init failed: {e}"))?;
234
+ let mut segments = Vec::new();
235
+ for (chunk_index, chunk) in samples.chunks(CHUNK_SAMPLES).enumerate() {
236
+ let chunk_offset_ms = chunk_index as i64 * 25_000;
237
+ segments.extend(infer(&mut state, chunk, language).into_iter().map(
238
+ |(start_ms, end_ms, text)| super::OfflineTranscriptSegment {
239
+ start_ms: chunk_offset_ms.saturating_add(start_ms),
240
+ end_ms: chunk_offset_ms.saturating_add(end_ms),
241
+ text,
242
+ },
243
+ ));
244
+ }
245
+ Ok(segments)
246
+ }
247
+
188
248
  // ---- resampling -------------------------------------------------------
189
249
 
190
250
  /// One linearly-interpolated 16 kHz output sample at output index `i`,
@@ -383,6 +443,12 @@ mod macos {
383
443
  /// deliberately deferred to the worker so we never allocate/compute on
384
444
  /// the realtime audio thread.
385
445
  fn push(&self, frames: &[f32]) {
446
+ // A bus publish may already have cloned this callback when the
447
+ // subscription is dropped. Refuse that final in-flight delivery
448
+ // once teardown has signalled the worker to stop.
449
+ if !self.running.load(Ordering::SeqCst) {
450
+ return;
451
+ }
386
452
  if let Ok(mut buf) = self.buf.lock() {
387
453
  buf.extend_from_slice(frames);
388
454
  }
@@ -826,18 +892,31 @@ mod macos {
826
892
  }
827
893
 
828
894
  struct Session {
895
+ app: AppHandle,
829
896
  // macOS 15+ meetings use a combined SCK capture, so there is no
830
897
  // competing AVAudioEngine / VoiceProcessingIO mic input to stop.
831
898
  mic_cap: Option<RawMicCapture>,
832
899
  // System capture is optional — skipped when the user turns system
833
900
  // audio off, so neither the recording nor the transcript include it.
834
901
  sys_cap: Option<RawSckAudioCapture>,
902
+ // When Rewind already owns the requested SCK audio sources, meeting
903
+ // Whisper receives fan-out PCM through this subscription and opens no
904
+ // physical mic/system recorder of its own.
905
+ _shared_audio: Option<AudioSubscription>,
906
+ temporary_audio: Option<crate::screen_memory::TemporaryAudioLease>,
835
907
  mic: Arc<WhisperStream>,
836
908
  sys: Option<Arc<WhisperStream>>,
837
909
  /// Who started this session — see `SessionOwner`.
838
910
  owner: SessionOwner,
839
911
  }
840
912
 
913
+ impl Drop for Session {
914
+ fn drop(&mut self) {
915
+ drop(self._shared_audio.take());
916
+ release_temporary_audio(&self.app, self.temporary_audio.take());
917
+ }
918
+ }
919
+
841
920
  // SAFETY: the capture handles hold refcounted ObjC objects (already
842
921
  // `Send`); the streams are `Arc` over `Send + Sync` interiors. We only move
843
922
  // the session through the `Mutex`, never alias across threads.
@@ -936,10 +1015,80 @@ mod macos {
936
1015
  as Arc<dyn Fn(&[f32]) + Send + Sync>
937
1016
  });
938
1017
 
939
- let combined_cap = if should_use_combined_sck_capture(
940
- owner,
941
- supports_sck_microphone_capture(),
942
- ) {
1018
+ // If Rewind is running visuals-only, upgrade that one physical
1019
+ // producer before touching the bus. Off/paused Rewind returns no lease
1020
+ // and preserves the legacy meeting capture path.
1021
+ let temporary_audio = if owner == SessionOwner::Meeting {
1022
+ match crate::screen_memory::acquire_temporary_audio_consumer(
1023
+ &app,
1024
+ MEETING_AUDIO_OWNER,
1025
+ crate::capture_graph::CaptureConsumer::Meeting,
1026
+ true,
1027
+ capture_system,
1028
+ ) {
1029
+ Ok(lease) => lease,
1030
+ Err(error) => {
1031
+ mic_stream.stop();
1032
+ if let Some(sys_stream) = &sys_stream {
1033
+ sys_stream.stop();
1034
+ }
1035
+ return Err(error);
1036
+ }
1037
+ }
1038
+ } else {
1039
+ None
1040
+ };
1041
+
1042
+ // Make the shared-vs-physical choice atomically inside the bus. If a
1043
+ // Rewind producer exists but lacks a requested source, `try_subscribe`
1044
+ // returns a typed error and we fail closed: no combined SCK fallback,
1045
+ // RawMicCapture, or raw system capture is opened.
1046
+ let shared_audio = if owner == SessionOwner::Meeting {
1047
+ let mic_for_shared = mic_stream.clone();
1048
+ let shared_mic = Arc::new(move |samples: &[f32], sample_rate: f64| {
1049
+ mic_for_shared.set_src_rate(sample_rate);
1050
+ mic_for_shared.push(samples);
1051
+ });
1052
+ let shared_system = sys_stream.as_ref().map(|stream| {
1053
+ let stream = stream.clone();
1054
+ Arc::new(move |samples: &[f32], sample_rate: f64| {
1055
+ stream.set_src_rate(sample_rate);
1056
+ stream.push(samples);
1057
+ }) as crate::capture_audio_bus::AudioCallback
1058
+ });
1059
+ match try_subscribe(
1060
+ AudioSources::new(true, capture_system),
1061
+ Some(shared_mic),
1062
+ shared_system,
1063
+ ) {
1064
+ Ok(SubscriptionAttempt::Subscribed(subscription)) => Some(subscription),
1065
+ Ok(SubscriptionAttempt::NoProducer) => {
1066
+ if temporary_audio.is_some() {
1067
+ release_temporary_audio(&app, temporary_audio);
1068
+ mic_stream.stop();
1069
+ if let Some(sys_stream) = &sys_stream {
1070
+ sys_stream.stop();
1071
+ }
1072
+ return Err("shared-audio-producer-unavailable-after-upgrade".into());
1073
+ }
1074
+ None
1075
+ }
1076
+ Err(error) => {
1077
+ release_temporary_audio(&app, temporary_audio);
1078
+ mic_stream.stop();
1079
+ if let Some(sys_stream) = &sys_stream {
1080
+ sys_stream.stop();
1081
+ }
1082
+ return Err(error.to_string());
1083
+ }
1084
+ }
1085
+ } else {
1086
+ None
1087
+ };
1088
+
1089
+ let combined_cap = if shared_audio.is_none()
1090
+ && should_use_combined_sck_capture(owner, supports_sck_microphone_capture())
1091
+ {
943
1092
  match start_raw_meeting_capture(
944
1093
  app.clone(),
945
1094
  mic_device_id.clone(),
@@ -963,7 +1112,10 @@ mod macos {
963
1112
  None
964
1113
  };
965
1114
 
966
- let (mic_cap, sys_cap) = if let Some(combined_cap) = combined_cap {
1115
+ let (mic_cap, sys_cap) = if shared_audio.is_some() {
1116
+ eprintln!("[whisper] using shared Rewind microphone/system PCM producer");
1117
+ (None, None)
1118
+ } else if let Some(combined_cap) = combined_cap {
967
1119
  // Both SCK outputs are configured at 48 kHz.
968
1120
  mic_stream.set_src_rate(48000.0);
969
1121
  (None, Some(combined_cap))
@@ -1004,10 +1156,30 @@ mod macos {
1004
1156
  (Some(mic_cap), sys_cap)
1005
1157
  };
1006
1158
 
1007
- let mut slot = session_slot().lock().map_err(|e| e.to_string())?;
1159
+ let mut slot = match session_slot().lock() {
1160
+ Ok(slot) => slot,
1161
+ Err(error) => {
1162
+ drop(shared_audio);
1163
+ release_temporary_audio(&app, temporary_audio);
1164
+ mic_stream.stop();
1165
+ if let Some(sys_stream) = &sys_stream {
1166
+ sys_stream.stop();
1167
+ }
1168
+ if let Some(mic_cap) = mic_cap {
1169
+ mic_cap.stop();
1170
+ }
1171
+ if let Some(sys_cap) = sys_cap {
1172
+ sys_cap.stop();
1173
+ }
1174
+ return Err(error.to_string());
1175
+ }
1176
+ };
1008
1177
  *slot = Some(Session {
1178
+ app: app.clone(),
1009
1179
  mic_cap,
1010
1180
  sys_cap,
1181
+ _shared_audio: shared_audio,
1182
+ temporary_audio,
1011
1183
  mic: mic_stream,
1012
1184
  sys: sys_stream,
1013
1185
  owner,
@@ -1039,12 +1211,23 @@ mod macos {
1039
1211
  eprintln!("[whisper] transcription timeline reset");
1040
1212
  }
1041
1213
 
1042
- pub fn stop(_app: &AppHandle) {
1214
+ fn release_temporary_audio(
1215
+ app: &AppHandle,
1216
+ lease: Option<crate::screen_memory::TemporaryAudioLease>,
1217
+ ) {
1218
+ if let Some(lease) = lease {
1219
+ if let Err(error) = crate::screen_memory::release_temporary_audio_consumer(app, lease) {
1220
+ eprintln!("[whisper] Rewind audio lease release failed: {error}");
1221
+ }
1222
+ }
1223
+ }
1224
+
1225
+ pub fn stop(app: &AppHandle) {
1043
1226
  let session = match session_slot().lock() {
1044
1227
  Ok(mut slot) => slot.take(),
1045
- Err(_) => return,
1228
+ Err(poisoned) => poisoned.into_inner().take(),
1046
1229
  };
1047
- let Some(session) = session else {
1230
+ let Some(mut session) = session else {
1048
1231
  return;
1049
1232
  };
1050
1233
  // Signal workers to stop. They flush a final transcript after the loop.
@@ -1053,12 +1236,16 @@ mod macos {
1053
1236
  sys.stop();
1054
1237
  }
1055
1238
  // Stop captures so no more samples arrive while workers flush.
1056
- if let Some(mic_cap) = session.mic_cap {
1239
+ if let Some(mic_cap) = session.mic_cap.take() {
1057
1240
  mic_cap.stop();
1058
1241
  }
1059
- if let Some(sys_cap) = session.sys_cap {
1242
+ if let Some(sys_cap) = session.sys_cap.take() {
1060
1243
  sys_cap.stop();
1061
1244
  }
1245
+ // Unsubscribe before a last-consumer release rotates the Rewind
1246
+ // producer back to its persisted visuals-only mode.
1247
+ drop(session._shared_audio.take());
1248
+ release_temporary_audio(app, session.temporary_audio.take());
1062
1249
  // Wait up to 4 s for both workers to finish their final flush so
1063
1250
  // trailing speech is not lost when the frontend unregisters listeners.
1064
1251
  let deadline = Instant::now() + Duration::from_secs(4);
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://schema.tauri.app/config/2",
3
+ "productName": "Clips Alpha",
4
+ "identifier": "com.clips.tray.alpha",
5
+ "bundle": {
6
+ "createUpdaterArtifacts": false,
7
+ "targets": ["app"],
8
+ "macOS": {
9
+ "minimumSystemVersion": "26.0"
10
+ }
11
+ },
12
+ "plugins": {
13
+ "updater": {
14
+ "active": false
15
+ }
16
+ }
17
+ }
@@ -33,7 +33,7 @@ export interface HttpMcpEntry {
33
33
  /** Build the HTTP MCP server entry for a deployed agent-native app. */
34
34
  export declare function buildHttpMcpEntry(mcpUrl: string, token?: string, headers?: Record<string, string>): HttpMcpEntry;
35
35
  export declare function buildHttpMcpEntryForClient(client: ClientId, mcpUrl: string, token?: string, headers?: Record<string, string>): Record<string, unknown>;
36
- export declare function buildLocalMcpEntryForClient(client: ClientId, args: string[], env?: Record<string, string>): Record<string, unknown>;
36
+ export declare function buildLocalMcpEntryForClient(client: ClientId, args: string[], env?: Record<string, string>, command?: string): Record<string, unknown>;
37
37
  /**
38
38
  * Cowork consumes MCP exactly like Claude Code (same JSON server-entry
39
39
  * shape). Resolved lazily so `os.homedir()` reflects the current `$HOME`.
@@ -78,7 +78,7 @@ export declare function hasJsonMcpEntry(file: string, name: string): boolean;
78
78
  export declare function hasJsonMcpEntryForClient(client: ClientId, file: string, name: string): boolean;
79
79
  /** Build a `[mcp_servers.<name>]` block for an HTTP-type MCP server. */
80
80
  export declare function buildCodexHttpBlock(name: string, mcpUrl: string, token?: string, headers?: Record<string, string>): string;
81
- export declare function buildCodexLocalBlock(name: string, args: string[], env?: Record<string, string>): string;
81
+ export declare function buildCodexLocalBlock(name: string, args: string[], env?: Record<string, string>, command?: string): string;
82
82
  /**
83
83
  * Replace (or append) the entire `[mcp_servers.<name>]` footprint in a TOML
84
84
  * file without disturbing other content. The footprint is the server's own
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-config-writers.d.ts","sourceRoot":"","sources":["../../src/cli/mcp-config-writers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-config-writers.d.ts","sourceRoot":"","sources":["../../src/cli/mcp-config-writers.ts"],"names":[],"mappings":"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"}
@@ -81,12 +81,12 @@ export function buildHttpMcpEntryForClient(client, mcpUrl, token, headers) {
81
81
  }
82
82
  return buildHttpMcpEntry(mcpUrl, token, headers);
83
83
  }
84
- export function buildLocalMcpEntryForClient(client, args, env) {
84
+ export function buildLocalMcpEntryForClient(client, args, env, command = "agent-native") {
85
85
  const cleanEnv = env ? Object.fromEntries(Object.entries(env)) : {};
86
86
  if (client === "opencode") {
87
87
  return {
88
88
  type: "local",
89
- command: ["agent-native", ...args],
89
+ command: [command, ...args],
90
90
  enabled: true,
91
91
  ...(Object.keys(cleanEnv).length ? { environment: cleanEnv } : {}),
92
92
  };
@@ -94,13 +94,13 @@ export function buildLocalMcpEntryForClient(client, args, env) {
94
94
  if (client === "github-copilot") {
95
95
  return {
96
96
  type: "stdio",
97
- command: "agent-native",
97
+ command,
98
98
  args,
99
99
  ...(Object.keys(cleanEnv).length ? { env: cleanEnv } : {}),
100
100
  };
101
101
  }
102
102
  return {
103
- command: "agent-native",
103
+ command,
104
104
  args,
105
105
  ...(Object.keys(cleanEnv).length ? { env: cleanEnv } : {}),
106
106
  };
@@ -454,9 +454,9 @@ export function buildCodexHttpBlock(name, mcpUrl, token, headers) {
454
454
  }
455
455
  return lines.join("\n") + "\n";
456
456
  }
457
- export function buildCodexLocalBlock(name, args, env) {
457
+ export function buildCodexLocalBlock(name, args, env, command = "agent-native") {
458
458
  const lines = [codexMcpHeader(name)];
459
- lines.push(`command = "agent-native"`);
459
+ lines.push(`command = ${tomlQuote(command)}`);
460
460
  lines.push(`args = [${args.map(tomlQuote).join(", ")}]`);
461
461
  const cleanEnv = env ? Object.fromEntries(Object.entries(env)) : {};
462
462
  if (Object.keys(cleanEnv).length) {