chore(voice): security, cleanup, resilience
All checks were successful
Deploy / deploy (push) Successful in 1m47s

Безопасность:
- Rate-limit на /api/voice/chat (20/мин per cookie/IP, env VOICE_RATE_LIMIT).
  Защищает от случайных циклов и утечки PIN.
- Усечение user prompt'а до 4000 символов в /api/voice/chat.
- Tool-loop защита от циклов: если LLM дважды просит тот же tool с теми же
  args — прерываем (раньше мог уйти в бесконечный цикл при tool error'ах).

Чистка кода:
- lib/debug.ts — vlog/vwarn/verror гейтят браузерные логи за
  NEXT_PUBLIC_VOICE_DEBUG=1 (или localStorage 'voice-debug=1').
  Серверные console.log оставлены — полезны в Docker logs.
- lib/audio-wav.ts — вынесена дублированная floatToWav из VoiceController.
- Удалены orphan компоненты FocusCard.tsx и CountdownCard.tsx
  (не подключены, отвергнуты по UX-фидбеку).

Resilience:
- WakeWordDetector: drop-on-busy в onChunk — на медленных устройствах
  (Android, бюджетный CPU) backlog inference больше не копится.
- voice-history fallback на /tmp/voice-history если /data не примонтирован
  (локальная разработка / нестандартная конфигурация).
This commit is contained in:
Cosmo
2026-04-27 12:44:18 +00:00
parent 3211d62198
commit 05b300d472
9 changed files with 169 additions and 683 deletions

View File

@@ -1,11 +1,19 @@
/**
* История диалога per-agent per-day. Файлы в /data/voice-history/{agent}-{date}.json.
* /data — это volume контейнера (на хосте /opt/digital-home/smart-home-tablet-data/).
*
* Fallback: если /data не существует (локальная разработка) — пишем в /tmp/voice-history.
*/
import { promises as fs } from 'node:fs'
import { existsSync } from 'node:fs'
import path from 'node:path'
const DATA_DIR = process.env.VOICE_HISTORY_DIR || '/data/voice-history'
const PRIMARY_DIR = process.env.VOICE_HISTORY_DIR || '/data/voice-history'
const DATA_DIR = (() => {
// Проверяем существование родителя (/data) — без него запись упадёт ENOENT.
const parent = path.dirname(PRIMARY_DIR)
return existsSync(parent) ? PRIMARY_DIR : '/tmp/voice-history'
})()
const MAX_HISTORY = parseInt(process.env.VOICE_MAX_HISTORY || '40', 10)
export type HistoryMessage = {