# Smoke test — voice plugin vs daemon real

End-to-end verification: WhatsApp inbound → voice reply via plugin.

## Prereqs

- `agent-creator-microapp` already running with WhatsApp paired.
- Daemon discovers plugins from `.dev-state/plugins/`.
- Plugin binary already installed at
  `agent-creator-microapp/.dev-state/plugins/voice-0.1.0/bin/nexo-plugin-voice`
  (done by bloqueador A setup).
- Operator config at
  `agent-creator-microapp/.dev-state/config/plugins/voice.yaml`.

## Steps

### 1. Verify discovery + spawn

Restart the daemon (or hit the admin restart endpoint). Watch stderr
for these log lines:

```
INFO  nexo_plugin_voice: voice plugin: applying configured settings
INFO  voice.auto_discovery: subscriber up topic=plugin.voice.http.request
INFO  voice.auto_discovery: subscriber up topic=plugin.voice.admin.>
INFO  voice.auto_discovery: subscriber up topic=plugin.voice.metrics.scrape
INFO  nexo_plugin_voice: voice subprocess plugin ready providers=1
```

If you see "smoke boot — NEXO_BROKER_URL not set" the daemon is not
seeding the env var. Check the daemon's plugin spawn path.

### 2. Verify tools registered

```bash
curl -s http://localhost:8080/admin/tools | jq '.tools[] | select(.name | startswith("voice_")) | .name'
```

Expected: 6 entries (`voice_inbound_transform`,
`voice_language_style_inbound_transform`, `voice_reply_transform`,
`voice_list_voices`, `voice_set`, `voice_unset`).

### 3. List voices

```bash
curl -s http://localhost:8080/api/voice/voices?locale=es-CO | jq '.voices[].id'
```

Expected: `"es-CO-SalomeNeural"` and any other es-CO entries.

### 4. Activate voice for a conversation

Pick a WhatsApp conversation key. Format used by the microapp:
`<agent_id>:session:<phone_jid>` — find one via
`/api/conversations` or your dashboard. Then:

```bash
curl -s -X PUT http://localhost:8080/api/voice \
  -H 'content-type: application/json' \
  -d '{
    "conversation_key": "<key from above>",
    "enabled": true,
    "voice_id": "es-CO-SalomeNeural",
    "provider_id": "edge"
  }' | jq .
```

Expected: `{"ok": true, "row": {...}}`.

### 5. Verify state

```bash
curl -s "http://localhost:8080/api/voice?conversation_key=<key>" | jq .
```

Expected: `{ enabled: true, voice_id: "es-CO-SalomeNeural", ... }`.

### 6. Send a WhatsApp inbound

From your phone, message the bot. The agent's reply should arrive
as a voice note (PTT bubble in WhatsApp).

### 7. Check audit events

```bash
curl -s http://localhost:8080/api/firehose/recent?topic=agent.event.voice.synth.attempt | jq .
```

Expected: at least one entry with `reason_code: "success"`,
`provider: "edge"`, your `conversation_key`.

### 8. Check Prometheus metrics

```bash
curl -s http://localhost:8080/metrics | grep '^nexo_voice_'
```

Expected: counters incremented (`synth_attempts_total{provider="edge",reason="success"}`
≥ 1, `synth_duration_seconds_count` ≥ 1).

### 9. Deactivate

```bash
curl -s -X PUT http://localhost:8080/api/voice \
  -H 'content-type: application/json' \
  -d '{"conversation_key": "<key>", "enabled": false}' | jq .
```

Next inbound → text reply (passthrough). Audit event for
`voice.toggle.changed { enabled: false, source: "ui" }`.

## Notes

- The existing `agent-creator-microapp/src/voice_mode/` legacy tools
  also register on the same daemon. They run BEFORE the plugin's
  tools in the transform chain. Since they read a DIFFERENT SQLite
  store (microapp's firehose pool, key `voice_mode` table), they
  pass through any conversation where the operator did NOT also
  toggle via the legacy `/api/voice_mode` endpoint.
- Operator should toggle ONLY via the plugin's `/api/voice` during
  the transition. Fase P (voice-plugin-extract) removes the legacy
  tools entirely.
- Reset state between runs:
  ```bash
  rm -rf <state_dir>/voice.db*
  ```

## Failure modes

| Symptom | Likely cause |
|---|---|
| Plugin doesn't appear in `/admin/tools` | Discovery path missing — check `.dev-state/config/plugins/discovery.yaml` |
| Plugin spawned but tools return `Unavailable` | Boot ran but broker connect failed — check `NEXO_BROKER_URL` env in spawned process |
| Voice toggle persists but reply still text | Legacy `voice_mode_reply_transform` ran AFTER plugin's, overwrote VoiceNote → Text. Check chain order in `/admin/tools` |
| Voice synth fails with `EmptyAudio` repeatedly | Edge endpoint flake — circuit breaker will open after 5 fails (60s backoff). Check `/admin/voice/stats`. |
| Audio plays but distorted | Provider returned non-mp3 / non-24kHz — check Edge's `audio_format` env override |
