# WAHA OpenClaw Plugin — Lessons Learned

## Architecture Lessons

### OpenClaw Gateway Target Routing (CRITICAL)
- `MESSAGE_ACTION_TARGET_MODE` is HARDCODED in the gateway. Plugins CANNOT extend it.
- Only actions listed in this map can accept targets. Everything else gets mode "none".
- When mode="none" and LLM passes a target → "Action X does not accept a target" error.
- The plugin code NEVER runs — gateway rejects before dispatch.
- **Solution**: Use gateway-recognized action names. For search/listing, use `search` (mode "none") and teach the LLM to pass query as parameter, not target.
- **Solution for standard ops**: Map custom WAHA operations to standard action names (poll, send, edit, etc.)

### Action Names Matter
- v1.8.x bug: returning ALL_ACTIONS from listActions() — gateway rejected custom names with targets.
- Fix: Return only STANDARD_ACTIONS + curated UTILITY_ACTIONS.
- Custom WAHA names (sendPoll, editMessage) have mode "none" — can't accept targets.
- The LLM sees action names from listActions() — keep the list curated (~50 tokens per action).

### Plugin Property Names
- ChannelPlugin type requires `actions` property, not `messageActions`. Renaming fixed visibility of all 80+ actions.

### Two Separate Message Paths
- `handleAction` (channel.ts) = action dispatch for agent TOOL CALLS (send, poll, resolveTarget, etc.)
- `deliverWahaReply` (inbound.ts) = reply delivery for CONVERSATION RESPONSES (text+voice)
- These are COMPLETELY SEPARATE. Changes to one don't affect the other.
- Voice messages go through deliverWahaReply → sendWahaMediaBatch, NOT through handleAction.

### Config Caching
- `readConfigFile()` can crash in outbound adapter context.
- `getCachedConfig()` caches config from handleAction for use by outbound methods.
- Without this, sendText/sendMedia/sendPoll all fail with "readConfigFile is not a function".

## Bug Fix History

### vCard Sending (v1.9.5)
- `[CONTACT:Name:Phone]` pattern was in `sendWahaText` but never fired.
- Sammie's replies go through `sendWahaMediaBatch` (media path), bypassing `sendWahaText`.
- Fix: Moved vCard interception to `deliverWahaReply` in inbound.ts, BEFORE the media/text branch.
- DO NOT MOVE back to sendWahaText — it must stay in deliverWahaReply.

### Media Sending (v1.9.3)
- Images/videos sent as file attachments instead of proper media.
- Root causes:
  1. `resolveMime()` failed on URLs with query params — fixed with URL parsing
  2. Extensionless URLs fell to sendFile — added HTTP HEAD fallback + default to sendImage
  3. Outbound adapter crashed — fixed with getCachedConfig() caching
- Added explicit sendImage/sendVideo/sendFile actions that bypass MIME detection.

### Image Analysis (v1.9.2)
- Plugin saved temp files to `/tmp/waha-media-*` — blocked by OpenClaw's `isInboundPathAllowed()`.
- Must save to `/tmp/openclaw/` (allowed root).
- Switched from custom LiteLLM vision to native `applyMediaUnderstanding()` pipeline.

### Poll Creation (v1.8.2)
- `handleAction` poll handler extracted chatId only from `params.chatId`.
- Gateway passes target as `params.to` or `toolContext.currentChannelId`.
- Fixed with 3-source fallback chain: params.to → params.chatId → toolContext.currentChannelId.

### Target Resolution (v1.10.0–v1.10.4)
- Auto name-to-JID resolution for standard actions via `autoResolveTarget()`.
- `looksLikeId` returns true for ALL non-empty strings (accepts names, not just JIDs).
- Utility actions (getGroups, resolveTarget) fail when LLM passes targets.
- Added `search` action (gateway-recognized, mode "none") — LLM naturally uses parameters.
- 30s TTL cache prevents WAHA API hammering on repeated resolves.

### Directory Issues (v1.8.3–v1.8.7)
- WAHA groups API returns dict (not array) — `toArr()` helper converts.
- `@s.whatsapp.net` normalized to `@c.us` to eliminate duplicates.
- Newsletter names resolved via `/channels` API.
- chats/overview used as fallback name source when contacts API returns 404.

## Deployment Lessons

### Always Deploy to BOTH Locations
- `~/.openclaw/extensions/waha/` (runtime — loaded by gateway)
- `~/.openclaw/workspace/skills/waha-openclaw-channel/` (dev — source for reinstalls)
- Missing either causes stale code to run or reinstalls to overwrite fixes.

### Gateway Restart Required
- File changes are NOT hot-reloaded. Must restart: `systemctl --user restart openclaw-gateway`
- Gateway takes ~10s to fully start. Check logs for errors.

### npm Publish Before Deploy
- Publish to npm FIRST, then deploy files to hpg6.
- Version must be bumped each time (npm rejects duplicate versions).

### WAHA Session Config
- PUT API REPLACES entire config — omitting a field DELETES it.
- Always include ALL fields: webhooks, noweb.store, markOnline, metadata.
- `.env` only sets defaults for NEW sessions — existing sessions need PUT API.

## WAHA Engine Limitations (NOWEB)
- poll.vote webhooks: <5% capture rate (vs WEBJS ~100%)
- Contacts API returns 404 without `noweb.store.enabled=true`
- Groups API returns dict, not array
- `/contacts/lids` returns dict, not array
- pinMessage and sendTextStatus may not work in all WAHA builds
- No webhook for group member join/leave events
- No scheduled message API
- Contacts API is read-only (Linked Device restriction)

## OpenClaw Platform Notes
- `tools.alsoAllow: ["message"]` — CRITICAL. The `coding` tools profile filters out `message` tool.
- `memory-core` plugin must be explicitly registered in openclaw.json.
- Plugin id must match across: openclaw.plugin.json, plugins.allow[], plugins.entries, plugins.installs.
- Template literal escaping: Never use `\\n` inside TS template literal `<script>` blocks — use `String.fromCharCode(10)`.
- OpenClaw allowed media roots: `/tmp/openclaw/`, `~/.openclaw/media/`, agent media dir.
- Config save path: `~/.openclaw/openclaw.json` (NOT `~/.openclaw/workspace/openclaw.json`).
