agentvibes 5.3.0 → 5.4.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/.agentvibes/LITE-MODE.md +236 -0
- package/.agentvibes/README.md +136 -0
- package/.agentvibes/backup/session-start-tts.sh.20251210_212814 +141 -0
- package/.agentvibes/backups/agents/analyst_20260204_144958.md +78 -0
- package/.agentvibes/backups/agents/architect_20260204_144958.md +72 -0
- package/.agentvibes/backups/agents/dev_20260204_144958.md +74 -0
- package/.agentvibes/backups/agents/pm_20260204_144958.md +72 -0
- package/.agentvibes/backups/agents/quick-flow-solo-dev_20260204_144958.md +64 -0
- package/.agentvibes/backups/agents/sm_20260204_144958.md +87 -0
- package/.agentvibes/backups/agents/tea_20260204_144958.md +79 -0
- package/.agentvibes/backups/agents/tech-writer_20260204_144958.md +82 -0
- package/.agentvibes/backups/agents/ux-designer_20260204_144958.md +80 -0
- package/.agentvibes/bmad/bmad-voices.md +69 -69
- package/.agentvibes/config/README-personality-defaults.md +162 -0
- package/.agentvibes/config/mode.txt +1 -0
- package/.agentvibes/config/personality-voice-defaults.default.json +21 -0
- package/.agentvibes/config/save-audio.txt +1 -0
- package/.agentvibes/config/voice-metadata.json +160 -0
- package/.agentvibes/config.json +24 -15
- package/.agentvibes/hooks/help.sh +191 -0
- package/.agentvibes/hooks/post-tool-use-lite.sh +111 -0
- package/.agentvibes/hooks/save-audio-manager.sh +162 -0
- package/.agentvibes/hooks/session-start-full-optimized.sh +102 -0
- package/.agentvibes/hooks/session-start-full.sh +142 -0
- package/.agentvibes/hooks/session-start-lite-v2.sh +34 -0
- package/.agentvibes/hooks/session-start-lite.sh +29 -0
- package/.agentvibes/hooks/stop-lite.sh +115 -0
- package/.agentvibes/hooks/switch-mode.sh +215 -0
- package/.agentvibes/output-styles/audio-summary.md +30 -0
- package/.claude/activation-instructions +54 -54
- package/.claude/audio/voice-samples/piper/alan.wav +0 -0
- package/.claude/audio/voice-samples/piper/amy.wav +0 -0
- package/.claude/audio/voice-samples/piper/charlotte.wav +0 -0
- package/.claude/audio/voice-samples/piper/joe.wav +0 -0
- package/.claude/audio/voice-samples/piper/john.wav +0 -0
- package/.claude/audio/voice-samples/piper/katherine.wav +0 -0
- package/.claude/audio/voice-samples/piper/kristin.wav +0 -0
- package/.claude/audio/voice-samples/piper/linda.wav +0 -0
- package/.claude/audio/voice-samples/piper/marcus.wav +0 -0
- package/.claude/audio/voice-samples/piper/ryan.wav +0 -0
- package/.claude/commands/agent-vibes/add.md +21 -21
- package/.claude/commands/agent-vibes/agent-vibes.md +101 -101
- package/.claude/commands/agent-vibes/agent.md +79 -79
- package/.claude/commands/agent-vibes/background-music.md +111 -111
- package/.claude/commands/agent-vibes/bmad.md +198 -198
- package/.claude/commands/agent-vibes/clean.md +18 -18
- package/.claude/commands/agent-vibes/cleanup.md +18 -18
- package/.claude/commands/agent-vibes/commands.json +145 -145
- package/.claude/commands/agent-vibes/effects.md +97 -97
- package/.claude/commands/agent-vibes/get.md +9 -9
- package/.claude/commands/agent-vibes/hide.md +91 -91
- package/.claude/commands/agent-vibes/language.md +23 -23
- package/.claude/commands/agent-vibes/learn.md +67 -67
- package/.claude/commands/agent-vibes/list.md +13 -13
- package/.claude/commands/agent-vibes/mute.md +37 -37
- package/.claude/commands/agent-vibes/preview.md +17 -17
- package/.claude/commands/agent-vibes/provider.md +68 -68
- package/.claude/commands/agent-vibes/replay-target.md +14 -14
- package/.claude/commands/agent-vibes/sample.md +12 -12
- package/.claude/commands/agent-vibes/set-favorite-voice.md +84 -84
- package/.claude/commands/agent-vibes/set-pretext.md +65 -65
- package/.claude/commands/agent-vibes/set-speed.md +41 -41
- package/.claude/commands/agent-vibes/show.md +84 -84
- package/.claude/commands/agent-vibes/switch.md +87 -87
- package/.claude/commands/agent-vibes/target-voice.md +26 -26
- package/.claude/commands/agent-vibes/target.md +30 -30
- package/.claude/commands/agent-vibes/translate.md +68 -68
- package/.claude/commands/agent-vibes/unmute.md +45 -45
- package/.claude/commands/agent-vibes/whoami.md +7 -7
- package/.claude/commands/agent-vibes-bmad-voices.md +117 -117
- package/.claude/commands/agent-vibes-rdp.md +24 -24
- package/.claude/config/audio-effects.cfg +4 -11
- package/.claude/config/audio-effects.cfg.sample +52 -52
- package/.claude/config/background-music-position.txt +27 -0
- package/.claude/config/background-music-volume.txt +1 -1
- package/.claude/config/background-music.cfg +1 -0
- package/.claude/config/background-music.txt +1 -0
- package/.claude/config/tts-speech-rate.txt +1 -4
- package/.claude/config/tts-verbosity.txt +1 -0
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/README-TTS-QUEUE.md +135 -135
- package/.claude/hooks/audio-cache-utils.sh +0 -0
- package/.claude/hooks/audio-processor.sh +60 -14
- package/.claude/hooks/background-music-manager.sh +0 -0
- package/.claude/hooks/bmad-party-manager.sh +225 -0
- package/.claude/hooks/bmad-speak-enhanced.sh +0 -0
- package/.claude/hooks/bmad-speak.sh +6 -13
- package/.claude/hooks/bmad-tts-injector.sh +0 -0
- package/.claude/hooks/bmad-voice-manager.sh +0 -0
- package/.claude/hooks/clawdbot-receiver-SECURE.sh +25 -23
- package/.claude/hooks/clawdbot-receiver.sh +4 -28
- package/.claude/hooks/clean-audio-cache.sh +0 -0
- package/.claude/hooks/cleanup-cache.sh +0 -0
- package/.claude/hooks/configure-rdp-mode.sh +0 -0
- package/.claude/hooks/download-extra-voices.sh +0 -0
- package/.claude/hooks/effects-manager.sh +0 -0
- package/.claude/hooks/github-star-reminder.sh +0 -0
- package/.claude/hooks/language-manager.sh +0 -0
- package/.claude/hooks/learn-manager.sh +0 -0
- package/.claude/hooks/macos-voice-manager.sh +0 -0
- package/.claude/hooks/migrate-background-music.sh +0 -0
- package/.claude/hooks/migrate-to-agentvibes.sh +0 -0
- package/.claude/hooks/optimize-background-music.sh +0 -0
- package/.claude/hooks/personality-manager.sh +0 -0
- package/.claude/hooks/piper-download-voices.sh +0 -0
- package/.claude/hooks/piper-installer.sh +1 -1
- package/.claude/hooks/piper-multispeaker-registry.sh +0 -0
- package/.claude/hooks/piper-voice-manager.sh +0 -0
- package/.claude/hooks/play-tts-enhanced.sh +0 -0
- package/.claude/hooks/play-tts-macos.sh +6 -12
- package/.claude/hooks/play-tts-piper.sh +50 -79
- package/.claude/hooks/play-tts-soprano.sh +9 -43
- package/.claude/hooks/play-tts-ssh-remote.sh +43 -215
- package/.claude/hooks/play-tts-termux-ssh.sh +0 -0
- package/.claude/hooks/play-tts.sh +31 -31
- package/.claude/hooks/post-response.sh +41 -0
- package/.claude/hooks/prepare-release.sh +0 -0
- package/.claude/hooks/provider-commands.sh +0 -0
- package/.claude/hooks/provider-manager.sh +0 -0
- package/.claude/hooks/replay-target-audio.sh +0 -0
- package/.claude/hooks/requirements.txt +6 -6
- package/.claude/hooks/sentiment-manager.sh +0 -0
- package/.claude/hooks/session-start-tts.sh +56 -39
- package/.claude/hooks/soprano-gradio-synth.py +139 -139
- package/.claude/hooks/speed-manager.sh +0 -0
- package/.claude/hooks/stop.sh +63 -0
- package/.claude/hooks/termux-installer.sh +0 -0
- package/.claude/hooks/translate-manager.sh +0 -0
- package/.claude/hooks/translator.py +237 -237
- package/.claude/hooks/tts-queue-worker.sh +0 -0
- package/.claude/hooks/tts-queue.sh +0 -0
- package/.claude/hooks/verbosity-manager.sh +0 -0
- package/.claude/hooks/voice-manager.sh +26 -4
- package/.claude/hooks-windows/audio-cache-utils.ps1 +119 -119
- package/.claude/hooks-windows/bmad-party-speak.ps1 +278 -278
- package/.claude/hooks-windows/bmad-speak.ps1 +264 -264
- package/.claude/hooks-windows/clean-audio-cache.ps1 +53 -53
- package/.claude/hooks-windows/effects-manager.ps1 +294 -294
- package/.claude/hooks-windows/language-manager.ps1 +193 -193
- package/.claude/hooks-windows/learn-manager.ps1 +241 -241
- package/.claude/hooks-windows/personality-manager.ps1 +266 -266
- package/.claude/hooks-windows/play-tts-soprano.ps1 +5 -5
- package/.claude/hooks-windows/play-tts-termux-ssh.ps1 +138 -138
- package/.claude/hooks-windows/play-tts-windows-piper.ps1 +164 -0
- package/.claude/hooks-windows/play-tts-windows-sapi.ps1 +108 -0
- package/.claude/hooks-windows/play-tts.ps1 +104 -513
- package/.claude/hooks-windows/provider-manager.ps1 +158 -192
- package/.claude/hooks-windows/session-start-tts.ps1 +55 -46
- package/.claude/hooks-windows/soprano-gradio-synth.py +153 -153
- package/.claude/hooks-windows/speed-manager.ps1 +166 -166
- package/.claude/hooks-windows/voice-manager-windows.ps1 +176 -260
- package/.claude/output-styles/agent-vibes.md +202 -202
- package/.claude/personalities/angry.md +14 -14
- package/.claude/personalities/annoying.md +14 -14
- package/.claude/personalities/crass.md +14 -14
- package/.claude/personalities/dramatic.md +14 -14
- package/.claude/personalities/dry-humor.md +50 -50
- package/.claude/personalities/flirty.md +20 -20
- package/.claude/personalities/funny.md +14 -14
- package/.claude/personalities/grandpa.md +32 -32
- package/.claude/personalities/millennial.md +14 -14
- package/.claude/personalities/moody.md +14 -14
- package/.claude/personalities/normal.md +16 -16
- package/.claude/personalities/pirate.md +14 -14
- package/.claude/personalities/poetic.md +14 -14
- package/.claude/personalities/professional.md +14 -14
- package/.claude/personalities/rapper.md +55 -55
- package/.claude/personalities/robot.md +14 -14
- package/.claude/personalities/sarcastic.md +38 -38
- package/.claude/personalities/sassy.md +14 -14
- package/.claude/personalities/surfer-dude.md +14 -14
- package/.claude/personalities/zen.md +14 -14
- package/.claude/piper-voices-dir.txt +1 -0
- package/.claude/settings.json +25 -15
- package/.claude/verbosity.txt +1 -1
- package/.clawdbot/README.md +105 -105
- package/.clawdbot/skill/SKILL.md +149 -145
- package/.mcp.json +30 -11
- package/CLAUDE.md +170 -215
- package/README.md +206 -525
- package/RELEASE_NOTES.md +1132 -1976
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +0 -0
- package/bin/agentvibes-voice-browser.js +64 -1289
- package/bin/agentvibes.js +0 -0
- package/bin/ensure-soprano-running.sh +43 -0
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +0 -0
- package/bin/test-bmad-pr +78 -78
- package/mcp-server/QUICK_START.md +203 -203
- package/mcp-server/README.md +345 -345
- package/mcp-server/WINDOWS_SETUP.md +260 -260
- package/mcp-server/docs/troubleshooting-audio.md +313 -313
- package/mcp-server/examples/claude_desktop_config.json +11 -11
- package/mcp-server/examples/claude_desktop_config_piper.json +9 -9
- package/mcp-server/examples/custom_instructions.md +169 -169
- package/mcp-server/install-deps.js +130 -130
- package/mcp-server/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +1451 -1578
- package/mcp-server/test_server.py +395 -395
- package/package.json +1 -3
- package/setup-windows.ps1 +815 -815
- package/src/installer.js +42 -5
- package/templates/agentvibes-receiver.sh +158 -483
- package/templates/audio/welcome-music.mp3 +0 -0
- package/.agentvibes/bmad-voice-map.json +0 -104
- package/.agentvibes/copilot-sessions.log +0 -4
- package/.claude/config/audio-effects-bmad.cfg +0 -50
- package/.claude/config/background-music-enabled.txt +0 -1
- package/.claude/config/intro-text.txt +0 -1
- package/.claude/config/personality.txt +0 -1
- package/.claude/config/piper-speech-rate.txt +0 -4
- package/.claude/config/piper-target-speech-rate.txt +0 -1
- package/.claude/config/reverb-level.txt +0 -1
- package/.claude/config/tts-target-speech-rate.txt +0 -1
- package/voice-assignments.json +0 -8245
- /package/{.claude → .agentvibes}/config/agentvibes.json +0 -0
|
@@ -1,278 +1,278 @@
|
|
|
1
|
-
#
|
|
2
|
-
# File: ~/.claude/hooks-windows/bmad-party-speak.ps1
|
|
3
|
-
#
|
|
4
|
-
# AgentVibes PostToolUse Hook - BMAD Party Mode TTS
|
|
5
|
-
#
|
|
6
|
-
# Fires after every Agent tool call. Detects BMAD party mode agents by
|
|
7
|
-
# fingerprinting the prompt, extracts the agent display name, maps it to
|
|
8
|
-
# the canonical agent ID via the manifest, then calls bmad-speak.ps1.
|
|
9
|
-
#
|
|
10
|
-
# Installed globally so it works in any BMAD project.
|
|
11
|
-
# Uses $env:CLAUDE_PROJECT_DIR to locate the project manifest at runtime.
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
# --- Read stdin safely ---
|
|
16
|
-
$raw = [Console]::In.ReadToEnd()
|
|
17
|
-
if (-not $raw -or $raw.Trim() -eq "") { exit 0 }
|
|
18
|
-
|
|
19
|
-
$data = $raw | ConvertFrom-Json
|
|
20
|
-
if (-not $data) { exit 0 }
|
|
21
|
-
|
|
22
|
-
# --- Only handle Agent tool ---
|
|
23
|
-
if ($data.tool_name -ne "Agent") { exit 0 }
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# --- Extract prompt ---
|
|
27
|
-
$prompt = $data.tool_input.prompt
|
|
28
|
-
if (-not $prompt) { exit 0 }
|
|
29
|
-
|
|
30
|
-
# --- Fingerprint: only fire for BMAD party mode agents ---
|
|
31
|
-
if ($prompt -notmatch "BMAD agent in a collaborative roundtable") { exit 0 }
|
|
32
|
-
|
|
33
|
-
# --- Extract display name from "You are {Name} (" ---
|
|
34
|
-
if ($prompt -notmatch "You are ([A-Za-z]+)\s*\(") { exit 0 }
|
|
35
|
-
$DisplayName = $Matches[1]
|
|
36
|
-
|
|
37
|
-
# --- Extract response text ---
|
|
38
|
-
$content = $data.tool_response.content
|
|
39
|
-
if (-not $content -or $content.Count -eq 0) { exit 0 }
|
|
40
|
-
$ResponseText = ($content | Where-Object { $_.type -eq "text" } | Select-Object -First 1).text
|
|
41
|
-
if (-not $ResponseText) { exit 0 }
|
|
42
|
-
|
|
43
|
-
# Strip leading icon + bold name header e.g. "📊 **Mary:**" or garbled "≡ƒôè **Mary:**"
|
|
44
|
-
# Trim first so leading newlines don't defeat the ^ anchor (fixes M7)
|
|
45
|
-
$ResponseText = $ResponseText.Trim()
|
|
46
|
-
$ResponseText = $ResponseText -replace '^\S*\s*\*\*[^:]+:\*\*\s*', ''
|
|
47
|
-
$ResponseText = $ResponseText.Trim()
|
|
48
|
-
if (-not $ResponseText) { exit 0 }
|
|
49
|
-
|
|
50
|
-
# Strip markdown so TTS doesn't speak asterisks, hashes, backticks, etc.
|
|
51
|
-
$ResponseText = $ResponseText -replace '\*{1,3}', '' # bold, italic, bold-italic
|
|
52
|
-
$ResponseText = $ResponseText -replace '`{1,3}[^`]*`{1,3}', '' # inline code / code blocks
|
|
53
|
-
$ResponseText = $ResponseText -replace '#{1,6}\s*', '' # headings
|
|
54
|
-
$ResponseText = $ResponseText -replace '_{1,2}', '' # underline/italic alt
|
|
55
|
-
$ResponseText = $ResponseText -replace '\[([^\]]+)\]\([^)]+\)', '$1' # links → label only
|
|
56
|
-
$ResponseText = $ResponseText -replace '!\[[^\]]*\]\([^)]+\)', '' # images
|
|
57
|
-
$ResponseText = $ResponseText -replace '(?m)^\s*[-*+]\s+', '' # bullet list markers (multiline)
|
|
58
|
-
$ResponseText = $ResponseText -replace '(?m)^\s*\d+\.\s+', '' # numbered list markers
|
|
59
|
-
$ResponseText = $ResponseText -replace '\\([!$*_`\\])', '$1' # escaped markdown chars
|
|
60
|
-
$ResponseText = $ResponseText.Trim()
|
|
61
|
-
if (-not $ResponseText) { exit 0 }
|
|
62
|
-
|
|
63
|
-
# --- Resolve paths ---
|
|
64
|
-
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
65
|
-
$ProjectRoot = $env:CLAUDE_PROJECT_DIR
|
|
66
|
-
|
|
67
|
-
# --- Find bmad-speak.ps1 (prefer project-local, fall back to global) ---
|
|
68
|
-
$BmadSpeak = $null
|
|
69
|
-
if ($ProjectRoot) {
|
|
70
|
-
$local = Join-Path $ProjectRoot ".claude\hooks-windows\bmad-speak.ps1"
|
|
71
|
-
if (Test-Path $local) { $BmadSpeak = $local }
|
|
72
|
-
}
|
|
73
|
-
if (-not $BmadSpeak) {
|
|
74
|
-
$global = Join-Path $ScriptDir "bmad-speak.ps1"
|
|
75
|
-
if (Test-Path $global) { $BmadSpeak = $global }
|
|
76
|
-
}
|
|
77
|
-
if (-not $BmadSpeak) { exit 0 }
|
|
78
|
-
|
|
79
|
-
# --- Look up canonical agent ID from project manifest (fixes m2: UTF8 encoding) ---
|
|
80
|
-
$AgentId = $DisplayName # fallback
|
|
81
|
-
if ($ProjectRoot) {
|
|
82
|
-
$ManifestFile = Join-Path $ProjectRoot "_bmad\_config\agent-manifest.csv"
|
|
83
|
-
if (Test-Path $ManifestFile) {
|
|
84
|
-
$rows = Import-Csv $ManifestFile -Encoding UTF8
|
|
85
|
-
foreach ($row in $rows) {
|
|
86
|
-
if ($row.displayName -ieq $DisplayName) {
|
|
87
|
-
$AgentId = $row.name
|
|
88
|
-
break
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
# --- Apply verbosity truncation ---
|
|
95
|
-
$Verbosity = "medium"
|
|
96
|
-
$verbosityPaths = @(
|
|
97
|
-
(Join-Path $env:USERPROFILE ".claude\tts-verbosity.txt")
|
|
98
|
-
)
|
|
99
|
-
if ($ProjectRoot) {
|
|
100
|
-
$verbosityPaths = @((Join-Path $ProjectRoot ".claude\tts-verbosity.txt")) + $verbosityPaths
|
|
101
|
-
}
|
|
102
|
-
foreach ($p in $verbosityPaths) {
|
|
103
|
-
if (Test-Path $p) {
|
|
104
|
-
$v = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
|
|
105
|
-
if ($v) { $Verbosity = $v; break }
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
switch ($Verbosity) {
|
|
110
|
-
"low" {
|
|
111
|
-
$sentences = [regex]::Split($ResponseText, '(?<=[.!?])\s+')
|
|
112
|
-
# Fall back to full text if no sentence-ending punctuation (fixes m1)
|
|
113
|
-
if ($sentences.Count -gt 0 -and $sentences[0]) { $ResponseText = $sentences[0] }
|
|
114
|
-
}
|
|
115
|
-
"medium" {
|
|
116
|
-
$sentences = [regex]::Split($ResponseText, '(?<=[.!?])\s+')
|
|
117
|
-
# Fall back to full text if no sentence-ending punctuation (fixes m1)
|
|
118
|
-
$truncated = ($sentences | Select-Object -First 2) -join " "
|
|
119
|
-
if ($truncated) { $ResponseText = $truncated }
|
|
120
|
-
}
|
|
121
|
-
# "high" = full text
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
# --- Pre-synthesize WAV before acquiring mutex so synthesis overlaps with previous agent's playback ---
|
|
125
|
-
$PreSynthWav = $null
|
|
126
|
-
try {
|
|
127
|
-
# Resolve agent voice from voice map
|
|
128
|
-
$VoiceMapLocal = if ($ProjectRoot) { Join-Path $ProjectRoot ".agentvibes\bmad-voice-map.json" } else { $null }
|
|
129
|
-
$VoiceMapGlobal = Join-Path $env:USERPROFILE ".agentvibes\bmad-voice-map.json"
|
|
130
|
-
$VoiceMapFile = if ($VoiceMapLocal -and (Test-Path $VoiceMapLocal)) { $VoiceMapLocal }
|
|
131
|
-
elseif (Test-Path $VoiceMapGlobal) { $VoiceMapGlobal }
|
|
132
|
-
else { $null }
|
|
133
|
-
|
|
134
|
-
$AgentVoiceName = $null
|
|
135
|
-
$SpeakerId = $null
|
|
136
|
-
$AgentPretext = $null
|
|
137
|
-
if ($VoiceMapFile) {
|
|
138
|
-
$vm = Get-Content $VoiceMapFile -Raw | ConvertFrom-Json
|
|
139
|
-
$profile = $vm.agents.$AgentId
|
|
140
|
-
if ($profile -and $profile.pretext) { $AgentPretext = $profile.pretext }
|
|
141
|
-
if ($profile -and $profile.voice) {
|
|
142
|
-
$raw = $profile.voice
|
|
143
|
-
if ($raw -match '::') {
|
|
144
|
-
$parts = $raw -split '::'
|
|
145
|
-
$AgentVoiceName = $parts[0]
|
|
146
|
-
$SpeakerName = if ($parts.Length -ge 2) { $parts[1] } else { "" }
|
|
147
|
-
# NOTE: The suffix number (e.g. "14" in "Yara-14") is a display disambiguator,
|
|
148
|
-
# NOT the piper speaker index. Must look up real index from speaker_id_map.
|
|
149
|
-
if ($SpeakerName) {
|
|
150
|
-
$VoicesDir = "$env:USERPROFILE\.claude\piper-voices"
|
|
151
|
-
$OnnxJsonPath = "$VoicesDir\$AgentVoiceName.onnx.json"
|
|
152
|
-
if (Test-Path $OnnxJsonPath) {
|
|
153
|
-
try {
|
|
154
|
-
$onnxData = Get-Content $OnnxJsonPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
155
|
-
$speakerIdMap = $onnxData.speaker_id_map
|
|
156
|
-
if ($speakerIdMap -and $speakerIdMap.PSObject.Properties[$SpeakerName]) {
|
|
157
|
-
$SpeakerId = [string]$speakerIdMap.PSObject.Properties[$SpeakerName].Value
|
|
158
|
-
}
|
|
159
|
-
} catch { }
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
$AgentVoiceName = $raw
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
# Fallback: parse bmad-voices.md markdown table if no JSON voice map found
|
|
169
|
-
if (-not $AgentPretext -and $ProjectRoot) {
|
|
170
|
-
$VoicesMdPaths = @(
|
|
171
|
-
(Join-Path $ProjectRoot ".agentvibes\bmad\bmad-voices.md"),
|
|
172
|
-
(Join-Path $env:USERPROFILE ".agentvibes\bmad\bmad-voices.md")
|
|
173
|
-
)
|
|
174
|
-
foreach ($mdPath in $VoicesMdPaths) {
|
|
175
|
-
if (-not (Test-Path $mdPath)) { continue }
|
|
176
|
-
$mdLines = Get-Content $mdPath -Encoding UTF8
|
|
177
|
-
# Strip bmad-agent- prefix for matching (manifest uses bmad-agent-pm, table uses pm)
|
|
178
|
-
$shortId = $AgentId -replace '^bmad-agent-', ''
|
|
179
|
-
foreach ($mdLine in $mdLines) {
|
|
180
|
-
if ($mdLine -notmatch '^\|') { continue }
|
|
181
|
-
if ($mdLine -match '^\|-') { continue } # separator row
|
|
182
|
-
if ($mdLine -match 'Agent ID') { continue } # header row
|
|
183
|
-
$cols = $mdLine -split '\|' | ForEach-Object { $_.Trim() }
|
|
184
|
-
# cols: [0]=empty, [1]=Agent ID, [2]=Agent Name, [3]=Intro, [4]=Piper TTS Voice, [5]=Piper Voice, [6]=Personality
|
|
185
|
-
if ($cols.Count -lt 6) { continue }
|
|
186
|
-
$tableId = $cols[1]
|
|
187
|
-
$tableName = $cols[2]
|
|
188
|
-
if ($tableId -ieq $shortId -or $tableId -ieq $AgentId -or $tableName -like "*$DisplayName*") {
|
|
189
|
-
if ($cols[3]) { $AgentPretext = $cols[3] }
|
|
190
|
-
# Use Piper Voice column (index 5) for piper provider
|
|
191
|
-
if (-not $AgentVoiceName -and $cols[5]) { $AgentVoiceName = $cols[5] }
|
|
192
|
-
if ($cols.Count -ge 7 -and $cols[6] -and $cols[6] -ine 'normal') {
|
|
193
|
-
# personality available for bmad-speak.ps1 downstream
|
|
194
|
-
}
|
|
195
|
-
break
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
if ($AgentPretext) { break }
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
# Locate piper
|
|
203
|
-
$PiperExe = "$env:LOCALAPPDATA\Programs\Piper\piper.exe"
|
|
204
|
-
if (-not (Test-Path $PiperExe)) {
|
|
205
|
-
$found = Get-Command piper.exe -ErrorAction SilentlyContinue
|
|
206
|
-
if ($found) { $PiperExe = $found.Source }
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (Test-Path $PiperExe) {
|
|
210
|
-
$VoicesDir = "$env:USERPROFILE\.claude\piper-voices"
|
|
211
|
-
# Fall back to first available voice if agent voice not found
|
|
212
|
-
if (-not $AgentVoiceName) {
|
|
213
|
-
$first = Get-ChildItem $VoicesDir -Filter "*.onnx" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
214
|
-
if ($first) { $AgentVoiceName = $first.BaseName }
|
|
215
|
-
}
|
|
216
|
-
if ($AgentVoiceName -and ($AgentVoiceName -match '^[a-zA-Z0-9_\-\.]+$')) {
|
|
217
|
-
$VoiceModel = Join-Path $VoicesDir "$AgentVoiceName.onnx"
|
|
218
|
-
if (Test-Path $VoiceModel) {
|
|
219
|
-
$AudioDir = "$env:USERPROFILE\.claude\audio"
|
|
220
|
-
if (-not (Test-Path $AudioDir)) { New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null }
|
|
221
|
-
$PreSynthWav = Join-Path $AudioDir "tts-presynth-$([System.IO.Path]::GetRandomFileName() -replace '\..*').wav"
|
|
222
|
-
$piperArgs = @("--model", $VoiceModel, "--output-file", $PreSynthWav)
|
|
223
|
-
if ($SpeakerId) { $piperArgs += @("--speaker", $SpeakerId) }
|
|
224
|
-
# Include pretext in pre-synthesis so it's spoken — bmad-speak.ps1 will
|
|
225
|
-
# skip synthesis (AGENTVIBES_PRESYNTHESIZED_WAV set) so pretext must be here.
|
|
226
|
-
$PreSynthText = if ($AgentPretext) { "$AgentPretext. $ResponseText" } else { $ResponseText }
|
|
227
|
-
$PreSynthText | & $PiperExe @piperArgs 2>$null
|
|
228
|
-
if (-not (Test-Path $PreSynthWav) -or (Get-Item $PreSynthWav).Length -eq 0) {
|
|
229
|
-
$PreSynthWav = $null
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
} catch {
|
|
235
|
-
$PreSynthWav = $null # degrade gracefully — will synthesize inside mutex instead
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
# --- Speak with queue serialization (named mutex, cross-process) ---
|
|
239
|
-
$mutex = New-Object System.Threading.Mutex($false, "AgentVibesPartyModeTTSQueue")
|
|
240
|
-
try {
|
|
241
|
-
$acquired = $false
|
|
242
|
-
try {
|
|
243
|
-
# WaitOne throws AbandonedMutexException if prior process crashed while holding it.
|
|
244
|
-
# That exception means we DID acquire the mutex — treat it as success (fixes M2).
|
|
245
|
-
# 600s timeout covers worst-case party-mode queue depth (9 agents x
|
|
246
|
-
# ~60s of speech each). Shorter timeouts silently dropped the last
|
|
247
|
-
# agents to speak. play-tts.ps1 bounds true stuck processes via its
|
|
248
|
-
# playback watchdog, so waiting longer here is safe.
|
|
249
|
-
$acquired = $mutex.WaitOne(600000)
|
|
250
|
-
} catch [System.Threading.AbandonedMutexException] {
|
|
251
|
-
$acquired = $true # abandoned = we now own it
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if ($acquired) {
|
|
255
|
-
try {
|
|
256
|
-
# Pass pre-synthesized WAV path so play-tts.ps1 skips synthesis (reduces gap between agents)
|
|
257
|
-
if ($PreSynthWav) { $env:AGENTVIBES_PRESYNTHESIZED_WAV = $PreSynthWav }
|
|
258
|
-
# Pass positional args directly after -File (spaces handled by quoting via array)
|
|
259
|
-
& powershell -NoProfile -ExecutionPolicy Bypass -File $BmadSpeak $AgentId $ResponseText
|
|
260
|
-
} finally {
|
|
261
|
-
$env:AGENTVIBES_PRESYNTHESIZED_WAV = ""
|
|
262
|
-
if ($PreSynthWav -and (Test-Path $PreSynthWav)) {
|
|
263
|
-
Remove-Item $PreSynthWav -Force -ErrorAction SilentlyContinue
|
|
264
|
-
}
|
|
265
|
-
$mutex.ReleaseMutex()
|
|
266
|
-
}
|
|
267
|
-
} else {
|
|
268
|
-
# Timed out — log to stderr so it's visible in hook error output (fixes M6)
|
|
269
|
-
[Console]::Error.WriteLine("[AgentVibes] Party mode TTS queue timeout for agent: $AgentId")
|
|
270
|
-
}
|
|
271
|
-
} finally {
|
|
272
|
-
$mutex.Close()
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
} catch {
|
|
276
|
-
# Silently exit — never block Claude
|
|
277
|
-
exit 0
|
|
278
|
-
}
|
|
1
|
+
#
|
|
2
|
+
# File: ~/.claude/hooks-windows/bmad-party-speak.ps1
|
|
3
|
+
#
|
|
4
|
+
# AgentVibes PostToolUse Hook - BMAD Party Mode TTS
|
|
5
|
+
#
|
|
6
|
+
# Fires after every Agent tool call. Detects BMAD party mode agents by
|
|
7
|
+
# fingerprinting the prompt, extracts the agent display name, maps it to
|
|
8
|
+
# the canonical agent ID via the manifest, then calls bmad-speak.ps1.
|
|
9
|
+
#
|
|
10
|
+
# Installed globally so it works in any BMAD project.
|
|
11
|
+
# Uses $env:CLAUDE_PROJECT_DIR to locate the project manifest at runtime.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
# --- Read stdin safely ---
|
|
16
|
+
$raw = [Console]::In.ReadToEnd()
|
|
17
|
+
if (-not $raw -or $raw.Trim() -eq "") { exit 0 }
|
|
18
|
+
|
|
19
|
+
$data = $raw | ConvertFrom-Json
|
|
20
|
+
if (-not $data) { exit 0 }
|
|
21
|
+
|
|
22
|
+
# --- Only handle Agent tool ---
|
|
23
|
+
if ($data.tool_name -ne "Agent") { exit 0 }
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# --- Extract prompt ---
|
|
27
|
+
$prompt = $data.tool_input.prompt
|
|
28
|
+
if (-not $prompt) { exit 0 }
|
|
29
|
+
|
|
30
|
+
# --- Fingerprint: only fire for BMAD party mode agents ---
|
|
31
|
+
if ($prompt -notmatch "BMAD agent in a collaborative roundtable") { exit 0 }
|
|
32
|
+
|
|
33
|
+
# --- Extract display name from "You are {Name} (" ---
|
|
34
|
+
if ($prompt -notmatch "You are ([A-Za-z]+)\s*\(") { exit 0 }
|
|
35
|
+
$DisplayName = $Matches[1]
|
|
36
|
+
|
|
37
|
+
# --- Extract response text ---
|
|
38
|
+
$content = $data.tool_response.content
|
|
39
|
+
if (-not $content -or $content.Count -eq 0) { exit 0 }
|
|
40
|
+
$ResponseText = ($content | Where-Object { $_.type -eq "text" } | Select-Object -First 1).text
|
|
41
|
+
if (-not $ResponseText) { exit 0 }
|
|
42
|
+
|
|
43
|
+
# Strip leading icon + bold name header e.g. "📊 **Mary:**" or garbled "≡ƒôè **Mary:**"
|
|
44
|
+
# Trim first so leading newlines don't defeat the ^ anchor (fixes M7)
|
|
45
|
+
$ResponseText = $ResponseText.Trim()
|
|
46
|
+
$ResponseText = $ResponseText -replace '^\S*\s*\*\*[^:]+:\*\*\s*', ''
|
|
47
|
+
$ResponseText = $ResponseText.Trim()
|
|
48
|
+
if (-not $ResponseText) { exit 0 }
|
|
49
|
+
|
|
50
|
+
# Strip markdown so TTS doesn't speak asterisks, hashes, backticks, etc.
|
|
51
|
+
$ResponseText = $ResponseText -replace '\*{1,3}', '' # bold, italic, bold-italic
|
|
52
|
+
$ResponseText = $ResponseText -replace '`{1,3}[^`]*`{1,3}', '' # inline code / code blocks
|
|
53
|
+
$ResponseText = $ResponseText -replace '#{1,6}\s*', '' # headings
|
|
54
|
+
$ResponseText = $ResponseText -replace '_{1,2}', '' # underline/italic alt
|
|
55
|
+
$ResponseText = $ResponseText -replace '\[([^\]]+)\]\([^)]+\)', '$1' # links → label only
|
|
56
|
+
$ResponseText = $ResponseText -replace '!\[[^\]]*\]\([^)]+\)', '' # images
|
|
57
|
+
$ResponseText = $ResponseText -replace '(?m)^\s*[-*+]\s+', '' # bullet list markers (multiline)
|
|
58
|
+
$ResponseText = $ResponseText -replace '(?m)^\s*\d+\.\s+', '' # numbered list markers
|
|
59
|
+
$ResponseText = $ResponseText -replace '\\([!$*_`\\])', '$1' # escaped markdown chars
|
|
60
|
+
$ResponseText = $ResponseText.Trim()
|
|
61
|
+
if (-not $ResponseText) { exit 0 }
|
|
62
|
+
|
|
63
|
+
# --- Resolve paths ---
|
|
64
|
+
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
65
|
+
$ProjectRoot = $env:CLAUDE_PROJECT_DIR
|
|
66
|
+
|
|
67
|
+
# --- Find bmad-speak.ps1 (prefer project-local, fall back to global) ---
|
|
68
|
+
$BmadSpeak = $null
|
|
69
|
+
if ($ProjectRoot) {
|
|
70
|
+
$local = Join-Path $ProjectRoot ".claude\hooks-windows\bmad-speak.ps1"
|
|
71
|
+
if (Test-Path $local) { $BmadSpeak = $local }
|
|
72
|
+
}
|
|
73
|
+
if (-not $BmadSpeak) {
|
|
74
|
+
$global = Join-Path $ScriptDir "bmad-speak.ps1"
|
|
75
|
+
if (Test-Path $global) { $BmadSpeak = $global }
|
|
76
|
+
}
|
|
77
|
+
if (-not $BmadSpeak) { exit 0 }
|
|
78
|
+
|
|
79
|
+
# --- Look up canonical agent ID from project manifest (fixes m2: UTF8 encoding) ---
|
|
80
|
+
$AgentId = $DisplayName # fallback
|
|
81
|
+
if ($ProjectRoot) {
|
|
82
|
+
$ManifestFile = Join-Path $ProjectRoot "_bmad\_config\agent-manifest.csv"
|
|
83
|
+
if (Test-Path $ManifestFile) {
|
|
84
|
+
$rows = Import-Csv $ManifestFile -Encoding UTF8
|
|
85
|
+
foreach ($row in $rows) {
|
|
86
|
+
if ($row.displayName -ieq $DisplayName) {
|
|
87
|
+
$AgentId = $row.name
|
|
88
|
+
break
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# --- Apply verbosity truncation ---
|
|
95
|
+
$Verbosity = "medium"
|
|
96
|
+
$verbosityPaths = @(
|
|
97
|
+
(Join-Path $env:USERPROFILE ".claude\tts-verbosity.txt")
|
|
98
|
+
)
|
|
99
|
+
if ($ProjectRoot) {
|
|
100
|
+
$verbosityPaths = @((Join-Path $ProjectRoot ".claude\tts-verbosity.txt")) + $verbosityPaths
|
|
101
|
+
}
|
|
102
|
+
foreach ($p in $verbosityPaths) {
|
|
103
|
+
if (Test-Path $p) {
|
|
104
|
+
$v = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
|
|
105
|
+
if ($v) { $Verbosity = $v; break }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
switch ($Verbosity) {
|
|
110
|
+
"low" {
|
|
111
|
+
$sentences = [regex]::Split($ResponseText, '(?<=[.!?])\s+')
|
|
112
|
+
# Fall back to full text if no sentence-ending punctuation (fixes m1)
|
|
113
|
+
if ($sentences.Count -gt 0 -and $sentences[0]) { $ResponseText = $sentences[0] }
|
|
114
|
+
}
|
|
115
|
+
"medium" {
|
|
116
|
+
$sentences = [regex]::Split($ResponseText, '(?<=[.!?])\s+')
|
|
117
|
+
# Fall back to full text if no sentence-ending punctuation (fixes m1)
|
|
118
|
+
$truncated = ($sentences | Select-Object -First 2) -join " "
|
|
119
|
+
if ($truncated) { $ResponseText = $truncated }
|
|
120
|
+
}
|
|
121
|
+
# "high" = full text
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# --- Pre-synthesize WAV before acquiring mutex so synthesis overlaps with previous agent's playback ---
|
|
125
|
+
$PreSynthWav = $null
|
|
126
|
+
try {
|
|
127
|
+
# Resolve agent voice from voice map
|
|
128
|
+
$VoiceMapLocal = if ($ProjectRoot) { Join-Path $ProjectRoot ".agentvibes\bmad-voice-map.json" } else { $null }
|
|
129
|
+
$VoiceMapGlobal = Join-Path $env:USERPROFILE ".agentvibes\bmad-voice-map.json"
|
|
130
|
+
$VoiceMapFile = if ($VoiceMapLocal -and (Test-Path $VoiceMapLocal)) { $VoiceMapLocal }
|
|
131
|
+
elseif (Test-Path $VoiceMapGlobal) { $VoiceMapGlobal }
|
|
132
|
+
else { $null }
|
|
133
|
+
|
|
134
|
+
$AgentVoiceName = $null
|
|
135
|
+
$SpeakerId = $null
|
|
136
|
+
$AgentPretext = $null
|
|
137
|
+
if ($VoiceMapFile) {
|
|
138
|
+
$vm = Get-Content $VoiceMapFile -Raw | ConvertFrom-Json
|
|
139
|
+
$profile = $vm.agents.$AgentId
|
|
140
|
+
if ($profile -and $profile.pretext) { $AgentPretext = $profile.pretext }
|
|
141
|
+
if ($profile -and $profile.voice) {
|
|
142
|
+
$raw = $profile.voice
|
|
143
|
+
if ($raw -match '::') {
|
|
144
|
+
$parts = $raw -split '::'
|
|
145
|
+
$AgentVoiceName = $parts[0]
|
|
146
|
+
$SpeakerName = if ($parts.Length -ge 2) { $parts[1] } else { "" }
|
|
147
|
+
# NOTE: The suffix number (e.g. "14" in "Yara-14") is a display disambiguator,
|
|
148
|
+
# NOT the piper speaker index. Must look up real index from speaker_id_map.
|
|
149
|
+
if ($SpeakerName) {
|
|
150
|
+
$VoicesDir = "$env:USERPROFILE\.claude\piper-voices"
|
|
151
|
+
$OnnxJsonPath = "$VoicesDir\$AgentVoiceName.onnx.json"
|
|
152
|
+
if (Test-Path $OnnxJsonPath) {
|
|
153
|
+
try {
|
|
154
|
+
$onnxData = Get-Content $OnnxJsonPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
155
|
+
$speakerIdMap = $onnxData.speaker_id_map
|
|
156
|
+
if ($speakerIdMap -and $speakerIdMap.PSObject.Properties[$SpeakerName]) {
|
|
157
|
+
$SpeakerId = [string]$speakerIdMap.PSObject.Properties[$SpeakerName].Value
|
|
158
|
+
}
|
|
159
|
+
} catch { }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
$AgentVoiceName = $raw
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Fallback: parse bmad-voices.md markdown table if no JSON voice map found
|
|
169
|
+
if (-not $AgentPretext -and $ProjectRoot) {
|
|
170
|
+
$VoicesMdPaths = @(
|
|
171
|
+
(Join-Path $ProjectRoot ".agentvibes\bmad\bmad-voices.md"),
|
|
172
|
+
(Join-Path $env:USERPROFILE ".agentvibes\bmad\bmad-voices.md")
|
|
173
|
+
)
|
|
174
|
+
foreach ($mdPath in $VoicesMdPaths) {
|
|
175
|
+
if (-not (Test-Path $mdPath)) { continue }
|
|
176
|
+
$mdLines = Get-Content $mdPath -Encoding UTF8
|
|
177
|
+
# Strip bmad-agent- prefix for matching (manifest uses bmad-agent-pm, table uses pm)
|
|
178
|
+
$shortId = $AgentId -replace '^bmad-agent-', ''
|
|
179
|
+
foreach ($mdLine in $mdLines) {
|
|
180
|
+
if ($mdLine -notmatch '^\|') { continue }
|
|
181
|
+
if ($mdLine -match '^\|-') { continue } # separator row
|
|
182
|
+
if ($mdLine -match 'Agent ID') { continue } # header row
|
|
183
|
+
$cols = $mdLine -split '\|' | ForEach-Object { $_.Trim() }
|
|
184
|
+
# cols: [0]=empty, [1]=Agent ID, [2]=Agent Name, [3]=Intro, [4]=Piper TTS Voice, [5]=Piper Voice, [6]=Personality
|
|
185
|
+
if ($cols.Count -lt 6) { continue }
|
|
186
|
+
$tableId = $cols[1]
|
|
187
|
+
$tableName = $cols[2]
|
|
188
|
+
if ($tableId -ieq $shortId -or $tableId -ieq $AgentId -or $tableName -like "*$DisplayName*") {
|
|
189
|
+
if ($cols[3]) { $AgentPretext = $cols[3] }
|
|
190
|
+
# Use Piper Voice column (index 5) for piper provider
|
|
191
|
+
if (-not $AgentVoiceName -and $cols[5]) { $AgentVoiceName = $cols[5] }
|
|
192
|
+
if ($cols.Count -ge 7 -and $cols[6] -and $cols[6] -ine 'normal') {
|
|
193
|
+
# personality available for bmad-speak.ps1 downstream
|
|
194
|
+
}
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if ($AgentPretext) { break }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# Locate piper
|
|
203
|
+
$PiperExe = "$env:LOCALAPPDATA\Programs\Piper\piper.exe"
|
|
204
|
+
if (-not (Test-Path $PiperExe)) {
|
|
205
|
+
$found = Get-Command piper.exe -ErrorAction SilentlyContinue
|
|
206
|
+
if ($found) { $PiperExe = $found.Source }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (Test-Path $PiperExe) {
|
|
210
|
+
$VoicesDir = "$env:USERPROFILE\.claude\piper-voices"
|
|
211
|
+
# Fall back to first available voice if agent voice not found
|
|
212
|
+
if (-not $AgentVoiceName) {
|
|
213
|
+
$first = Get-ChildItem $VoicesDir -Filter "*.onnx" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
214
|
+
if ($first) { $AgentVoiceName = $first.BaseName }
|
|
215
|
+
}
|
|
216
|
+
if ($AgentVoiceName -and ($AgentVoiceName -match '^[a-zA-Z0-9_\-\.]+$')) {
|
|
217
|
+
$VoiceModel = Join-Path $VoicesDir "$AgentVoiceName.onnx"
|
|
218
|
+
if (Test-Path $VoiceModel) {
|
|
219
|
+
$AudioDir = "$env:USERPROFILE\.claude\audio"
|
|
220
|
+
if (-not (Test-Path $AudioDir)) { New-Item -ItemType Directory -Path $AudioDir -Force | Out-Null }
|
|
221
|
+
$PreSynthWav = Join-Path $AudioDir "tts-presynth-$([System.IO.Path]::GetRandomFileName() -replace '\..*').wav"
|
|
222
|
+
$piperArgs = @("--model", $VoiceModel, "--output-file", $PreSynthWav)
|
|
223
|
+
if ($SpeakerId) { $piperArgs += @("--speaker", $SpeakerId) }
|
|
224
|
+
# Include pretext in pre-synthesis so it's spoken — bmad-speak.ps1 will
|
|
225
|
+
# skip synthesis (AGENTVIBES_PRESYNTHESIZED_WAV set) so pretext must be here.
|
|
226
|
+
$PreSynthText = if ($AgentPretext) { "$AgentPretext. $ResponseText" } else { $ResponseText }
|
|
227
|
+
$PreSynthText | & $PiperExe @piperArgs 2>$null
|
|
228
|
+
if (-not (Test-Path $PreSynthWav) -or (Get-Item $PreSynthWav).Length -eq 0) {
|
|
229
|
+
$PreSynthWav = $null
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
$PreSynthWav = $null # degrade gracefully — will synthesize inside mutex instead
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# --- Speak with queue serialization (named mutex, cross-process) ---
|
|
239
|
+
$mutex = New-Object System.Threading.Mutex($false, "AgentVibesPartyModeTTSQueue")
|
|
240
|
+
try {
|
|
241
|
+
$acquired = $false
|
|
242
|
+
try {
|
|
243
|
+
# WaitOne throws AbandonedMutexException if prior process crashed while holding it.
|
|
244
|
+
# That exception means we DID acquire the mutex — treat it as success (fixes M2).
|
|
245
|
+
# 600s timeout covers worst-case party-mode queue depth (9 agents x
|
|
246
|
+
# ~60s of speech each). Shorter timeouts silently dropped the last
|
|
247
|
+
# agents to speak. play-tts.ps1 bounds true stuck processes via its
|
|
248
|
+
# playback watchdog, so waiting longer here is safe.
|
|
249
|
+
$acquired = $mutex.WaitOne(600000)
|
|
250
|
+
} catch [System.Threading.AbandonedMutexException] {
|
|
251
|
+
$acquired = $true # abandoned = we now own it
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if ($acquired) {
|
|
255
|
+
try {
|
|
256
|
+
# Pass pre-synthesized WAV path so play-tts.ps1 skips synthesis (reduces gap between agents)
|
|
257
|
+
if ($PreSynthWav) { $env:AGENTVIBES_PRESYNTHESIZED_WAV = $PreSynthWav }
|
|
258
|
+
# Pass positional args directly after -File (spaces handled by quoting via array)
|
|
259
|
+
& powershell -NoProfile -ExecutionPolicy Bypass -File $BmadSpeak $AgentId $ResponseText
|
|
260
|
+
} finally {
|
|
261
|
+
$env:AGENTVIBES_PRESYNTHESIZED_WAV = ""
|
|
262
|
+
if ($PreSynthWav -and (Test-Path $PreSynthWav)) {
|
|
263
|
+
Remove-Item $PreSynthWav -Force -ErrorAction SilentlyContinue
|
|
264
|
+
}
|
|
265
|
+
$mutex.ReleaseMutex()
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
# Timed out — log to stderr so it's visible in hook error output (fixes M6)
|
|
269
|
+
[Console]::Error.WriteLine("[AgentVibes] Party mode TTS queue timeout for agent: $AgentId")
|
|
270
|
+
}
|
|
271
|
+
} finally {
|
|
272
|
+
$mutex.Close()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
} catch {
|
|
276
|
+
# Silently exit — never block Claude
|
|
277
|
+
exit 0
|
|
278
|
+
}
|