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.
- package/.claude/config/audio-effects.cfg +1 -1
- package/.claude/config/audio-effects.cfg.bak-kokoro +7 -0
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/audio-processor.sh +1 -1
- package/.claude/hooks/background-music-manager.sh +2 -1
- package/.claude/hooks/bmad-speak-enhanced.sh +37 -22
- package/.claude/hooks/bmad-speak.sh +26 -8
- package/.claude/hooks/play-tts-agentvibes-receiver-for-voiceless-connections.sh +1 -1
- package/.claude/hooks/play-tts-kokoro.sh +9 -0
- package/.claude/hooks/play-tts-piper.sh +13 -1
- package/.claude/hooks/play-tts-ssh-remote.sh +117 -7
- package/.claude/hooks/play-tts-termux-ssh.sh +1 -1
- package/.claude/hooks/play-tts.sh +106 -13
- package/.claude/hooks-windows/background-music-manager.ps1 +2 -1
- package/.claude/hooks-windows/play-tts-kokoro.ps1 +14 -0
- package/.claude/hooks-windows/play-tts.ps1 +163 -30
- package/.claude/hooks-windows/tts-watcher.ps1 +55 -0
- package/.claude/hooks-windows/voice-manager-windows.ps1 +101 -1
- package/README.md +11 -6
- package/RELEASE_NOTES.md +67 -0
- package/bin/mcp-server.sh +6 -19
- package/bin/resolve-utterance.js +137 -0
- package/mcp-server/server.py +280 -99
- package/mcp-server/test_mcp_correctness.py +486 -0
- package/mcp-server/test_windows_script_parity.py +341 -336
- package/package.json +3 -2
- package/setup-windows.ps1 +867 -815
- package/src/console/app.js +14 -11
- package/src/console/footer-config.js +0 -4
- package/src/console/navigation.js +0 -1
- package/src/console/tabs/agents-tab.js +96 -11
- package/src/console/tabs/music-tab.js +108 -3
- package/src/console/tabs/placeholder-tab.js +0 -2
- package/src/console/tabs/settings-tab.js +41 -2
- package/src/console/tabs/setup-tab.js +34 -6
- package/src/console/tabs/voices-tab.js +19 -9
- package/src/console/widgets/track-picker.js +3 -3
- package/src/i18n/de.js +0 -1
- package/src/i18n/en.js +0 -1
- package/src/i18n/es.js +0 -1
- package/src/i18n/fr.js +0 -1
- package/src/i18n/hi.js +0 -1
- package/src/i18n/ja.js +0 -1
- package/src/i18n/ko.js +0 -1
- package/src/i18n/pt.js +0 -1
- package/src/i18n/zh-CN.js +0 -1
- package/src/installer.js +205 -15
- package/src/services/config-service.js +264 -264
- package/src/services/llm-provider-service.js +7 -7
- package/src/services/navigation-service.js +1 -1
- package/src/services/utterance-loader.js +280 -0
- package/src/services/utterance-resolver.js +526 -0
- package/templates/agentvibes-receiver.sh +74 -12
- 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.
|
|
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
|
-
#
|
|
324
|
-
|
|
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
|
-
$
|
|
402
|
-
|
|
403
|
-
#
|
|
404
|
-
#
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
"
|
|
423
|
-
"
|
|
424
|
-
|
|
425
|
-
|
|
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.
|
|
578
|
-
|
|
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
|
-
## 🆕
|
|
61
|
+
## 🆕 A stronger core (v5.12.0)
|
|
62
62
|
|
|
63
|
-
|
|
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
|
-
-
|
|
66
|
-
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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="
|
|
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
|
|
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
|
|
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
|