refactor: change default mode from dedicated to self-chat and reorder UI settings

- Change default mode to self-chat across all modules
- Update README to reflect self-chat as primary mode with security warning
- Move session/media storage from usr/whatsapp to tmp/whatsapp
- Reorder config UI: move Mode above Allowed Numbers
- Add warning banner when allowed_numbers is empty in self-chat mode
- Move Bridge Port and Poll Interval to bottom of settings
- Update mode descriptions to clarify self-chat handles both self
This commit is contained in:
frdel 2026-03-31 09:47:02 +02:00
parent 9fece911b5
commit 5193ef7501
10 changed files with 73 additions and 51 deletions

View file

@ -21,18 +21,19 @@ Dependencies are auto-installed on first bridge start if missing.
### Configure and pair
1. Enable the plugin in Settings > External > WhatsApp Integration
2. Configure allowed phone numbers (optional)
2. Configure allowed phone numbers
3. Click Show QR Code and scan with WhatsApp on your phone
4. Send a message from an allowed number to start a chat
The WhatsApp session persists across restarts in `usr/whatsapp/session/`. No re-pairing needed unless you disconnect via settings.
The WhatsApp session persists across restarts in `tmp/whatsapp/session/`. No re-pairing needed unless you disconnect via settings.
Be careful: if you use your personal number and leave `allowed_numbers` open, other people could misuse your Agent Zero.
## Configuration
| Setting | Description | Default |
|---------|-------------|---------|
| `enabled` | Enable bridge and polling | `false` |
| `mode` | `dedicated` (separate number) or `self-chat` (personal number) | `dedicated` |
| `mode` | `self-chat` (personal number) or `dedicated` (separate number) | `self-chat` |
| `allow_group` | Respond in group chats when mentioned or replied to | `false` |
| `bridge_port` | Local HTTP port for bridge | `3100` |
| `poll_interval_seconds` | Poll frequency (min 2) | `3` |
@ -43,10 +44,11 @@ The WhatsApp session persists across restarts in `usr/whatsapp/session/`. No re-
## How It Works
1. The bridge connects to WhatsApp via Baileys and exposes HTTP endpoints on localhost
2. The plugin polls the bridge for new messages every few seconds
3. Incoming messages are routed to existing chats by WhatsApp chat ID or new chats are created
4. Agent responses are sent back via the bridge as WhatsApp messages
5. Media (images, documents) is supported in both directions
2. In personal-number mode, you can message your own WhatsApp number to talk to the agent, and the agent can also handle messages that other people send to that number
3. The plugin polls the bridge for new messages every few seconds
4. Incoming messages are routed to existing chats by WhatsApp chat ID or new chats are created
5. Agent responses are sent back via the bridge as WhatsApp messages
6. Media (images, documents) is supported in both directions
## Architecture

View file

@ -13,7 +13,7 @@ class QrCode(ApiHandler):
async def process(self, input: dict, request: Request) -> dict:
config = plugins.get_plugin_config(PLUGIN_NAME) or {}
port = int(config.get("bridge_port", 3100))
mode = config.get("mode", "dedicated")
mode = config.get("mode", "self-chat")
from plugins._whatsapp_integration.helpers.bridge_manager import (
ensure_bridge_http_up,

View file

@ -13,7 +13,7 @@ class Start(ApiHandler):
async def process(self, input: dict, request: Request) -> dict:
config = plugins.get_plugin_config(PLUGIN_NAME) or {}
port = int(config.get("bridge_port", 3100))
mode = config.get("mode", "dedicated")
mode = config.get("mode", "self-chat")
from plugins._whatsapp_integration.helpers.bridge_manager import (
ensure_bridge_http_up,

View file

@ -1,7 +1,7 @@
enabled: false
mode: dedicated
mode: self-chat
# dedicated: separate phone number — people message it directly
# self-chat: personal number — you message yourself to talk to the agent
# self-chat: personal number — you can message yourself, and the agent can also handle messages sent to your number
bridge_port: 3100
poll_interval_seconds: 3
allow_group: false

View file

@ -63,7 +63,7 @@ async def _poll_loop() -> None:
port = int(config.get("bridge_port", 3100))
session_dir = get_bridge_session_dir()
cache_dir = get_bridge_media_dir()
mode = config.get("mode", "dedicated")
mode = config.get("mode", "self-chat")
# Detect config changes that require bridge restart
desired = {"port": port, "mode": mode}

View file

@ -105,7 +105,7 @@ async def start_bridge(
port: int,
session_dir: str,
cache_dir: str,
mode: str = "dedicated",
mode: str = "self-chat",
) -> bool:
async with _get_bridge_lock():
return await _ensure_bridge_started(
@ -137,7 +137,7 @@ async def ensure_bridge_http_up(
port: int,
session_dir: str,
cache_dir: str,
mode: str = "dedicated",
mode: str = "self-chat",
) -> bool:
"""Start bridge if needed and wait for HTTP server only (not WA connection)."""
async with _get_bridge_lock():

View file

@ -317,7 +317,7 @@ async def send_wa_reply(
response_text = _md_to_whatsapp(response_text)
# Prefix response in self-chat mode so user can distinguish agent messages
mode = config.get("mode", "dedicated")
mode = config.get("mode", "self-chat")
if mode == "self-chat":
response_text = context.agent0.read_prompt(
"fw.wa.self_chat_prefix.md", response_text=response_text,

View file

@ -4,8 +4,8 @@ from helpers import files
def get_bridge_session_dir() -> str:
return files.get_abs_path(files.USER_DIR, "whatsapp", "session")
return files.get_abs_path(files.TEMP_DIR, "whatsapp", "session")
def get_bridge_media_dir() -> str:
return files.get_abs_path(files.USER_DIR, "whatsapp", "media")
return files.get_abs_path(files.TEMP_DIR, "whatsapp", "media")

View file

@ -30,6 +30,11 @@
if (Array.isArray(value)) return value.join(', ');
return typeof value === 'string' ? value : '';
},
allowed_is_empty() {
const value = config?.allowed_numbers;
if (Array.isArray(value)) return value.length === 0;
return !String(value || '').trim();
},
set_allowed(val) {
config.allowed_numbers = val.split(',')
.map(s => s.trim())
@ -104,10 +109,7 @@
<template x-if="config">
<div>
<div class="section-title">WhatsApp Integration</div>
<div class="section-description">
Connect Agent Zero to WhatsApp using a Baileys bridge.
Requires Node.js installed on the system.
</div>
<div class="field">
<div class="field-label">
@ -122,23 +124,6 @@
</div>
</div>
<div class="field">
<div class="field-label">
<div class="field-title">Mode</div>
<div class="field-description"
x-text="config.mode === 'self-chat'
? 'Message yourself to talk to the agent — no extra number needed'
: 'Uses a second phone number — others message it directly'">
</div>
</div>
<div class="field-control">
<select x-model="config.mode">
<option value="dedicated">Separate number (dedicated)</option>
<option value="self-chat">Personal number (self-chat)</option>
</select>
</div>
</div>
<!-- WhatsApp Account (shown when enabled) -->
<template x-if="config.enabled">
<div>
@ -221,28 +206,43 @@
<div class="field">
<div class="field-label">
<div class="field-title">Bridge Port</div>
<div class="field-description">Local port for the WhatsApp bridge HTTP server</div>
<div class="field-title">Mode</div>
<div class="field-description">
<span x-show="config.mode === 'self-chat'">
Use your personal number. You can message yourself to talk to the agent, and the agent can also handle messages that other people send to your number.
</span>
<span x-show="config.mode !== 'self-chat'">
Use a separate WhatsApp number dedicated to Agent Zero conversations.
</span>
</div>
</div>
<div class="field-control">
<input type="number" x-model.number="config.bridge_port" placeholder="3100" />
<select x-model="config.mode">
<option value="self-chat">Personal number (self-chat)</option>
<option value="dedicated">Separate number (dedicated)</option>
</select>
</div>
</div>
<div class="field">
<div class="field-label">
<div class="field-title">Poll Interval (seconds)</div>
<div class="field-description">How often to check for new messages (minimum 2)</div>
<template x-if="config.enabled && allowed_is_empty()">
<div style="margin: 8px 0 20px; padding: 12px 14px; border-radius: 10px;
border: 1px solid rgba(255, 170, 0, 0.45);
background: rgba(255, 170, 0, 0.12);
color: var(--color-warning-text);">
<div style="font-weight: 600; display: flex; align-items: center; gap: 8px;">
<span aria-hidden="true">&#9888;</span>
<span>Warning</span>
</div>
<div style="margin-top: 4px; line-height: 1.45;">
Allowed Numbers is empty. If other people can message this WhatsApp number, they can use your Agent Zero.
</div>
</div>
<div class="field-control">
<input type="number" x-model.number="config.poll_interval_seconds" min="2" placeholder="3" />
</div>
</div>
</template>
<div class="field">
<div class="field-label">
<div class="field-title">Allowed Numbers</div>
<div class="field-description">Comma-separated phone numbers. Matching is normalized by the backend, so punctuation and + prefixes are okay. Empty = allow all</div>
<div class="field-description">Comma-separated phone numbers. Matching is normalized by the backend, so punctuation and + prefixes are okay. Empty = allow all.</div>
</div>
<div class="field-control">
<input type="text" :value="allowed_text()" @change="set_allowed($event.target.value)" placeholder="+1 (415) 555-1234, +44 7911 123456" />
@ -287,6 +287,26 @@
</div>
</div>
<div class="field">
<div class="field-label">
<div class="field-title">Bridge Port</div>
<div class="field-description">Local port for the WhatsApp bridge HTTP server</div>
</div>
<div class="field-control">
<input type="number" x-model.number="config.bridge_port" placeholder="3100" />
</div>
</div>
<div class="field">
<div class="field-label">
<div class="field-title">Poll Interval (seconds)</div>
<div class="field-description">How often to check for new messages (minimum 2)</div>
</div>
<div class="field-control">
<input type="number" x-model.number="config.poll_interval_seconds" min="2" placeholder="3" />
</div>
</div>
<!-- Test connection -->
<div style="margin-top: 16px; display: flex; align-items: center; gap: 12px;">
<button class="btn btn-field" @click="test_connection()" :disabled="testing">

View file

@ -46,7 +46,7 @@ const PORT = parseInt(getArg('port', '3100'), 10);
const SESSION_DIR = getArg('session', path.join(DEFAULT_DATA_ROOT, 'session'));
const CACHE_DIR = getArg('cache-dir', path.join(DEFAULT_DATA_ROOT, 'media'));
const PAIR_ONLY = args.includes('--pair-only');
const MODE = getArg('mode', 'dedicated'); // "dedicated" or "self-chat"
const MODE = getArg('mode', 'self-chat'); // "dedicated" or "self-chat"
mkdirSync(SESSION_DIR, { recursive: true });