61 lines
3.6 KiB
Markdown
61 lines
3.6 KiB
Markdown
---
|
|
name: dankvoice-silence-triage
|
|
description: "Triage silent or late dankvoice auto-speak on the fleet: arming key, /say grammar, endpoint/sink, event granularity, humanizer latency, plus the black-box bats driver."
|
|
---
|
|
|
|
# Triage "dankvoice is silent" on the fleet
|
|
|
|
Symptom that recurs: auto-speak only speaks when `/say <text>` is typed
|
|
explicitly. Work the gates in this order; each has an objective check.
|
|
|
|
1. **Armed?** `cat ~/.omp/cache/dankvoice.json | jq .auto` — must be `true`
|
|
(or `DANKVOICE_AUTO=1`). Arming is read at **startup only** and `/say on`
|
|
never persists, so a session started before the key was added is silent even
|
|
though fresh sessions work. `/say start` is *not* a subcommand: unknown
|
|
arguments are spoken verbatim (it read out the word "start"). `/say on` is
|
|
the verb; `/say status` must print `auto-speak on`.
|
|
2. **Endpoint + sink?** `/say status` must show a real endpoint (not
|
|
`NO ENDPOINT`) and the saved sink must exist:
|
|
`pw-dump | grep -o 'alsa_output\.[^"]*' | sort -u`.
|
|
3. **Did it synthesize?** `pgrep -a pw-play` during a turn: one process per
|
|
segment, `--target <sink>`, wav under `/tmp/dankvoice-*/say-<epochms>.wav`.
|
|
One temp **directory per utterance chain** — count dirs to count utterances,
|
|
pids to count segments.
|
|
4. **Event granularity (the structural cause).** `agent_end` fires once per
|
|
*prompt* when the loop settles and is skipped when the harness maps a
|
|
non-terminal settle (`willContinue`) — long runs with background jobs or
|
|
subagents therefore narrate nothing. Mid-run narration needs `turn_end`
|
|
(fires per assistant message + tool results). Verify delivery, don't infer
|
|
it: skill `omp-extension-event-probe`.
|
|
5. **Latency is not silence.** Narration passes through the humanizer
|
|
(frank-zealot) before synthesis, so an utterance lands several seconds after
|
|
its turn; under endpoint load that stretches to tens of seconds.
|
|
`/say humanize off` = mechanical voice, near-instant. Judge cadence from
|
|
probe-log timestamps, never from when audio starts.
|
|
|
|
## Design rules the plugin now encodes (dank/dankvoice#2→#3)
|
|
|
|
- Narrate `turn_end` per turn **plus** `agent_end` for the settled answer;
|
|
dedupe identical text per run (reset on `agent_start`) so the answer speaks once.
|
|
- A turn landing while the player is alive is **dropped** — no queue, no
|
|
interrupting; a tool loop would cut every sentence off after a word.
|
|
Busy test: `state.child !== undefined && state.child.exitCode === null`.
|
|
- Tool-only turns (no prose, no narration-flagged thinking) stay silent.
|
|
- `print`/`json` modes stay silent, so benches never chatter.
|
|
- Settings writes must **round-trip the file** (`{...onDisk, ...owned keys}`).
|
|
Rebuilding from owned keys deleted `"auto"` on any menu choice and silently
|
|
un-armed every later session — the classic cause of "it worked yesterday".
|
|
|
|
## Black-box test harness (repo `dank/dankvoice`)
|
|
|
|
`tests/behavior.bats` drives the built artifact through an in-process driver:
|
|
`const say = DV_EXT(ctx, cfg)[0]`, handlers via `const events = new Map();
|
|
const pi = { on: (n,h)=>events.set(n,h) }`, then `events.get("turn_end")?.({
|
|
turnIndex:0, message:{role:"assistant",content:[{type:"text",text:"…"}]},
|
|
toolResults:[]}, ctx)`. Assert on `CALL`/`PLAYS`/`NOTIFY` lines and
|
|
`SETTINGS <file contents>`; stubs are `DV_PLAYER_CMD` (logs voice+input, honours
|
|
`DV_HOLD`) and `DV_REWRITE_URL` (404 → falls back to mechanical text). `setup()`
|
|
runs `bun run build` per test → serialize suites on the shared `dist/`.
|
|
`tests/packaging.bats` requires `dist/` **committed** (compares to HEAD), so
|
|
build → commit → the drift pin goes green.
|