agentvibes 5.11.2 → 5.12.0-alpha.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/config.json +1 -15
- package/.claude/activation-instructions +54 -54
- 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 +5 -5
- package/.claude/config/background-music-enabled.txt +1 -1
- package/.claude/docs/TERMUX_SETUP.md +408 -408
- 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 +78 -5
- package/.claude/hooks/play-tts-termux-ssh.sh +1 -1
- package/.claude/hooks/play-tts.sh +85 -4
- 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/voice-manager-windows.ps1 +101 -1
- package/.clawdbot/README.md +105 -105
- package/LICENSE +190 -190
- package/RELEASE_NOTES.md +29 -0
- package/WINDOWS-SETUP.md +208 -208
- package/bin/agent-vibes +39 -39
- package/bin/mcp-server.js +121 -121
- package/bin/mcp-server.sh +6 -19
- package/bin/resolve-utterance.js +137 -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/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/pyproject.toml +52 -52
- package/mcp-server/requirements.txt +2 -2
- package/mcp-server/server.py +280 -99
- package/mcp-server/test_mcp_correctness.py +486 -0
- package/mcp-server/test_server.py +395 -395
- package/mcp-server/test_windows_script_parity.py +341 -336
- package/package.json +3 -2
- package/setup-windows.ps1 +867 -815
- package/src/cli/list-personalities.js +110 -110
- package/src/cli/list-voices.js +114 -114
- package/src/commands/bmad-voices.js +394 -394
- package/src/console/app.js +13 -2
- package/src/console/brand-colors.js +13 -13
- package/src/console/constants/personalities.js +44 -44
- package/src/console/tabs/agents-tab.js +82 -9
- package/src/console/tabs/help-tab.js +314 -314
- package/src/console/tabs/music-tab.js +7 -1
- package/src/console/tabs/readme-tab.js +272 -272
- package/src/console/tabs/settings-tab.js +29 -1
- package/src/console/tabs/setup-tab.js +4 -4
- package/src/console/widgets/destroy-list.js +25 -25
- package/src/console/widgets/notice.js +55 -55
- 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/language-screen.js +31 -31
- package/src/installer/music-file-input.js +304 -304
- package/src/installer.js +205 -15
- package/src/services/config-service.js +264 -264
- package/src/services/language-service.js +47 -47
- package/src/services/llm-provider-service.js +7 -7
- package/src/services/utterance-loader.js +280 -0
- package/src/services/utterance-resolver.js +526 -0
- package/src/utils/audio-format-validator.js +277 -277
- package/src/utils/dependency-checker.js +469 -469
- package/src/utils/file-ownership-verifier.js +358 -358
- package/src/utils/music-file-validator.js +285 -285
- package/src/utils/preview-list-prompt.js +144 -144
- package/src/utils/secure-music-storage.js +412 -412
- package/templates/agentvibes-receiver.sh +74 -12
- package/voice-assignments.json +8245 -0
- package/.agentvibes/install-manifest.json +0 -342
- package/.claude/config/background-music-position.txt +0 -27
- package/.claude/config/background-music-volume.txt +0 -1
- package/.claude/config/background-music.cfg +0 -1
- package/.claude/config/background-music.txt +0 -1
- package/.claude/config/language.txt +0 -1
- package/.claude/config/personality.txt +0 -1
- package/.claude/config/tts-speech-rate.txt +0 -1
- package/.claude/config/tts-verbosity.txt +0 -1
- package/.claude/hooks-windows/audio-cache-utils.ps1.user.bak +0 -119
- package/.claude/hooks-windows/soprano-gradio-synth.py.user.bak +0 -153
- package/.claude/piper-voices-dir.txt +0 -1
- package/.mcp.json +0 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20260704
|
|
@@ -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")
|
|
@@ -149,7 +149,8 @@ fi
|
|
|
149
149
|
|
|
150
150
|
SOX_EFFECTS=""
|
|
151
151
|
BG_FILE=""
|
|
152
|
-
|
|
152
|
+
# TODO(AVI-S8.6): generate this constant from the shared JSON source of truth.
|
|
153
|
+
BG_VOLUME="0.20"
|
|
153
154
|
|
|
154
155
|
# Use CLAUDE_PROJECT_DIR (injected via --project-dir by play-tts.sh) so the
|
|
155
156
|
# agent-name / default row is read from the user's project, not the package.
|
|
@@ -280,6 +281,76 @@ case "${PROVIDER:-}" in
|
|
|
280
281
|
*) PROVIDER="piper" ;;
|
|
281
282
|
esac
|
|
282
283
|
|
|
284
|
+
# ---------------------------------------------------------------------------
|
|
285
|
+
# Resolve the project-level .claude dir the way the local managers do:
|
|
286
|
+
# CLAUDE_PROJECT_DIR (the real user project, injected via --project-dir by
|
|
287
|
+
# play-tts.sh) wins; otherwise fall back to the package PROJECT_ROOT.
|
|
288
|
+
# ---------------------------------------------------------------------------
|
|
289
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" && -d "$CLAUDE_PROJECT_DIR/.claude" ]]; then
|
|
290
|
+
_RESOLVE_PROJECT_DIR="$CLAUDE_PROJECT_DIR"
|
|
291
|
+
else
|
|
292
|
+
_RESOLVE_PROJECT_DIR="$PROJECT_ROOT"
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
# ---------------------------------------------------------------------------
|
|
296
|
+
# Resolve MUTE to forward (map §C 'mute never forwarded on SSH', R17).
|
|
297
|
+
# Mirrors play-tts.sh's 3-level file check exactly:
|
|
298
|
+
# project-unmute (overrides global) > project-mute > global-mute.
|
|
299
|
+
# The receiver is authoritative and applies a SAFETY-OR:
|
|
300
|
+
# effective_mute = receiver_local_mute OR forwarded_mute
|
|
301
|
+
# so if EITHER side is muted, playback stays silent. We forward this as a
|
|
302
|
+
# STRING ("true"/"false") — the receiver does a plain string compare, and it
|
|
303
|
+
# keeps the JSON boolean out of python's "True"/"False" capitalization.
|
|
304
|
+
# ---------------------------------------------------------------------------
|
|
305
|
+
_GLOBAL_MUTE_FILE="$HOME/.agentvibes-muted"
|
|
306
|
+
_PROJECT_MUTE_FILE="$_RESOLVE_PROJECT_DIR/.claude/agentvibes-muted"
|
|
307
|
+
_PROJECT_UNMUTE_FILE="$_RESOLVE_PROJECT_DIR/.claude/agentvibes-unmuted"
|
|
308
|
+
MUTE="false"
|
|
309
|
+
if [[ -f "$_PROJECT_UNMUTE_FILE" ]]; then
|
|
310
|
+
MUTE="false" # project explicitly unmuted overrides global
|
|
311
|
+
elif [[ -f "$_PROJECT_MUTE_FILE" ]]; then
|
|
312
|
+
MUTE="true"
|
|
313
|
+
elif [[ -f "$_GLOBAL_MUTE_FILE" ]]; then
|
|
314
|
+
MUTE="true"
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
# ---------------------------------------------------------------------------
|
|
318
|
+
# Resolve LANGUAGE to forward (map §D 'language | Forward on SSH', R18).
|
|
319
|
+
# Precedence mirrors the local chain (learn-manager.sh / language-manager.sh):
|
|
320
|
+
# learning-mode target (tts-target-language.txt when tts-learn-mode.txt=="ON")
|
|
321
|
+
# > tts-language.txt.
|
|
322
|
+
# Forwarded as a DEFAULT only — the receiver stays authoritative and its own
|
|
323
|
+
# language config (if any) wins. language-manager.sh stores tts-language.txt at
|
|
324
|
+
# the .claude/ root; the resolver contract tests use the .claude/config/ subdir,
|
|
325
|
+
# so we accept both. Project scope wins over the global home fallback.
|
|
326
|
+
# ---------------------------------------------------------------------------
|
|
327
|
+
LANGUAGE=""
|
|
328
|
+
for _cdir in "$_RESOLVE_PROJECT_DIR/.claude" "$HOME/.claude"; do
|
|
329
|
+
# Priority 1: learning-mode target language (only when learn mode is ON).
|
|
330
|
+
if [[ -z "$LANGUAGE" && -f "$_cdir/tts-learn-mode.txt" ]]; then
|
|
331
|
+
if [[ "$(cat "$_cdir/tts-learn-mode.txt" 2>/dev/null || true)" == "ON" ]]; then
|
|
332
|
+
[[ -f "$_cdir/tts-target-language.txt" ]] && \
|
|
333
|
+
LANGUAGE=$(cat "$_cdir/tts-target-language.txt" 2>/dev/null || true)
|
|
334
|
+
fi
|
|
335
|
+
fi
|
|
336
|
+
# Priority 2: tts-language.txt (root or config/ subdir).
|
|
337
|
+
if [[ -z "$LANGUAGE" && -f "$_cdir/tts-language.txt" ]]; then
|
|
338
|
+
LANGUAGE=$(cat "$_cdir/tts-language.txt" 2>/dev/null || true)
|
|
339
|
+
fi
|
|
340
|
+
if [[ -z "$LANGUAGE" && -f "$_cdir/config/tts-language.txt" ]]; then
|
|
341
|
+
LANGUAGE=$(cat "$_cdir/config/tts-language.txt" 2>/dev/null || true)
|
|
342
|
+
fi
|
|
343
|
+
[[ -n "$LANGUAGE" ]] && break
|
|
344
|
+
done
|
|
345
|
+
# Trim surrounding whitespace
|
|
346
|
+
LANGUAGE="${LANGUAGE#"${LANGUAGE%%[![:space:]]*}"}"
|
|
347
|
+
LANGUAGE="${LANGUAGE%"${LANGUAGE##*[![:space:]]}"}"
|
|
348
|
+
# SECURITY: only forward a plain language name (letters, spaces, hyphen); jq
|
|
349
|
+
# --arg escapes anyway, but reject odd values rather than propagate them.
|
|
350
|
+
if [[ -n "$LANGUAGE" ]] && [[ ! "$LANGUAGE" =~ ^[a-zA-Z][a-zA-Z\ -]*$ ]]; then
|
|
351
|
+
LANGUAGE=""
|
|
352
|
+
fi
|
|
353
|
+
|
|
283
354
|
# ---------------------------------------------------------------------------
|
|
284
355
|
# Build JSON payload
|
|
285
356
|
# ---------------------------------------------------------------------------
|
|
@@ -298,15 +369,17 @@ build_json_payload() {
|
|
|
298
369
|
--arg speed "$SPEED" \
|
|
299
370
|
--arg provider "$PROVIDER" \
|
|
300
371
|
--arg llm "$LLM_NAME" \
|
|
301
|
-
|
|
372
|
+
--arg mute "$MUTE" \
|
|
373
|
+
--arg language "$LANGUAGE" \
|
|
374
|
+
'{text: $text, voice: $voice, effects: $effects, music: $music, volume: $volume, project: $project, pretext: $pretext, speed: $speed, provider: $provider, llm: $llm, mute: $mute, language: $language}'
|
|
302
375
|
else
|
|
303
376
|
# Manual JSON — escape backslashes, quotes, control chars
|
|
304
377
|
local escaped_text
|
|
305
378
|
escaped_text=$(printf '%s' "$TEXT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' ' | sed 's/\r//g')
|
|
306
379
|
local escaped_pretext
|
|
307
380
|
escaped_pretext=$(printf '%s' "$PRETEXT" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
308
|
-
printf '{"text":"%s","voice":"%s","effects":"%s","music":"%s","volume":"%s","project":"%s","pretext":"%s","speed":"%s","provider":"%s","llm":"%s"}' \
|
|
309
|
-
"$escaped_text" "$VOICE" "$SOX_EFFECTS" "$BG_FILE" "$BG_VOLUME" "$PROJECT_NAME" "$escaped_pretext" "$SPEED" "$PROVIDER" "$LLM_NAME"
|
|
381
|
+
printf '{"text":"%s","voice":"%s","effects":"%s","music":"%s","volume":"%s","project":"%s","pretext":"%s","speed":"%s","provider":"%s","llm":"%s","mute":"%s","language":"%s"}' \
|
|
382
|
+
"$escaped_text" "$VOICE" "$SOX_EFFECTS" "$BG_FILE" "$BG_VOLUME" "$PROJECT_NAME" "$escaped_pretext" "$SPEED" "$PROVIDER" "$LLM_NAME" "$MUTE" "$LANGUAGE"
|
|
310
383
|
fi
|
|
311
384
|
}
|
|
312
385
|
|
|
@@ -338,7 +411,7 @@ SSH_ARGS=()
|
|
|
338
411
|
[[ -n "$SSH_KEY" && -f "$SSH_KEY" ]] && SSH_ARGS+=(-i "$SSH_KEY")
|
|
339
412
|
# Only pass -p for an explicit, non-default port. Port 22 is ssh's universal
|
|
340
413
|
# 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
|
|
414
|
+
# real port of an ~/.ssh/config Host alias (e.g. my-laptop -> Port 2222),
|
|
342
415
|
# silently delivering the payload to the wrong port. Empty or "22" means
|
|
343
416
|
# "use ssh / ~/.ssh/config defaults" -- never force it.
|
|
344
417
|
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
|
|
|
@@ -320,6 +390,17 @@ PYEOF
|
|
|
320
390
|
fi
|
|
321
391
|
fi
|
|
322
392
|
|
|
393
|
+
# Adopt the resolver's engine for LOCAL playback (AVI-S8.5 Stage 2): this cures
|
|
394
|
+
# the kokoro/piper voice→engine coupling (R1) and normalizes engine aliases,
|
|
395
|
+
# replacing the detect_voice_provider heuristic below. Transports are left
|
|
396
|
+
# untouched — the plan forwards the voice and the receiver picks its own engine.
|
|
397
|
+
if [[ -n "$PLAN_OK" ]]; then
|
|
398
|
+
case "$ACTIVE_PROVIDER" in
|
|
399
|
+
ssh-remote|agentvibes-receiver|termux-ssh) : ;; # transport — keep it
|
|
400
|
+
*) [[ -n "${AV_ENGINE:-}" ]] && ACTIVE_PROVIDER="$AV_ENGINE" ;;
|
|
401
|
+
esac
|
|
402
|
+
fi
|
|
403
|
+
|
|
323
404
|
# Show GitHub star reminder (once per day)
|
|
324
405
|
bash "$SCRIPT_DIR/github-star-reminder.sh" 2>/dev/null || true
|
|
325
406
|
|
|
@@ -338,10 +419,10 @@ detect_voice_provider() {
|
|
|
338
419
|
fi
|
|
339
420
|
}
|
|
340
421
|
|
|
341
|
-
#
|
|
342
|
-
#
|
|
343
|
-
#
|
|
344
|
-
if [[ -n "$VOICE_OVERRIDE" ]]; then
|
|
422
|
+
# Legacy mixed-provider heuristic — ONLY on the fallback path (no resolver plan).
|
|
423
|
+
# When PLAN_OK is set, the resolver already coupled voice→engine correctly above
|
|
424
|
+
# (including kokoro, which this heuristic misses — the R1 silence bug), so skip it.
|
|
425
|
+
if [[ -z "$PLAN_OK" && -n "$VOICE_OVERRIDE" ]]; then
|
|
345
426
|
case "$ACTIVE_PROVIDER" in
|
|
346
427
|
ssh-remote|agentvibes-receiver|termux-ssh)
|
|
347
428
|
# Transport provider — don't override, voice info is forwarded to receiver
|
|
@@ -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
|