Commit Graph

474 Commits

Author SHA1 Message Date
Claude Worker claude2 dd6df1fa69 docs(ir): example IR + authoring gap note 2026-06-11 03:07:14 +02:00
Claude Worker claude2 7e690f5a20 feat(ir): step puzzle + scene fields 2026-06-10 22:57:14 +02:00
Claude Worker claude2 9e78dd7b6b chore: ignore .worktrees/ 2026-06-10 22:52:22 +02:00
Claude Worker claude2 a4838dccf0 docs: plan impl enigmes locales Freenove
Repo State / repo-state (push) Failing after 13m46s
2026-06-10 13:42:56 +02:00
Claude Worker claude2 7ee051827f docs: spec enigmes locales Freenove P1/P3
Repo State / repo-state (push) Failing after 1m7s
2026-06-10 13:34:39 +02:00
Claude Worker claude2 19bce1075a chore: bump ESP32_ZACUS (BOX-3 BSP fix)
Repo State / repo-state (push) Failing after 3m20s
2026-06-10 13:22:03 +02:00
Claude Worker claude2 e1a116d0f5 feat(hw): PLIP telephone KiCad project
Repo State / repo-state (push) Failing after 48s
Validate Zacus refactor / validate (pull_request) Successful in 11m10s
Repo State / repo-state (pull_request) Failing after 12m5s
Custom ESP32-WROOM-32E + Si3210 SLIC board replacing the ESP32-A1S
dev kit (final PLIP target, RJ9 handset). Generated via tools/
gen_si3210_lib.py (symbol + QFN-38 footprint), gen_schematic.py
(parses standard KiCad libs, 45 instances, 157 global labels on pin
ends) and gen_pcb.py (58x38mm outline, 41 footprints placed per the
plan, nets assigned, unrouted).

ERC: 0 violations. Schematic follows the spec GPIO map and the plan's
passive network. PCB placed not routed (Freerouting + DRC + gerbers
remain — see README). BOM in JLCPCB format; CP2102N/transformer/RJ9
LCSC parts marked TBD.

WARNINGS (README): the Si3210 -72V DC-DC stage is simplified and must
be checked against the Skyworks datasheet/AN35 before fab; line
protection is minimal; human review required before ordering.
2026-06-10 12:52:43 +02:00
Claude Worker claude2 8b9151851f chore: bump ESP32_ZACUS (BOX-3 on-screen phone)
Repo State / repo-state (push) Failing after 43s
Validate Zacus refactor / validate (pull_request) Failing after 14m28s
Repo State / repo-state (pull_request) Failing after 14m48s
Submodule points at the merged on-screen phone + touch keypad + hook
button (PR ESP32_ZACUS#6), flashed and verified on hardware.
2026-06-10 12:44:42 +02:00
Claude Worker claude2 f5328d9d74 chore: bump ESP32_ZACUS (virtual PLIP on BOX-3)
Repo State / repo-state (push) Failing after 43s
Repo State / repo-state (pull_request) Failing after 12m46s
Validate Zacus refactor / validate (pull_request) Successful in 13m39s
Submodule points at the merged phone-less PLIP annex
(PR ESP32_ZACUS#5): PLIP REST contract + speaker ring + BOOT hook
switch + /voice/hook reporting on the BOX-3.
2026-06-10 12:30:30 +02:00
Claude Worker claude2 db63da7d2e feat: sim log in console + TTS rate/emotion
Repo State / repo-state (pull_request) Failing after 43s
Repo State / repo-state (push) Failing after 38s
Validate Zacus refactor / validate (pull_request) Failing after 21m48s
- ConsolePane shows the ScenePlayer narration (simLog, green) under
  the validation entries, plus a current-scene badge in the header.
- voice-bridge accepts rate + emotion on /tts: rate (clamped 0.5..2.0)
  drives the F5 sampler speed; emotion is accepted and cache-keyed but
  not rendered yet (no F5 emotion control). Legacy cache keys are
  preserved when both fields are absent/default — the NPC pool
  generator contract is untouched.
- Bump ESP32_ZACUS: GET/POST /game/peers (PR ESP32_ZACUS#4) — PLIP
  provisioning by curl, live registration without reboot.

Verified: py_compile OK, tsc clean, vite build green, master firmware
build green.
2026-06-10 11:56:25 +02:00
Claude Worker claude2 34b7c3144a feat(atelier): scene player for the 3D stage sim
Repo State / repo-state (push) Failing after 11m12s
Repo State / repo-state (pull_request) Failing after 10m58s
Validate Zacus refactor / validate (pull_request) Successful in 11m42s
The Run button fed blocks_studio v2 YAML to the legacy gameplay engine,
which only parses old zacus_v3 scenarios. loadScenario now tries the v2
document first and plays it through a new ScenePlayer: « Zacus dit »
drives the NPC bubble + Web Speech (rate applied, emotion mapped to the
stage mood), timers wait, goto/branch/intent jump between scenes,
hardware actions are narrated into a capped simLog. Legacy YAML still
falls back to ZacusScenarioEngine. Visualization fidelity only — the
authoritative compiler remains blocks_to_runtime3.py.

Verified: tsc clean, vite build green.
2026-06-10 11:43:57 +02:00
Claude Worker claude2 089cea409e feat(atelier): Runtime 3 scene toolbox, shared
Repo State / repo-state (push) Failing after 49s
Repo State / repo-state (pull_request) Failing after 1m0s
Validate Zacus refactor / validate (pull_request) Successful in 11m31s
The Atelier exposed a legacy 12-block gameplay-config language whose
export the gateway could not compile (no blocks_studio_version) — and
no start/end blocks. It now speaks the same language as the zacus-hub
native Studio: the 38-block scene catalog (sceneStart/sceneEnd, npcSay
voice lines, HW/ESP-NOW/M5/PLIP, logic) lives once in
@zacus/shared/blockly; the hub copy is GENERATED via
tools/dev/sync_blockly_catalog.sh.

- Export/import rewritten to blocks_studio_version: 2 (flat nodes,
  next pointers, logicIf slots) via shared blocks-doc.ts — what
  POST /v1/flash compiles to Runtime 3 IR, and what the hub Studio
  reads/writes (full interop).
- « Zacus dit » gains voice (voice-bridge voice_ref), emotion and rate
  fields; the compiler forwards them as optional tts_say fields
  (omitted at defaults, backward compatible).
- Editor validation now mirrors compiler rules: missing sceneStart,
  duplicate scene ids, unknown goto targets, empty voice lines.

Verified: tsc clean, vite build green, v2->IR round-trip OK
(entry STEP_intro, tts_say{voice_ref,emotion,rate}).
2026-06-10 11:35:24 +02:00
Claude Worker claude2 102ee99729 chore: bump ESP32_ZACUS (scenario_mesh dedup)
Repo State / repo-state (push) Failing after 47s
Repo State / repo-state (pull_request) Failing after 33s
Validate Zacus refactor / validate (pull_request) Successful in 11m26s
Submodule now points at the merged scenario_mesh hoist (single copy
in lib/, EXTRA_COMPONENT_DIRS) + sdkconfig.old cleanup.
2026-06-10 10:52:37 +02:00
Claude Worker claude2 6ad17ab3ec feat(atelier): deploy button with board selector
Repo State / repo-state (pull_request) Failing after 10m11s
Repo State / repo-state (push) Failing after 11m11s
Validate Zacus refactor / validate (pull_request) Successful in 11m31s
DeployPanel + useDeploy hook: saves the current Blockly workspace YAML
to the gateway (PUT /v1/studio/scenario/atelier_live.yaml) then flashes
the selected board (POST /v1/flash/{board}). Boards without an IP that
the master relays over ESP-NOW (plip, box3, p7_coffre) are flagged in
the selector and routed through the master automatically; the panel
shows the FlashResult steps and relay fan-out. Gateway gains a
CORSMiddleware (ZACUS_HUB_CORS_ORIGINS, default *) so the browser
client can reach it — auth stays on the bearer token
(VITE_GATEWAY_TOKEN / VITE_GATEWAY_BASE_URL).

Verified: tsc --noEmit clean, vite build green, py_compile OK.
2026-06-10 10:41:01 +02:00
Claude Worker claude2 c1afa65c55 chore: bump ESP32_ZACUS (PLIP doc re-scope)
Repo State / repo-state (pull_request) Failing after 1m27s
Repo State / repo-state (push) Failing after 11m31s
Validate Zacus refactor / validate (pull_request) Successful in 12m18s
Submodule now points at the merged doc update marking PLIP as in
scope for the scenario hot-load receiver.
2026-06-10 10:26:44 +02:00
Claude Worker claude2 e7a974e1be feat(plip): scenario hot-load via ESP-NOW
PLIP was out of scope by design (Wi-Fi/HTTP-only); brought back in by
request. New src/scenario_now.{h,cpp}: owns the single esp_now recv
callback (no MSG_* demux needed — PLIP has no legacy ESP-NOW traffic),
reassembles scenario_mesh frames ({seq:u16 LE, total:u16 LE} + <=236 B
payload, <=64 KiB, 5 s sender-silence timeout) in a worker task, then
persists to LittleFS /scenario.json (temp-then-rename). Optional
consumer hook for a future Runtime 3 engine; stored-and-logged without
one. network_task calls scenario_now_init() once the station is up
(idempotent, repeated on reconnect).

Build green: pio run (devkit_es8388), RAM 14.8%, Flash 63.1%.
2026-06-10 10:24:01 +02:00
Claude Worker claude2 26969640a3 feat(hub): live game-master state + bump firmware submodule
Repo State / repo-state (push) Failing after 42s
Repo State / repo-state (pull_request) Failing after 14m5s
Validate Zacus refactor / validate (pull_request) Successful in 12m54s
Gateway (tools/zacus-gateway):
- /v1/state now aggregates live per-group progress from the hints engine
  /hints/sessions (the only backend exposing game state), with graceful
  fallback when the engine is offline or admin-protected. Flows through both
  the REST endpoint and the WS push (shared build_state). Documents
  HINTS_ADMIN_KEY.

App (apps/zacus-hub):
- GameMaster "Groupes en cours" section: active groups, hint/penalty counts,
  current puzzle, and a state_detail note. SessionSnapshot DTO + extended
  GameState.

Submodule:
- ESP32_ZACUS -> feat/idf-migration b2267f2 (ESP-NOW scenario hot-load
  receivers; three firmwares build green under ESP-IDF 5.4.4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 04:11:45 +02:00
electron 6788d9b249 chore: point ESP32_ZACUS submodule to Gitea
Repo State / repo-state (push) Failing after 3m35s
Validate Zacus refactor / validate (push) Failing after 14m30s
2026-06-03 16:41:30 +02:00
L'électron rare a73647783b docs(specs): hub app + firmware hot-load plan
Two new specs:

  - 2026-05-24-zacus-hub-app.md — architecture, sprints, auth,
    open decisions for the SwiftUI hub app + gateway.
  - 2026-05-24-firmware-scenario-hotload.md — firmware Phase 2
    plan for /game/scenario + ESP-NOW mesh relay (the master
    side has shipped on the submodule; the relay + peer
    receivers + scenario_engine_reload remain to do).

Submodule ESP32_ZACUS bumped to d220d94 on feat/idf-migration
(POST /game/scenario hot-load on master, scenario_server.c on
BOX-3, ota_server max_uri_handlers 8->16, sdkconfig.qemu).
2026-05-24 10:52:08 +02:00
L'électron rare 3babe2b3ab feat(apps): zacus-hub SwiftUI hub (macOS + iOS)
New multiplatform SwiftUI app under apps/zacus-hub/ generated via
XcodeGen (project.yml). Three modes share a HubSession actor that
talks to tools/zacus-gateway over Tailscale-only HTTP/WS.

Game-master: live state stream (WebSocket /v1/state/ws), backend
health badges, hint trigger, journal.

Companion: push-to-talk via AVAudioRecorder (M4A AAC 16 kHz),
fallback file picker, hint request, transcript via voice-bridge
proxy.

Studio: YAML editor with validate/save (existing /v1/studio
routes), plus a Scratch tab hosting a Blockly + zelos-renderer
workspace in WKWebView. 38 custom Zacus block types across 10
categories (Scènes, NPC, Audio, LCD, Hardware ESP32, ESP-NOW,
BOX-3, M5, PLIP, Logique), .sb3 export/import via JSZip (procedure
calls + embedded YAML for lossless round-trip), Flasher sheet that
calls /v1/flash/{board} with auto/hot/cold strategies and shows
per-board step results including the cold-flash idf.py command.

ATS exception domains cover the Tailscale CGNAT range plus the
electron-server / studio / macm1 hostnames so the WebView and
URLSession can hit plain HTTP on the tailnet without TLS.

Token stays in the Keychain (cc.saillant.zacus.hub) — never
committed to source.
2026-05-24 10:51:26 +02:00
L'électron rare 035e01bb41 feat(gateway): zacus-hub FastAPI proxy
New thin FastAPI aggregator under tools/zacus-gateway/ that sits
between the SwiftUI Hub app and the existing voice/hints/firmware
surfaces. Exposes:

  - GET  /v1/auth/ping, /v1/backends/health, /v1/state (+ WS)
  - POST /v1/gm/hint, /v1/companion/{hint,voice/intent,voice/
    transcribe,voice/tts}
  - GET/PUT/validate/compile /v1/studio/scenario/*
  - GET  /v1/flash/boards, POST /v1/flash/{board}

blocks_to_runtime3.py walks a blocks_studio_version: 2 YAML and
emits a Runtime 3 IR with the new condition action for logicIf
slots (body/else), keeping branching inside a single step.

boards.yaml registry declares master / box3 / plip / p7_coffre with
hot_endpoint, cold_data_dir, and espnow_relay_peers; the flash
endpoint compiles the IR then either POSTs to the board's HTTP
surface or stages it for cold-flash via idf.py.

flash_bridge.sh works around the docker bridge subnet collision
on electron-server by chaining gateway compile + scp + POST from a
host already on the WiFi LAN.

systemd-friendly: bearer auth via ZACUS_HUB_TOKEN, request
fan-out via a single httpx AsyncClient. .env / .venv git-ignored.
2026-05-24 10:51:01 +02:00
L'électron rare 658ca4ca2a feat(runtime3): condition action + dict actions
Extend the Runtime 3 IR spec with a standalone 'condition' action
kind that takes {expr, then[], else[]} sublists, keeping branching
local to the current step (no extra transitions or step duplication).

The validator now accepts both string-form (legacy firmware import)
and dict-form actions, recursively validating condition.then /
condition.else as nested action lists. STANDARD_ACTION_KINDS gains
the full set of blocks_studio kinds (audio, LCD, hardware, ESP-NOW,
BOX-3, M5, PLIP) so the blocks-to-IR codec output passes validation
end-to-end.
2026-05-24 10:50:34 +02:00
L'électron rare dcbbd07ec9 docs(specs): two voice-pipeline specs
Both chantiers live in other repos but the validating infrastructure
(Kyutai :8304, voice-bridge :8200, Kokoro :8002, LiteLLM :4000) is
documented here. Specs are implementable as-is — file layout, API
shapes, integration order, bench targets, risks, concrete entry
commands.

  ailiance/realtime/ : OpenAI Realtime API wrap. ~9 events,
  5 modules, single process, one upstream Kyutai WS per client.

  baby-brain stt migration : router keeps whisperx for diarisation
  (WML needs speakers), routes the conversational path to Kyutai.
  Includes the ssh one-liner to add a babybrain-tailnet auth key
  to Studio Kyutai config when work starts.
2026-05-24 01:48:17 +02:00
L'électron rare 95e4bf6c21 feat(voice-bridge): Kokoro-FR fallback (TTS chain)
Adds Kokoro-82M FR (ff_siwis voice, mlx-audio runtime) as the
second-tier TTS fallback after F5, before Piper, on the /voice/ws
path. Reason: Piper Tower is English-only as of 2026-05 and butchers
French utterances; Kokoro-FR is small (1.5 GB venv), fast on warm
(~1.4 s for 5 words), and FR-native.

New tools/macstudio/kokoro-fr/ houses the self-contained FastAPI
server (POST /synthesize, drop-in field shape with Piper). Persisted
on Studio :8002 via crontab @reboot. Verified roundtrip: Kokoro FR
synth → Kyutai STT → 'USON est sensible, vous savez, ...' (clean).

voice-bridge gains:
  - KOKORO_URL env (default http://localhost:8002, set to 0 to disable)
  - _kokoro_fallback() helper symmetric to _piper_fallback()
  - /voice/ws chain: cache -> F5 -> Kokoro -> Piper -> empty speak_end
2026-05-24 01:45:54 +02:00
L'électron rare 6d5edc7025 feat(voice-bridge): /voice/ws auto-EOU safety
RMS-based silence detector on the receive loop closes the
utterance automatically when WS_VAD_SILENCE_TIMEOUT_MS of
contiguous silence follows at least WS_VAD_MIN_SPEECH_MS of
detected speech. Firmware's local VAD AFE still sends
{type:end} normally — this is a backstop for the cases where
the firmware loses the end frame or crashes mid-stream.

Two trigger paths:
  - frame-level: silence accumulates across incoming PCM frames
  - idle-timeout: no frame arrives at all for the silence
    window (firmware froze mid-stream)

Zero deps added — pure numpy RMS. Disable via WS_VAD_ENABLED=0;
tune thresholds via env. Smoke-tested with a client that never
sends {type:end}: server closes via auto-EOU, full pipeline runs.
2026-05-24 01:37:42 +02:00
L'électron rare 966ff731bd feat(voice-bridge): STT post-correction aliases
Server-side fuzzy alias table applied to the final STT transcript
before forwarding to /voice/intent. Closes the metier-vocabulary
gap of generic STT models (U-SON, LA 440 Hz, Zacus, ...) without
needing per-request prompt biasing — which moshi-server's Rust
runtime does not expose anyway.

Loader is soft on pyyaml (degrades to no-op if missing) and on
the yaml file itself (logs + skips on parse error). Patterns are
case-insensitive with auto word-boundaries, with a (?...) escape
hatch for advanced regex.

Per-puzzle entries layer on top of globals — firmware passes
puzzle_id in the hello frame; legacy clients omit it and get
just the global polish. Loaded once at module import; restart
voice-bridge to reload.

Unit tested on 5 representative transcripts (eu son nez -> U-SON,
diapazon -> diapason, le rasus -> Zacus, le la 440 hertz -> LA 440 Hz).
2026-05-24 01:27:47 +02:00
L'électron rare 5c650049b2 docs: voice pipeline roadmap
Snapshot of state after this session + actionable plan for the
five remaining chantiers (glossary biasing, server-side EOU,
TTS fallback, ailiance Realtime wrap, baby-brain migration).

Each chantier documents what was discovered (e.g. Kyutai Rust
server has no TextPrompt message, Step VAD prs are undocumented),
why we did/didn't act on it this session, and a precise entry
point for the next session — so the next person/agent doesn't
have to re-grep the moshi-server source to know what's possible.
2026-05-24 01:22:54 +02:00
L'électron rare a5b00aa38d feat(voice-bridge): /voice/ws Piper fallback
The /tts HTTP handler already chained cache -> F5 -> Piper Tower.
The /voice/ws path was missing the Piper branch — on F5 timeout
or load failure it sent an empty speak_end and the firmware got
silence.

Wire the same _piper_fallback() helper after the F5 path, mirror
the wav-to-pcm conversion done on the cache hit branch. Best
effort: any error in the fallback leaves pcm=None and the WS
behaves as before. backend tag distinguishes 'f5' / 'cache' /
'piper_fallback' in the ws_tts_* log events.

Note: Piper on Tower is EN-only today (per root CLAUDE.md), so
a FR utterance falls back to EN pronunciation. Deploying a Piper
FR model is the real follow-up; this just plugs the silence.
2026-05-24 01:21:26 +02:00
L'électron rare 43b5ddc8c5 feat(voice-bridge): Kyutai streaming STT
/voice/ws no longer waits for the full audio buffer + a single
whisper.cpp batch call. STT_BACKEND=kyutai (default) routes
through the new kyutai_stt module: streams PCM16 16 kHz -> 24 kHz
to moshi-server on :8304, emits {type:stt,final:false,text:...}
partials to the firmware as Word events arrive, then a final:true
after the marker echo.

Fallback to the legacy whisper.cpp batch path on KyutaiSttError so
a downed moshi-server keeps the firmware loop alive. Flip to
STT_BACKEND=whispercpp to disable Kyutai during upgrade / A-B.

Verified end-to-end on say-Thomas WAV: 18 partials + 1 final, then
intent + F5 TTS as before. Server logs backend=kyutai per call.
2026-05-24 01:12:35 +02:00
L'électron rare 85f42a564e docs(macstudio): Kyutai STT runbook
moshi-server 0.6.4 Rust (Candle/Metal) on Studio :8304 serves
kyutai/stt-1b-en_fr-candle through /api/asr-streaming WebSocket
(msgpack). Persisted via crontab @reboot matching the other
Studio service start pattern.

Validated against Zacus F5 WAV (hallucinations, all STT engines
share them — corpus issue) and a clean 'say -v Thomas' FR WAV
(14/17 words verbatim). Wiring into voice-bridge is left for a
dedicated chantier — see file.
2026-05-24 00:49:35 +02:00
L'électron rare b2988c6b1d docs(macstudio): macM1 hints engine runbook
Phase A deploy on 2026-05-23: hints engine on macM1:8311 calling
back to Studio LiteLLM:4000 (which routes to local MLX hints-deep).

LiteLLM macM1 dropped from plan: Studio MLX workers bind to
127.0.0.1 only (lsof confirms), so a macM1 proxy can't reach them.
Rebinding the workers to 0.0.0.0 is Phase B.
2026-05-24 00:33:11 +02:00
L'électron rare 04bab81ec9 fix(hints): LOG vs log in startup warnings
Module logger is LOG (line 130). The bare 'log' references in the
default-key / default-admin warnings crashed the server at boot
whenever LITELLM_MASTER_KEY was unset (default placeholder path).

NameError surfaced when bringing up the hints engine on macM1.
2026-05-24 00:33:11 +02:00
L'électron rare ae9390c19c docs(claude): add nested CLAUDE.md files
Init-deep pass: contextual guidance auto-loaded when Claude reads
files in those directories. Closest-CLAUDE.md-wins precedence.

  dashboard/   backend wiring (BOX-3 WS, voice-bridge REST, hints SSE)
  packages/    workspace scope, deps direction, NodeNext ESM rule
    +scenario-engine/  pure TS interpreter, no React/DOM
    +shared/           types contract, single js-yaml runtime dep
    +ui/               headless+tokens, promotion-bar policy
  hardware/    KiCad/PIO satellites, boundary with submodule master
2026-05-24 00:32:40 +02:00
L'électron rare db7149b897 fix(plip): HTTPClient.POST const cast
Problem: pio build failed because HTTPClient::POST expects a non-const uint8_t* and the slice 14 client passed const uint8_t* via reinterpret_cast. Approach: drop the const qualifier — body is a local stack buffer, so the cast is safe (no shared mutable aliasing). Tradeoffs: kept the comment in place to document why the cast is benign so a future reader does not re-introduce const for ergonomics and re-break the build.
2026-05-04 01:22:35 +02:00
L'électron rare 05176ce7aa feat(dashboard): voice usage stats panel
Problem: voice-bridge GET /usage/stats (commit 1ac5741) exposes
LLM token + F5 TTS + STT cost counters but the dashboard had no
window into them — GMs were blind to runaway token spend during
playtests.

Approach: dedicated useVoiceUsage hook (separate 5 s polling cadence
from the 2 s /health/ready probe) keeps a bounded ring of the last
60 snapshots in memory for delta math, exposes resetUsage() that
sends X-Admin-Key when configured. VoiceUsagePanel renders LLM
total + tokens/min with a tiny inline SVG sparkline, F5 TTS calls/
seconds/cache hit rate, STT calls/seconds, and a confirm-gated
Reset button. Status pill flips to burst above 1000 tok/min.

Tradeoffs: rolling-window tok/min uses receivedAtMs (local clock,
not server uptime_s deltas) so we tolerate clock skew but lose
precision when the page sleeps. Sparkline is plain SVG to avoid
pulling in a charting lib. Counters reset on bridge restart, same
caveat as the prom textfile dumper (commit ba56395).
2026-05-04 01:17:49 +02:00
L'électron rare ba56395636 feat(macstudio): prom textfile + grafana docs
Problem: voice-bridge /usage/stats (commit 1ac5741) exposes JSON
counters but had no path into the existing studio:9100 node_exporter
for Grafana dashboarding.

Approach: small bash dumper (usage_to_prom.sh) renders the JSON into
the Prometheus textfile format, atomic mv into
/Users/clems/textfile_collector/. Cron once a minute. Doc covers
metric semantics, PromQL queries (token rate, cache ratio, inverse
RTF), and a 4-row Grafana panel layout.

Tradeoffs: counters reset on bridge restart (in-memory state in
main.py) — panels must use rate()/increase() with reset-tolerant
windows. node_exporter on studio still lacks the
--collector.textfile.directory flag (sudo/user edit needed); README
documents the one-line crontab change required to expose the metrics.
2026-05-04 01:16:08 +02:00
L'électron rare 80f3a80361 fix(plip): platformio deps + WiFi/mDNS init
Problem: PlatformIO build failed because lib_deps listed
schreibfaul1/ESP32-audioI2S, which is not published on the registry,
and network_task was an empty stub so the slice 14 hook client could
not actually reach zacus-master.local.

Approach: drop the unresolvable schreibfaul1 dep (esphome's fork
already covers ES8388 I2S). Flesh out network_task to read WiFi
credentials from NVS (namespace "wifi"), fall back to an open
ZACUS-SETUP SSID for bringup, wait up to 30 s for association,
advertise plip.local via ESPmDNS, and probe zacus-master.local once
so the hook client's hostname-based POSTs resolve. A 5 s health loop
reconnects on drop and re-reads NVS in case the desktop pushes new
credentials at runtime.

Tradeoffs: the open SSID fallback is dev-only and must be replaced
with SoftAP provisioning before shipping. mDNS probe is logged but
non-fatal so the hook client can still retry resolution per-request.
Build verification with `pio run` was blocked by sandbox in this
session; the dep change is mechanical and the source compiles
against stock ESP32 Arduino headers (WiFi.h, ESPmDNS.h, Preferences.h).
2026-05-04 01:14:33 +02:00
L'électron rare 1ac5741319 feat(macstudio): voice-bridge usage stats endpoint
Problem: the voice-bridge synthesizes audio, runs whisper, and forwards
to LiteLLM npc-fast and (in the future) hints-deep without any
visibility on cumulative token consumption or audio compute time. Soak
runs and cost extrapolation are blind.

Approach: aggregate per-bucket counters in module-level state under an
asyncio Lock. Buckets: npc_fast, hints_deep, tts (f5_calls,
f5_seconds, cache_hits), stt (calls, audio_seconds). Hooks live in
/voice/intent (LiteLLM usage dict), _intent_complete in /voice/ws,
/tts (cache hit vs F5 with audio duration computed from PCM), WS TTS
path, and /voice/transcribe with a best-effort RIFF sniffer for the
multipart body. New endpoints GET /usage/stats (Cache-Control no-cache,
no auth — LAN only) and POST /usage/reset (gated by VOICE_BRIDGE_ADMIN_KEY
when set). Background asyncio task logs a usage_tick JSON snapshot every
USAGE_TICK_INTERVAL_S (default 300 s) for grep-friendly history.

Tradeoffs: chose dict-under-lock over a real metrics library
(prometheus_client) to keep the daemon dependency-light and because
the readout is meant to feed a Prometheus textfile collector cron from
node_exporter (recommended in the README), not a direct /metrics scrape.
audio_seconds may stay 0 for transcribe calls when the WAV header is
unrecognisable — best-effort, calls counter still increments. Reset
endpoint follows the same admin-key-optional pattern as DELETE /tts/cache.
2026-05-04 01:11:58 +02:00
L'électron rare 214d5df443 feat(plip): /voice/hook client (master REST) 2026-05-04 01:04:50 +02:00
L'électron rare 92ac9e7daa docs: specs slice 12/13 + part13 livré + sec note
MacStudio spec: P1 part13 marked delivered (commit d59282a, voice-bridge 0.5 with CORSMiddleware + NPC_SYSTEM_PROMPT injection for npc-fast persona). Risks section gains a new entry pointing to the LAN-only security checklist in tools/macstudio/README.md (commit 695b7f7). Acceptance gates: 4 new boxes checked.

Voice-pipeline ESP-SR spec: C_slice 12 marked delivered (commit 71bc9a5, mDNS zacus-master.local + game_endpoint REST NVS-backed) and C_slice 13 (commit a743cfc, voice_dispatcher fuzzy keyword aliases). Risks rewritten: PLIP /voice/hook decroche is now the primary trigger, custom Espressif wake-word is optional, microWakeWord remains an open-source alternative path. Gates: 3 new boxes checked.

Tradeoffs: kept the wholesale 'Total core' line and the §9 Open questions entry on mDNS untouched even though both are now obsolete — minimum-diff to satisfy the verrou and keep review easy. Both can be revised in a follow-up pass.
2026-05-04 01:04:11 +02:00
L'électron rare 695b7f770b security: gitignore + .env.example + boot warns
Problem: the LITELLM master key was committed in plain text across five
files. The .gitignore did not block .env files, leaving the door open
for future accidental commits of real credentials. Admin DELETE on the
voice-bridge cache and the hints engine sessions endpoints fall back to
no-auth when their ADMIN_KEY env is unset, with no warning to operators.

Approach: keep the placeholder default so the LAN-only dev workflow
keeps working without any breaking change, but make every operator
aware of the risk. Added gitignore patterns (.env*, *.secret,
secrets.{yaml,yml,json}, credentials.json, *-secret.*) with a
!.env.example whitelist for the template. Created
tools/macstudio/.env.example documenting the four secrets (master key,
two admin keys, CORS) and recommending ~/.zacus.env as the local store.
Voice-bridge and hints engine now log a boot WARN when the default key
or no admin key is in use. README gained a Security section listing the
LAN-only assumption, the no-auth surfaces, and a four-step
pre-deployment checklist (rotate key, source env, audit Cloudflare
tunnel routes, verify boot logs).

Tradeoffs: chose loud-warning over fail-fast because smoke runs and
tests would otherwise need the real key wired before they can boot at
all; loud warnings surface the issue without breaking dev. Did not
rewrite git history to scrub the placeholder — its value is now
treated as known-public, and any deployment outside the closed LAN
must rotate it before going live.
2026-05-04 00:58:34 +02:00
L'électron rare d59282a0e1 feat(macstudio): CORS + npc-fast persona prompt
voice-bridge 0.5: CORSMiddleware allowing dashboard/atelier dev origins (env CORS_ORIGINS), and inject NPC_SYSTEM_PROMPT (Professeur Zacus persona, accepts Zaku/Zacusse variants, FR 2-sentence dramatic, never reveal solution) before npc-fast calls in /voice/intent and /voice/ws. Caller-supplied messages or system override the default. Bump ESP32_ZACUS to slice-13 voice_dispatcher fuzzy aliases.
2026-05-04 00:55:23 +02:00
L'électron rare 86563b7841 feat(atelier): game group_profile config panel 2026-05-04 00:47:14 +02:00
L'électron rare 33a5b128dc ci: smoke-voice workflow (cron 6h + manual) 2026-05-04 00:43:02 +02:00
L'électron rare 5a0a6cc073 feat(tools): voice loop E2E smoke script
Add tools/macstudio/smoke_e2e.py exercising the full voice-bridge loop
(/health/ready, /tts, /voice/ws hello+stream+intent+TTS, /tts/cache/stats)
without requiring the ESP32 firmware. Three modes (tts/ws/full); generates
a French PCM16 16 kHz fixture via `say -v Thomas` + afconvert when needed;
saves received WAVs and a structured JSON report under /tmp.

Wires `make smoke-voice` and documents the smoke test under
tools/macstudio/README.md (prereqs, exit codes 0/1/2, typical latencies).

Local run hit /tts cache miss + WS round-trip in ~3 s with cache hit on
the second pass; cache delta logging confirmed.
2026-05-04 00:35:45 +02:00
L'électron rare c901770f5a chore: bump submodule + frontend voice panel
ESP32_ZACUS bump to 71bc9a5: mDNS zacus-master.local + POST /game/group_profile + GET /game/group_profile NVS-backed. Frontend dashboard adds Voice Bridge Activity panel (commit 21a0e51 in frontend pkg, 3 new tests, build 230KB).
2026-05-04 00:35:24 +02:00
L'électron rare 21a0e51ac4 feat(dashboard): voice bridge activity panel 2026-05-04 00:32:39 +02:00
L'électron rare 8db4ee986f chore: bump ESP32_ZACUS (D_part5 + partition 2MB) 2026-05-04 00:25:18 +02:00
L'électron rare 3301a8831a docs: voice spec slices 8/9/9b/10 livré + PLIP
§1 architecture: real delivered chain (PLIP /voice/hook, I2S0/I2S1 split, ws speak_*). §4 wire format: /voice/ws (slice 7+9b), POST /voice/hook, GET /voice/hook/state. §5 phased: P5 c3a771c, P7 6d3e989+d47c230, P7b 3585a65. §6 risks: wake-word custom delayed, PLIP /voice/hook as alternative path. §7 gates: 6 checked. §9 open: wake-word decision, mDNS, D_part5, device smoke.
2026-05-04 00:23:21 +02:00
L'électron rare e320e5d946 chore: bump ESP32_ZACUS (PLIP hook + slice 9b) 2026-05-04 00:16:36 +02:00