npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@robottwo/openclaw-tts-voice-router

v0.1.0

Published

OpenClaw plugin that routes TTS synthesis to agent-specific voice configurations using text similarity correlation

Readme

🎙️ openclaw-tts-voice-router

CI Node.js License

An OpenClaw plugin that gives each agent its own voice. When multiple agents share a conversation (e.g., a group chat with Robby, 3PO, and a main assistant), this plugin automatically routes TTS synthesis to the correct voice for whichever agent just spoke — no manual configuration needed.

The Problem

In multi-agent setups, all TTS output typically uses a single voice. If Robby the Robot and a human-sounding assistant both reply in the same group chat, they sound identical when read aloud. There is no built-in mechanism to match a TTS request back to the agent that generated the text.

The Solution

This plugin correlates LLM output with TTS requests using text similarity matching:

┌─────────────┐       llm_output        ┌────────────────────┐
│   LLM       │ ──────────────────────► │  Correlation Store │
│  (any agent)│   agentId + text        │  (trigram index)   │
└─────────────┘                          └────────┬───────────┘
                                                   │
                                                   │ findBestMatch()
                                                   │ trigram Jaccard similarity
                                                   ▼
┌─────────────┐     synthesize(text)    ┌────────────────────┐
│   TTS       │ ◄────────────────────── │  Voice Router      │
│  Pipeline   │  voiceId + settings     │  (proxy provider)  │
└─────────────┘                          └────────────────────┘
  1. The llm_output hook fires after every LLM turn, capturing the agentId and the assistant's text
  2. Text is normalized (strip markdown, TTS directives, collapse whitespace) and indexed as trigrams
  3. When TTS is requested, the incoming text is compared against stored entries using trigram Jaccard similarity
  4. If a match exceeds the threshold (default 0.7), the request is routed to that agent's configured voice
  5. The matched entry is evicted — each correlation is used exactly once

Installation

openclaw plugins install robottwo/openclaw-tts-voice-router

Or clone and build locally:

git clone https://github.com/robottwo/openclaw-tts-voice-router.git
cd openclaw-tts-voice-router
npm install
npm run build

Configuration

Minimal Setup

Give a single agent a custom voice while all others use the default:

plugins:
  entries:
    tts-voice-router:
      config:
        agents:
          robby:
            voiceId: "21m00Tcm4TlvDq8ikWAM"  # ElevenLabs "Rachel"

Multi-Agent Setup

Different voices for different agents in a group chat:

plugins:
  entries:
    tts-voice-router:
      config:
        agents:
          robby:
            voiceId: "pNInz6obpgDQGcFmaJgB"  # ElevenLabs "Adam" — robotic tone
            voiceSettings:
              stability: 0.5
              similarity_boost: 0.75
          main:
            voiceId: "21m00Tcm4TlvDq8ikWAM"  # ElevenLabs "Rachel" — natural voice
          3po:
            voiceId: "AZnzlk1XvdvUeBnXmlld"  # ElevenLabs "Domi" — warm, friendly
            modelId: "eleven_multilingual_v2"
        defaultAgent: "main"  # Fallback when correlation misses
        debug: true

Different Providers Per Agent

Route specific agents to different TTS backends:

plugins:
  entries:
    tts-voice-router:
      config:
        agents:
          robby:
            providerId: "openai"     # OpenAI TTS
            voiceId: "onyx"
            modelId: "tts-1-hd"
          main:
            providerId: "elevenlabs"  # ElevenLabs
            voiceId: "21m00Tcm4TlvDq8ikWAM"
          3po:
            providerId: "google"      # Google Cloud TTS
            voiceId: "en-GB-Standard-A"
        defaultAgent: "main"

Reference

| Option | Type | Default | Description | |--------|------|---------|-------------| | agents | object | (required) | Per-agent voice configuration. Keys are agent IDs. | | agents.<id>.voiceId | string | — | Voice ID for this agent | | agents.<id>.providerId | string | (global) | TTS provider override for this agent | | agents.<id>.modelId | string | — | Model ID override (e.g., eleven_multilingual_v2) | | agents.<id>.voiceSettings | object | — | Provider-specific voice settings | | agents.<id>.normalization | boolean | true | Enable voice normalization for this agent | | defaultAgent | string | — | Fallback agent ID when no correlation match is found | | minSimilarity | number | 0.7 | Minimum Jaccard similarity threshold (0.1–1.0) | | ttlMs | number | 60000 | How long correlation entries live (milliseconds) | | maxEntries | number | 50 | Maximum stored correlation entries | | debug | boolean | false | Log matching decisions and similarity scores |

Architecture

src/
├── index.ts              # Plugin entry point — hooks + provider registration
├── normalize.ts          # Text normalization (replicates OpenClaw's TTS pipeline)
├── correlation.ts        # Trigram extraction, Jaccard similarity, correlation store
├── voice-router-provider.ts  # Proxy SpeechProviderPlugin with per-agent routing
├── types.ts              # Internal types
└── openclaw-types.d.ts   # Ambient declarations for plugin SDK

test/
├── normalize.test.ts     # 17 tests — markdown stripping, directive removal
├── correlation.test.ts   # 27 tests — trigram Jaccard, store, TTL, eviction
├── voice-router-provider.test.ts  # 14 tests — routing, fallback, debug logging
└── types.test.ts         # 6 tests  — type smoke tests

64 tests, 0 dependencies (Node.js stdlib only for the matching algorithm).

Development

# Install
npm install

# Run all checks (lint + typecheck + test)
npm run check

# Individual commands
npm run lint          # oxlint
npm run typecheck     # tsc --noEmit
npm run test          # vitest
npm run build         # tsc (outputs to dist/)

Running with Ralph

The repo includes a ralph.yml configuration (excluded from version control) for Ralph Orchestrator development loops. To run:

ralph run -c ralph.yml -H builtin:code-assist

Contributing

Contributions are welcome! This is an open-source plugin for the OpenClaw ecosystem.

Getting Started

  1. Fork the repository
  2. Clone your fork: git clone https://github.com/<your-username>/openclaw-tts-voice-router.git
  3. Install dependencies: npm install
  4. Run checks: npm run check — all must pass

Making Changes

  • One logical change per commit — keep the history clean and reviewable
  • Tests first — write failing tests before implementation (TDD)
  • Follow existing patterns — the codebase uses oxlint rules consistent with the OpenClaw project
  • No external dependencies — the correlation algorithm uses only Node.js stdlib
  • TypeScript strict modetsc --noEmit must pass with zero errors

Code Style

  • Curly braces required on all if/for/while blocks (enforced by oxlint)
  • No any types except in test files (enforced by oxlint)
  • ESM only"type": "module" in package.json
  • Full forms — no contractions in code comments

Pull Request Process

  1. Create a feature branch from main
  2. Make your changes with passing tests
  3. Run npm run check locally — CI runs the same pipeline
  4. Open a PR with a clear description of the change
  5. CI must pass on Node.js 20 and 22 before merge

Reporting Issues

Please open a GitHub issue with:

  • What happened — the observed behavior
  • What you expected — the desired behavior
  • Reproduction steps — config, agent setup, minimal example
  • Logs — enable debug: true and include the matching output

License

MIT