Long answers now speak as sequential, sentence-sized synthesis requests
instead of one monolithic POST /v1/audio/speech that the agent_end
handler awaited.
Two bugs died here:
The 30 s handler timeout.agent_end awaited one whole-answer
synthesis + spawn; the harness caps extension handlers at 30000 ms, so any
slow answer produced Extension "..." error: handler timed out after 30000ms while the audio (still) played late or not at all.
The stall itself. One frank-tts request for an entire answer is the
request most likely to outlive any timeout budget.
How
Instead of hand-rolling a chunker, the utterance-segmentation state machine
is ported from the built-in speak-aloud vocalizer (minified class fHe
helpers in the agent bundle's dist/cli.js) — the known-working stock
implementation:
strong boundaries are [.!?…]runs followed by whitespace — !!!!!
and ... stay one boundary, never a pile of cuts;
boundaries after e.g./etc./Mr. or inside inline code don't count; 3.5 and 1,5 survive (a boundary needs trailing whitespace);
line starts classified: tables and fenced code swallowed whole (never
read aloud), heading/bullet markers vanish, ordered markers speak their
number ("1, …"), quote markers strip;
run-ons break at commas/word wraps before 280 chars.
Playback is strictly sequential: segment N+1 is synthesized only after
segment N's player process exits — audio can't stack. agent_end and /say <text> fire-and-forget the chain (it legitimately outlives the
handler timeout), and a run token retires superseded chains: /say stop,
disarming, or a newer utterance kills the current player and skips the
pending segments. DANKVOICE_MAX is now a per-answer budget over whole
leading segments; the hand-rolled stripForSpeech truncator is deleted.
No LLM "humanization" rewrite is performed — the bundle's speech.enhanced
rewrites with the tiny/smol model; dankvoice speaks mechanically cleaned
text and lets the cloned frank-tts voices carry the expression.
Verify
bats tests/dankvoice.bats — 17/17, incl. new scenarios: three-sentence
utterance → exactly 3 sequential requests/plays; Model 3.5 … Version 1,5
→ exactly 2 (no decimal splits); budget 60 → only the leading segment;
markdown fixture's fenced code and URL never reach the endpoint.
## What
Long answers now speak as **sequential, sentence-sized synthesis requests**
instead of one monolithic `POST /v1/audio/speech` that the `agent_end`
handler awaited.
Two bugs died here:
1. **The 30 s handler timeout.** `agent_end` awaited one whole-answer
synthesis + spawn; the harness caps extension handlers at 30000 ms, so any
slow answer produced `Extension "..." error: handler timed out after
30000ms` while the audio (still) played late or not at all.
2. **The stall itself.** One frank-tts request for an entire answer is the
request most likely to outlive any timeout budget.
## How
Instead of hand-rolling a chunker, the utterance-segmentation state machine
is **ported from the built-in speak-aloud vocalizer** (minified class `fHe`
+ helpers in the agent bundle's `dist/cli.js`) — the known-working stock
implementation:
* strong boundaries are `[.!?…]` **runs** followed by whitespace — `!!!!!`
and `...` stay one boundary, never a pile of cuts;
* boundaries after `e.g.`/`etc.`/`Mr.` or inside inline code don't count;
`3.5` and `1,5` survive (a boundary needs trailing whitespace);
* line starts classified: tables and fenced code **swallowed whole** (never
read aloud), heading/bullet markers vanish, ordered markers speak their
number ("1, …"), quote markers strip;
* per-segment cleanup: links → label, URLs → host, deep paths → basename,
emphasis markup dies;
* run-ons break at commas/word wraps before 280 chars.
Playback is strictly sequential: segment N+1 is synthesized only **after
segment N's player process exits** — audio can't stack. `agent_end` and
`/say <text>` fire-and-forget the chain (it legitimately outlives the
handler timeout), and a run token retires superseded chains: `/say stop`,
disarming, or a newer utterance kills the current player and skips the
pending segments. `DANKVOICE_MAX` is now a per-answer budget over whole
leading segments; the hand-rolled `stripForSpeech` truncator is deleted.
No LLM "humanization" rewrite is performed — the bundle's `speech.enhanced`
rewrites with the tiny/smol model; dankvoice speaks mechanically cleaned
text and lets the cloned frank-tts voices carry the expression.
## Verify
* `bats tests/dankvoice.bats` — 17/17, incl. new scenarios: three-sentence
utterance → exactly 3 sequential requests/plays; `Model 3.5 … Version 1,5`
→ exactly 2 (no decimal splits); budget 60 → only the leading segment;
markdown fixture's fenced code and URL never reach the endpoint.
* Full suite `bats tests/` — 53 tests, 0 failures.
* Docs: `omp-docs/core/dankvoice.md` § Segmentation + log entry.
---
cwd: /home/blank/.omp/wt/wt-20261018-204442-7c8a99d · session: 01a1525d-0a05-7615-ba60-58e9b20495d1
agent_end awaited one whole-answer synthesis; long answers hit the
harness 30s extension-handler timeout (handler timed out after 30000ms)
and the single frank-tts request was the stall. Port the built-in
speak-aloud vocalizer's utterance segmenter (fHe in dist/cli.js) so
segmentation matches the known-working stock path: [.!?…]-run
boundaries, abbreviation/decimal/inline-code guards, fenced code and
tables swallowed, markup cleaned. Play segment N to player exit before
synthesizing N+1 (audio never stacks); fire-and-forget the chain so
utterances outlive the handler timeout; a run token retires superseded
chains. DANKVOICE_MAX becomes a per-answer budget of whole segments;
stripForSpeech is gone. Refs #23 follow-up.
dank
merged commit 0075ca3bbf into main2026-10-19 01:25:04 -07:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
Long answers now speak as sequential, sentence-sized synthesis requests
instead of one monolithic
POST /v1/audio/speechthat theagent_endhandler awaited.
Two bugs died here:
agent_endawaited one whole-answersynthesis + spawn; the harness caps extension handlers at 30000 ms, so any
slow answer produced
Extension "..." error: handler timed out after 30000mswhile the audio (still) played late or not at all.request most likely to outlive any timeout budget.
How
Instead of hand-rolling a chunker, the utterance-segmentation state machine
is ported from the built-in speak-aloud vocalizer (minified class
fHedist/cli.js) — the known-working stockimplementation:
[.!?…]runs followed by whitespace —!!!!!and
...stay one boundary, never a pile of cuts;e.g./etc./Mr.or inside inline code don't count;3.5and1,5survive (a boundary needs trailing whitespace);read aloud), heading/bullet markers vanish, ordered markers speak their
number ("1, …"), quote markers strip;
emphasis markup dies;
Playback is strictly sequential: segment N+1 is synthesized only after
segment N's player process exits — audio can't stack.
agent_endand/say <text>fire-and-forget the chain (it legitimately outlives thehandler timeout), and a run token retires superseded chains:
/say stop,disarming, or a newer utterance kills the current player and skips the
pending segments.
DANKVOICE_MAXis now a per-answer budget over wholeleading segments; the hand-rolled
stripForSpeechtruncator is deleted.No LLM "humanization" rewrite is performed — the bundle's
speech.enhancedrewrites with the tiny/smol model; dankvoice speaks mechanically cleaned
text and lets the cloned frank-tts voices carry the expression.
Verify
bats tests/dankvoice.bats— 17/17, incl. new scenarios: three-sentenceutterance → exactly 3 sequential requests/plays;
Model 3.5 … Version 1,5→ exactly 2 (no decimal splits); budget 60 → only the leading segment;
markdown fixture's fenced code and URL never reach the endpoint.
bats tests/— 53 tests, 0 failures.omp-docs/core/dankvoice.md§ Segmentation + log entry.cwd: /home/blank/.omp/wt/wt-20261018-204442-7c8a99d · session: 01a1525d-0a05-7615-ba60-58e9b20495d1