agentvibes 5.11.2 → 5.12.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 (54) hide show
  1. package/.claude/config/audio-effects.cfg +1 -1
  2. package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
  3. package/.claude/github-star-reminder.txt +1 -1
  4. package/.claude/hooks/audio-processor.sh +1 -1
  5. package/.claude/hooks/background-music-manager.sh +2 -1
  6. package/.claude/hooks/bmad-speak-enhanced.sh +37 -22
  7. package/.claude/hooks/bmad-speak.sh +26 -8
  8. package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +1 -1
  9. package/.claude/hooks/play-tts-kokoro.sh +9 -0
  10. package/.claude/hooks/play-tts-piper.sh +13 -1
  11. package/.claude/hooks/play-tts-ssh-remote.sh +117 -7
  12. package/.claude/hooks/play-tts-termux-ssh.sh +1 -1
  13. package/.claude/hooks/play-tts.sh +106 -13
  14. package/.claude/hooks-windows/background-music-manager.ps1 +2 -1
  15. package/.claude/hooks-windows/play-tts-kokoro.ps1 +14 -0
  16. package/.claude/hooks-windows/play-tts.ps1 +163 -30
  17. package/.claude/hooks-windows/tts-watcher.ps1 +55 -0
  18. package/.claude/hooks-windows/voice-manager-windows.ps1 +101 -1
  19. package/README.md +11 -6
  20. package/RELEASE_NOTES.md +67 -0
  21. package/bin/mcp-server.sh +6 -19
  22. package/bin/resolve-utterance.js +137 -0
  23. package/mcp-server/server.py +280 -99
  24. package/mcp-server/test_mcp_correctness.py +486 -0
  25. package/mcp-server/test_windows_script_parity.py +341 -336
  26. package/package.json +3 -2
  27. package/setup-windows.ps1 +867 -815
  28. package/src/console/app.js +14 -11
  29. package/src/console/footer-config.js +0 -4
  30. package/src/console/navigation.js +0 -1
  31. package/src/console/tabs/agents-tab.js +96 -11
  32. package/src/console/tabs/music-tab.js +108 -3
  33. package/src/console/tabs/placeholder-tab.js +0 -2
  34. package/src/console/tabs/settings-tab.js +41 -2
  35. package/src/console/tabs/setup-tab.js +34 -6
  36. package/src/console/tabs/voices-tab.js +19 -9
  37. package/src/console/widgets/track-picker.js +3 -3
  38. package/src/i18n/de.js +0 -1
  39. package/src/i18n/en.js +0 -1
  40. package/src/i18n/es.js +0 -1
  41. package/src/i18n/fr.js +0 -1
  42. package/src/i18n/hi.js +0 -1
  43. package/src/i18n/ja.js +0 -1
  44. package/src/i18n/ko.js +0 -1
  45. package/src/i18n/pt.js +0 -1
  46. package/src/i18n/zh-CN.js +0 -1
  47. package/src/installer.js +205 -15
  48. package/src/services/config-service.js +264 -264
  49. package/src/services/llm-provider-service.js +7 -7
  50. package/src/services/navigation-service.js +1 -1
  51. package/src/services/utterance-loader.js +280 -0
  52. package/src/services/utterance-resolver.js +526 -0
  53. package/templates/agentvibes-receiver.sh +74 -12
  54. package/voice-assignments.json +8245 -0
@@ -213,7 +213,7 @@ if ($BgEnabled -or $HasReverb) {
213
213
  # 1. Key - Must start with "llm:" followed by the LLM name
214
214
  # 2. REVERB_PRESET - One of: off, light, medium, heavy, cathedral (or blank)
215
215
  # 3. BACKGROUND_FILE - Filename relative to .claude/audio/tracks/ (or blank)
216
- # 4. BACKGROUND_VOLUME - Float 0.0-1.0 (or blank for default 0.25)
216
+ # 4. BACKGROUND_VOLUME - Float 0.0-1.0 (or blank for default 0.20)
217
217
  # 5. VOICE - Provider voice name to use (or blank for global default)
218
218
  # 6. PRETEXT - Text prepended to all TTS utterances (or blank)
219
219
  # 7. ENGINE - Windows engine: windows-sapi, windows-piper, soprano (or blank)
@@ -263,6 +263,99 @@ if (-not $llm) {
263
263
  # the POSIX play-tts.sh so the cross-platform contract is symmetric.
264
264
  $env:AGENTVIBES_LLM_KEY = "llm:$llm"
265
265
 
266
+ # ── Utterance Resolver (AVI-S8.5 Stage 2) ────────────────────────────────────
267
+ # Single source of truth for the voice + engine decision, mirroring the bash
268
+ # play-tts.sh port. Resolve the plan ONCE and adopt its voice (per-LLM voice
269
+ # wins over an LLM-echoed explicit override — R2) and, below, its local engine
270
+ # (a kokoro-shaped voice forces the kokoro engine and engine aliases normalize —
271
+ # R1/F5). FAIL-SAFE: if node or the resolver bundle isn't reachable (e.g. an
272
+ # installed ~/.claude that predates the bundle-shipping installer), $PlanOk
273
+ # stays $false and the legacy logic below runs unchanged — Windows TTS never
274
+ # breaks on a missing bridge. AGENTVIBES_RESOLVER_CLI lets the installer (or
275
+ # tests) point at the resolver bundle when it lives outside the hooks tree.
276
+
277
+ # Map a resolver engine name to its Windows provider script. Returns $null for
278
+ # engines with no local Windows script (e.g. elevenlabs/macos) so the caller
279
+ # keeps the current provider rather than breaking playback.
280
+ function Resolve-ProviderScriptForEngine {
281
+ param([string]$Engine, [string]$HooksRoot)
282
+ switch ($Engine) {
283
+ { $_ -in "windows-sapi", "sapi" } {
284
+ $s = "$HooksRoot\play-tts-sapi.ps1"
285
+ if (-not (Test-Path $s)) { $s = "$HooksRoot\play-tts-windows-sapi.ps1" }
286
+ return $s
287
+ }
288
+ { $_ -in "windows-piper", "piper" } {
289
+ $s = "$HooksRoot\play-tts-piper.ps1"
290
+ if (-not (Test-Path $s)) { $s = "$HooksRoot\play-tts-windows-piper.ps1" }
291
+ return $s
292
+ }
293
+ "soprano" { return "$HooksRoot\play-tts-soprano.ps1" }
294
+ "kokoro" { return "$HooksRoot\play-tts-kokoro.ps1" }
295
+ default { return $null }
296
+ }
297
+ }
298
+
299
+ $_OrigExplicitVoice = $VoiceOverride # raw positional voice, before any per-LLM override
300
+ $PlanOk = $false
301
+ $PlanVoice = ""
302
+ $PlanEngine = ""
303
+ $PlanVoiceIsOverride = $false
304
+ # F-3: the SSH-receiver watcher forwards the sender's engine via -ProviderOverride
305
+ # (a parameter the resolver can't see). Seed AGENTVIBES_FORCE_PROVIDER from it so
306
+ # the resolver honors the forwarded provider instead of defaulting the plan engine
307
+ # to piper (which dropped a forwarded windows-sapi/soprano voice → wrong/no audio).
308
+ if ($ProviderOverride) {
309
+ # A fresh -ProviderOverride for THIS request must win over any stale/inherited
310
+ # AGENTVIBES_FORCE_PROVIDER in the environment (a validated allowlist only).
311
+ switch ($ProviderOverride) {
312
+ { $_ -in @('piper','soprano','macos','windows-sapi','sapi','kokoro','elevenlabs','windows-piper') } {
313
+ $env:AGENTVIBES_FORCE_PROVIDER = $ProviderOverride
314
+ }
315
+ }
316
+ }
317
+ $_ResolverCli = ""
318
+ foreach ($_cand in @(
319
+ $env:AGENTVIBES_RESOLVER_CLI,
320
+ (Join-Path $ScriptPath "..\agentvibes-resolver\bin\resolve-utterance.js"),
321
+ (Join-Path $ScriptPath "..\..\bin\resolve-utterance.js"),
322
+ (Join-Path $ScriptPath "resolve-utterance.js"))) {
323
+ if ($_cand -and (Test-Path $_cand)) { $_ResolverCli = $_cand; break }
324
+ }
325
+ $_NodeCmd = Get-Command node -ErrorAction SilentlyContinue
326
+ if ($_ResolverCli -and $_NodeCmd) {
327
+ # Voice provenance (F-1): AGENTVIBES_VOICE_SOURCE lets a caller declare it
328
+ # (MCP/watcher → user-explicit/agent-profile); audition never demotes; else
329
+ # llm-echo (parroted get_config voice, which the per-LLM row overrides — R2).
330
+ $_VoiceSource = if ($env:AGENTVIBES_VOICE_SOURCE) { $env:AGENTVIBES_VOICE_SOURCE }
331
+ elseif ($env:AGENTVIBES_EFFECTS_PREVIEW) { "audition" }
332
+ else { "llm-echo" }
333
+ $_ResolverPackageRoot = Split-Path -Parent $ClaudeDir
334
+ $_ResolverProjectDir = if ($env:CLAUDE_PROJECT_DIR) { $env:CLAUDE_PROJECT_DIR } else { $_ResolverPackageRoot }
335
+ $_ResolverArgs = @('--format', 'json', '--text', $Text, '--llm', $llm,
336
+ '--voice-source', $_VoiceSource, '--project-dir', $_ResolverProjectDir,
337
+ '--package-root', $_ResolverPackageRoot)
338
+ # Only pass --voice when there IS an explicit voice; an absent flag tells the
339
+ # resolver "no explicit override" (so per-LLM routing applies cleanly).
340
+ if ($_OrigExplicitVoice) { $_ResolverArgs += @('--voice', $_OrigExplicitVoice) }
341
+ try {
342
+ $_PlanJson = & $_NodeCmd.Source $_ResolverCli @_ResolverArgs 2>$null
343
+ if ($LASTEXITCODE -eq 0 -and $_PlanJson) {
344
+ $_Plan = ($_PlanJson | Out-String).Trim() | ConvertFrom-Json
345
+ if ($_Plan) {
346
+ $PlanOk = $true
347
+ if ($_Plan.voice) { $PlanVoice = [string]$_Plan.voice }
348
+ if ($_Plan.engine) { $PlanEngine = [string]$_Plan.engine }
349
+ $PlanVoiceIsOverride = [bool]$_Plan.voiceIsOverride
350
+ }
351
+ }
352
+ } catch {
353
+ # Any bridge failure (bad JSON, nonzero exit, missing bundle) fails safe
354
+ # to the legacy logic below.
355
+ $PlanOk = $false
356
+ }
357
+ }
358
+
266
359
  # --- Lookup per-LLM config in audio-effects.cfg ------------------------------
267
360
  # Scan project config first, then user-profile config. Stop at first match.
268
361
  # Variables are intentionally prefixed with _ to distinguish LLM-local state
@@ -320,8 +413,20 @@ $_LlmFound = $false
320
413
  # 3. BMAD agent voice from bmad-voice-map.json (resolved in provider scripts)
321
414
  # 4. Global active voice from tts-provider.txt / active-voice.txt
322
415
 
323
- # Apply LLM-specific voice only when no explicit -VoiceOverride was passed
324
- if ($_LlmVoice -and -not $VoiceOverride) {
416
+ # Adopt the resolver's voice when a plan resolved (AVI-S8.5 Stage 2). The plan
417
+ # already applied the R2 precedence (per-LLM voice wins over an LLM-echoed
418
+ # explicit override; genuine explicit/audition voices still win), so take it
419
+ # verbatim. FAIL-SAFE fallback: with no plan, use the legacy explicit-wins order
420
+ # (explicit -VoiceOverride > per-LLM voice).
421
+ # F-2: adopt the plan voice ONLY when it's a real override (explicit pick or
422
+ # per-LLM row) — not the provider's stored voice file. A plain provider-file
423
+ # voice is left empty here so the provider script does its own file+model+speaker
424
+ # resolution (adopting it as an explicit override skips piper's multi-speaker
425
+ # lookup and plays speaker 0). Engine coupling (R1) still applies regardless.
426
+ if ($PlanOk -and $PlanVoiceIsOverride -and $PlanVoice) {
427
+ $VoiceOverride = $PlanVoice
428
+ }
429
+ elseif (-not $PlanOk -and $_LlmVoice -and -not $VoiceOverride) {
325
430
  $VoiceOverride = $_LlmVoice
326
431
  }
327
432
 
@@ -398,31 +503,45 @@ if ($OverrideEffects -ne "" -and $OverrideEffects -in @("off", "light", "medium"
398
503
  # piper/sapi for that LLM's normal text responses that use Piper voices — must
399
504
  # NOT redirect it to an incompatible engine, or synthesis fails silently
400
505
  # (Piper can't find the Kokoro voice model → no audio, exit 0).
401
- $_VoiceIsKokoro = $VoiceOverride -match '^[a-z]{2}_[a-z0-9_]+$'
402
- if ($_VoiceIsKokoro) {
403
- # A Kokoro-format voice forces the Kokoro engine regardless of the per-LLM
404
- # ENGINE column or the global tts-provider.txt default.
405
- $ProviderScript = "$HooksDir\play-tts-kokoro.ps1"
406
- }
407
- elseif ($_LlmEngine) {
408
- # Accept both canonical Windows names and the cross-platform aliases the TUI
409
- # writes (e.g. "piper" saved on a Linux/WSL install that is later read on
410
- # Windows, or "sapi" as a short form). Unknown values keep the global default.
411
- # Mirror the global-provider switch: prefer the PS-5.1-compatible script name,
412
- # fall back to the alternate name if the first doesn't exist on disk.
413
- switch ($_LlmEngine) {
414
- { $_ -in "windows-sapi", "sapi" } {
415
- $ProviderScript = "$HooksDir\play-tts-sapi.ps1"
416
- if (-not (Test-Path $ProviderScript)) { $ProviderScript = "$HooksDir\play-tts-windows-sapi.ps1" }
417
- }
418
- { $_ -in "windows-piper", "piper" } {
419
- $ProviderScript = "$HooksDir\play-tts-piper.ps1"
420
- if (-not (Test-Path $ProviderScript)) { $ProviderScript = "$HooksDir\play-tts-windows-piper.ps1" }
421
- }
422
- "soprano" { $ProviderScript = "$HooksDir\play-tts-soprano.ps1" }
423
- "kokoro" { $ProviderScript = "$HooksDir\play-tts-kokoro.ps1" }
424
- default {
425
- Write-Host "[INFO] play-tts.ps1: Unrecognised engine '$_LlmEngine' keeping default provider" -ForegroundColor DarkGray
506
+ if ($PlanOk -and $PlanEngine) {
507
+ # Resolver plan is authoritative for the LOCAL engine (AVI-S8.5 Stage 2):
508
+ # this cures the kokoro/piper voice->engine coupling (R1) and normalizes
509
+ # engine aliases like windows-sapi->sapi (F5), replacing the legacy heuristic
510
+ # below. An engine with no local Windows script (elevenlabs/macos) leaves the
511
+ # current provider in place.
512
+ $_PlanScript = Resolve-ProviderScriptForEngine -Engine $PlanEngine -HooksRoot $HooksDir
513
+ if ($_PlanScript) { $ProviderScript = $_PlanScript }
514
+ }
515
+ else {
516
+ # Legacy voice->engine heuristic ONLY on the fallback path (no resolver
517
+ # plan). When a plan resolved, the resolver already coupled voice->engine
518
+ # correctly above, so this is skipped.
519
+ $_VoiceIsKokoro = $VoiceOverride -match '^[a-z]{2}_[a-z0-9_]+$'
520
+ if ($_VoiceIsKokoro) {
521
+ # A Kokoro-format voice forces the Kokoro engine regardless of the per-LLM
522
+ # ENGINE column or the global tts-provider.txt default.
523
+ $ProviderScript = "$HooksDir\play-tts-kokoro.ps1"
524
+ }
525
+ elseif ($_LlmEngine) {
526
+ # Accept both canonical Windows names and the cross-platform aliases the TUI
527
+ # writes (e.g. "piper" saved on a Linux/WSL install that is later read on
528
+ # Windows, or "sapi" as a short form). Unknown values keep the global default.
529
+ # Mirror the global-provider switch: prefer the PS-5.1-compatible script name,
530
+ # fall back to the alternate name if the first doesn't exist on disk.
531
+ switch ($_LlmEngine) {
532
+ { $_ -in "windows-sapi", "sapi" } {
533
+ $ProviderScript = "$HooksDir\play-tts-sapi.ps1"
534
+ if (-not (Test-Path $ProviderScript)) { $ProviderScript = "$HooksDir\play-tts-windows-sapi.ps1" }
535
+ }
536
+ { $_ -in "windows-piper", "piper" } {
537
+ $ProviderScript = "$HooksDir\play-tts-piper.ps1"
538
+ if (-not (Test-Path $ProviderScript)) { $ProviderScript = "$HooksDir\play-tts-windows-piper.ps1" }
539
+ }
540
+ "soprano" { $ProviderScript = "$HooksDir\play-tts-soprano.ps1" }
541
+ "kokoro" { $ProviderScript = "$HooksDir\play-tts-kokoro.ps1" }
542
+ default {
543
+ Write-Host "[INFO] play-tts.ps1: Unrecognised engine '$_LlmEngine' — keeping default provider" -ForegroundColor DarkGray
544
+ }
426
545
  }
427
546
  }
428
547
  }
@@ -437,7 +556,20 @@ elseif ($_LlmEngine) {
437
556
 
438
557
  # --- Diagnostic output -------------------------------------------------------
439
558
  # Set AGENTVIBES_VERBOSE=1 in the shell environment to print routing state.
559
+ # The bare `provider=`/`voice=`/`plan=` lines mirror play-tts.sh's verbose
560
+ # DECISION echo so the resolved engine/voice can be characterization-tested
561
+ # without producing real audio (see test/windows/play-tts-resolver.Tests.ps1).
440
562
  if ($env:AGENTVIBES_VERBOSE -eq "1") {
563
+ $_ProviderName = switch -Wildcard ($ProviderScript) {
564
+ "*play-tts-kokoro.ps1" { "kokoro" }
565
+ "*soprano*" { "soprano" }
566
+ "*piper*" { "piper" }
567
+ "*sapi*" { "sapi" }
568
+ default { Split-Path -Leaf $ProviderScript }
569
+ }
570
+ Write-Output "provider=$_ProviderName"
571
+ Write-Output "voice=$VoiceOverride"
572
+ Write-Output ("plan=" + $(if ($PlanOk) { "ok" } else { "fallback" }))
441
573
  Write-Host "[DEBUG] play-tts.ps1 LLM routing: llm=$llm | voice=$VoiceOverride | engine=$_LlmEngine | pretext=$_LlmPretext" -ForegroundColor DarkCyan
442
574
  Write-Host "[DEBUG] play-tts.ps1 LLM routing: reverb=$ReverbLevel | HasFfmpeg=$HasFfmpeg | BgEnabled=$BgEnabled | script=$ProviderScript" -ForegroundColor DarkCyan
443
575
  }
@@ -574,8 +706,9 @@ if (($BgEnabled -or $HasReverb) -and $HasFfmpeg) {
574
706
  $BgTrackPath = Join-Path $TracksDir "agent_vibes_bachata_v1_loop.mp3"
575
707
  }
576
708
 
577
- # Get volume (default 0.25) — per-message override takes precedence
578
- $BgVolume = "0.25"
709
+ # Get volume (default 0.20) — per-message override takes precedence
710
+ # TODO(AVI-S8.6): generate this constant from the shared JSON source of truth.
711
+ $BgVolume = "0.20"
579
712
  $VolumeFile = "$ConfigDir\background-music-volume.txt"
580
713
  if (Test-Path $VolumeFile) {
581
714
  $vol = (Get-Content $VolumeFile -Raw).Trim()
@@ -31,10 +31,65 @@ try {
31
31
  try { Rename-Item $f.FullName $procFile -ErrorAction Stop } catch { continue }
32
32
  try {
33
33
  $req = Get-Content $procFile -Raw | ConvertFrom-Json
34
+
35
+ # Music preview: play/stop a standalone background-music track
36
+ # ASYNCHRONOUSLY (Start-Process) so it never blocks the TTS queue,
37
+ # tracking the ffplay PID so a new request (or an explicit stop)
38
+ # replaces the previous track instead of stacking playback.
39
+ # kind=music → stop any current preview, then play the track
40
+ # kind=music-stop → stop any current preview (no new playback)
41
+ if ($req.kind -eq 'music' -or $req.kind -eq 'music-stop') {
42
+ $pidFile = Join-Path $env:USERPROFILE '.agentvibes\music-preview.pid'
43
+ # Stop the currently-playing preview, if any. Verify the PID is
44
+ # actually ffplay so a recycled PID can't kill an unrelated proc.
45
+ if (Test-Path $pidFile) {
46
+ $oldPid = (Get-Content $pidFile -Raw -ErrorAction SilentlyContinue).Trim()
47
+ if ($oldPid -match '^\d+$') {
48
+ $op = Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue
49
+ if ($op -and $op.ProcessName -eq 'ffplay') { Stop-Process -Id ([int]$oldPid) -Force -ErrorAction SilentlyContinue }
50
+ }
51
+ Remove-Item $pidFile -Force -ErrorAction SilentlyContinue
52
+ }
53
+ if ($req.kind -eq 'music') {
54
+ $trackName = [string]$req.music
55
+ if ($trackName -match '^[A-Za-z0-9._][A-Za-z0-9._\-]*\.mp3$') {
56
+ $tracksDir = Join-Path $env:USERPROFILE '.claude\audio\tracks'
57
+ $full = [System.IO.Path]::GetFullPath((Join-Path $tracksDir $trackName))
58
+ $baseFull = [System.IO.Path]::GetFullPath($tracksDir)
59
+ if (-not $baseFull.EndsWith([IO.Path]::DirectorySeparatorChar)) { $baseFull += [IO.Path]::DirectorySeparatorChar }
60
+ if ($full.StartsWith($baseFull, [StringComparison]::OrdinalIgnoreCase) -and (Test-Path $full)) {
61
+ $ffplay = Get-Command ffplay -ErrorAction SilentlyContinue
62
+ if ($ffplay) {
63
+ Write-WatcherLog "INFO" "music-preview id=$($req.id) track=$trackName"
64
+ $mp = Start-Process -FilePath $ffplay.Source -ArgumentList @('-autoexit','-nodisp','-loglevel','quiet','-volume','80',$full) -WindowStyle Hidden -PassThru
65
+ if ($mp) { Set-Content -Path $pidFile -Value $mp.Id -NoNewline -ErrorAction SilentlyContinue }
66
+ } else {
67
+ Write-WatcherLog "WARN" "ffplay not found - cannot preview music id=$($req.id)"
68
+ }
69
+ } else {
70
+ Write-WatcherLog "WARN" "music track not found id=$($req.id) track=$trackName"
71
+ }
72
+ } else {
73
+ Write-WatcherLog "WARN" "invalid music track name id=$($req.id)"
74
+ }
75
+ } else {
76
+ Write-WatcherLog "INFO" "music-stop id=$($req.id)"
77
+ }
78
+ Remove-Item $procFile -Force -ErrorAction SilentlyContinue
79
+ continue
80
+ }
81
+
34
82
  # Validate voice before passing to command line
35
83
  $safeVoice = if ($req.voice -and $req.voice -match '^[a-zA-Z0-9_\-\. :]+$') { $req.voice } else { "" }
36
84
  $env:CLAUDE_PROJECT_DIR = $env:USERPROFILE
37
85
  $env:AGENTVIBES_NO_PRETEXT = "1"
86
+ # Carry the sender's project (folder name/path) through to the
87
+ # TalkingHead forward so the avatar badge/tab shows the real remote
88
+ # origin instead of this machine's own profile dir. (Grafted from master WIP.)
89
+ if ($req.project) { $env:AGENTVIBES_PROJECT = $req.project }
90
+ else { [System.Environment]::SetEnvironmentVariable("AGENTVIBES_PROJECT", $null, "Process") }
91
+ if ($req.projectPath) { $env:AGENTVIBES_PROJECT_PATH = $req.projectPath }
92
+ else { [System.Environment]::SetEnvironmentVariable("AGENTVIBES_PROJECT_PATH", $null, "Process") }
38
93
  # Use SetEnvironmentVariable to truly unset (assignment to $null leaves empty string)
39
94
  if ($req.music) { $env:AGENTVIBES_OVERRIDE_MUSIC = $req.music }
40
95
  else { [System.Environment]::SetEnvironmentVariable("AGENTVIBES_OVERRIDE_MUSIC", $null, "Process") }
@@ -6,7 +6,7 @@
6
6
 
7
7
  param(
8
8
  [Parameter(Position = 0)]
9
- [ValidateSet('list', 'switch', 'get')]
9
+ [ValidateSet('list', 'switch', 'get', 'replay')]
10
10
  [string]$Command = 'list',
11
11
 
12
12
  [Parameter(Position = 1)]
@@ -155,6 +155,102 @@ function Show-CurrentVoice {
155
155
  }
156
156
  }
157
157
 
158
+ # Play an audio file without blocking the caller (mirrors voice-manager.sh's
159
+ # `afplay "$AUDIO_FILE" &` / `paplay ... &` background playback). Prefers
160
+ # ffplay (handles wav/mp3/aiff); falls back to System.Media.SoundPlayer
161
+ # (.Play(), which is inherently async — wav only).
162
+ function Start-ReplayPlayback {
163
+ param([string]$FilePath)
164
+
165
+ # Stay silent while the automated test suite is running (same convention
166
+ # as play-tts.ps1's Invoke-AudioPlay).
167
+ if (Test-Path (Join-Path $env:USERPROFILE ".agentvibes-tests-running")) { return }
168
+
169
+ $ffplayCmd = Get-Command ffplay -ErrorAction SilentlyContinue
170
+ if ($ffplayCmd) {
171
+ Start-Process -FilePath $ffplayCmd.Source `
172
+ -ArgumentList @("-autoexit", "-nodisp", "-loglevel", "quiet", $FilePath) `
173
+ -WindowStyle Hidden | Out-Null
174
+ return
175
+ }
176
+
177
+ try {
178
+ $player = New-Object System.Media.SoundPlayer $FilePath
179
+ $player.Play()
180
+ }
181
+ catch {
182
+ Write-Host "[WARN] Could not start playback: $_" -ForegroundColor Yellow
183
+ }
184
+ }
185
+
186
+ # Replay the Nth most recent TTS audio file (mirrors voice-manager.sh's
187
+ # `replay` case: resolve the audio dir, validate N (1-10), pick the file by
188
+ # most-recent-mtime, and play it in the background).
189
+ function Replay-Audio {
190
+ param([string]$NArg)
191
+
192
+ $AudioDir = $null
193
+ if ($env:CLAUDE_PROJECT_DIR -and (Test-Path (Join-Path $env:CLAUDE_PROJECT_DIR ".claude"))) {
194
+ $AudioDir = Join-Path $env:CLAUDE_PROJECT_DIR ".claude\audio"
195
+ }
196
+ else {
197
+ # Walk up from cwd looking for a .claude directory (same fallback as voice-manager.sh).
198
+ # Use a plain string path throughout: Get-Location returns a PathInfo (.Path),
199
+ # but Get-Item returns a DirectoryInfo (no .Path) — mixing them made the second
200
+ # iteration do Join-Path $null and throw a terminating error.
201
+ $dirPath = (Get-Location).Path
202
+ while ($dirPath) {
203
+ $candidate = Join-Path $dirPath ".claude"
204
+ if (Test-Path $candidate) {
205
+ $AudioDir = Join-Path $candidate "audio"
206
+ break
207
+ }
208
+ $parent = Split-Path $dirPath -Parent
209
+ if (-not $parent -or $parent -eq $dirPath) { break }
210
+ $dirPath = $parent
211
+ }
212
+ if (-not $AudioDir) {
213
+ $AudioDir = Join-Path $ClaudeDir "audio"
214
+ }
215
+ }
216
+
217
+ $N = 1
218
+ if ($NArg) {
219
+ if ($NArg -notmatch '^\d+$') {
220
+ Write-Host "[ERROR] Invalid argument. Please use a number (1-10)" -ForegroundColor Red
221
+ exit 1
222
+ }
223
+ $N = [int]$NArg
224
+ }
225
+ if ($N -lt 1 -or $N -gt 10) {
226
+ Write-Host "[ERROR] Number out of range. Please choose 1-10" -ForegroundColor Red
227
+ exit 1
228
+ }
229
+
230
+ if (-not (Test-Path $AudioDir)) {
231
+ Write-Host "[ERROR] No audio history found" -ForegroundColor Red
232
+ Write-Host "Audio files are stored in: $AudioDir"
233
+ exit 1
234
+ }
235
+
236
+ $files = @(Get-ChildItem -Path $AudioDir -File -ErrorAction SilentlyContinue |
237
+ Where-Object { $_.Name -match '^tts-.*\.(wav|mp3|aiff)$' } |
238
+ Sort-Object LastWriteTime -Descending)
239
+
240
+ if ($files.Count -lt $N) {
241
+ Write-Host "[ERROR] Audio #$N not found in history" -ForegroundColor Red
242
+ Write-Host "Total audio files available: $($files.Count)"
243
+ exit 1
244
+ }
245
+
246
+ $target = $files[$N - 1]
247
+ Write-Host "[REPLAY] Replaying audio #${N}:" -ForegroundColor Cyan
248
+ Write-Host " File: $($target.Name)"
249
+ Write-Host " Path: $($target.FullName)"
250
+
251
+ Start-ReplayPlayback $target.FullName
252
+ }
253
+
158
254
  # Main command routing
159
255
  switch ($Command) {
160
256
  'list' {
@@ -173,4 +269,8 @@ switch ($Command) {
173
269
  'get' {
174
270
  Show-CurrentVoice
175
271
  }
272
+
273
+ 'replay' {
274
+ Replay-Audio $VoiceName
275
+ }
176
276
  }
package/README.md CHANGED
@@ -58,14 +58,19 @@ New here? The [**Quick Start guide**](docs/quick-start.md) walks you through you
58
58
 
59
59
  ---
60
60
 
61
- ## 🆕 Neural voices (v5.11.0)
61
+ ## 🆕 A stronger core (v5.12.0)
62
62
 
63
- Your agent team can now sound genuinely human each agent with its own distinct voice.
63
+ During a week of early access to Anthropic's new **Fable** model, we rebuilt the heart of AgentVibes into **one shared core**. The voice / engine / routing / volume / mute logic that used to be copied across four scripts (and drift apart) now lives in a single place simpler, more consistent, and steadier.
64
64
 
65
- - **🧠 Kokoro** local neural TTS that runs on your **CPU, no GPU required**, with **Chinese, Japanese, and Korean** voices built in.
66
- - **☁️ ElevenLabs** — premium cloud neural voices when you want the absolute best.
65
+ - **🔊 Previews play in the right place** with SSH remote configured, voice and **music previews** play on your receiver; otherwise they play locally.
66
+ - **🧠 One shared core** — Kokoro-on-Linux silence and per-voice drift fixed at the source, with a safe fallback if needed.
67
+ - **🧹 Removed the redundant Voices tab** — pick a voice for any provider in Setup.
67
68
 
68
- Plus combinable audio effects landed too: stack **reverb**, **echo**, and **chorus** on any voice.
69
+ ### v5.11.0 Neural voices
70
+
71
+ - **🧠 Kokoro** — local neural TTS on your **CPU, no GPU required** (Chinese, Japanese, Korean built in).
72
+ - **☁️ ElevenLabs** — premium cloud neural voices.
73
+ - Combinable audio effects: stack **reverb**, **echo**, and **chorus** on any voice.
69
74
 
70
75
  <div align="center">
71
76
 
@@ -219,7 +224,7 @@ Running a full BMAD agent team? **Every agent gets its own distinct voice**, aut
219
224
 
220
225
  ## About
221
226
 
222
- **AgentVibes** · v5.11.2 · Licensed under [Apache-2.0](LICENSE)
227
+ **AgentVibes** · v5.12.0 · Licensed under [Apache-2.0](LICENSE)
223
228
 
224
229
  Built by **Paul Preibisch** — [@997Fire on X](https://x.com/997Fire) · [agentvibes.org](https://agentvibes.org) · [github.com/paulpreibisch/AgentVibes](https://github.com/paulpreibisch/AgentVibes)
225
230
 
package/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,72 @@
1
1
  # AgentVibes Release Notes
2
2
 
3
+ ## 🎉 v5.12.0 — The Fable Week Overhaul (Stable)
4
+
5
+ **Released:** 2026-07-05 · now on `latest` — `npm install agentvibes`
6
+
7
+ This turns the "Fable Week" alpha into a stable release. During a week of early access to Anthropic's new **Fable** model, we pointed it at the whole AgentVibes codebase and rebuilt the core properly.
8
+
9
+ ### A stronger, shared core
10
+
11
+ Every time AgentVibes speaks it makes a lot of decisions — which voice, which engine, whether to play here or send the audio to another machine, background music, volume, mute. That logic had been copied into several separate scripts (Mac/Linux, Windows, remote, and the voice server), and the copies slowly **drifted apart** — a fix in one was missed in the others, which is why certain glitches kept coming back.
12
+
13
+ We replaced all of it with **one shared core** that every part of AgentVibes now follows — one place to fix, one place to trust. What you'll notice:
14
+
15
+ - **Kokoro voices that were silent on Linux now work everywhere.**
16
+ - **Your voice choices stick** — settings no longer get quietly overridden.
17
+ - **Volume, mute, and remote playback behave the same** across Mac, Linux, and Windows.
18
+ - **Safe by default** — if the new core isn't available on your machine, AgentVibes falls back to the old behaviour, so it never just stops talking.
19
+
20
+ ### Previews now play in the right place
21
+
22
+ Previewing a voice or track used to play on whatever machine you were sitting at — which was silent if you'd set AgentVibes up to send its audio elsewhere. Now:
23
+
24
+ - **If you have SSH remote configured, previews play on your receiver; otherwise they play locally, like before.**
25
+ - This covers **voice previews** (Piper and Kokoro) from the Setup, Agent, and Settings screens, and **music/track previews** — press Space to play, Space again to stop.
26
+
27
+ ### A simpler voice menu
28
+
29
+ - We **removed the redundant Voices tab.** It only ever listed Piper voices and confused people, since choosing a voice for any provider already lives in **Setup**.
30
+
31
+ ### Groundwork for what's next
32
+
33
+ - The receiver now also receives the **full path of the project folder** a message came from (a new `projectPath` field, alongside the project name it already got) — laying the groundwork for upcoming enhancements.
34
+
35
+ ### Reviewed before shipping
36
+
37
+ We ran three independent reviews over the changes — security, correctness, and regressions — and fixed every real issue before release.
38
+
39
+ ---
40
+
41
+ ## 🧪 v5.12.0-alpha.0 — The "Fable Week" Overhaul (ALPHA / testing)
42
+
43
+ **Published:** 2026-07-04 · dist-tag **`alpha`** (not `latest`). Try it with `npm install agentvibes@alpha`.
44
+
45
+ ### The story behind this release
46
+
47
+ For one week, Anthropic gave us early access to their brand-new **Fable** model. Instead of keeping that to ourselves, we decided to **spend our tokens on the community** — we pointed Fable at the entire AgentVibes codebase and asked it to find everything that was fragile, duplicated, or quietly broken, and to help us rebuild it properly. This alpha is the result: a real overhaul, paid for with that week of access, so **everyone who uses AgentVibes benefits**.
48
+
49
+ ### What was wrong (in plain terms)
50
+
51
+ AgentVibes has to make a lot of decisions every time it speaks — which voice, which engine, play here or send to another machine, background music on or off, how loud, muted or not. Over time, that decision-making logic had been **copied into four different places**, written in four different languages (the Mac/Linux script, the Windows script, the remote-audio script, and the voice-control server). Those copies slowly **drifted apart** — a fix made in one was missed in the others. That's why the same kinds of glitches kept coming back: a voice that played silence on one system but worked on another, volume that ignored your setting, mute that behaved differently everywhere.
52
+
53
+ ### What we did
54
+
55
+ We built **one "brain"** that makes all those decisions in a single place, and everything else simply does what the brain says. One place to fix, one place to trust — which retires that whole family of recurring bugs. Concretely in this alpha:
56
+
57
+ - **Kokoro voices that played silence on Linux now work** on every system.
58
+ - **Your per-voice assignments stick** — the assistant repeating back its own settings no longer overrides them.
59
+ - **Team/party voices are back** — a hidden crash that made every agent speak with the same voice is fixed; each agent has its own voice again.
60
+ - **No more grabbing the wrong audio file** — a long-standing shortcut that could play another agent's clip is gone for good.
61
+ - **Consistent volume, mute, and remote playback** across Mac, Linux, and Windows.
62
+ - **Safety first:** if anything about the new brain isn't available on your machine, AgentVibes automatically falls back to the old behaviour — so your voice **never just stops working**.
63
+
64
+ Before shipping, we had Fable act as a tough reviewer of our own work — it caught **three real bugs and a packaging mistake** we'd have otherwise released, and we fixed them first.
65
+
66
+ **This is an alpha for testing.** `latest` is unchanged; please try it and tell us what you find. Thank you, Anthropic, for the week with Fable — and thanks to this community for making it worth spending on. 🙏
67
+
68
+ ---
69
+
3
70
  ## 🐛 v5.11.2 — Kokoro Installer Fix (PEP 668)
4
71
 
5
72
  **Released:** 2026-06-27
package/bin/mcp-server.sh CHANGED
@@ -72,7 +72,7 @@ if ! python3 -c "import mcp" 2>/dev/null; then
72
72
  print_section "📦 Installing Python MCP Package"
73
73
  echo "Installing 'mcp' package to enable Claude Desktop integration..." >&2
74
74
 
75
- if python3 -m pip install --user mcp --quiet 2>&1; then
75
+ if python3 -m pip install --user mcp --quiet >&2; then
76
76
  echo "✅ Python MCP package installed successfully!" >&2
77
77
  else
78
78
  echo "❌ Failed to auto-install MCP package" >&2
@@ -86,9 +86,10 @@ else
86
86
  echo "✅ Python MCP package already installed" >&2
87
87
  fi
88
88
 
89
- # Check provider (Piper vs ElevenLabs)
89
+ # Check provider — ElevenLabs was removed from the product; Piper (free,
90
+ # offline) is the only provider this wrapper does auto-setup for.
90
91
  PROVIDER_FILE="$PACKAGE_ROOT/.claude/tts-provider.txt"
91
- PROVIDER="elevenlabs" # Default
92
+ PROVIDER="piper" # Default
92
93
 
93
94
  if [ -f "$PROVIDER_FILE" ]; then
94
95
  PROVIDER=$(cat "$PROVIDER_FILE" | tr -d '[:space:]')
@@ -104,7 +105,7 @@ if [ "$PROVIDER" = "piper" ]; then
104
105
  print_section "📦 Installing pipx"
105
106
  echo "pipx is needed to install Piper TTS..." >&2
106
107
 
107
- if python3 -m pip install --user pipx --quiet 2>&1; then
108
+ if python3 -m pip install --user pipx --quiet >&2; then
108
109
  # Add pipx to PATH for this session
109
110
  export PATH="$HOME/.local/bin:$PATH"
110
111
 
@@ -137,7 +138,7 @@ if [ "$PROVIDER" = "piper" ]; then
137
138
  print_section "📦 Installing Piper TTS"
138
139
  echo "Installing Piper TTS (free, offline voice synthesis)..." >&2
139
140
 
140
- if pipx install piper-tts --quiet 2>&1; then
141
+ if pipx install piper-tts --quiet >&2; then
141
142
  echo "✅ Piper TTS installed successfully!" >&2
142
143
 
143
144
  # Add pipx bin to PATH
@@ -181,20 +182,6 @@ if [ "$PROVIDER" = "piper" ]; then
181
182
  fi
182
183
  fi
183
184
  fi
184
- elif [ "$PROVIDER" = "elevenlabs" ]; then
185
- # Check for ElevenLabs API key
186
- if [ -z "$ELEVENLABS_API_KEY" ]; then
187
- echo "⚠️ ElevenLabs selected but ELEVENLABS_API_KEY not set" >&2
188
- echo "" >&2
189
- echo "📖 Set your API key:" >&2
190
- echo " export ELEVENLABS_API_KEY='your-key-here'" >&2
191
- echo "" >&2
192
- echo "💡 Or switch to Piper TTS (free):" >&2
193
- echo " echo 'piper' > $PACKAGE_ROOT/.claude/tts-provider.txt" >&2
194
- echo "" >&2
195
- else
196
- echo "✅ ElevenLabs API key configured" >&2
197
- fi
198
185
  fi
199
186
 
200
187
  # All checks passed, run the MCP server