Landing blocker for #61 and #63: the deployment checkout is 12 commits behind and `merge --ff-only` refuses while tracked paths are dirty. Discard is not available here (workspace policy: no `checkout`/`stash` on shared state), so the dirt is committed. - `agent/APPEND_SYSTEM.md`: the point-of-use / one-work-log / PR-link doctrine, already byte-identical to `origin/main` (`cmp` against the trunk blob: no difference). No new prose — the commit exists so the trunk merge can proceed. - `agent/config.yml` and `agent/managed-skills/**`: the machine-live tier the heartbeat owns (AGENTS.md § Live-sync tiers). `ALLOW_SECRETS=1` was used, deliberately and after auditing the staged diff by hand: the only credential-shaped strings in it are two copies of `AKIA0000000000000EXAMPLE` — the inert all-zero AWS example id — and they appear inside the two skill pages that teach that exact placeholder shape. This checkout is still running the pre-#61 hook; the merged gate (value-scoped placeholder filter, `71601f7`) exempts this value, so the refusal dies with this landing. No `--no-verify` was used: the gate ran and its verdict is recorded here. Refs #61
4.5 KiB
name, description
| name | description |
|---|---|
| frank-tts-repoint-voice-consumers | Rotate/rename a frank-tts voice across all consumers (repo DEFAULT_VOICE, Open Notebook speaker profiles over REST, docs) after packs change. |
When
frank-tts voice packs on /home/dank/voices (Dankclaw) changed — packs added, removed, or renamed — and old voice names (e.g. narrator1/2/3) no longer resolve. Consumers still sending a dead name now 400 with unknown voice 'x'; available voices: .... This is a maintenance sweep, NOT a bug; confirm with the operator whether the change is intentional before "fixing".
Facts that make this non-obvious
- frank-tts (:7772) resolves a voice as a directory:
<name>/reference.wavmust exist.modelfield is ignored; the voice comes solely fromvoice/voice_id. - Live census:
curl -s http://127.0.0.1:7772/v1/audio/voices(and/healthreturns the same list). This is the truth; the vault docs may be stale. - Two consumers carry voice names, in different stores:
- repo:
scripts/frank-tts.envDEFAULT_VOICE=— answers a voice-less request (the omp harness's normal shape). Deploy via git. - app-side: Open Notebook speaker profiles (SurrealDB) and OpenWebUI
audio.ttsvoice — NOT repo files.
- repo:
Step 1 — repo: DEFAULT_VOICE
Edit scripts/frank-tts.env DEFAULT_VOICE=<new real voice>. Trunk-based: this worktree can't git checkout -b (shared-workspace policy); commit on HEAD and git push origin HEAD:refs/heads/<branch>, open PR vs main. No bats test pins the DEFAULT_VOICE value (the tts bats use narrator1 as a self-contained fixture that builds its own reference.wav — mechanism test, leave it).
Step 2 — Open Notebook speaker profiles (REST, host-side, takes effect immediately)
Voice ID is free text passed to the TTS request untouched (no enum/whitelist). The active fleet profile is dank-duo (episode profile dank-daily → speaker_config). PW is the host OPEN_NOTEBOOK_PASSWORD.
ssh dankclaw bash -s <<'REMOTE'
PW=$(grep -m1 '^OPEN_NOTEBOOK_PASSWORD=' /home/dank/Containers/open-notebook/env | cut -d= -f2- | tr -d '"')
cfg=$(mktemp); chmod 600 "$cfg"; printf 'header = "Authorization: Bearer %s"\n' "$PW" > "$cfg"
base=http://127.0.0.1:5056/api/speaker-profiles
# census: name -> speaker/voice_id
curl -s -K "$cfg" "$base" | jq -c '.[]|{name,speakers:[.speakers[]|{name,voice_id}]}'
# repoint by NAME (GET-by-name returns full record incl id; PUT whole transformed object to /{id})
curl -s -K "$cfg" "$base/dank-duo" > /tmp/s.json
id=$(jq -r .id /tmp/s.json)
jq '(.speakers[].voice_id) |= ({"OLD1":"NEW1","OLD2":"NEW2"}[.] // .)' /tmp/s.json > /tmp/s2.json
curl -s -K "$cfg" -X PUT -H 'Content-Type: application/json' -d @/tmp/s2.json "$base/$id" -w ' HTTP %{http_code}\n'
curl -s -K "$cfg" "$base/dank-duo" | jq -c '[.speakers[]|{name,voice_id}]' # verify
rm -f "$cfg" /tmp/s.json /tmp/s2.json
REMOTE
- A duo has only 2 speakers — an extra voice is available-but-unassigned, not missing.
- Upstream's seeded profiles (
business_panel,solo_expert,tech_experts) use OpenAI names +voice_model:null; they aren't wired to frank-tts — leave them.
Step 3 — OpenWebUI (check before touching)
curl -s http://127.0.0.1:<webui-port>/api/config | jq '.audio.tts' (port from podman ps). If engine/openai.* are null, the active path is browser-kokoro (synthesizes in-browser) and the stored voice is dormant, not broken — flag, don't edit a live UI unasked.
Step 4 — docs (point of use, one owner)
Update the census + speaker-mapping lines in serverAppConfigs-docs/Dankclaw/Frank TTS.md and Open Notebook.md to the live packs. Leave DATED measurement logs (e.g. a 2026-10-19 /health probe) as history. Do NOT chase scripts/voice-packs.env / Voice Packs.md narrator builder defs — ask the operator whether those packs are being rebuilt or retired.
Verify (behavior, not config text)
Synthesize each new voice and expect 200 + distinct bytes:
curl -s -o /dev/null -w '%{http_code} %{size_download}B\n' -X POST http://127.0.0.1:7772/v1/audio/speech \
-H 'Content-Type: application/json' -d '{"model":"voxcpm2","input":"test","voice":"<name>"}'
tests/run.sh EXIT=0 before commit. DEFAULT_VOICE only lands after merge + ./scripts/deploy.sh dankclaw --service frank-tts; run that from the dev main checkout (/home/blank/Projects/serverAppConfig), NOT the agent worktree (deploy.sh refuses a non-main branch and pushes main forward — it must run on the dev box, not the target).