live: heartbeat sync 2026-10-19T14:21:41-07:00 (Refs #11)

This commit is contained in:
Frank committed 2026-10-19 14:21:41 -07:00
1 parent 36ff3742b4
commit 6e3e5594eb
24 files changed
+657 -102

No files matched your search

@@ -0,0 +1,60 @@
---
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.