From 69c52a275e513867c01a51ce8161e2c562364f0f Mon Sep 17 00:00:00 2001 From: "Timo S." Date: Mon, 5 Jan 2026 12:13:22 +0100 Subject: [PATCH 1/2] chore: add .worktrees to gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .gitignore | 1 + PACKS.md | 16 +- ...26-01-05-voice-server-extra-args-design.md | 188 ++++++++++++++++++ 3 files changed, 197 insertions(+), 8 deletions(-) create mode 100644 docs/plans/2026-01-05-voice-server-extra-args-design.md diff --git a/.gitignore b/.gitignore index 9fd0febdbe..1802f50491 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,4 @@ credentials/ .claude/pai_updates/ .claude/pai_backups/ .claude/.pai-sync-history +.worktrees/ diff --git a/PACKS.md b/PACKS.md index 3503a56aeb..5ba91b09b9 100644 --- a/PACKS.md +++ b/PACKS.md @@ -255,19 +255,19 @@ Browse all packs: [Packs/](Packs/) | Pack | Type | Description | |------|------|-------------| -| [kai-hook-system](Packs/kai-hook-system.md) | Foundation | Event-driven automation framework for hook-based capabilities | -| [kai-history-system](Packs/kai-history-system.md) | Infrastructure | Automatic context-tracking for all work, decisions, and learnings | -| [kai-core-install](Packs/kai-core-install.md) | Core | Skills + Identity + Architecture - complete foundation pack | -| [kai-voice-system](Packs/kai-voice-system.md) | Notifications | Voice output with ElevenLabs TTS and prosody enhancement | -| [kai-observability-server](Packs/kai-observability-server.md) | Observability | Real-time multi-agent monitoring dashboard | +| [kai-hook-system](Packs/kai-hook-system) | Foundation | Event-driven automation framework for hook-based capabilities | +| [kai-history-system](Packs/kai-history-system) | Infrastructure | Automatic context-tracking for all work, decisions, and learnings | +| [kai-core-install](Packs/kai-core-install) | Core | Skills + Identity + Architecture - complete foundation pack | +| [kai-voice-system](Packs/kai-voice-system) | Notifications | Voice output with ElevenLabs TTS and prosody enhancement | +| [kai-observability-server](Packs/kai-observability-server) | Observability | Real-time multi-agent monitoring dashboard | ### Skill Packs | Pack | Type | Description | |------|------|-------------| -| [kai-art-skill](Packs/kai-art-skill.md) | Skill | Visual content generation with charcoal architectural sketch aesthetic | -| [kai-agents-skill](Packs/kai-agents-skill.md) | Skill | Dynamic agent composition with specialized personalities and voices | -| [kai-prompting-skill](Packs/kai-prompting-skill.md) | Skill | Meta-prompting system for programmatic prompt generation | +| [kai-art-skill](Packs/kai-art-skill) | Skill | Visual content generation with charcoal architectural sketch aesthetic | +| [kai-agents-skill](Packs/kai-agents-skill) | Skill | Dynamic agent composition with specialized personalities and voices | +| [kai-prompting-skill](Packs/kai-prompting-skill) | Skill | Meta-prompting system for programmatic prompt generation | **Installation order:** hooks → history → core-install → voice → observability (optional) → skill packs diff --git a/docs/plans/2026-01-05-voice-server-extra-args-design.md b/docs/plans/2026-01-05-voice-server-extra-args-design.md new file mode 100644 index 0000000000..cdc8cfd418 --- /dev/null +++ b/docs/plans/2026-01-05-voice-server-extra-args-design.md @@ -0,0 +1,188 @@ +# Voice Server Extra Args Configuration + +**Date:** 2026-01-05 +**Status:** Approved +**Package:** kai-voice-system + +## Problem + +The voice server hardcodes audio player arguments, making it impossible to configure platform-specific settings like PulseAudio output (`-o pulse`) needed for container environments. + +## Solution + +Add `VOICE_SERVER_EXTRA_ARGS` configuration that appends custom arguments to all audio players. + +## Configuration + +### Environment Variable +```bash +# In $PAI_DIR/.env +VOICE_SERVER_EXTRA_ARGS="-o pulse" +``` + +### CLI Flag +```bash +bun run server.ts --extra-args="-o pulse" +``` + +### Precedence +CLI `--extra-args` > `VOICE_SERVER_EXTRA_ARGS` env var > empty + +## Implementation + +### 1. Add `findPlayer()` function (replaces hardcoded paths) + +```typescript +import { execSync } from 'child_process'; + +function findPlayer(name: string): string | null { + try { + return execSync(`which ${name}`, { encoding: 'utf8' }).trim() || null; + } catch { + return null; + } +} +``` + +### 2. Add CLI argument parsing + +```typescript +import { parseArgs } from 'util'; + +const cliArgs = parseArgs({ + args: process.argv.slice(2), + options: { + 'extra-args': { type: 'string', default: '' }, + }, +}); +``` + +### 3. Add `getExtraArgs()` function + +```typescript +function getExtraArgs(): string[] { + const raw = cliArgs.values['extra-args'] || process.env.VOICE_SERVER_EXTRA_ARGS || ''; + return raw.trim() ? raw.trim().split(/\s+/) : []; +} +``` + +### 4. Update `playAudio()` function + +**Before (hardcoded paths, separate if blocks):** +```typescript +if (existsSync('/usr/bin/mpg123')) { + player = '/usr/bin/mpg123'; + args = ['-q', tempFile]; +} else if (existsSync('/usr/bin/mpv')) { + player = '/usr/bin/mpv'; + args = ['--no-terminal', tempFile]; +} else if (existsSync('/snap/bin/mpv')) { + player = '/snap/bin/mpv'; + args = ['--no-terminal', tempFile]; +} +``` + +**After (dynamic detection with extra args):** +```typescript +// Linux: use which to find player +const mpg123 = findPlayer('mpg123'); +const mpv = findPlayer('mpv'); + +if (mpg123) { + player = mpg123; + playerArgs = ['-q', tempFile, ...getExtraArgs()]; +} else if (mpv) { + player = mpv; + playerArgs = ['--no-terminal', tempFile, ...getExtraArgs()]; +} + +// Mac: afplay (always at /usr/bin/afplay) +if (process.platform === 'darwin') { + player = '/usr/bin/afplay'; + playerArgs = [tempFile, ...getExtraArgs()]; +} +``` + +### 5. Add startup logging + +```typescript +const extraArgs = getExtraArgs(); +if (extraArgs.length > 0) { + console.log(`[Voice Server] Extra player args: ${extraArgs.join(' ')}`); +} +``` + +## Files Changed + +| File | Changes | +|------|---------| +| `src/voice/server.ts` | Add findPlayer(), getExtraArgs(), CLI parsing, update playAudio() | +| `README.md` | Document VOICE_SERVER_EXTRA_ARGS configuration | + +## README Addition + +Add to README.md after "Google Cloud TTS Voices" section: + +````markdown +### Audio Player Arguments + +Configure extra arguments for audio players (useful for containers or specific audio setups): + +**Environment Variable:** +```bash +# In $PAI_DIR/.env +VOICE_SERVER_EXTRA_ARGS="-o pulse" +``` + +**CLI Flag (overrides env var):** +```bash +bun run server.ts --extra-args="-o pulse" +``` + +**Common use cases:** +| Use Case | Configuration | +|----------|---------------| +| Container with PulseAudio | `VOICE_SERVER_EXTRA_ARGS="-o pulse"` | +| Specific ALSA device | `VOICE_SERVER_EXTRA_ARGS="-o alsa -a hw:1,0"` | + +### Devcontainer Setup + +To use voice notifications inside a devcontainer: + +1. **Mount PulseAudio socket** from host in `devcontainer.json` +2. **Set `VOICE_SERVER_EXTRA_ARGS`** in your `.env` file + +**Example `devcontainer.json`:** +```json +{ + "name": "My Devcontainer", + "image": "mcr.microsoft.com/devcontainers/python:2-3.12-bullseye", + "mounts": [ + "source=/run/user/1000/pulse,target=/run/user/1000/pulse,type=bind" + ], + "runArgs": [ + "--device=/dev/snd:/dev/snd" + ], + "containerEnv": { + "PULSE_SERVER": "unix:/run/user/1000/pulse/native" + } +} +``` + +**In your `$PAI_DIR/.env`:** +```bash +VOICE_SERVER_EXTRA_ARGS="-o pulse" +``` + +**Key points:** +- Mount `/run/user/1000/pulse` to access host's PulseAudio +- Set `PULSE_SERVER` in container so audio apps find the socket +- Set `VOICE_SERVER_EXTRA_ARGS` in `.env` (not devcontainer.json) +```` + +## Benefits + +1. **Flexibility**: Configure audio output without code changes +2. **Container support**: Enable PulseAudio/ALSA in containerized environments +3. **Simplified code**: `which` replaces hardcoded paths +4. **Consistent**: Same extra args apply to all players (mpg123, mpv, afplay) From 92769ed7b7e3781a63791547466d07244be1405f Mon Sep 17 00:00:00 2001 From: "Timo S." Date: Wed, 7 Jan 2026 23:21:23 +0100 Subject: [PATCH 2/2] feat(voice): unified voice system with ENV var configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unified voice system architecture: - Voice IDs read from ENV vars (ELEVENLABS_VOICE_*) - single source of truth - AGENT tag format [AGENT:voice] for personality-specific voice routing - Case-insensitive voice lookup throughout the pipeline - Smart .env path resolution with fallback chain Changes to kai-agents-skill (v1.2.0): - Removed hardcoded voice_registry from Traits.yaml - Added Handlebars lowercase helper for consistent voice names - Updated named agent personalities with AGENT tag response format - Updated INSTALL.md with comprehensive ENV var voice configuration Changes to kai-voice-system (v1.4.0): - Support direct assistant text responses in subagent hook - Correct regex patterns for markdown bold COMPLETED detection - Pass voice NAME to server instead of resolved ID - Add all dynamic agent voices to voice-personalities.json Authors: danielmiessler, sti0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .gitignore | 2 + PACKS.md | 16 +- Packs/kai-agents-skill/INSTALL.md | 40 +++- Packs/kai-agents-skill/README.md | 14 +- .../src/skills/Agents/AgentPersonalities.md | 58 ++---- .../src/skills/Agents/Data/Traits.yaml | 161 ++++----------- .../skills/Agents/Templates/DynamicAgent.hbs | 17 +- .../src/skills/Agents/Tools/AgentFactory.ts | 3 + Packs/kai-voice-system/README.md | 23 ++- .../config/voice-personalities.json | 70 +++++-- .../src/hooks/stop-hook-voice.ts | 5 +- .../src/hooks/subagent-stop-hook-voice.ts | 58 +++++- Packs/kai-voice-system/src/voice/server.ts | 52 ++++- README.md | 78 -------- ...26-01-05-voice-server-extra-args-design.md | 188 ------------------ 15 files changed, 298 insertions(+), 487 deletions(-) delete mode 100644 docs/plans/2026-01-05-voice-server-extra-args-design.md diff --git a/.gitignore b/.gitignore index 1802f50491..31b696c9cf 100644 --- a/.gitignore +++ b/.gitignore @@ -66,4 +66,6 @@ credentials/ .claude/pai_updates/ .claude/pai_backups/ .claude/.pai-sync-history + +# Worktrees .worktrees/ diff --git a/PACKS.md b/PACKS.md index 5ba91b09b9..3503a56aeb 100644 --- a/PACKS.md +++ b/PACKS.md @@ -255,19 +255,19 @@ Browse all packs: [Packs/](Packs/) | Pack | Type | Description | |------|------|-------------| -| [kai-hook-system](Packs/kai-hook-system) | Foundation | Event-driven automation framework for hook-based capabilities | -| [kai-history-system](Packs/kai-history-system) | Infrastructure | Automatic context-tracking for all work, decisions, and learnings | -| [kai-core-install](Packs/kai-core-install) | Core | Skills + Identity + Architecture - complete foundation pack | -| [kai-voice-system](Packs/kai-voice-system) | Notifications | Voice output with ElevenLabs TTS and prosody enhancement | -| [kai-observability-server](Packs/kai-observability-server) | Observability | Real-time multi-agent monitoring dashboard | +| [kai-hook-system](Packs/kai-hook-system.md) | Foundation | Event-driven automation framework for hook-based capabilities | +| [kai-history-system](Packs/kai-history-system.md) | Infrastructure | Automatic context-tracking for all work, decisions, and learnings | +| [kai-core-install](Packs/kai-core-install.md) | Core | Skills + Identity + Architecture - complete foundation pack | +| [kai-voice-system](Packs/kai-voice-system.md) | Notifications | Voice output with ElevenLabs TTS and prosody enhancement | +| [kai-observability-server](Packs/kai-observability-server.md) | Observability | Real-time multi-agent monitoring dashboard | ### Skill Packs | Pack | Type | Description | |------|------|-------------| -| [kai-art-skill](Packs/kai-art-skill) | Skill | Visual content generation with charcoal architectural sketch aesthetic | -| [kai-agents-skill](Packs/kai-agents-skill) | Skill | Dynamic agent composition with specialized personalities and voices | -| [kai-prompting-skill](Packs/kai-prompting-skill) | Skill | Meta-prompting system for programmatic prompt generation | +| [kai-art-skill](Packs/kai-art-skill.md) | Skill | Visual content generation with charcoal architectural sketch aesthetic | +| [kai-agents-skill](Packs/kai-agents-skill.md) | Skill | Dynamic agent composition with specialized personalities and voices | +| [kai-prompting-skill](Packs/kai-prompting-skill.md) | Skill | Meta-prompting system for programmatic prompt generation | **Installation order:** hooks → history → core-install → voice → observability (optional) → skill packs diff --git a/Packs/kai-agents-skill/INSTALL.md b/Packs/kai-agents-skill/INSTALL.md index 188ee86586..07e82c944a 100644 --- a/Packs/kai-agents-skill/INSTALL.md +++ b/Packs/kai-agents-skill/INSTALL.md @@ -82,16 +82,46 @@ cd "$PAI_DIR/skills/Agents/Tools" bun add yaml handlebars ``` -### Step 4: Configure Voice IDs (Optional) +### Step 4: Configure Voice Environment Variables (Optional) -If using kai-voice-system, edit `Traits.yaml` to replace placeholder voice IDs: +If using kai-voice-system for voice notifications, add voice IDs to your `.env` file. +> **Important:** Voice IDs MUST be configured in `$PAI_DIR/.env`, NOT in `settings.json` or other config files. The voice server reads ElevenLabs voice IDs exclusively from environment variables. + +**$PAI_DIR/.env:** ```bash -# Edit voice mappings with your TTS provider's voice IDs -nano "$PAI_DIR/skills/Agents/Data/Traits.yaml" +# Required: ElevenLabs API key +ELEVENLABS_API_KEY=sk_your_api_key_here + +# Dynamic agent voices (from trait mapping) +ELEVENLABS_VOICE_PROFESSIONAL= +ELEVENLABS_VOICE_AUTHORITATIVE= +ELEVENLABS_VOICE_ACADEMIC= +ELEVENLABS_VOICE_WARM= +ELEVENLABS_VOICE_GENTLE= +ELEVENLABS_VOICE_ENERGETIC= +ELEVENLABS_VOICE_DYNAMIC= +ELEVENLABS_VOICE_SOPHISTICATED= +ELEVENLABS_VOICE_INTENSE= +ELEVENLABS_VOICE_GRITTY= + +# Named agent voices +ELEVENLABS_VOICE_PAI= +ELEVENLABS_VOICE_INTERN= +ELEVENLABS_VOICE_ARCHITECT= +ELEVENLABS_VOICE_ENGINEER= + +# Fallback voice (used when specific voice not configured) +ELEVENLABS_VOICE_ID= ``` -Replace `YOUR_*_VOICE_ID` placeholders with actual voice IDs from your TTS provider. +**Configuration Split:** +| What | Where | Example | +|------|-------|---------| +| Voice IDs (secrets) | `$PAI_DIR/.env` | `ELEVENLABS_VOICE_ACADEMIC=dCnu06FiOZma2KVNUoPZ` | +| Voice settings | `kai-voice-system/config/voice-personalities.json` | `{ "stability": 0.62, "similarity_boost": 0.80 }` | + +> **Note:** Get your voice IDs from the [ElevenLabs Voice Library](https://elevenlabs.io/voice-library). Each voice has a unique ID like `dCnu06FiOZma2KVNUoPZ`. --- diff --git a/Packs/kai-agents-skill/README.md b/Packs/kai-agents-skill/README.md index eb657bca9a..7c5f0b8cf9 100644 --- a/Packs/kai-agents-skill/README.md +++ b/Packs/kai-agents-skill/README.md @@ -1,8 +1,8 @@ --- name: Kai Agents Skill -pack-id: danielmiessler-agents-skill-core-v1.1.1 -version: 1.1.1 -author: danielmiessler +pack-id: danielmiessler-agents-skill-core-v1.2.0 +version: 1.2.0 +author: [danielmiessler, sti0] description: Dynamic agent composition and orchestration system - create custom agents with unique personalities, voices, and trait combinations on-the-fly type: skill purpose-type: [productivity, automation, development] @@ -93,6 +93,14 @@ bun run AgentFactory.ts --list ## Changelog +### 1.2.0 - 2026-01-07 +- **Unified voice system integration**: Voice IDs now read from ENV vars (`ELEVENLABS_VOICE_*`) +- **AGENT tag format**: Dynamic agents output `[AGENT:voice]` tags for personality-specific voice routing +- Removed hardcoded voice registry from `Traits.yaml` - single source of truth in `.env` +- Added Handlebars `lowercase` helper for consistent voice name formatting +- Updated named agent personalities with AGENT tag response format +- Updated INSTALL.md with comprehensive ENV var voice configuration + ### 1.1.1 - 2026-01-03 - Added missing `SpawnParallelAgents.md` workflow (was referenced but didn't exist) - Fixed workflow validation diff --git a/Packs/kai-agents-skill/src/skills/Agents/AgentPersonalities.md b/Packs/kai-agents-skill/src/skills/Agents/AgentPersonalities.md index 2a511370ca..63f94dab53 100644 --- a/Packs/kai-agents-skill/src/skills/Agents/AgentPersonalities.md +++ b/Packs/kai-agents-skill/src/skills/Agents/AgentPersonalities.md @@ -18,7 +18,11 @@ Use named agents for recurring work where relationship continuity matters. ### The Intern - "The Brilliant Overachiever" -**Voice Settings**: Fast rate (270 wpm), Low stability (0.30) +**Response Format:** +Always end responses with: +``` +🎯 **COMPLETED**: [AGENT:intern] [Brief summary - max 12 words] +``` **Backstory:** Youngest person accepted into competitive program. Skipped grades, constantly @@ -39,7 +43,11 @@ love or hate them. Fast talker because brain races ahead of mouth. ### The Architect - "The Academic Visionary" -**Voice Settings**: Slow rate (205 wpm), High stability (0.75) +**Response Format:** +Always end responses with: +``` +🎯 **COMPLETED**: [AGENT:architect] [Brief summary - max 12 words] +``` **Backstory:** Started in academia (CS research) before industry. PhD work on distributed @@ -60,7 +68,11 @@ patterns are timeless vs trends. ### The Engineer - "The Battle-Scarred Leader" -**Voice Settings**: Slow rate (212 wpm), High stability (0.72) +**Response Format:** +Always end responses with: +``` +🎯 **COMPLETED**: [AGENT:engineer] [Brief summary - max 12 words] +``` **Backstory:** 15 years from junior to technical lead. Scars from architectural decisions that @@ -79,47 +91,11 @@ before diving into solutions. --- -## Voice Configuration - -For voice notifications, map agent names to your TTS provider's voice IDs: - -```json -{ - "default_rate": 175, - "voices": { - "intern": { - "voice_id": "YOUR_INTERN_VOICE_ID", - "voice_name": "Energetic", - "rate_wpm": 270, - "stability": 0.30, - "similarity_boost": 0.65, - "description": "High-energy eager delivery" - }, - "architect": { - "voice_id": "YOUR_ARCHITECT_VOICE_ID", - "voice_name": "Academic", - "rate_wpm": 205, - "stability": 0.75, - "similarity_boost": 0.88, - "description": "Measured academic wisdom" - }, - "engineer": { - "voice_id": "YOUR_ENGINEER_VOICE_ID", - "voice_name": "Leader", - "rate_wpm": 212, - "stability": 0.72, - "similarity_boost": 0.88, - "description": "Deliberate leadership presence" - } - } -} -``` - ## Adding Your Own Named Agents 1. Define backstory and personality traits -2. Choose voice settings that match personality -3. Map to your TTS provider's voice ID +2. Add voice entry to `voice-personalities.json` +3. Set ENV var: `ELEVENLABS_VOICE_AGENTNAME=your_voice_id` 4. Document communication style examples Named agents create relationship continuity - the same "person" helping across sessions. diff --git a/Packs/kai-agents-skill/src/skills/Agents/Data/Traits.yaml b/Packs/kai-agents-skill/src/skills/Agents/Data/Traits.yaml index 78f6ce8b00..b874bfd5e1 100644 --- a/Packs/kai-agents-skill/src/skills/Agents/Data/Traits.yaml +++ b/Packs/kai-agents-skill/src/skills/Agents/Data/Traits.yaml @@ -363,91 +363,12 @@ approach: # ============================================================================== # VOICE MAPPINGS - How dynamic agents map to TTS voices # ============================================================================== -# CUSTOMIZE: Replace voice_id values with your TTS provider's voice IDs +# Voice IDs come from ENV vars: ELEVENLABS_VOICE_${name.toUpperCase()} +# Voice settings (stability, similarity_boost) come from voice-personalities.json # voice_mappings: # Default voice when no mapping matches - default: "Default" - default_voice_id: "YOUR_DEFAULT_VOICE_ID" - - # =========================================================================== - # VOICE REGISTRY - Define your available voices - # =========================================================================== - voice_registry: - # --- AUTHORITATIVE VOICES --- - Authoritative: - voice_id: "YOUR_AUTHORITATIVE_VOICE_ID" - characteristics: ["authoritative", "measured", "intellectual"] - description: "Deep authoritative voice for serious analysis" - stability: 0.70 - similarity_boost: 0.85 - - Professional: - voice_id: "YOUR_PROFESSIONAL_VOICE_ID" - characteristics: ["professional", "balanced", "neutral"] - description: "Balanced professional voice for general use" - stability: 0.65 - similarity_boost: 0.80 - - # --- WARM/FRIENDLY VOICES --- - Warm: - voice_id: "YOUR_WARM_VOICE_ID" - characteristics: ["warm", "friendly", "engaging"] - description: "Warm friendly voice for supportive interactions" - stability: 0.50 - similarity_boost: 0.75 - - Gentle: - voice_id: "YOUR_GENTLE_VOICE_ID" - characteristics: ["warm", "calm", "gentle"] - description: "Calm gentle voice for empathetic guidance" - stability: 0.55 - similarity_boost: 0.75 - - # --- ENERGETIC VOICES --- - Energetic: - voice_id: "YOUR_ENERGETIC_VOICE_ID" - characteristics: ["energetic", "excited", "dynamic"] - description: "High-energy voice for enthusiastic delivery" - stability: 0.35 - similarity_boost: 0.65 - - Dynamic: - voice_id: "YOUR_DYNAMIC_VOICE_ID" - characteristics: ["energetic", "fast", "charismatic"] - description: "Fast-paced dynamic voice for exciting content" - stability: 0.40 - similarity_boost: 0.70 - - # --- INTELLECTUAL VOICES --- - Academic: - voice_id: "YOUR_ACADEMIC_VOICE_ID" - characteristics: ["intellectual", "warm", "academic"] - description: "Academic voice for research and analysis" - stability: 0.62 - similarity_boost: 0.80 - - Sophisticated: - voice_id: "YOUR_SOPHISTICATED_VOICE_ID" - characteristics: ["intellectual", "sophisticated", "smooth"] - description: "Sophisticated voice for nuanced discussion" - stability: 0.60 - similarity_boost: 0.80 - - # --- EDGY/DISTINCTIVE VOICES --- - Intense: - voice_id: "YOUR_INTENSE_VOICE_ID" - characteristics: ["edgy", "gravelly", "intense"] - description: "Intense voice for adversarial/security work" - stability: 0.55 - similarity_boost: 0.75 - - Gritty: - voice_id: "YOUR_GRITTY_VOICE_ID" - characteristics: ["edgy", "raspy", "authentic"] - description: "Gritty authentic voice for skeptical analysis" - stability: 0.50 - similarity_boost: 0.70 + default: "pai" # =========================================================================== # TRAIT -> VOICE MAPPINGS @@ -455,84 +376,84 @@ voice_mappings: mappings: # Skeptical combinations -> Intellectual voices - traits: ["contrarian", "skeptical"] - voice: "Intense" + voice: "intense" reason: "Contrarian skepticism needs intensity" - traits: ["skeptical", "analytical"] - voice: "Academic" + voice: "academic" reason: "Skeptical analysis suits academic warmth" - traits: ["skeptical", "meticulous"] - voice: "Sophisticated" + voice: "sophisticated" reason: "Meticulous skepticism suits sophistication" # Creative combinations -> Energetic voices - traits: ["enthusiastic", "creative"] - voice: "Energetic" + voice: "energetic" reason: "Creative enthusiasm needs high energy" - traits: ["creative", "exploratory"] - voice: "Dynamic" + voice: "dynamic" reason: "Creative exploration suits dynamic delivery" # Analytical combinations -> Professional voices - traits: ["analytical", "technical"] - voice: "Authoritative" + voice: "authoritative" reason: "Technical analysis suits authority" - traits: ["analytical", "systematic"] - voice: "Professional" + voice: "professional" reason: "Systematic analysis suits professionalism" # Empathetic combinations -> Warm voices - traits: ["empathetic", "consultative"] - voice: "Warm" + voice: "warm" reason: "Empathetic consulting suits warmth" - traits: ["empathetic", "synthesizing"] - voice: "Gentle" + voice: "gentle" reason: "Synthesizing empathy suits gentleness" # Security combinations -> Edgy voices - traits: ["security", "adversarial"] - voice: "Intense" + voice: "intense" reason: "Security adversary suits intensity" - traits: ["security", "skeptical"] - voice: "Gritty" + voice: "gritty" reason: "Security skepticism suits gritty authenticity" # =========================================================================== # FALLBACKS - Single trait -> voice # =========================================================================== fallbacks: - skeptical: "Academic" - enthusiastic: "Energetic" - analytical: "Authoritative" - cautious: "Professional" - bold: "Intense" - creative: "Dynamic" - empathetic: "Warm" - contrarian: "Intense" - pragmatic: "Professional" - meticulous: "Sophisticated" - - security: "Intense" - legal: "Authoritative" - finance: "Professional" - medical: "Warm" - technical: "Professional" - research: "Academic" - communications: "Dynamic" - business: "Professional" - data: "Academic" - - thorough: "Professional" - rapid: "Dynamic" - systematic: "Professional" - exploratory: "Dynamic" - adversarial: "Intense" - consultative: "Warm" + skeptical: "academic" + enthusiastic: "energetic" + analytical: "authoritative" + cautious: "professional" + bold: "intense" + creative: "dynamic" + empathetic: "warm" + contrarian: "intense" + pragmatic: "professional" + meticulous: "sophisticated" + + security: "intense" + legal: "authoritative" + finance: "professional" + medical: "warm" + technical: "professional" + research: "academic" + communications: "dynamic" + business: "professional" + data: "academic" + + thorough: "professional" + rapid: "dynamic" + systematic: "professional" + exploratory: "dynamic" + adversarial: "intense" + consultative: "warm" # ============================================================================== # COMPOSITION EXAMPLES diff --git a/Packs/kai-agents-skill/src/skills/Agents/Templates/DynamicAgent.hbs b/Packs/kai-agents-skill/src/skills/Agents/Templates/DynamicAgent.hbs index 4bf602324f..fb92aee2e2 100644 --- a/Packs/kai-agents-skill/src/skills/Agents/Templates/DynamicAgent.hbs +++ b/Packs/kai-agents-skill/src/skills/Agents/Templates/DynamicAgent.hbs @@ -36,12 +36,23 @@ How you work on tasks: {{/each}} {{/if}} -{{#if voiceId}} +{{#if voice}} ## Voice Output -Your assigned voice: **{{voice}}** (ID: {{voiceId}}) +Your assigned voice: **{{voice}}** -If voice notifications are enabled, use this voice ID for TTS output. +**REQUIRED Response Format:** +Always end your response with this exact format: + +``` +🎯 **COMPLETED**: [AGENT:{{lowercase voice}}] [Brief summary - max 12 words] +``` + +The `[AGENT:xxx]` tag enables personality-specific voice notifications. + +**Examples:** +- `🎯 **COMPLETED**: [AGENT:{{lowercase voice}}] Found 15 security vulnerabilities in auth module` +- `🎯 **COMPLETED**: [AGENT:{{lowercase voice}}] Analyzed architecture and identified 3 patterns` {{/if}} {{#if task}} diff --git a/Packs/kai-agents-skill/src/skills/Agents/Tools/AgentFactory.ts b/Packs/kai-agents-skill/src/skills/Agents/Tools/AgentFactory.ts index 3d34974933..6d83416967 100644 --- a/Packs/kai-agents-skill/src/skills/Agents/Tools/AgentFactory.ts +++ b/Packs/kai-agents-skill/src/skills/Agents/Tools/AgentFactory.ts @@ -23,6 +23,9 @@ import { readFileSync, existsSync } from "fs"; import { parse as parseYaml } from "yaml"; import Handlebars from "handlebars"; +// Register Handlebars helpers +Handlebars.registerHelper('lowercase', (str: string) => str?.toLowerCase() || ''); + // Paths - adjust PAI_DIR as needed const PAI_DIR = process.env.PAI_DIR || `${process.env.HOME}/.config/pai`; const TRAITS_PATH = `${PAI_DIR}/skills/Agents/Data/Traits.yaml`; diff --git a/Packs/kai-voice-system/README.md b/Packs/kai-voice-system/README.md index 686937257e..7a654698e7 100644 --- a/Packs/kai-voice-system/README.md +++ b/Packs/kai-voice-system/README.md @@ -1,8 +1,8 @@ --- name: Kai Voice System -pack-id: danielmiessler-kai-voice-system-core-v1.3.0 -version: 1.3.0 -author: danielmiessler +pack-id: danielmiessler-kai-voice-system-core-v1.4.0 +version: 1.4.0 +author: [danielmiessler, sti0] description: Voice notification system with multi-provider TTS (Google Cloud or ElevenLabs), prosody enhancement for natural speech, and agent personality-driven voice delivery type: feature purpose-type: [notifications, accessibility, automation] @@ -169,3 +169,20 @@ The prosody enhancer detects emotional context from message patterns: - **kai-hook-system** (required) - Hooks trigger voice notifications - **kai-core-install** (required) - Response format provides 🎯 COMPLETED line - **kai-history-system** - Complementary functionality + +--- + +## Changelog + +### v1.4.0 (2026-01-06) + +- Unified voice system: single source of truth with ENV var voice ID resolution +- Case-insensitive voice name lookup throughout the pipeline +- Fixed markdown bold `**COMPLETED**:` pattern detection in hooks +- Added direct assistant text detection for subagents (not just Task tool results) + +### v1.3.0 + +- Initial release with multi-provider TTS (Google Cloud / ElevenLabs) +- Prosody enhancement pipeline with emotional detection +- Agent personality-driven voice delivery diff --git a/Packs/kai-voice-system/config/voice-personalities.json b/Packs/kai-voice-system/config/voice-personalities.json index 0a602b114b..33010dba26 100644 --- a/Packs/kai-voice-system/config/voice-personalities.json +++ b/Packs/kai-voice-system/config/voice-personalities.json @@ -2,64 +2,96 @@ "default_volume": 0.8, "voices": { "pai": { - "voice_id": "${ELEVENLABS_VOICE_PAI}", - "voice_name": "PAI", "stability": 0.38, "similarity_boost": 0.75, - "description": "Professional, expressive - Daniel's primary AI assistant" + "description": "Professional, expressive - primary AI assistant" + }, + "professional": { + "stability": 0.65, + "similarity_boost": 0.80, + "description": "Balanced professional voice for general use" + }, + "authoritative": { + "stability": 0.70, + "similarity_boost": 0.85, + "description": "Deep authoritative voice for serious analysis" + }, + "academic": { + "stability": 0.62, + "similarity_boost": 0.80, + "description": "Academic voice for research and analysis" + }, + "warm": { + "stability": 0.50, + "similarity_boost": 0.75, + "description": "Warm friendly voice for supportive interactions" + }, + "gentle": { + "stability": 0.55, + "similarity_boost": 0.75, + "description": "Calm gentle voice for empathetic guidance" + }, + "energetic": { + "stability": 0.35, + "similarity_boost": 0.65, + "description": "High-energy voice for enthusiastic delivery" + }, + "dynamic": { + "stability": 0.40, + "similarity_boost": 0.70, + "description": "Fast-paced dynamic voice for exciting content" + }, + "sophisticated": { + "stability": 0.60, + "similarity_boost": 0.80, + "description": "Sophisticated voice for nuanced discussion" + }, + "intense": { + "stability": 0.55, + "similarity_boost": 0.75, + "description": "Intense voice for adversarial/security work" + }, + "gritty": { + "stability": 0.50, + "similarity_boost": 0.70, + "description": "Gritty authentic voice for skeptical analysis" }, "intern": { - "voice_id": "${ELEVENLABS_VOICE_INTERN}", - "voice_name": "Intern", "stability": 0.30, "similarity_boost": 0.85, "description": "Enthusiastic, chaotic energy - eager 176 IQ genius" }, "engineer": { - "voice_id": "${ELEVENLABS_VOICE_ENGINEER}", - "voice_name": "Engineer", "stability": 0.72, "similarity_boost": 0.80, "description": "Wise leader, stable - Fortune 10 principal engineer" }, "architect": { - "voice_id": "${ELEVENLABS_VOICE_ARCHITECT}", - "voice_name": "Architect", "stability": 0.75, "similarity_boost": 0.78, "description": "Wise leader, deliberate - PhD-level system designer" }, "researcher": { - "voice_id": "${ELEVENLABS_VOICE_RESEARCHER}", - "voice_name": "Researcher", "stability": 0.64, "similarity_boost": 0.82, "description": "Analyst, measured - comprehensive research specialist" }, "designer": { - "voice_id": "${ELEVENLABS_VOICE_DESIGNER}", - "voice_name": "Designer", "stability": 0.52, "similarity_boost": 0.80, "description": "Critic, measured - exacting UX/UI specialist" }, "artist": { - "voice_id": "${ELEVENLABS_VOICE_ARTIST}", - "voice_name": "Artist", "stability": 0.20, "similarity_boost": 0.90, "description": "Enthusiast, chaotic - visual content creator" }, "pentester": { - "voice_id": "${ELEVENLABS_VOICE_PENTESTER}", - "voice_name": "Pentester", "stability": 0.18, "similarity_boost": 0.88, "description": "Enthusiast, chaotic - offensive security specialist" }, "writer": { - "voice_id": "${ELEVENLABS_VOICE_WRITER}", - "voice_name": "Writer", "stability": 0.45, "similarity_boost": 0.85, "description": "Professional, expressive - content creation specialist" diff --git a/Packs/kai-voice-system/src/hooks/stop-hook-voice.ts b/Packs/kai-voice-system/src/hooks/stop-hook-voice.ts index f894b7107c..75722c77fa 100644 --- a/Packs/kai-voice-system/src/hooks/stop-hook-voice.ts +++ b/Packs/kai-voice-system/src/hooks/stop-hook-voice.ts @@ -46,9 +46,10 @@ function extractCompletion(text: string, agentType: string = 'pai'): string { text = text.replace(/[\s\S]*?<\/system-reminder>/g, ''); // Look for COMPLETED section + // Format: 🎯 **COMPLETED**: message (colon is AFTER closing asterisks) const patterns = [ - /🎯\s*\*{0,2}COMPLETED:?\*{0,2}\s*(.+?)(?:\n|$)/i, - /\*{0,2}COMPLETED:?\*{0,2}\s*(.+?)(?:\n|$)/i + /🎯\s*\*{0,2}COMPLETED\*{0,2}:?\s*(.+?)(?:\n|$)/i, + /\*{0,2}COMPLETED\*{0,2}:?\s*(.+?)(?:\n|$)/i ]; for (const pattern of patterns) { diff --git a/Packs/kai-voice-system/src/hooks/subagent-stop-hook-voice.ts b/Packs/kai-voice-system/src/hooks/subagent-stop-hook-voice.ts index 21830193aa..b84824489d 100644 --- a/Packs/kai-voice-system/src/hooks/subagent-stop-hook-voice.ts +++ b/Packs/kai-voice-system/src/hooks/subagent-stop-hook-voice.ts @@ -18,6 +18,25 @@ async function delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } +/** + * Convert Claude content array to plain text + */ +function contentToText(content: unknown): string { + if (typeof content === 'string') return content; + if (Array.isArray(content)) { + return content + .map(c => { + if (typeof c === 'string') return c; + if (c?.text) return c.text; + if (c?.content) return contentToText(c.content); + return ''; + }) + .join(' ') + .trim(); + } + return ''; +} + async function findTaskResult(transcriptPath: string, maxAttempts: number = 2): Promise<{ result: string | null; agentType: string | null; @@ -52,6 +71,7 @@ async function findTaskResult(transcriptPath: string, maxAttempts: number = 2): const transcript = readFileSync(actualTranscriptPath, 'utf-8'); const lines = transcript.trim().split('\n'); + // Strategy 1: Look for Task tool results (nested subagents) for (let i = lines.length - 1; i >= 0; i--) { try { const entry = JSON.parse(lines[i]); @@ -92,6 +112,24 @@ async function findTaskResult(transcriptPath: string, maxAttempts: number = 2): // Skip invalid lines } } + + // Strategy 2: Look for direct assistant text with COMPLETED (direct responses) + // Search backwards to find the last assistant message with a completion phrase + for (let i = lines.length - 1; i >= 0; i--) { + try { + const entry = JSON.parse(lines[i]); + + if (entry.type === 'assistant' && entry.message?.content) { + const text = contentToText(entry.message.content); + // Check if this message contains a COMPLETED phrase + if (text && /COMPLETED/i.test(text)) { + return { result: text, agentType: 'default', description: null }; + } + } + } catch (e) { + // Skip invalid lines + } + } } catch (e) { // Will retry } @@ -102,9 +140,10 @@ async function findTaskResult(transcriptPath: string, maxAttempts: number = 2): function extractCompletionMessage(taskOutput: string): { message: string | null; agentType: string | null } { // Look for COMPLETED section with agent tag + // Format: 🎯 **COMPLETED**: [AGENT:xxx] message (handles markdown bold) const agentPatterns = [ - /🎯\s*COMPLETED:\s*\[AGENT:(\w+[-\w]*)\]\s*(.+?)(?:\n|$)/is, - /COMPLETED:\s*\[AGENT:(\w+[-\w]*)\]\s*(.+?)(?:\n|$)/is, + /🎯\s*\*{0,2}COMPLETED\*{0,2}:?\s*\[AGENT:(\w+[-\w]*)\]\s*(.+?)(?:\n|$)/is, + /\*{0,2}COMPLETED\*{0,2}:?\s*\[AGENT:(\w+[-\w]*)\]\s*(.+?)(?:\n|$)/is, /🎯.*COMPLETED.*\[AGENT:(\w+[-\w]*)\]\s*(.+?)(?:\n|$)/is, ]; @@ -135,10 +174,10 @@ function extractCompletionMessage(taskOutput: string): { message: string | null; } } - // Fallback patterns + // Fallback patterns (handles markdown bold) const genericPatterns = [ - /🎯\s*COMPLETED:\s*(.+?)(?:\n|$)/i, - /COMPLETED:\s*(.+?)(?:\n|$)/i, + /🎯\s*\*{0,2}COMPLETED\*{0,2}:?\s*(.+?)(?:\n|$)/i, + /\*{0,2}COMPLETED\*{0,2}:?\s*(.+?)(?:\n|$)/i, ]; for (const pattern of genericPatterns) { @@ -227,20 +266,17 @@ async function main() { process.exit(0); } - // Determine agent type + // Determine agent type (voice name) const finalAgentType = extractedAgentType || agentType || 'default'; - // Get voice ID for this agent type - const voiceId = getVoiceId(finalAgentType); - - // Send voice notification + // Send voice notification - server resolves voice_id from ENV const agentName = finalAgentType.charAt(0).toUpperCase() + finalAgentType.slice(1); await sendNotification({ title: agentName, message: completionMessage, voice_enabled: true, - voice_id: voiceId + voice_id: finalAgentType // Pass voice NAME, server does ENV lookup }); process.exit(0); diff --git a/Packs/kai-voice-system/src/voice/server.ts b/Packs/kai-voice-system/src/voice/server.ts index 4336732cea..8a8dc2a867 100644 --- a/Packs/kai-voice-system/src/voice/server.ts +++ b/Packs/kai-voice-system/src/voice/server.ts @@ -11,8 +11,32 @@ import { join } from "path"; import { existsSync, readFileSync } from "fs"; // Load .env from PAI directory (single source of truth for all API keys) -const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); -const envPath = join(paiDir, '.env'); +// Fallback chain: PAI_DIR env → parent of script dir → ~/.config/pai +function resolveEnvPath(): { paiDir: string; envPath: string; source: string } { + // 1. Explicit PAI_DIR environment variable + if (process.env.PAI_DIR) { + const envPath = join(process.env.PAI_DIR, '.env'); + if (existsSync(envPath)) { + return { paiDir: process.env.PAI_DIR, envPath, source: 'PAI_DIR env' }; + } + } + + // 2. Parent directory of script (server.ts is typically in $PAI_DIR/voice-server/) + const scriptDir = import.meta.dir; + const parentDir = join(scriptDir, '..'); + const parentEnvPath = join(parentDir, '.env'); + if (existsSync(parentEnvPath)) { + return { paiDir: parentDir, envPath: parentEnvPath, source: 'script parent dir' }; + } + + // 3. Default fallback ~/.config/pai + const defaultDir = join(homedir(), '.config', 'pai'); + return { paiDir: defaultDir, envPath: join(defaultDir, '.env'), source: 'default (~/.config/pai)' }; +} + +const { paiDir, envPath, source: envSource } = resolveEnvPath(); +let envLoaded = false; + if (existsSync(envPath)) { const envContent = await Bun.file(envPath).text(); envContent.split('\n').forEach(line => { @@ -21,8 +45,13 @@ if (existsSync(envPath)) { process.env[key.trim()] = value.trim(); } }); + envLoaded = true; } +// Startup logging for debugging +console.log(`📂 PAI_DIR: ${paiDir} (${envSource})`); +console.log(`📄 .env: ${envLoaded ? 'loaded' : 'NOT FOUND'} (${envPath})`) + const PORT = parseInt(process.env.PAI_VOICE_PORT || "8888"); // ============================================================================= @@ -123,10 +152,17 @@ function extractEmotionalMarker(message: string): { cleaned: string; emotion?: s return { cleaned: message }; } -// Get voice configuration by voice ID or agent name +// Get voice configuration by voice ID or agent name (case-insensitive) function getVoiceConfig(identifier: string): VoiceConfig | null { if (!voicesConfig) return null; - if (voicesConfig.voices[identifier]) return voicesConfig.voices[identifier]; + + // Normalize to lowercase for lookup + const normalizedId = identifier.toLowerCase(); + + // Direct match (lowercase key) + if (voicesConfig.voices[normalizedId]) return voicesConfig.voices[normalizedId]; + + // Search by voice_id for (const config of Object.values(voicesConfig.voices)) { if (config.voice_id === identifier) return config; } @@ -366,6 +402,10 @@ async function sendNotification( const voice = voiceId || DEFAULT_VOICE_ID; const voiceConfig = getVoiceConfig(voice); + // Resolve actual voice_id from environment variable + const envKey = `ELEVENLABS_VOICE_${voice.toUpperCase()}`; + const resolvedVoiceId = process.env[envKey] || voice; + // Determine voice settings (priority: emotional > personality > defaults) let voiceSettings = { stability: 0.5, similarity_boost: 0.5 }; @@ -380,9 +420,9 @@ async function sendNotification( console.log(`👤 Personality: ${voiceConfig.description}`); } - console.log(`🎙️ Generating speech (voice: ${voice}, stability: ${voiceSettings.stability})`); + console.log(`🎙️ Generating speech (voice: ${voice} → ${resolvedVoiceId}, stability: ${voiceSettings.stability})`); - const audioBuffer = await generateSpeech(safeMessage, voice, voiceSettings); + const audioBuffer = await generateSpeech(safeMessage, resolvedVoiceId, voiceSettings); await playAudio(audioBuffer); } catch (error) { console.error("Failed to generate/play speech:", error); diff --git a/README.md b/README.md index bcb3c6ce36..5fe9616e35 100644 --- a/README.md +++ b/README.md @@ -222,84 +222,6 @@ The packs are extracted from Kai - real capabilities that have been running in p --- -## How PAI Works - -This section explains the technical architecture, installation process, and runtime mechanics of the Personal AI Infrastructure (PAI) system. - -### 1. High-Level Architecture - -PAI is not a standalone application but a **configuration and automation layer** that sits on top of **Claude Code** (Anthropic's CLI agent). It transforms a generic AI agent into a personalized system with persistent memory, security controls, and defined skills. - -The architecture consists of three main layers: - -1. **The Engine (Claude Code)**: The underlying AI agent that executes commands and processes prompts. -2. **The Middleware (Hooks)**: A system of event listeners that intercept Claude Code's operations (like tool use, session start) to enforce security, inject context, and log activity. -3. **The Content (Packs)**: Modular bundles of markdown files and scripts that define "Skills" (workflows), "Tools" (executable code), and "Identity" (system prompts). - -### 2. The Hook System (The "Magic") - -The core mechanism that makes PAI work is the **Hook System**. Claude Code has a native capability to run scripts when certain events occur. PAI leverages this to inject its logic. - -**How Hooks Work:** -1. **Configuration**: Hooks are registered in `~/.claude/settings.json`. This file maps events (like `PreToolUse`) to specific scripts. -2. **Events**: - * `SessionStart`: Fires when you open Claude Code. PAI uses this to load your "CORE" skill and context. - * `PreToolUse`: Fires before the AI runs a command (e.g., `bash`, `edit`). PAI uses this for **Security Validation** (blocking `rm -rf`, etc.). - * `PostToolUse`: Fires after a command. Used for logging and observability. - * `UserPromptSubmit`: Fires when you type a message. Used to update terminal tab titles. -3. **Execution**: When an event fires, Claude Code runs the corresponding TypeScript script (using `bun`) located in `~/.claude/hooks/`. -4. **Communication**: The script receives event data via `stdin` (JSON) and can control the outcome (e.g., allow or block a command) via exit codes or `stdout`. - -### 3. Installation Process - -The installation is a two-phase process: **Bootstrapping** (Manual) and **Pack Installation** (AI-Driven). - -**Phase 1: Bootstrapping (The `install.ts` script)** -The user runs `bun run Bundles/Kai/install.ts`. This script **does not** install the full system. Instead, it: -1. **Creates Directory Structure**: Sets up `~/.claude/` (or `$PAI_DIR`) with folders for `skills`, `hooks`, `history`, etc. -2. **Generates Config Files**: Creates `SKILL.md`, `Contacts.md`, and `CoreStack.md` with user preferences (name, timezone). -3. **Sets Environment Variables**: Updates `.zshrc` or `.bashrc` with `DA` (Assistant Name), `PAI_DIR`, etc. -4. **Updates `settings.json`**: Injects environment variables into Claude Code's settings. - -**Crucially, this phase does not install the hooks or skills.** It prepares the environment for the AI to do it. - -**Phase 2: Pack Installation (AI-Driven)** -The user is instructed to "give each pack file to your AI". This is where the actual installation happens. -1. **User Action**: The user pastes the content of a pack file (e.g., `Packs/kai-hook-system.md`) into Claude Code. -2. **AI Execution**: The pack file contains natural language instructions and code blocks. The AI reads these instructions and: - * **Writes Files**: Creates the TypeScript hook files (e.g., `hooks/security-validator.ts`) and skill definitions. - * **Configures System**: Updates `settings.json` to register the new hooks. - * **Verifies**: Runs verification commands to ensure the pack is working. - -This "Inception-style" installation (using the AI to build the AI's infrastructure) ensures that the system is self-documenting and the AI "knows" about its own components. - -### 4. Runtime Flow - -Here is what happens when you use PAI: - -1. **Start**: You run `claude`. -2. **Initialization (`SessionStart`)**: - * Claude Code fires `SessionStart`. - * `hooks/initialize-session.ts` runs. - * `hooks/load-core-context.ts` runs. It reads `skills/CORE/SKILL.md` and injects it into the context. Now the AI knows who it is and what skills it has. -3. **User Interaction**: You ask "Create a new blog post". -4. **Routing**: The AI (guided by the injected `SKILL.md`) recognizes this matches a skill (e.g., `CreateContent`). It loads the specific workflow for that skill. -5. **Execution (`PreToolUse`)**: The AI decides to run a command (e.g., `touch blog.md`). - * Claude Code fires `PreToolUse`. - * `hooks/security-validator.ts` runs. It checks if `touch blog.md` is safe. - * If safe (Exit Code 0), the command runs. - * If unsafe (Exit Code 1+), the command is blocked. -6. **Completion**: The AI finishes the task and updates its memory (via `kai-history-system` hooks). - -### 5. Key Components - -* **`kai-hook-system`**: The engine room. Provides the event bus and security layer. -* **`kai-core-install`**: The brain. Defines the "CORE" skill, identity, and routing logic. -* **`kai-history-system`**: The memory. Captures session data and learnings. -* **`kai-voice-system`**: (Optional) Adds voice capabilities via ElevenLabs. - ---- - ## 📦 Available Packs ### Features (Architectural Systems) diff --git a/docs/plans/2026-01-05-voice-server-extra-args-design.md b/docs/plans/2026-01-05-voice-server-extra-args-design.md deleted file mode 100644 index cdc8cfd418..0000000000 --- a/docs/plans/2026-01-05-voice-server-extra-args-design.md +++ /dev/null @@ -1,188 +0,0 @@ -# Voice Server Extra Args Configuration - -**Date:** 2026-01-05 -**Status:** Approved -**Package:** kai-voice-system - -## Problem - -The voice server hardcodes audio player arguments, making it impossible to configure platform-specific settings like PulseAudio output (`-o pulse`) needed for container environments. - -## Solution - -Add `VOICE_SERVER_EXTRA_ARGS` configuration that appends custom arguments to all audio players. - -## Configuration - -### Environment Variable -```bash -# In $PAI_DIR/.env -VOICE_SERVER_EXTRA_ARGS="-o pulse" -``` - -### CLI Flag -```bash -bun run server.ts --extra-args="-o pulse" -``` - -### Precedence -CLI `--extra-args` > `VOICE_SERVER_EXTRA_ARGS` env var > empty - -## Implementation - -### 1. Add `findPlayer()` function (replaces hardcoded paths) - -```typescript -import { execSync } from 'child_process'; - -function findPlayer(name: string): string | null { - try { - return execSync(`which ${name}`, { encoding: 'utf8' }).trim() || null; - } catch { - return null; - } -} -``` - -### 2. Add CLI argument parsing - -```typescript -import { parseArgs } from 'util'; - -const cliArgs = parseArgs({ - args: process.argv.slice(2), - options: { - 'extra-args': { type: 'string', default: '' }, - }, -}); -``` - -### 3. Add `getExtraArgs()` function - -```typescript -function getExtraArgs(): string[] { - const raw = cliArgs.values['extra-args'] || process.env.VOICE_SERVER_EXTRA_ARGS || ''; - return raw.trim() ? raw.trim().split(/\s+/) : []; -} -``` - -### 4. Update `playAudio()` function - -**Before (hardcoded paths, separate if blocks):** -```typescript -if (existsSync('/usr/bin/mpg123')) { - player = '/usr/bin/mpg123'; - args = ['-q', tempFile]; -} else if (existsSync('/usr/bin/mpv')) { - player = '/usr/bin/mpv'; - args = ['--no-terminal', tempFile]; -} else if (existsSync('/snap/bin/mpv')) { - player = '/snap/bin/mpv'; - args = ['--no-terminal', tempFile]; -} -``` - -**After (dynamic detection with extra args):** -```typescript -// Linux: use which to find player -const mpg123 = findPlayer('mpg123'); -const mpv = findPlayer('mpv'); - -if (mpg123) { - player = mpg123; - playerArgs = ['-q', tempFile, ...getExtraArgs()]; -} else if (mpv) { - player = mpv; - playerArgs = ['--no-terminal', tempFile, ...getExtraArgs()]; -} - -// Mac: afplay (always at /usr/bin/afplay) -if (process.platform === 'darwin') { - player = '/usr/bin/afplay'; - playerArgs = [tempFile, ...getExtraArgs()]; -} -``` - -### 5. Add startup logging - -```typescript -const extraArgs = getExtraArgs(); -if (extraArgs.length > 0) { - console.log(`[Voice Server] Extra player args: ${extraArgs.join(' ')}`); -} -``` - -## Files Changed - -| File | Changes | -|------|---------| -| `src/voice/server.ts` | Add findPlayer(), getExtraArgs(), CLI parsing, update playAudio() | -| `README.md` | Document VOICE_SERVER_EXTRA_ARGS configuration | - -## README Addition - -Add to README.md after "Google Cloud TTS Voices" section: - -````markdown -### Audio Player Arguments - -Configure extra arguments for audio players (useful for containers or specific audio setups): - -**Environment Variable:** -```bash -# In $PAI_DIR/.env -VOICE_SERVER_EXTRA_ARGS="-o pulse" -``` - -**CLI Flag (overrides env var):** -```bash -bun run server.ts --extra-args="-o pulse" -``` - -**Common use cases:** -| Use Case | Configuration | -|----------|---------------| -| Container with PulseAudio | `VOICE_SERVER_EXTRA_ARGS="-o pulse"` | -| Specific ALSA device | `VOICE_SERVER_EXTRA_ARGS="-o alsa -a hw:1,0"` | - -### Devcontainer Setup - -To use voice notifications inside a devcontainer: - -1. **Mount PulseAudio socket** from host in `devcontainer.json` -2. **Set `VOICE_SERVER_EXTRA_ARGS`** in your `.env` file - -**Example `devcontainer.json`:** -```json -{ - "name": "My Devcontainer", - "image": "mcr.microsoft.com/devcontainers/python:2-3.12-bullseye", - "mounts": [ - "source=/run/user/1000/pulse,target=/run/user/1000/pulse,type=bind" - ], - "runArgs": [ - "--device=/dev/snd:/dev/snd" - ], - "containerEnv": { - "PULSE_SERVER": "unix:/run/user/1000/pulse/native" - } -} -``` - -**In your `$PAI_DIR/.env`:** -```bash -VOICE_SERVER_EXTRA_ARGS="-o pulse" -``` - -**Key points:** -- Mount `/run/user/1000/pulse` to access host's PulseAudio -- Set `PULSE_SERVER` in container so audio apps find the socket -- Set `VOICE_SERVER_EXTRA_ARGS` in `.env` (not devcontainer.json) -```` - -## Benefits - -1. **Flexibility**: Configure audio output without code changes -2. **Container support**: Enable PulseAudio/ALSA in containerized environments -3. **Simplified code**: `which` replaces hardcoded paths -4. **Consistent**: Same extra args apply to all players (mpg123, mpv, afplay)