KeiSeiKit-1.0/_primitives/_rust/kei-buddy
Parfii-bot 26dc8c85f7 feat(kei-buddy): AskLanguage i18n + real proposeTopicSources + voice handling
Three follow-up atomics on top of the contacts/topics/sync wave.

## 1. AskLanguage state + ru/en localisation (default en)

New state `AskLanguage` inserted between `Intro` and `AskName`. Intro now
sends a bilingual greeting + language picker. AskLanguage parses
en/english/1/ru/русский/2/etc → persona_patch{"language":"<code>"} →
transitions to AskName with that language's prompt.

All later prompts (AskName / AskTone / AskInterests / AskHobbies /
TopicSpecifics / TopicNowLater / TopicResearch / AskSchedule / Ready)
read persona.language via Lang::from_persona and dispatch through
Strings::* helpers — two language tables, no fallthrough.

Back-compat migration: existing chats without `language` key (like the
user currently in topic_now_later) get an implicit "ru" patch on next
turn so their Russian onboarding continues without regression.

New files: strings.rs (164), machine_lang.rs (145).
Modified: state.rs (+AskLanguage variant), machine.rs (Intro→AskLanguage,
AskLanguage arm, migration guard), machine_helpers.rs, machine_tests.rs.

5 new tests (intro_to_ask_language, ask_language_en, ask_language_ru,
ask_language_invalid, migration_sets_ru_when_language_missing).

## 2. Real proposeTopicSources — removed TODO(phase2) stub

machine_lang.rs::step_topic_research now calls
extractor.extract(prompt, topic_title) with a {name, url, why} schema.
Parses JSON, formats numbered source list, transitions to TopicSources.

Failure paths (LLM error, empty array): graceful fallback prompt asking
user to suggest their own — still transitions to TopicSources so flow
doesn't deadlock.

3 new tests in machine_tests_topic_research.rs:
topic_research_yes_proposes_sources,
topic_research_yes_empty_sources_still_advances,
topic_research_no_skips_topic_sources.

## 3. Voice-message handling (Telegram voice/audio → STT → text pipeline)

kei-telegram-webhook: added Voice/Audio sub-structs on Message and
WebhookEvent::Voice variant. classify() detects message.voice OR
message.audio. 2 new tests in event.rs.

kei-buddy/src/voice.rs (178 LOC):
VoiceHandler { bot_token, stt: Arc<dyn SttBackend>, http }
transcribe_file(file_id, mime_type) does:
  1. GET https://api.telegram.org/bot{token}/getFile?file_id=...
  2. GET https://api.telegram.org/file/bot{token}/{file_path}
  3. SttRequest { audio_bytes, mime_type, language: None } → backend.transcribe
  4. Returns transcript text.
2 wiremock tests (download chain + 500 error mapping).

serve.rs adds voice: Option<Arc<VoiceHandler>> to BuddyContext;
on_event Voice arm: whitelist check → transcribe → handle_text (same
pipeline as if user typed). Voice unavailable: warn + ignore.

serve_runner.rs builds VoiceHandler from KEI_BUDDY_STT_BACKEND env.

kei-stt added as optional dep gated by serve feature. Default backend
whisper-local (no extra build deps).

TTS reply path deferred (next atomic).

## Verify

  * cargo check --workspace: PASS
  * cargo test -p kei-buddy --lib: 55 passed / 0 failed (was 41 → 50 → 53 → 55)
  * cargo test -p kei-telegram-webhook --lib: 7 passed (was 5, +2 voice)
  * cargo build -p kei-buddy --release: PASS (23.7s)

NOT deployed yet — three new things to roll out next:
  * новые миграции (нет — БД без изменений)
  * новые env: KEI_BUDDY_STT_BACKEND (optional)
  * установка faster-whisper / piper-tts на сервер для STT
    (без него Voice event просто warn-логируется и игнорируется)
2026-05-12 17:49:06 +08:00
..
src feat(kei-buddy): AskLanguage i18n + real proposeTopicSources + voice handling 2026-05-12 17:49:06 +08:00
tests feat(kei-buddy fleet): 5 atomics — google/apple contacts + classifier + tick + slash-commands 2026-05-12 16:33:58 +08:00
Cargo.toml feat(kei-buddy): AskLanguage i18n + real proposeTopicSources + voice handling 2026-05-12 17:49:06 +08:00
README.md feat(kei-buddy): functional MVP — store + state-machine port + serve binary 2026-05-12 14:21:33 +08:00

kei-buddy

Maturity: concept / scaffold — no business logic yet.

Purpose

kei-buddy is the runtime crate that composes existing KeiSeiKit primitives (kei-pet, kei-memory-sqlite, kei-cortex, kei-notify-telegram) into a personal-assistant Telegram bot called KeiBuddy.

On first contact the bot walks the user through an 11-state onboarding flow: name, tone, interests, hobbies, per-topic decomposition (specifics → now-or-later → research preference → source selection), and digest schedule. After onboarding the bot enters ongoing conversation mode, drawing on the stored persona and memory.

This crate provides the state-machine enum and skeleton driver. The onboarding FSM is ported from keisei-marketplace/src/lib/keibuddy/chat-onboard.ts.

Status

Scaffold only. The OnboardState enum and TransitionInput struct are defined. All transition logic is stubbed (next() returns self.clone()). The binary entry point prints a placeholder message and exits 0.

Running

Environment variables

Variable Required Default Description
TELEGRAM_BOT_TOKEN yes (serve) Bot token from @BotFather
TELEGRAM_WEBHOOK_SECRET yes (serve) Secret token for webhook verification
KEI_BUDDY_PORT no 8080 HTTP port to bind
KEI_BUDDY_DB_PATH no ./kei-buddy.db SQLite database path
OPENAI_API_KEY no Enables OpenAiExtractor when set (requires extractor-openai feature)

Subcommands

# Apply schema (idempotent; run once before first serve)
kei-buddy migrate

# Register the webhook URL with Telegram
kei-buddy webhook-set https://your-domain.com/webhook

# Start the HTTP server
kei-buddy serve

# Remove the registered webhook (revert to polling)
kei-buddy webhook-delete

Example systemd unit

[Unit]
Description=KeiBuddy Telegram bot
After=network.target

[Service]
EnvironmentFile=/etc/kei-buddy/env
ExecStart=/usr/local/bin/kei-buddy serve
Restart=on-failure
User=keisei

[Install]
WantedBy=multi-user.target

Roadmap

  • OpenAiExtractor wiring — pass real OPENAI_API_KEY to OpenAiExtractor in serve.rs when feature enabled.
  • Persona binding — read persona manifest via kei-pet; apply tone overlay to outgoing replies.
  • Digest scheduling — wire kei-cron-scheduler for morning/evening digest delivery.