Skip to content

Commit 5a8c419

Browse files
sharpninjaCopilot
andcommitted
feat(voice): silence beeps, skip tool markers in TTS, add --yolo
- Mute Android notification stream around SpeechRecognizer to suppress beeps - Extend speech silence timeouts (3s complete, 10s minimum) - Remove redundant PlayChime() from conversation loop restarts - Add IsToolProgressLine() filter: skip Copilot CLI tool markers (spinners, fold summaries, shell commands, indented output) during text-to-speech - Markdown rendering for assistant bubbles, Pause/Stop on same row - Submodule: add --yolo flag to Copilot CLI invocations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 53b8a8f commit 5a8c419

4 files changed

Lines changed: 62 additions & 11 deletions

File tree

lib/McpServer

src/McpServerManager.Android/Services/AndroidVoiceAudioServices.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,19 @@ await RunOnUiThreadAsync(activity, () =>
213213
?? throw new InvalidOperationException("Failed to create Android speech recognizer.");
214214
recognizer.SetRecognitionListener(_listener);
215215

216+
// Mute notification stream to suppress the recognizer beep
217+
var audioMgr = activity.GetSystemService(Context.AudioService) as AudioManager;
218+
audioMgr?.AdjustStreamVolume(Stream.Notification, Adjust.Mute, VolumeNotificationFlags.RemoveSoundAndVibrate);
219+
216220
using var intent = BuildRecognizerIntent(activity, languageTag);
217221
recognizer.StartListening(intent);
222+
223+
// Restore after a short delay so the beep window has passed
224+
Task.Delay(600).ContinueWith(_ =>
225+
{
226+
try { audioMgr?.AdjustStreamVolume(Stream.Notification, Adjust.Unmute, 0); }
227+
catch { /* best effort */ }
228+
});
218229
}).ConfigureAwait(false);
219230

220231
using var _ = cancellationToken.Register(() =>
@@ -358,6 +369,10 @@ private static Intent BuildRecognizerIntent(Activity activity, string? languageT
358369
intent.PutExtra(RecognizerIntent.ExtraCallingPackage, activity.PackageName);
359370
intent.PutExtra(RecognizerIntent.ExtraPartialResults, false);
360371
intent.PutExtra(RecognizerIntent.ExtraMaxResults, 3);
372+
// Extend silence timeouts so the mic doesn't cut off prematurely
373+
intent.PutExtra("android.speech.extra.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS", 10000);
374+
intent.PutExtra("android.speech.extra.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS", 3000);
375+
intent.PutExtra("android.speech.extra.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS", 3000);
361376

362377
if (!string.IsNullOrWhiteSpace(languageTag))
363378
{

src/McpServerManager.Android/Views/SimplifiedVoiceView.axaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@
6565
<StackPanel Grid.Row="3" Spacing="6" Margin="12,4,12,8" HorizontalAlignment="Center">
6666
<TextBlock Text="{Binding StatusText}" HorizontalAlignment="Center"
6767
TextWrapping="Wrap" FontSize="16" Opacity="0.8"/>
68-
<Button x:Name="PauseResumeButton" Content="Pause" Click="OnPauseResumeClick"
69-
Width="72" Height="72" CornerRadius="36" FontSize="18" FontWeight="Bold"
70-
HorizontalAlignment="Center" HorizontalContentAlignment="Center"
71-
VerticalContentAlignment="Center" IsEnabled="False"/>
72-
<Button x:Name="StopButton" Content="Stop" Click="OnStopClick"
73-
Padding="16,6" IsEnabled="False" HorizontalAlignment="Center"/>
68+
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
69+
<Button x:Name="PauseResumeButton" Content="Pause" Click="OnPauseResumeClick"
70+
Padding="16,6" FontSize="18" FontWeight="Bold" IsEnabled="False"/>
71+
<Button x:Name="StopButton" Content="Stop" Click="OnStopClick"
72+
Padding="16,6" IsEnabled="False"/>
73+
</StackPanel>
7474
<CheckBox x:Name="AutoContinueCheck" Content="Auto-continue after response"
7575
IsChecked="True" HorizontalAlignment="Center"/>
7676
<TextBlock Text="send now · start over · clear chat · end chat · pause/resume"

src/McpServerManager.Android/Views/SimplifiedVoiceView.axaml.cs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,6 @@ private async Task RunConversationLoopAsync(CancellationToken ct)
202202
if (vm == null) return;
203203

204204
// Signal that we're now listening
205-
PlayChime();
206205
SetStatus("Listening...");
207206

208207
do
@@ -255,6 +254,10 @@ private async Task RunConversationLoopAsync(CancellationToken ct)
255254
assistantBubble.Text = accumulated.ToString();
256255
ScrollToBottom();
257256

257+
// Skip tool-progress lines for TTS (still displayed in chat)
258+
if (IsToolProgressLine(evt.Text))
259+
continue;
260+
258261
// Detect complete sentences and speak them as they arrive
259262
sentenceBuffer.Append(evt.Text);
260263
var bufText = sentenceBuffer.ToString();
@@ -436,7 +439,6 @@ private async Task<ListenResult> ListenForCommandAsync(CancellationToken ct)
436439
{
437440
_isPaused = false;
438441
UpdateButtons();
439-
PlayChime();
440442
SetStatus("Resumed. Listening...");
441443
}
442444
continue;
@@ -480,7 +482,6 @@ private async Task<ListenResult> ListenForCommandAsync(CancellationToken ct)
480482

481483
case VoiceCommand.ResumeChat:
482484
// Already not paused, treat as no-op
483-
PlayChime();
484485
continue;
485486

486487
case VoiceCommand.Continue:
@@ -619,7 +620,7 @@ private void OnPauseResumeClick(object? sender, RoutedEventArgs e)
619620
}
620621
else
621622
{
622-
if (!_isPaused) PlayChime();
623+
if (!_isPaused) SetStatus("Resumed. Listening...");
623624
SetStatus(_isPaused ? "Paused. Say 'resume chat' or tap Resume." : "Resumed. Listening...");
624625
}
625626
}
@@ -792,6 +793,41 @@ private async Task EndSessionInternalAsync()
792793
SetStatus("Chat ended.");
793794
}
794795

796+
/// <summary>
797+
/// Returns true if the line is a Copilot CLI tool-execution marker that
798+
/// should be displayed but not spoken (spinners, commands, fold summaries).
799+
/// </summary>
800+
private static bool IsToolProgressLine(string line)
801+
{
802+
if (string.IsNullOrEmpty(line)) return false;
803+
var trimmed = line.TrimStart();
804+
if (trimmed.Length == 0) return false;
805+
806+
char first = trimmed[0];
807+
808+
// Spinner / tool-start markers: ● ◐ ◑ ◒ ◓
809+
if (first is '\u25CF' or '\u25D0' or '\u25D1' or '\u25D2' or '\u25D3')
810+
return true;
811+
812+
// Result fold: └
813+
if (first == '\u2514')
814+
return true;
815+
816+
// Error/success markers: ✗ ✘ ✓
817+
if (first is '\u2717' or '\u2718' or '\u2713')
818+
return true;
819+
820+
// Shell command lines (indented with $)
821+
if (line.Length >= 3 && line[0] == ' ' && line[1] == ' ' && line[2] == '$')
822+
return true;
823+
824+
// Indented command output (2+ leading spaces)
825+
if (line.Length >= 2 && line[0] == ' ' && line[1] == ' ')
826+
return true;
827+
828+
return false;
829+
}
830+
795831
private static void PlayChime()
796832
{
797833
try

0 commit comments

Comments
 (0)