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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
llm:default|light||0.15|en_US-lessac-high||piper
|
|
3
|
-
llm:claude-code|light|agent_vibes_chillwave_v2_loop.mp3|0.05
|
|
3
|
+
llm:claude-code|light|agent_vibes_chillwave_v2_loop.mp3|0.05|en_US-amy-medium|Agent Vibes local here|piper
|
|
4
4
|
llm:copilot|light|agent_vibes_bossa_nova_v2_loop.mp3|0.15|en_US-libritts-high::Anna-11|Copilot here|piper
|
|
5
5
|
llm:codex|light|agent_vibes_chillwave_v2_loop.mp3|0.15|en_US-lessac-high|Codex here|piper
|
|
6
6
|
llm:hermes|light|agent_vibes_bachata_v1_loop.mp3|0.15|en_US-libritts-high::Leo-8|Hermes here|piper
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
|
|
2
|
+
llm:default|light||0.15|en_US-lessac-high||piper
|
|
3
|
+
llm:claude-code|light|agent_vibes_chillwave_v2_loop.mp3|0.05|af_river|Agent Vibes local here|kokoro
|
|
4
|
+
llm:copilot|light|agent_vibes_bossa_nova_v2_loop.mp3|0.15|en_US-libritts-high::Anna-11|Copilot here|piper
|
|
5
|
+
llm:codex|light|agent_vibes_chillwave_v2_loop.mp3|0.15|en_US-lessac-high|Codex here|piper
|
|
6
|
+
llm:hermes|light|agent_vibes_bachata_v1_loop.mp3|0.15|en_US-libritts-high::Leo-8|Hermes here|piper
|
|
7
|
+
default||agent_vibes_arabic_v2_loop.mp3|0.30
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20260703
|
|
@@ -577,7 +577,7 @@ main() {
|
|
|
577
577
|
fi
|
|
578
578
|
|
|
579
579
|
local used_background=""
|
|
580
|
-
if
|
|
580
|
+
if [[ "$_bg_allowed" == "true" ]] && [[ -n "$background_path" ]] && [[ -f "$background_path" ]] && [[ "${bg_volume:-0}" != "0" ]] && [[ "${bg_volume:-0}" != "0.0" ]]; then
|
|
581
581
|
local bg_display_name
|
|
582
582
|
if [[ "$background_path" == "$custom_music_path" ]]; then
|
|
583
583
|
bg_display_name="custom music"
|
|
@@ -33,7 +33,8 @@ ENABLED_FILE="$CONFIG_DIR/background-music-enabled.txt"
|
|
|
33
33
|
VOLUME_FILE="$CONFIG_DIR/background-music-volume.txt"
|
|
34
34
|
|
|
35
35
|
# Defaults
|
|
36
|
-
|
|
36
|
+
# TODO(AVI-S8.6): generate this constant from the shared JSON source of truth.
|
|
37
|
+
DEFAULT_VOLUME="0.20"
|
|
37
38
|
|
|
38
39
|
# Ensure config directory exists
|
|
39
40
|
mkdir -p "$CONFIG_DIR"
|
|
@@ -134,29 +134,44 @@ TEMP_FILE="$AUDIO_DIR/tts-enhanced-$(date +%s)-$$.wav"
|
|
|
134
134
|
# Determine voice and generate audio
|
|
135
135
|
if [[ -n "$AGENT_VOICE" ]]; then
|
|
136
136
|
echo "🎤 Agent: $AGENT_FOR_EFFECTS | Voice: $AGENT_VOICE"
|
|
137
|
-
# Call piper directly to generate audio without playback
|
|
138
|
-
"$SCRIPT_DIR/play-tts-piper.sh" "$FULL_TEXT" "$AGENT_VOICE" > /dev/null 2>&1 &
|
|
139
|
-
PIPER_PID=$!
|
|
140
|
-
|
|
141
|
-
# Wait for piper to generate (it outputs the file path)
|
|
142
|
-
wait $PIPER_PID 2>/dev/null || true
|
|
143
|
-
|
|
144
|
-
# Find the most recent TTS file
|
|
145
|
-
GENERATED_FILE=$(ls -t "$AUDIO_DIR"/tts-padded-*.wav 2>/dev/null | head -1)
|
|
146
|
-
|
|
147
|
-
if [[ -n "$GENERATED_FILE" ]] && [[ -f "$GENERATED_FILE" ]]; then
|
|
148
|
-
# Apply audio effects and background mixing
|
|
149
|
-
if [[ -f "$SCRIPT_DIR/audio-processor.sh" ]]; then
|
|
150
|
-
PROCESSED_FILE="$AUDIO_DIR/tts-enhanced-processed-$(date +%s)-$$.wav"
|
|
151
|
-
"$SCRIPT_DIR/audio-processor.sh" "$GENERATED_FILE" "$AGENT_FOR_EFFECTS" "$PROCESSED_FILE" 2>/dev/null || {
|
|
152
|
-
# Fallback to original if processing fails
|
|
153
|
-
PROCESSED_FILE="$GENERATED_FILE"
|
|
154
|
-
}
|
|
155
137
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
138
|
+
# Synthesize WITHOUT playback (this pipeline plays the post-processed file
|
|
139
|
+
# itself), CAPTURING the provider's stdout so we can read its AV_OUTPUT:
|
|
140
|
+
# sentinel — the EXACT absolute path piper wrote for THIS invocation.
|
|
141
|
+
#
|
|
142
|
+
# We deliberately do NOT scan the audio directory for the newest file (the
|
|
143
|
+
# banned most-recent-file / directory-listing heuristic): it races in party
|
|
144
|
+
# mode (grabs another agent's audio) and masks synthesis failures. See Story
|
|
145
|
+
# AVI-S8.5 (R6/R7) and memory: feedback_no_most_recent_file_heuristic.
|
|
146
|
+
PIPER_STDOUT=""
|
|
147
|
+
# Skip piper's internal (default-row) audio-processor pass: we run our OWN
|
|
148
|
+
# per-agent audio-processor pass below, so letting piper process first would
|
|
149
|
+
# double the effects/background-music (double reverb, overlapping tracks).
|
|
150
|
+
if ! PIPER_STDOUT=$(AGENTVIBES_NO_PLAYBACK=true AGENTVIBES_SKIP_INTERNAL_PROCESSOR=1 "$SCRIPT_DIR/play-tts-piper.sh" "$FULL_TEXT" "$AGENT_VOICE" 2>/dev/null); then
|
|
151
|
+
echo "❌ bmad-speak: piper synthesis failed for agent '$AGENT_FOR_EFFECTS'" >&2
|
|
152
|
+
exit 1
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# Capture the exact output path from the AV_OUTPUT: sentinel. FAIL LOUD if it
|
|
156
|
+
# is absent — never fall back to a directory listing.
|
|
157
|
+
GENERATED_FILE=$(printf '%s\n' "$PIPER_STDOUT" | sed -n 's/^AV_OUTPUT://p' | tail -1)
|
|
158
|
+
|
|
159
|
+
if [[ -z "$GENERATED_FILE" ]] || [[ ! -f "$GENERATED_FILE" ]]; then
|
|
160
|
+
echo "❌ bmad-speak: no AV_OUTPUT sentinel from play-tts-piper.sh — refusing to guess the output file (agent '$AGENT_FOR_EFFECTS')" >&2
|
|
161
|
+
exit 1
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# Apply audio effects and background mixing
|
|
165
|
+
if [[ -f "$SCRIPT_DIR/audio-processor.sh" ]]; then
|
|
166
|
+
PROCESSED_FILE="$AUDIO_DIR/tts-enhanced-processed-$(date +%s)-$$.wav"
|
|
167
|
+
"$SCRIPT_DIR/audio-processor.sh" "$GENERATED_FILE" "$AGENT_FOR_EFFECTS" "$PROCESSED_FILE" 2>/dev/null || {
|
|
168
|
+
# Fallback to original if processing fails
|
|
169
|
+
PROCESSED_FILE="$GENERATED_FILE"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Play the processed file
|
|
173
|
+
if [[ -f "$PROCESSED_FILE" ]]; then
|
|
174
|
+
(mpv "$PROCESSED_FILE" || aplay "$PROCESSED_FILE" || paplay "$PROCESSED_FILE") >/dev/null 2>&1 &
|
|
160
175
|
fi
|
|
161
176
|
fi
|
|
162
177
|
else
|
|
@@ -104,7 +104,7 @@ read_agent_profile_all() {
|
|
|
104
104
|
|
|
105
105
|
# Validate agent_id format (prevent injection)
|
|
106
106
|
if [[ ! "$agent_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
107
|
-
echo "
|
|
107
|
+
echo "||||||"
|
|
108
108
|
return
|
|
109
109
|
fi
|
|
110
110
|
|
|
@@ -173,13 +173,24 @@ map_to_agent_id() {
|
|
|
173
173
|
|
|
174
174
|
AGENT_ID=$(map_to_agent_id "$AGENT_NAME_OR_ID")
|
|
175
175
|
|
|
176
|
-
#
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
# Populate the per-agent profile (voice/pretext/reverb/personality/music) from
|
|
178
|
+
# bmad-voice-map.json. THIS is the population step that a botched merge
|
|
179
|
+
# (610af0f2) dropped, leaving $PROFILE_VOICE et al. below referenced-but-never-
|
|
180
|
+
# -assigned — a fatal "unbound variable" under `set -u`. read_agent_profile_all()
|
|
181
|
+
# already existed (defined above) but was never called; wire it in here.
|
|
182
|
+
# Always initialize (even to "") so downstream references never crash, and a
|
|
183
|
+
# missing/unknown agent just falls through to normal TTS below.
|
|
184
|
+
PROFILE_VOICE=""
|
|
185
|
+
PROFILE_PRETEXT=""
|
|
186
|
+
PROFILE_REVERB=""
|
|
187
|
+
PROFILE_PERSONALITY=""
|
|
188
|
+
PROFILE_MUSIC_TRACK=""
|
|
189
|
+
PROFILE_MUSIC_VOLUME=""
|
|
190
|
+
PROFILE_MUSIC_ENABLED=""
|
|
191
|
+
_PROFILE_ALL="$(read_agent_profile_all "${AGENT_ID:-}")"
|
|
192
|
+
IFS='|' read -r PROFILE_VOICE PROFILE_PRETEXT PROFILE_REVERB PROFILE_PERSONALITY \
|
|
193
|
+
PROFILE_MUSIC_TRACK PROFILE_MUSIC_VOLUME PROFILE_MUSIC_ENABLED <<< "$_PROFILE_ALL"
|
|
183
194
|
|
|
184
195
|
# Read global background music volume as fallback (stored as 0.0-1.0, convert to 0-100 integer)
|
|
185
196
|
_BG_VOL_FILE="${CLAUDE_PROJECT_DIR:-$PROJECT_ROOT}/.claude/config/background-music-volume.txt"
|
|
@@ -271,6 +282,13 @@ trap 'rmdir "$SPEECH_LOCK" 2>/dev/null' EXIT
|
|
|
271
282
|
# Speak with agent's voice, passing the temp profile path as arg 3 so
|
|
272
283
|
# play-tts-piper.sh → audio-processor.sh can read per-agent music settings
|
|
273
284
|
# without any env vars (safe for concurrent multi-project use).
|
|
285
|
+
#
|
|
286
|
+
# Declare voice provenance so the resolver never demotes a BMAD agent's own voice
|
|
287
|
+
# to the per-LLM/default row (F-1). This must be set independently of whether a
|
|
288
|
+
# TEMP_PROFILE file was created: a voice-only agent (no reverb/personality/music)
|
|
289
|
+
# has an empty TEMP_PROFILE, so the arg-3 heuristic in play-tts.sh alone would
|
|
290
|
+
# miss it. AGENT_VOICE is an agent-profile voice, always.
|
|
291
|
+
export AGENTVIBES_VOICE_SOURCE="agent-profile"
|
|
274
292
|
if [[ -n "$AGENT_VOICE" ]]; then
|
|
275
293
|
bash "$SCRIPT_DIR/play-tts.sh" "$FULL_TEXT" "$AGENT_VOICE" "$TEMP_PROFILE"
|
|
276
294
|
else
|
|
@@ -99,7 +99,7 @@ echo "📱 Sending to $SSH_HOST for local playback..." >&2
|
|
|
99
99
|
SSH_ARGS=()
|
|
100
100
|
[[ -n "$SSH_KEY" && -f "$SSH_KEY" ]] && SSH_ARGS+=(-i "$SSH_KEY")
|
|
101
101
|
# Never force "-p 22" (ssh default) -- it would override an ~/.ssh/config
|
|
102
|
-
# Host alias port (e.g. laptop
|
|
102
|
+
# Host alias port (e.g. my-laptop -> 2222). Empty/22 => defer to ssh config.
|
|
103
103
|
if [[ -n "$SSH_PORT" && "$SSH_PORT" != "22" ]]; then SSH_ARGS+=(-p "$SSH_PORT"); fi
|
|
104
104
|
|
|
105
105
|
# Try receiver scripts in order — single SSH call, no separate probe
|
|
@@ -94,7 +94,10 @@ fi
|
|
|
94
94
|
|
|
95
95
|
# Skip the (slow) synthesis + playback entirely in test mode — nothing to play
|
|
96
96
|
# and no need to spin up the kokoro model. Mirrors the sibling Piper provider.
|
|
97
|
+
# Still emit the AV_OUTPUT sentinel so consumers get the exact intended path
|
|
98
|
+
# (Story AVI-S8.5, R6): the path is machine-parseable regardless of playback.
|
|
97
99
|
if [[ "${AGENTVIBES_TEST_MODE:-false}" == "true" ]]; then
|
|
100
|
+
printf 'AV_OUTPUT:%s\n' "$TEMP_WAV"
|
|
98
101
|
trap '' EXIT
|
|
99
102
|
exit 0
|
|
100
103
|
fi
|
|
@@ -110,6 +113,12 @@ if [[ ! -f "$TEMP_WAV" || ! -s "$TEMP_WAV" ]]; then
|
|
|
110
113
|
exit 3
|
|
111
114
|
fi
|
|
112
115
|
|
|
116
|
+
# AV_OUTPUT sentinel (Story AVI-S8.5, R6/R7): emit the EXACT absolute path of the
|
|
117
|
+
# wav this invocation produced, on its own machine-parseable stdout line. Consumers
|
|
118
|
+
# capture THIS path — never a directory listing (memory:
|
|
119
|
+
# feedback_no_most_recent_file_heuristic).
|
|
120
|
+
printf 'AV_OUTPUT:%s\n' "$TEMP_WAV"
|
|
121
|
+
|
|
113
122
|
# ---------------------------------------------------------------------------
|
|
114
123
|
# Play audio — try players in order (WAV-capable)
|
|
115
124
|
# Skip playback in test mode or no-playback mode (matches the Piper provider).
|
|
@@ -474,7 +474,11 @@ fi
|
|
|
474
474
|
# @returns Updates $TEMP_FILE to processed version, sets $BACKGROUND_MUSIC if used
|
|
475
475
|
# @sideeffects Applies audio effects and background music
|
|
476
476
|
BACKGROUND_MUSIC=""
|
|
477
|
-
|
|
477
|
+
# AGENTVIBES_SKIP_INTERNAL_PROCESSOR=1 lets a caller that will run its OWN
|
|
478
|
+
# audio-processor pass (e.g. bmad-speak-enhanced.sh applying per-agent effects)
|
|
479
|
+
# skip this generic default-row pass — otherwise effects/background-music get
|
|
480
|
+
# applied twice (double reverb / overlapping music).
|
|
481
|
+
if [[ "${AGENTVIBES_SKIP_INTERNAL_PROCESSOR:-}" != "1" && -f "$SCRIPT_DIR/audio-processor.sh" ]]; then
|
|
478
482
|
_tmp=$(mktemp "$AUDIO_DIR/tts-processed-XXXXXX"); PROCESSED_FILE="${_tmp}.wav"; mv "$_tmp" "$PROCESSED_FILE"
|
|
479
483
|
_CLEANUP_FILES+=("$PROCESSED_FILE")
|
|
480
484
|
# audio-processor.sh returns: FILE_PATH|BACKGROUND_FILE
|
|
@@ -630,6 +634,14 @@ fi
|
|
|
630
634
|
# Display with file count (now showing accurate post-cleanup size)
|
|
631
635
|
echo -e "${WHITE}💾 Saved to:${NC} ${CYAN}$TEMP_FILE${NC} ${YELLOW}$FILE_COUNT${NC} ${WHITE}🗄️${NC} ${CACHE_COLOR}$SIZE_HUMAN${NC} ${WHITE}🧹${NC}${GOLD}[${AUTO_CLEAN_THRESHOLD}mb]${NC}"
|
|
632
636
|
|
|
637
|
+
# AV_OUTPUT sentinel (Story AVI-S8.5, R6/R7): emit the EXACT absolute path of the
|
|
638
|
+
# wav this invocation produced, on its own machine-parseable stdout line. Consumers
|
|
639
|
+
# (e.g. bmad-speak-enhanced.sh) capture THIS path — never `ls -t | head -1`, which
|
|
640
|
+
# races in party mode and masks synthesis failures (memory:
|
|
641
|
+
# feedback_no_most_recent_file_heuristic). Additive: the human "Saved to:" line
|
|
642
|
+
# above is intact for the MCP server's loose parser.
|
|
643
|
+
printf 'AV_OUTPUT:%s\n' "$TEMP_FILE"
|
|
644
|
+
|
|
633
645
|
if [[ -n "$BACKGROUND_MUSIC" ]]; then
|
|
634
646
|
# Extract just the filename to save space
|
|
635
647
|
MUSIC_FILENAME=$(basename "$BACKGROUND_MUSIC")
|
|
@@ -34,8 +34,32 @@ fi
|
|
|
34
34
|
# Default to "default" so remote always has an LLM key to look up
|
|
35
35
|
LLM_NAME="${LLM_NAME:-default}"
|
|
36
36
|
|
|
37
|
-
#
|
|
38
|
-
|
|
37
|
+
# Music-only preview mode: AGENTVIBES_MUSIC_ONLY=<track.mp3> tells the receiver to
|
|
38
|
+
# play a standalone background-music track (no speech). Used by the Music tab's
|
|
39
|
+
# remote preview so a track can be auditioned on the receiver. In this mode empty
|
|
40
|
+
# TEXT is allowed and the track name is forwarded as the "music" field with
|
|
41
|
+
# kind=music; the receiver resolves the file from its own ~/.claude/audio/tracks/.
|
|
42
|
+
PAYLOAD_KIND="speak"
|
|
43
|
+
MUSIC_ONLY_TRACK=""
|
|
44
|
+
if [[ -n "${AGENTVIBES_MUSIC_STOP:-}" ]]; then
|
|
45
|
+
# Stop whatever music preview is currently playing on the receiver.
|
|
46
|
+
PAYLOAD_KIND="music-stop"
|
|
47
|
+
elif [[ -n "${AGENTVIBES_MUSIC_ONLY:-}" ]]; then
|
|
48
|
+
# Only a bare .mp3 filename is allowed (no path separators, no leading dash) —
|
|
49
|
+
# the receiver resolves it against its own tracks dir, so reject path-like input.
|
|
50
|
+
if [[ "$AGENTVIBES_MUSIC_ONLY" =~ ^[A-Za-z0-9._][A-Za-z0-9._-]*\.mp3$ ]]; then
|
|
51
|
+
PAYLOAD_KIND="music"
|
|
52
|
+
MUSIC_ONLY_TRACK="$AGENTVIBES_MUSIC_ONLY"
|
|
53
|
+
else
|
|
54
|
+
echo "Invalid music track name: $AGENTVIBES_MUSIC_ONLY" >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Validate required input (music / music-stop modes carry no speech text)
|
|
60
|
+
if [[ "$PAYLOAD_KIND" == "music" || "$PAYLOAD_KIND" == "music-stop" ]]; then
|
|
61
|
+
TEXT="" # no speech in a music preview / stop
|
|
62
|
+
elif [[ -z "$TEXT" ]]; then
|
|
39
63
|
echo "Usage: $0 <text> [voice] [agent_name]" >&2
|
|
40
64
|
exit 1
|
|
41
65
|
fi
|
|
@@ -46,6 +70,9 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
46
70
|
|
|
47
71
|
# Derive project name from directory
|
|
48
72
|
PROJECT_NAME=$(basename "$PROJECT_ROOT")
|
|
73
|
+
# Absolute path, forwarded so the receiver-side avatar can show/learn the real
|
|
74
|
+
# remote folder (mirrors the local forward-to-avatar.sh's projectPath field).
|
|
75
|
+
PROJECT_PATH="$PROJECT_ROOT"
|
|
49
76
|
|
|
50
77
|
# ---------------------------------------------------------------------------
|
|
51
78
|
# Get SSH connection details from config
|
|
@@ -149,7 +176,8 @@ fi
|
|
|
149
176
|
|
|
150
177
|
SOX_EFFECTS=""
|
|
151
178
|
BG_FILE=""
|
|
152
|
-
|
|
179
|
+
# TODO(AVI-S8.6): generate this constant from the shared JSON source of truth.
|
|
180
|
+
BG_VOLUME="0.20"
|
|
153
181
|
|
|
154
182
|
# Use CLAUDE_PROJECT_DIR (injected via --project-dir by play-tts.sh) so the
|
|
155
183
|
# agent-name / default row is read from the user's project, not the package.
|
|
@@ -280,6 +308,76 @@ case "${PROVIDER:-}" in
|
|
|
280
308
|
*) PROVIDER="piper" ;;
|
|
281
309
|
esac
|
|
282
310
|
|
|
311
|
+
# ---------------------------------------------------------------------------
|
|
312
|
+
# Resolve the project-level .claude dir the way the local managers do:
|
|
313
|
+
# CLAUDE_PROJECT_DIR (the real user project, injected via --project-dir by
|
|
314
|
+
# play-tts.sh) wins; otherwise fall back to the package PROJECT_ROOT.
|
|
315
|
+
# ---------------------------------------------------------------------------
|
|
316
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" && -d "$CLAUDE_PROJECT_DIR/.claude" ]]; then
|
|
317
|
+
_RESOLVE_PROJECT_DIR="$CLAUDE_PROJECT_DIR"
|
|
318
|
+
else
|
|
319
|
+
_RESOLVE_PROJECT_DIR="$PROJECT_ROOT"
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# ---------------------------------------------------------------------------
|
|
323
|
+
# Resolve MUTE to forward (map §C 'mute never forwarded on SSH', R17).
|
|
324
|
+
# Mirrors play-tts.sh's 3-level file check exactly:
|
|
325
|
+
# project-unmute (overrides global) > project-mute > global-mute.
|
|
326
|
+
# The receiver is authoritative and applies a SAFETY-OR:
|
|
327
|
+
# effective_mute = receiver_local_mute OR forwarded_mute
|
|
328
|
+
# so if EITHER side is muted, playback stays silent. We forward this as a
|
|
329
|
+
# STRING ("true"/"false") — the receiver does a plain string compare, and it
|
|
330
|
+
# keeps the JSON boolean out of python's "True"/"False" capitalization.
|
|
331
|
+
# ---------------------------------------------------------------------------
|
|
332
|
+
_GLOBAL_MUTE_FILE="$HOME/.agentvibes-muted"
|
|
333
|
+
_PROJECT_MUTE_FILE="$_RESOLVE_PROJECT_DIR/.claude/agentvibes-muted"
|
|
334
|
+
_PROJECT_UNMUTE_FILE="$_RESOLVE_PROJECT_DIR/.claude/agentvibes-unmuted"
|
|
335
|
+
MUTE="false"
|
|
336
|
+
if [[ -f "$_PROJECT_UNMUTE_FILE" ]]; then
|
|
337
|
+
MUTE="false" # project explicitly unmuted overrides global
|
|
338
|
+
elif [[ -f "$_PROJECT_MUTE_FILE" ]]; then
|
|
339
|
+
MUTE="true"
|
|
340
|
+
elif [[ -f "$_GLOBAL_MUTE_FILE" ]]; then
|
|
341
|
+
MUTE="true"
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
# ---------------------------------------------------------------------------
|
|
345
|
+
# Resolve LANGUAGE to forward (map §D 'language | Forward on SSH', R18).
|
|
346
|
+
# Precedence mirrors the local chain (learn-manager.sh / language-manager.sh):
|
|
347
|
+
# learning-mode target (tts-target-language.txt when tts-learn-mode.txt=="ON")
|
|
348
|
+
# > tts-language.txt.
|
|
349
|
+
# Forwarded as a DEFAULT only — the receiver stays authoritative and its own
|
|
350
|
+
# language config (if any) wins. language-manager.sh stores tts-language.txt at
|
|
351
|
+
# the .claude/ root; the resolver contract tests use the .claude/config/ subdir,
|
|
352
|
+
# so we accept both. Project scope wins over the global home fallback.
|
|
353
|
+
# ---------------------------------------------------------------------------
|
|
354
|
+
LANGUAGE=""
|
|
355
|
+
for _cdir in "$_RESOLVE_PROJECT_DIR/.claude" "$HOME/.claude"; do
|
|
356
|
+
# Priority 1: learning-mode target language (only when learn mode is ON).
|
|
357
|
+
if [[ -z "$LANGUAGE" && -f "$_cdir/tts-learn-mode.txt" ]]; then
|
|
358
|
+
if [[ "$(cat "$_cdir/tts-learn-mode.txt" 2>/dev/null || true)" == "ON" ]]; then
|
|
359
|
+
[[ -f "$_cdir/tts-target-language.txt" ]] && \
|
|
360
|
+
LANGUAGE=$(cat "$_cdir/tts-target-language.txt" 2>/dev/null || true)
|
|
361
|
+
fi
|
|
362
|
+
fi
|
|
363
|
+
# Priority 2: tts-language.txt (root or config/ subdir).
|
|
364
|
+
if [[ -z "$LANGUAGE" && -f "$_cdir/tts-language.txt" ]]; then
|
|
365
|
+
LANGUAGE=$(cat "$_cdir/tts-language.txt" 2>/dev/null || true)
|
|
366
|
+
fi
|
|
367
|
+
if [[ -z "$LANGUAGE" && -f "$_cdir/config/tts-language.txt" ]]; then
|
|
368
|
+
LANGUAGE=$(cat "$_cdir/config/tts-language.txt" 2>/dev/null || true)
|
|
369
|
+
fi
|
|
370
|
+
[[ -n "$LANGUAGE" ]] && break
|
|
371
|
+
done
|
|
372
|
+
# Trim surrounding whitespace
|
|
373
|
+
LANGUAGE="${LANGUAGE#"${LANGUAGE%%[![:space:]]*}"}"
|
|
374
|
+
LANGUAGE="${LANGUAGE%"${LANGUAGE##*[![:space:]]}"}"
|
|
375
|
+
# SECURITY: only forward a plain language name (letters, spaces, hyphen); jq
|
|
376
|
+
# --arg escapes anyway, but reject odd values rather than propagate them.
|
|
377
|
+
if [[ -n "$LANGUAGE" ]] && [[ ! "$LANGUAGE" =~ ^[a-zA-Z][a-zA-Z\ -]*$ ]]; then
|
|
378
|
+
LANGUAGE=""
|
|
379
|
+
fi
|
|
380
|
+
|
|
283
381
|
# ---------------------------------------------------------------------------
|
|
284
382
|
# Build JSON payload
|
|
285
383
|
# ---------------------------------------------------------------------------
|
|
@@ -294,22 +392,34 @@ build_json_payload() {
|
|
|
294
392
|
--arg music "$BG_FILE" \
|
|
295
393
|
--arg volume "$BG_VOLUME" \
|
|
296
394
|
--arg project "$PROJECT_NAME" \
|
|
395
|
+
--arg projectPath "$PROJECT_PATH" \
|
|
297
396
|
--arg pretext "$PRETEXT" \
|
|
298
397
|
--arg speed "$SPEED" \
|
|
299
398
|
--arg provider "$PROVIDER" \
|
|
300
399
|
--arg llm "$LLM_NAME" \
|
|
301
|
-
|
|
400
|
+
--arg mute "$MUTE" \
|
|
401
|
+
--arg language "$LANGUAGE" \
|
|
402
|
+
--arg kind "$PAYLOAD_KIND" \
|
|
403
|
+
'{text: $text, voice: $voice, effects: $effects, music: $music, volume: $volume, project: $project, projectPath: $projectPath, pretext: $pretext, speed: $speed, provider: $provider, llm: $llm, mute: $mute, language: $language, kind: $kind}'
|
|
302
404
|
else
|
|
303
405
|
# Manual JSON — escape backslashes, quotes, control chars
|
|
304
406
|
local escaped_text
|
|
305
407
|
escaped_text=$(printf '%s' "$TEXT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' ' | sed 's/\r//g')
|
|
306
408
|
local escaped_pretext
|
|
307
409
|
escaped_pretext=$(printf '%s' "$PRETEXT" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
308
|
-
|
|
309
|
-
|
|
410
|
+
local escaped_project_path
|
|
411
|
+
escaped_project_path=$(printf '%s' "$PROJECT_PATH" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
412
|
+
printf '{"text":"%s","voice":"%s","effects":"%s","music":"%s","volume":"%s","project":"%s","projectPath":"%s","pretext":"%s","speed":"%s","provider":"%s","llm":"%s","mute":"%s","language":"%s","kind":"%s"}' \
|
|
413
|
+
"$escaped_text" "$VOICE" "$SOX_EFFECTS" "$BG_FILE" "$BG_VOLUME" "$PROJECT_NAME" "$escaped_project_path" "$escaped_pretext" "$SPEED" "$PROVIDER" "$LLM_NAME" "$MUTE" "$LANGUAGE" "$PAYLOAD_KIND"
|
|
310
414
|
fi
|
|
311
415
|
}
|
|
312
416
|
|
|
417
|
+
# Music-only preview: force the track as the "music" field (overriding any
|
|
418
|
+
# per-LLM/agent background-music config) so the receiver plays exactly this track.
|
|
419
|
+
if [[ "$PAYLOAD_KIND" == "music" ]]; then
|
|
420
|
+
BG_FILE="$MUSIC_ONLY_TRACK"
|
|
421
|
+
fi
|
|
422
|
+
|
|
313
423
|
JSON_PAYLOAD=$(build_json_payload)
|
|
314
424
|
|
|
315
425
|
# SECURITY: Base64-encode entire payload — safe for SSH transport
|
|
@@ -338,7 +448,7 @@ SSH_ARGS=()
|
|
|
338
448
|
[[ -n "$SSH_KEY" && -f "$SSH_KEY" ]] && SSH_ARGS+=(-i "$SSH_KEY")
|
|
339
449
|
# Only pass -p for an explicit, non-default port. Port 22 is ssh's universal
|
|
340
450
|
# default, so forcing "-p 22" is at best a no-op and at worst overrides the
|
|
341
|
-
# real port of an ~/.ssh/config Host alias (e.g. laptop
|
|
451
|
+
# real port of an ~/.ssh/config Host alias (e.g. my-laptop -> Port 2222),
|
|
342
452
|
# silently delivering the payload to the wrong port. Empty or "22" means
|
|
343
453
|
# "use ssh / ~/.ssh/config defaults" -- never force it.
|
|
344
454
|
if [[ -n "$SSH_PORT" && "$SSH_PORT" != "22" ]]; then SSH_ARGS+=(-p "$SSH_PORT"); fi
|
|
@@ -166,7 +166,7 @@ SAFE_TEXT="${TEXT//\'/\'\\\'\'}"
|
|
|
166
166
|
SSH_ARGS=(-o ConnectTimeout=5)
|
|
167
167
|
[[ -n "$SSH_KEY" && -f "$SSH_KEY" ]] && SSH_ARGS+=(-i "$SSH_KEY")
|
|
168
168
|
# Never force "-p 22" (ssh default) -- it would override an ~/.ssh/config
|
|
169
|
-
# Host alias port (e.g. laptop
|
|
169
|
+
# Host alias port (e.g. my-laptop -> 2222). Empty/22 => defer to ssh config.
|
|
170
170
|
if [[ -n "$SSH_PORT" && "$SSH_PORT" != "22" ]]; then SSH_ARGS+=(-p "$SSH_PORT"); fi
|
|
171
171
|
|
|
172
172
|
# Send TTS command to Android device via SSH
|
|
@@ -145,6 +145,7 @@ unset _POSITIONAL_ARGS
|
|
|
145
145
|
TEXT="${1:-}"
|
|
146
146
|
VOICE_OVERRIDE="${2:-}" # Optional: voice name or ID
|
|
147
147
|
AGENT_PROFILE_FILE="${3:-}" # Optional: path to agent profile file
|
|
148
|
+
_ORIG_EXPLICIT_VOICE="$VOICE_OVERRIDE" # raw positional voice, before any per-LLM override (fed to the resolver as the explicit voice)
|
|
148
149
|
|
|
149
150
|
# Security: Validate inputs
|
|
150
151
|
if [[ -z "$TEXT" ]]; then
|
|
@@ -244,6 +245,75 @@ if [[ -n "$_PRETEXT" ]]; then
|
|
|
244
245
|
TEXT="${_PRETEXT}, ${TEXT}"
|
|
245
246
|
fi
|
|
246
247
|
|
|
248
|
+
# ── Utterance Resolver (AVI-S8.5 Stage 2) ────────────────────────────────────
|
|
249
|
+
# Single source of truth for the voice + engine decision. Resolve the plan once
|
|
250
|
+
# and adopt its voice (per-LLM voice wins over an LLM-echoed override — R2) and,
|
|
251
|
+
# below, its local engine (a kokoro-shaped voice forces the kokoro engine on
|
|
252
|
+
# Linux too — R1, curing the detect_voice_provider silence bug). FAIL-SAFE: if
|
|
253
|
+
# node or the resolver bundle isn't reachable (e.g. an installed ~/.claude that
|
|
254
|
+
# predates the bundle-shipping installer change), PLAN_OK stays empty and the
|
|
255
|
+
# legacy logic below runs unchanged — TTS never breaks on a missing bridge.
|
|
256
|
+
PLAN_OK=""
|
|
257
|
+
_RESOLVER_CLI=""
|
|
258
|
+
# AGENTVIBES_RESOLVER_CLI lets the installer (or tests) point at the resolver
|
|
259
|
+
# bundle when it lives outside the hooks tree — e.g. an installed ~/.claude/hooks
|
|
260
|
+
# whose sibling package dir holds bin/resolve-utterance.js.
|
|
261
|
+
# Candidate order: explicit env override; the installed dedicated bundle dir
|
|
262
|
+
# (has its own package.json so ESM works on every Node version — F-5); the repo
|
|
263
|
+
# layout (dev); a co-located copy.
|
|
264
|
+
for _cand in "${AGENTVIBES_RESOLVER_CLI:-}" \
|
|
265
|
+
"$SCRIPT_DIR/../agentvibes-resolver/bin/resolve-utterance.js" \
|
|
266
|
+
"$SCRIPT_DIR/../../bin/resolve-utterance.js" \
|
|
267
|
+
"$SCRIPT_DIR/resolve-utterance.js"; do
|
|
268
|
+
[[ -n "$_cand" && -f "$_cand" ]] && { _RESOLVER_CLI="$_cand"; break; }
|
|
269
|
+
done
|
|
270
|
+
if [[ -n "$_RESOLVER_CLI" ]] && command -v node &>/dev/null; then
|
|
271
|
+
# Voice provenance (F-1): a per-agent profile ($3) is an 'agent-profile' voice
|
|
272
|
+
# (BMAD party mode — each agent its OWN voice, must not be demoted to the
|
|
273
|
+
# per-LLM/default voice); a caller may declare provenance via
|
|
274
|
+
# AGENTVIBES_VOICE_SOURCE (e.g. MCP text_to_speech → 'user-explicit'); the
|
|
275
|
+
# voice-browser sets 'audition'; otherwise it's an 'llm-echo' (the LLM parroting
|
|
276
|
+
# back get_config, which the per-LLM row rightly overrides — R2).
|
|
277
|
+
_vsource="${AGENTVIBES_VOICE_SOURCE:-llm-echo}"
|
|
278
|
+
# Whitelist the provenance — an unknown value falls back to llm-echo (safe default).
|
|
279
|
+
case "$_vsource" in
|
|
280
|
+
llm-echo|user-explicit|agent-profile|translation-target|audition) ;;
|
|
281
|
+
*) _vsource="llm-echo" ;;
|
|
282
|
+
esac
|
|
283
|
+
# A per-agent profile ($3) also implies agent-profile provenance (belt-and-suspenders
|
|
284
|
+
# with bmad-speak.sh's explicit AGENTVIBES_VOICE_SOURCE export).
|
|
285
|
+
[[ -n "${AGENT_PROFILE_FILE:-}" && "$_vsource" == "llm-echo" ]] && _vsource="agent-profile"
|
|
286
|
+
[[ -n "${AGENTVIBES_EFFECTS_PREVIEW:-}" ]] && _vsource="audition"
|
|
287
|
+
# `timeout 5` caps a wedged bridge so the fail-safe promise ("TTS never breaks
|
|
288
|
+
# on the bridge") also covers a hung node, not just a missing one.
|
|
289
|
+
_av_timeout="timeout 5"; command -v timeout &>/dev/null || _av_timeout=""
|
|
290
|
+
if _plan_sh=$($_av_timeout node "$_RESOLVER_CLI" --format sh \
|
|
291
|
+
--text "$TEXT" \
|
|
292
|
+
--llm "$LLM_PROVIDER" \
|
|
293
|
+
--voice "$_ORIG_EXPLICIT_VOICE" \
|
|
294
|
+
--voice-source "$_vsource" \
|
|
295
|
+
--project-dir "${CLAUDE_PROJECT_DIR:-$PROJECT_ROOT}" \
|
|
296
|
+
--package-root "$PROJECT_ROOT" 2>/dev/null); then
|
|
297
|
+
# Guard the eval: a malformed OR EMPTY plan must fall back to legacy, never
|
|
298
|
+
# abort the script mid-run under `set -euo pipefail`. `eval ""` succeeds with
|
|
299
|
+
# no AV_* vars set, which would falsely set PLAN_OK — so require non-empty
|
|
300
|
+
# output AND a field that is ALWAYS present in a valid plan. AV_TRANSPORT is
|
|
301
|
+
# always 'local' or a transport name (never empty), unlike AV_ENGINE which is
|
|
302
|
+
# legitimately empty on a remote plan (F-7).
|
|
303
|
+
if [[ -n "$_plan_sh" ]] && eval "$_plan_sh" 2>/dev/null && [[ -n "${AV_TRANSPORT:-}" ]]; then # AV_* shell-quoted, safe
|
|
304
|
+
PLAN_OK=1
|
|
305
|
+
# Adopt the voice into VOICE_OVERRIDE ONLY when it's a real override
|
|
306
|
+
# (F-2): a plain provider-file voice is left for play-tts-piper.sh's own
|
|
307
|
+
# file+model+speaker-id resolution (adopting it as an explicit override
|
|
308
|
+
# skips the multi-speaker lookup and plays speaker 0). Engine coupling (R1)
|
|
309
|
+
# still applies below regardless.
|
|
310
|
+
if [[ "${AV_VOICE_IS_OVERRIDE:-false}" == "true" && -n "${AV_VOICE:-}" ]]; then
|
|
311
|
+
VOICE_OVERRIDE="$AV_VOICE"
|
|
312
|
+
fi
|
|
313
|
+
fi
|
|
314
|
+
fi
|
|
315
|
+
fi
|
|
316
|
+
|
|
247
317
|
# Source provider manager to get active provider
|
|
248
318
|
source "$SCRIPT_DIR/provider-manager.sh"
|
|
249
319
|
|
|
@@ -259,7 +329,12 @@ case "$ACTIVE_PROVIDER" in
|
|
|
259
329
|
# Transport — keep it. The receiver's audio-effects.cfg picks the engine.
|
|
260
330
|
;;
|
|
261
331
|
*)
|
|
262
|
-
if [[
|
|
332
|
+
if [[ "$VOICE_OVERRIDE" =~ ^(af|am|bf|bm|jf|jm|kf|km|zf|zm|ff|fm|hf|hm|if|im|pf|pm|ef|em|nf|nm)_[a-zA-Z0-9]+$ ]]; then
|
|
333
|
+
# Kokoro-shaped voice override (e.g. af_heart) must win over the LLM row's
|
|
334
|
+
# engine column, else it is synthesized as a Piper voice, 404s downloading
|
|
335
|
+
# a model that doesn't exist, and stays silent. (Grafted from master WIP.)
|
|
336
|
+
ACTIVE_PROVIDER="kokoro"
|
|
337
|
+
elif [[ -n "$_LLM_ENGINE" ]]; then
|
|
263
338
|
ACTIVE_PROVIDER="$_LLM_ENGINE"
|
|
264
339
|
fi
|
|
265
340
|
;;
|
|
@@ -320,6 +395,17 @@ PYEOF
|
|
|
320
395
|
fi
|
|
321
396
|
fi
|
|
322
397
|
|
|
398
|
+
# Adopt the resolver's engine for LOCAL playback (AVI-S8.5 Stage 2): this cures
|
|
399
|
+
# the kokoro/piper voice→engine coupling (R1) and normalizes engine aliases,
|
|
400
|
+
# replacing the detect_voice_provider heuristic below. Transports are left
|
|
401
|
+
# untouched — the plan forwards the voice and the receiver picks its own engine.
|
|
402
|
+
if [[ -n "$PLAN_OK" ]]; then
|
|
403
|
+
case "$ACTIVE_PROVIDER" in
|
|
404
|
+
ssh-remote|agentvibes-receiver|termux-ssh) : ;; # transport — keep it
|
|
405
|
+
*) [[ -n "${AV_ENGINE:-}" ]] && ACTIVE_PROVIDER="$AV_ENGINE" ;;
|
|
406
|
+
esac
|
|
407
|
+
fi
|
|
408
|
+
|
|
323
409
|
# Show GitHub star reminder (once per day)
|
|
324
410
|
bash "$SCRIPT_DIR/github-star-reminder.sh" 2>/dev/null || true
|
|
325
411
|
|
|
@@ -338,10 +424,10 @@ detect_voice_provider() {
|
|
|
338
424
|
fi
|
|
339
425
|
}
|
|
340
426
|
|
|
341
|
-
#
|
|
342
|
-
#
|
|
343
|
-
#
|
|
344
|
-
if [[ -n "$VOICE_OVERRIDE" ]]; then
|
|
427
|
+
# Legacy mixed-provider heuristic — ONLY on the fallback path (no resolver plan).
|
|
428
|
+
# When PLAN_OK is set, the resolver already coupled voice→engine correctly above
|
|
429
|
+
# (including kokoro, which this heuristic misses — the R1 silence bug), so skip it.
|
|
430
|
+
if [[ -z "$PLAN_OK" && -n "$VOICE_OVERRIDE" ]]; then
|
|
345
431
|
case "$ACTIVE_PROVIDER" in
|
|
346
432
|
ssh-remote|agentvibes-receiver|termux-ssh)
|
|
347
433
|
# Transport provider — don't override, voice info is forwarded to receiver
|
|
@@ -432,12 +518,17 @@ speak_text() {
|
|
|
432
518
|
termux-ssh)
|
|
433
519
|
bash "$SCRIPT_DIR/play-tts-termux-ssh.sh" "$text" "$voice"
|
|
434
520
|
;;
|
|
435
|
-
ssh-remote)
|
|
521
|
+
ssh-remote|agentvibes-receiver)
|
|
522
|
+
# Both route through the base64-JSON sender that the modern receiver
|
|
523
|
+
# (templates/agentvibes-receiver.ps1 / .sh, installed as play-remote.*)
|
|
524
|
+
# understands. The old voiceless-connections sender spoke a 3-positional-arg
|
|
525
|
+
# protocol for a legacy ~/.agentvibes/play-remote.sh receiver that no current
|
|
526
|
+
# receiver install produces, so it delivered a non-base64 command the modern
|
|
527
|
+
# receiver rejects ("Payload must be base64-encoded"). play-tts-ssh-remote.sh
|
|
528
|
+
# resolves the host from any transport-config mode=remote entry (Priority 2b),
|
|
529
|
+
# so the agentvibes-receiver section is found without an ssh-remote section.
|
|
436
530
|
bash "$SCRIPT_DIR/play-tts-ssh-remote.sh" "$text" "$voice" "" "${profile_file:-}"
|
|
437
531
|
;;
|
|
438
|
-
agentvibes-receiver)
|
|
439
|
-
bash "$SCRIPT_DIR/play-tts-agentvibes-receiver-for-voiceless-connections.sh" "$text" "$voice"
|
|
440
|
-
;;
|
|
441
532
|
*)
|
|
442
533
|
echo "❌ Unknown provider: $provider" >&2
|
|
443
534
|
return 1
|
|
@@ -572,12 +663,14 @@ case "$ACTIVE_PROVIDER" in
|
|
|
572
663
|
termux-ssh)
|
|
573
664
|
exec bash "$SCRIPT_DIR/play-tts-termux-ssh.sh" "$TEXT" "$VOICE_OVERRIDE"
|
|
574
665
|
;;
|
|
575
|
-
ssh-remote)
|
|
666
|
+
ssh-remote|agentvibes-receiver)
|
|
667
|
+
# Both route through the base64-JSON sender the modern receiver understands.
|
|
668
|
+
# The legacy voiceless-connections sender spoke a 3-arg protocol for an old
|
|
669
|
+
# ~/.agentvibes/play-remote.sh receiver that no current install produces, so it
|
|
670
|
+
# delivered a non-base64 command the modern receiver rejects with
|
|
671
|
+
# "Payload must be base64-encoded".
|
|
576
672
|
exec bash "$SCRIPT_DIR/play-tts-ssh-remote.sh" "$TEXT" "$VOICE_OVERRIDE" "" "${AGENT_PROFILE_FILE:-}"
|
|
577
673
|
;;
|
|
578
|
-
agentvibes-receiver)
|
|
579
|
-
exec bash "$SCRIPT_DIR/play-tts-agentvibes-receiver-for-voiceless-connections.sh" "$TEXT" "$VOICE_OVERRIDE"
|
|
580
|
-
;;
|
|
581
674
|
*)
|
|
582
675
|
echo "❌ Unknown provider: $ACTIVE_PROVIDER" >&2
|
|
583
676
|
echo " Run: /agent-vibes:provider list" >&2
|
|
@@ -25,7 +25,8 @@ $EnabledFile = Join-Path $ConfigDir "background-music-enabled.txt"
|
|
|
25
25
|
$VolumeFile = Join-Path $ConfigDir "background-music-volume.txt"
|
|
26
26
|
$AudioEffectsCfg = Join-Path $ConfigDir "audio-effects.cfg"
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
# TODO(AVI-S8.6): generate this constant from the shared JSON source of truth.
|
|
29
|
+
$DefaultVolume = "0.20"
|
|
29
30
|
|
|
30
31
|
# Ensure config directory exists
|
|
31
32
|
if (-not (Test-Path $ConfigDir)) {
|
|
@@ -160,6 +160,20 @@ try {
|
|
|
160
160
|
Write-Host "[OK] Saved to: $AudioFile" -ForegroundColor Green
|
|
161
161
|
Write-Host "[VOICE] Voice used: $VoiceName (Kokoro TTS)" -ForegroundColor Green
|
|
162
162
|
|
|
163
|
+
# Kokoro outputs at a lower level than Piper/SAPI, and ffmpeg's amix halves
|
|
164
|
+
# the voice when background music is mixed in. Apply a 2.5x (≈8 dB) volume
|
|
165
|
+
# boost here so the voice is audible in all playback paths.
|
|
166
|
+
$ffplayCheck = Get-Command ffmpeg -ErrorAction SilentlyContinue
|
|
167
|
+
if ($ffplayCheck) {
|
|
168
|
+
$boostedFile = "$AudioDir\tts-boosted-$([System.IO.Path]::GetRandomFileName() -replace '\..*').wav"
|
|
169
|
+
$boostArgs = "-y -i `"$AudioFile`" -af `"volume=2.5`" `"$boostedFile`""
|
|
170
|
+
$boostProc = Start-Process -FilePath "ffmpeg" -ArgumentList $boostArgs -NoNewWindow -Wait -PassThru -RedirectStandardError "NUL"
|
|
171
|
+
if ($boostProc.ExitCode -eq 0 -and (Test-Path $boostedFile) -and (Get-Item $boostedFile).Length -gt 0) {
|
|
172
|
+
Remove-Item $AudioFile -Force -ErrorAction SilentlyContinue
|
|
173
|
+
$AudioFile = $boostedFile
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
163
177
|
# Apply per-agent audio effects via audio-processor.ps1, but SKIP when
|
|
164
178
|
# AGENTVIBES_NO_PLAY is set - that means the parent play-tts.ps1 will do its
|
|
165
179
|
# own reverb / background-music post-processing and running effects here too
|