agentvibes 4.6.5 → 4.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- 20260329
1
+ 20260403
@@ -30,7 +30,14 @@ DIALOGUE="${DIALOGUE//\\\$/\$}"
30
30
 
31
31
  # Strip markdown formatting — prevent Piper from speaking "asterisk asterisk" literally.
32
32
  # play-tts-piper.sh also strips via perl, but do it here early as defense-in-depth.
33
- DIALOGUE=$(printf '%s' "$DIALOGUE" | sed 's/\*\*//g; s/\*//g; s/`//g; s/^[[:space:]]*#\+[[:space:]]*//')
33
+ DIALOGUE=$(printf '%s' "$DIALOGUE" | sed \
34
+ -e 's/\*\{1,3\}//g' \
35
+ -e 's/`\{1,3\}[^`]*`\{1,3\}//g' \
36
+ -e 's/^[[:space:]]*#\{1,6\}[[:space:]]*//g' \
37
+ -e 's/__//g' -e 's/_//g' \
38
+ -e 's/\[([^]]*)\]([^)]*)//g' \
39
+ -e 's/^[[:space:]]*[-*+] //g' \
40
+ -e 's/^[[:space:]]*[0-9]\+\. //g')
34
41
 
35
42
  # Check if party mode is enabled
36
43
  if [[ -f "$PROJECT_ROOT/.agentvibes/bmad/bmad-party-mode-disabled.flag" ]]; then
@@ -47,6 +47,19 @@ try {
47
47
  $ResponseText = $ResponseText.Trim()
48
48
  if (-not $ResponseText) { exit 0 }
49
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
+
50
63
  # --- Resolve paths ---
51
64
  $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
52
65
  $ProjectRoot = $env:CLAUDE_PROJECT_DIR
@@ -118,11 +131,13 @@ try {
118
131
  elseif (Test-Path $VoiceMapGlobal) { $VoiceMapGlobal }
119
132
  else { $null }
120
133
 
121
- $AgentVoiceName = $null
122
- $SpeakerId = $null
134
+ $AgentVoiceName = $null
135
+ $SpeakerId = $null
136
+ $AgentPretext = $null
123
137
  if ($VoiceMapFile) {
124
138
  $vm = Get-Content $VoiceMapFile -Raw | ConvertFrom-Json
125
139
  $profile = $vm.agents.$AgentId
140
+ if ($profile -and $profile.pretext) { $AgentPretext = $profile.pretext }
126
141
  if ($profile -and $profile.voice) {
127
142
  $raw = $profile.voice
128
143
  if ($raw -match '::') {
@@ -150,6 +165,40 @@ try {
150
165
  }
151
166
  }
152
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
+
153
202
  # Locate piper
154
203
  $PiperExe = "$env:LOCALAPPDATA\Programs\Piper\piper.exe"
155
204
  if (-not (Test-Path $PiperExe)) {
@@ -172,7 +221,10 @@ try {
172
221
  $PreSynthWav = Join-Path $AudioDir "tts-presynth-$([System.IO.Path]::GetRandomFileName() -replace '\..*').wav"
173
222
  $piperArgs = @("--model", $VoiceModel, "--output-file", $PreSynthWav)
174
223
  if ($SpeakerId) { $piperArgs += @("--speaker", $SpeakerId) }
175
- $ResponseText | & $PiperExe @piperArgs 2>$null
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
176
228
  if (-not (Test-Path $PreSynthWav) -or (Get-Item $PreSynthWav).Length -eq 0) {
177
229
  $PreSynthWav = $null
178
230
  }
@@ -27,8 +27,15 @@ if ($env:CLAUDE_PROJECT_DIR -and (Test-Path "$env:CLAUDE_PROJECT_DIR\_bmad")) {
27
27
  }
28
28
 
29
29
  # Strip markdown formatting — prevent SAPI/Piper from speaking asterisks literally
30
- $Dialogue = $Dialogue -replace '\*\*', '' -replace '\*', '' -replace '`', ''
31
- $Dialogue = $Dialogue -replace '\\!', '!' -replace '\\\$', '$'
30
+ $Dialogue = $Dialogue -replace '\*{1,3}', '' # bold, italic, bold-italic
31
+ $Dialogue = $Dialogue -replace '`{1,3}[^`]*`{1,3}', '' # inline code / code blocks
32
+ $Dialogue = $Dialogue -replace '#{1,6}\s*', '' # headings
33
+ $Dialogue = $Dialogue -replace '_{1,2}', '' # underline/italic alt
34
+ $Dialogue = $Dialogue -replace '\[([^\]]+)\]\([^)]+\)', '$1' # links → label only
35
+ $Dialogue = $Dialogue -replace '!\[[^\]]*\]\([^)]+\)', '' # images
36
+ $Dialogue = $Dialogue -replace '(?m)^\s*[-*+]\s+', '' # bullet list markers (multiline)
37
+ $Dialogue = $Dialogue -replace '(?m)^\s*\d+\.\s+', '' # numbered list markers
38
+ $Dialogue = $Dialogue -replace '\\([!$*_`\\])', '$1' # escaped markdown chars
32
39
 
33
40
  # Check if party mode is disabled
34
41
  $PartyModeDisabledFlag = Join-Path $ProjectRoot ".agentvibes\bmad\bmad-party-mode-disabled.flag"
@@ -117,13 +124,45 @@ if (Test-Path $VoiceMapFile) {
117
124
  }
118
125
  }
119
126
 
127
+ # Fallback: parse bmad-voices.md markdown table if JSON voice map had no data
128
+ if ((-not $AgentPretext -or -not $AgentVoice) -and $AgentId) {
129
+ $VoicesMdPaths = @(
130
+ (Join-Path $ProjectRoot ".agentvibes\bmad\bmad-voices.md"),
131
+ (Join-Path $env:USERPROFILE ".agentvibes\bmad\bmad-voices.md")
132
+ )
133
+ $shortId = $AgentId -replace '^bmad-agent-', ''
134
+ foreach ($mdPath in $VoicesMdPaths) {
135
+ if (-not (Test-Path $mdPath)) { continue }
136
+ $mdLines = Get-Content $mdPath -Encoding UTF8
137
+ foreach ($mdLine in $mdLines) {
138
+ if ($mdLine -notmatch '^\|') { continue }
139
+ if ($mdLine -match '^\|-') { continue }
140
+ if ($mdLine -match 'Agent ID') { continue }
141
+ $cols = $mdLine -split '\|' | ForEach-Object { $_.Trim() }
142
+ if ($cols.Count -lt 6) { continue }
143
+ $tableId = $cols[1]
144
+ if ($tableId -ieq $shortId -or $tableId -ieq $AgentId -or $tableId -ieq $AgentNameOrId) {
145
+ if (-not $AgentPretext -and $cols[3]) { $AgentPretext = $cols[3] }
146
+ if (-not $AgentVoice -and $cols[5]) { $AgentVoice = $cols[5] }
147
+ if (-not $AgentPersonality -and $cols.Count -ge 7 -and $cols[6] -and $cols[6] -ine 'normal') {
148
+ $AgentPersonality = $cols[6]
149
+ }
150
+ break
151
+ }
152
+ }
153
+ if ($AgentPretext) { break }
154
+ }
155
+ }
156
+
120
157
  # Fall back to default pretext if none stored: "DisplayName, Title here."
121
158
  # Matches AgentVoiceStore.getDefaultPretext() in agent-voice-store.js
122
- if (-not $AgentPretext -and $AgentDisplayName) {
123
- if ($AgentTitle) {
159
+ if (-not $AgentPretext) {
160
+ if ($AgentDisplayName -and $AgentTitle) {
124
161
  $AgentPretext = "$AgentDisplayName, $AgentTitle here."
125
- } else {
162
+ } elseif ($AgentDisplayName) {
126
163
  $AgentPretext = "$AgentDisplayName here."
164
+ } elseif ($AgentNameOrId) {
165
+ $AgentPretext = "$AgentNameOrId here."
127
166
  }
128
167
  }
129
168
 
@@ -1,124 +1,114 @@
1
- #
2
- # File: .claude/hooks-windows/session-start-tts.ps1
3
- #
4
- # AgentVibes SessionStart Hook for Windows - Optimized (Issue #80, Phase 1)
5
- # Token target: ~250 (down from ~500)
6
- #
7
- # Prints TTS protocol instructions to stdout so Claude knows to use TTS.
8
- #
9
-
10
- $ErrorActionPreference = "Stop"
11
-
12
- # Get script directory
13
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
14
-
15
- # Check if AgentVibes is installed
16
- if (-not (Test-Path (Join-Path $ScriptDir "play-tts.ps1"))) {
17
- # AgentVibes not installed, don't inject anything
18
- exit 0
19
- }
20
-
21
- # Resolve project .claude dir from script location (avoids CWD-relative path issues)
22
- $ProjectClaudeDir = Split-Path -Parent (Split-Path -Parent $ScriptDir)
23
- $ProjectClaudeDir = Join-Path $ProjectClaudeDir ".claude"
24
-
25
- # Check for sentiment (priority) or personality (fallback)
26
- $Sentiment = ""
27
- $sentimentPaths = @("$ProjectClaudeDir\tts-sentiment.txt", "$env:USERPROFILE\.claude\tts-sentiment.txt")
28
- foreach ($p in $sentimentPaths) {
29
- if (Test-Path $p) {
30
- $Sentiment = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
31
- if ($Sentiment) { break }
32
- }
33
- }
34
-
35
- $Personality = "normal"
36
- $personalityPaths = @("$ProjectClaudeDir\tts-personality.txt", "$env:USERPROFILE\.claude\tts-personality.txt")
37
- foreach ($p in $personalityPaths) {
38
- if (Test-Path $p) {
39
- $val = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
40
- if ($val) { $Personality = $val; break }
41
- }
42
- }
43
-
44
- # Determine style
45
- if ($Sentiment) {
46
- $Style = $Sentiment
47
- } else {
48
- $Style = $Personality
49
- }
50
-
51
- # Get verbosity level (Issue #32)
52
- $Verbosity = "low"
53
- $verbosityPaths = @("$ProjectClaudeDir\tts-verbosity.txt", "$env:USERPROFILE\.claude\tts-verbosity.txt")
54
- foreach ($p in $verbosityPaths) {
55
- if (Test-Path $p) {
56
- $val = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
57
- if ($val) { $Verbosity = $val; break }
58
- }
59
- }
60
-
61
- # Output optimized TTS protocol
62
- Write-Output @"
63
-
64
- # AgentVibes TTS Protocol
65
-
66
- **CRITICAL: You MUST execute TTS at TWO points for EVERY user interaction:**
67
-
68
- 1. **Acknowledgment** - Start of task: ``Bash: powershell -NoProfile -ExecutionPolicy Bypass -File ".claude\hooks-windows\play-tts.ps1" "[action]"``
69
- 2. **Completion** - End of task: ``Bash: powershell -NoProfile -ExecutionPolicy Bypass -File ".claude\hooks-windows\play-tts.ps1" "[result + key details]"``
70
-
71
- **Example:**
72
- ``````
73
- [Bash: powershell -NoProfile -ExecutionPolicy Bypass -File ".claude\hooks-windows\play-tts.ps1" "Checking git status"]
74
- [work...]
75
- [Bash: powershell -NoProfile -ExecutionPolicy Bypass -File ".claude\hooks-windows\play-tts.ps1" "Repository is clean, no changes"]
76
- ``````
77
-
78
- "@
79
-
80
- # Add verbosity-specific protocol (Issue #32)
81
- switch ($Verbosity) {
82
- "low" {
83
- Write-Output @"
84
- ## Verbosity: LOW
85
- - Acknowledgment: Action only
86
- - Completion: Result + errors only
87
- - Skip: Reasoning, decisions
88
-
89
- "@
90
- }
91
- "medium" {
92
- Write-Output @"
93
- ## Verbosity: MEDIUM
94
- - Acknowledgment: Action + key approach
95
- - Completion: Result + important decisions
96
- - Include: Major choices only
97
-
98
- "@
99
- }
100
- "high" {
101
- Write-Output @"
102
- ## Verbosity: HIGH
103
- - Acknowledgment: Action + approach + why
104
- - Completion: Result + decisions + trade-offs
105
- - Include: Full reasoning, alternatives
106
-
107
- "@
108
- }
109
- }
110
-
111
- # Add style info and rules
112
- Write-Output @"
113
- ## Style: $Style
114
-
115
- ## Rules
116
- 1. Never skip acknowledgment TTS
117
- 2. Never skip completion TTS
118
- 3. Match verbosity level
119
- 4. Keep under 150 chars
120
- 5. Always include errors
121
-
122
- Quick Ref: low=action+result | medium=+key decisions | high=+full reasoning
123
-
124
- "@
1
+ #
2
+ # File: .claude/hooks-windows/session-start-tts.ps1
3
+ #
4
+ # AgentVibes SessionStart Hook for Windows
5
+ # Outputs JSON with hookSpecificOutput.additionalContext for reliable context injection.
6
+ # Mirrors session-start-tts.sh — keep both in sync.
7
+ #
8
+
9
+ $ErrorActionPreference = "Stop"
10
+
11
+ # Get script directory
12
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
13
+
14
+ # Check if AgentVibes is installed
15
+ if (-not (Test-Path (Join-Path $ScriptDir "play-tts.ps1"))) {
16
+ exit 0
17
+ }
18
+
19
+ # Resolve project .claude dir from script location (avoids CWD-relative path issues)
20
+ $ProjectClaudeDir = Split-Path -Parent (Split-Path -Parent $ScriptDir)
21
+ $ProjectClaudeDir = Join-Path $ProjectClaudeDir ".claude"
22
+
23
+ # Check for sentiment (priority) or personality (fallback)
24
+ $Sentiment = ""
25
+ $sentimentPaths = @("$ProjectClaudeDir\tts-sentiment.txt", "$env:USERPROFILE\.claude\tts-sentiment.txt")
26
+ foreach ($p in $sentimentPaths) {
27
+ if (Test-Path $p) {
28
+ $val = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
29
+ if ($val) { $Sentiment = $val; break }
30
+ }
31
+ }
32
+
33
+ $Personality = "normal"
34
+ $personalityPaths = @("$ProjectClaudeDir\tts-personality.txt", "$env:USERPROFILE\.claude\tts-personality.txt")
35
+ foreach ($p in $personalityPaths) {
36
+ if (Test-Path $p) {
37
+ $val = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
38
+ if ($val) { $Personality = $val; break }
39
+ }
40
+ }
41
+
42
+ $Style = if ($Sentiment) { $Sentiment } else { $Personality }
43
+
44
+ # Get verbosity level
45
+ $Verbosity = "low"
46
+ $verbosityPaths = @("$ProjectClaudeDir\tts-verbosity.txt", "$env:USERPROFILE\.claude\tts-verbosity.txt")
47
+ foreach ($p in $verbosityPaths) {
48
+ if (Test-Path $p) {
49
+ $val = (Get-Content $p -Raw -ErrorAction SilentlyContinue).Trim()
50
+ if ($val) { $Verbosity = $val; break }
51
+ }
52
+ }
53
+
54
+ # Build verbosity section
55
+ $VerbositySection = switch ($Verbosity) {
56
+ "low" { "## Verbosity: LOW`n- Acknowledgment: Action only`n- Completion: Result + errors only`n- Skip: Reasoning, decisions" }
57
+ "medium" { "## Verbosity: MEDIUM`n- Acknowledgment: Action + key approach`n- Completion: Result + important decisions`n- Include: Major choices only" }
58
+ "high" { "## Verbosity: HIGH`n- Acknowledgment: Action + approach + why`n- Completion: Result + decisions + trade-offs`n- Include: Full reasoning, alternatives" }
59
+ default { "## Verbosity: LOW`n- Acknowledgment: Action only`n- Completion: Result + errors only" }
60
+ }
61
+
62
+ # Build full context string
63
+ $Context = @"
64
+ # AgentVibes TTS Protocol
65
+
66
+ **CRITICAL: You MUST execute TTS at TWO points for EVERY user interaction:**
67
+
68
+ 1. **Acknowledgment** - Start of task: ``Bash: powershell -NoProfile -ExecutionPolicy Bypass -File ".claude\hooks-windows\play-tts.ps1" "[action]"``
69
+ 2. **Completion** - End of task: ``Bash: powershell -NoProfile -ExecutionPolicy Bypass -File ".claude\hooks-windows\play-tts.ps1" "[result + key details]"``
70
+
71
+ **Example:**
72
+ ``````
73
+ [Bash: powershell -NoProfile -ExecutionPolicy Bypass -File ".claude\hooks-windows\play-tts.ps1" "Checking git status"]
74
+ [work...]
75
+ [Bash: powershell -NoProfile -ExecutionPolicy Bypass -File ".claude\hooks-windows\play-tts.ps1" "Repository is clean, no changes"]
76
+ ``````
77
+
78
+ $VerbositySection
79
+
80
+ ## Style: $Style
81
+
82
+ ## Rules
83
+ 1. Never skip acknowledgment TTS
84
+ 2. Never skip completion TTS
85
+ 3. Match verbosity level
86
+ 4. Keep under 150 chars
87
+ 5. Always include errors
88
+
89
+ Quick Ref: low=action+result | medium=+key decisions | high=+full reasoning
90
+
91
+ ## BMAD Agent Voice Routing
92
+ If ``.bmad-agent-context`` exists, check its content:
93
+
94
+ **Single agent mode** (content is an agent ID like "analyst"):
95
+ Use ``bmad-speak.ps1`` instead of ``play-tts.ps1``:
96
+
97
+
98
+ **Party mode** (content is "party-mode"):
99
+ Each agent speaks via ``bmad-speak.ps1`` with their display name. The queue system ensures sequential playback — agents speak one at a time, not overlapping:
100
+
101
+ IMPORTANT: In party mode, do NOT use ``play-tts.ps1``. Always use ``bmad-speak.ps1`` with the agent's display name. Do NOT call multiple bmad-speak.ps1 in parallel — call them sequentially so the queue processes them in order.
102
+
103
+ If ``.bmad-agent-context`` does NOT exist, use ``play-tts.ps1`` as normal.
104
+ "@
105
+
106
+ # Build JSON via hashtable so ConvertTo-Json handles all escaping (Unicode, backslashes, quotes)
107
+ $Output = @{
108
+ hookSpecificOutput = @{
109
+ hookEventName = "SessionStart"
110
+ additionalContext = $Context
111
+ }
112
+ }
113
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
114
+ Write-Output ($Output | ConvertTo-Json -Compress -Depth 5)