- llm.py: log each Ollama call's caller + total/load/prompt/gen durations
so a slow voice turn is attributable to a specific internal call
(router/enrichment/digest/main); a RELOAD marker flags cold reloads.
- voice.ts: track in-flight Opus captures and abort them on session
destroy(); drop any utterance that finishes after the user left, so no
trailing post-leave VAD turns are reported.
- userbot.ts: show the speaker's Discord user ID on each transcript line
(answered and dropped) so it's clear whose audio produced the turn.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The transcript channel only showed STT and LLM seconds. Add wall-clock
start/end times and durations for listening, LLM and TTS so it's obvious
what takes long; STT surfaces as the gap between listening end and LLM start.
- bridge: emit llm_start_ms/llm_end_ms on meta and tts_*_ms on the end event
- bot: capture the listening window, assemble full timing after the stream,
and render a per-stage breakdown in the transcript message
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The transcript channel only showed successful transcripts, so dropped utterances
(the 47/50 misses) were invisible. Now every captured utterance is mirrored with
its outcome and per-stage timing:
- too-short blip (<300ms), VAD "음성 아님(VAD 차단)", "인식 실패", "답변 없음", or "ok"
- transcript + reply (or "(무응답)")
- ⏱️ stt/llm seconds
The bridge meta now carries note + stt_sec + think_sec; voice.ts fires onTurn for
every turn (not only non-empty transcripts) and for the too-short drop; userbot
formats the diagnostic line.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Set DISCORD_TRANSCRIPT_CHANNEL_ID to a text channel and the userbot posts
"들은 말: <transcript> / 답변: <reply or (무응답)>" for every voice turn, so you can
verify what the bot understood even when it doesn't answer aloud.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The "couple broadcast to voice" feature only wired auto-start into the
normal-bot join handler. Userbot mode (the only mode that can actually Go
Live) was added later with Go-Live deferred to a "stage 2" that never
landed, so the running deployment had no path to start a broadcast.
Extract the coupling into a shared bot/src/broadcast.ts (auto-start on
join, report live state to the brain each turn, voice toggle) and wire it
into both index.ts and userbot.ts so both modes behave identically. Add a
behavioural test for the auto-start + toggle contract.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The selfbot library's native voice connection cannot complete Discord's current
DAVE/v8 voice handshake and times out (15s). Root cause: DAVE E2EE (mandated
~March 2026). The bundled @discordjs/voice 0.18.0 also lacked DAVE.
Fix: upgrade @discordjs/voice to 0.19.2 + add @snazzah/davey (the DAVE protocol
lib) and drive the selfbot client through @discordjs/voice's joinVoiceChannel /
receiver via the client's voiceAdapterCreator — i.e. reuse the normal-bot
VoiceSession with a user-account channel. Verified: the userbot now reaches a
READY voice connection (host test) and joins the channel in-container with no
timeout. This also fixes the normal-bot voice path under DAVE. Drops the
hand-rolled selfbot-native receiver (audio.ts).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A user account is the only kind Discord lets Go Live, so add a userbot run mode:
when DISCORD_BOT_TOKEN is absent but DISCORD_SELFBOT_TOKEN is set, the app runs
as a userbot instead of the normal slash-command bot.
Stage 1 (conversation): log in with discord.js-selfbot-v13, auto-join
DISCORD_VOICE_CHANNEL_ID on startup (or "!자비스 join" when unset), listen via the
selfbot VoiceReceiver (pcm), transcribe+reply through the bridge, and speak the
reply back with playAudio. Shared PCM/WAV helpers extracted to audio.ts.
run-bot.sh now starts in either userbot or normal mode. Go-Live broadcast +
broadcast-coupled routing land in stage 2 on the same voice connection.
ToS note: selfbots violate Discord ToS; burner account only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>