mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-04-28 03:30:23 +00:00
commit5193ef7501Author: frdel <38891707+frdel@users.noreply.github.com> Date: Tue Mar 31 09:47:02 2026 +0200 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 commit9fece911b5Author: frdel <38891707+frdel@users.noreply.github.com> Date: Tue Mar 31 09:20:35 2026 +0200 refactor: centralize WhatsApp storage paths and improve bridge dependency handling - Add storage_paths.py helper for consistent session/media/runtime paths - Replace hardcoded usr/whatsapp paths across all modules - Fix bridge lock to be event-loop-aware (recreate per loop) - Add automatic dependency reinstall on startup failures - Track bridge startup output for better error diagnostics - Add dependency state tracking with package.json hash validation - Implement force reinstall when node_modules appears commitbc511d221dAuthor: linuztx <linuztx@gmail.com> Date: Tue Mar 31 09:07:46 2026 +0800 fix: stop poll loop immediately when Node.js is not installed commita9554e132fAuthor: linuztx <linuztx@gmail.com> Date: Tue Mar 31 08:49:15 2026 +0800 fix: auto-reinstall corrupt node_modules and stop poll loop after repeated bridge failures _ensure_npm_install now verifies key package exists, not just node_modules dir. Wipes and reinstalls if corrupt. Poll loop stops after 5 consecutive bridge start failures instead of spamming errors and making A0 unusable. commit61fa1bf487Author: linuztx <linuztx@gmail.com> Date: Tue Mar 31 08:38:51 2026 +0800 fix: move allowed_numbers filtering from JS bridge to Python handler The JS bridge used LIDs (internal WhatsApp identifiers) for sender matching which never matched actual phone numbers. Moved filtering to Python handler.py where config is read fresh each poll cycle. - Add senderNumber (resolved phone) to bridge message payload - Filter in poll_messages() with normalized number comparison - Remove --allowed-numbers CLI arg and JS-side filtering - Fix ensure_bridge_http_up not recording _bridge_config - Fix falsy empty-dict check in bridge restart detection commit64ee177897Author: linuztx <linuztx@gmail.com> Date: Sat Mar 28 23:34:23 2026 +0800 refactor: move email agent instructions to system prompt and update prompt labels commit0f53b41d80Author: linuztx <linuztx@gmail.com> Date: Sat Mar 28 10:59:44 2026 +0800 Add node_modules to gitignore commiteb6a4d3ad2Author: linuztx <linuztx@gmail.com> Date: Sat Mar 28 10:53:59 2026 +0800 Add WhatsApp plugin thumbnail commit39bed4f538Author: linuztx <linuztx@gmail.com> Date: Sat Mar 28 10:51:47 2026 +0800 refactor: rename allowed_users to allowed_numbers across plugin commite4991b6e6eAuthor: linuztx <linuztx@gmail.com> Date: Fri Mar 27 21:58:29 2026 +0800 improve: move agent instructions from per-message to system prompt commit4f1be15fa7Author: linuztx <linuztx@gmail.com> Date: Fri Mar 27 21:00:25 2026 +0800 improve: add macOS port kill support and bridge process destructor cleanup commitf5349753d7Author: linuztx <linuztx@gmail.com> Date: Fri Mar 27 17:09:56 2026 +0800 improve: remove redundant bridge_manager from execute, rely on poll loop finally commit9d9dd4bd7fAuthor: linuztx <linuztx@gmail.com> Date: Fri Mar 27 14:41:14 2026 +0800 fix: stop bridge and poll loop when plugin is disabled or toggled off commit66b0a7d3e0Author: linuztx <linuztx@gmail.com> Date: Fri Mar 27 11:05:58 2026 +0800 improve: fix allowed users input, auto-strip + prefix, log ignored messages commit938e7b9312Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 23:26:42 2026 +0800 improve: add line break to allowed users description commit4ef64b9121Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 22:44:55 2026 +0800 feat: convert markdown to WhatsApp formatting before sending replies commitf549b49f44Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 22:34:56 2026 +0800 improve: add progress update instructions to system context prompt commit66e5d51dcfAuthor: linuztx <linuztx@gmail.com> Date: Thu Mar 26 22:23:32 2026 +0800 fix: stop typing indicator on agent error or generation failure commit3dd01cd04cAuthor: linuztx <linuztx@gmail.com> Date: Thu Mar 26 18:31:38 2026 +0800 improve: persistent typing indicator with poll-based refresh commit8d0ec86f15Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 17:11:25 2026 +0800 Update README.md commite664673c1cAuthor: linuztx <linuztx@gmail.com> Date: Thu Mar 26 16:05:44 2026 +0800 feat: add agent prefix to self-chat replies for visual distinction commit18c5716d10Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 15:43:01 2026 +0800 fix: clear typing indicator after sending reply in self-chat mode commit7c653c9d56Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 14:43:06 2026 +0800 improve: merge WhatsApp Link and Disconnect into single Account field commit57c95e6f13Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 14:11:05 2026 +0800 feat: add disconnect account option to switch WhatsApp accounts commitc62695356eAuthor: linuztx <linuztx@gmail.com> Date: Thu Mar 26 14:00:00 2026 +0800 improve: move mode description inline and reorder Allow Group after Allowed Users commit18a56ea446Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 13:44:17 2026 +0800 fix: remove duplicate typing indicator before sending reply commit44c90a118fAuthor: linuztx <linuztx@gmail.com> Date: Thu Mar 26 13:30:06 2026 +0800 improve: remove sender number from DM prompt commit64fe7d0302Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 13:17:29 2026 +0800 fix: handle documentWithCaptionMessage wrapper for captioned documents commit00b6657185Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 13:06:40 2026 +0800 feat: add attachment reader/writer with RFC and download all media types commit8041c085d2Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 11:45:17 2026 +0800 improve: update group prompt and reply instructions commit71a6eb7557Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 11:26:36 2026 +0800 feat: reply to specific messages in group chats with quote commit6bf63eb9c6Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 09:57:34 2026 +0800 feat: detect replies to bot messages in group chats commitb4492e0759Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 09:20:27 2026 +0800 improve: resolve group names and sender LIDs in bridge messages commit14e673f165Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 04:44:50 2026 +0800 feat: add allow_group toggle to respond only when mentioned in group chats commit40f4884319Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 03:20:02 2026 +0800 refactor: rename mode value from bot to dedicated commit50af7c2bdeAuthor: linuztx <linuztx@gmail.com> Date: Thu Mar 26 02:34:51 2026 +0800 fix: kill orphaned bridge process on port before starting new one commit45b21c093aAuthor: linuztx <linuztx@gmail.com> Date: Thu Mar 26 02:07:45 2026 +0800 improve: auto-restart bridge when config changes commita12183ba6eAuthor: linuztx <linuztx@gmail.com> Date: Thu Mar 26 01:39:55 2026 +0800 feat: add bot and self-chat mode selection for WhatsApp bridge commitbb8961ab73Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 00:56:56 2026 +0800 improve: send typing indicator immediately on message receive commit84c12b0c23Author: linuztx <linuztx@gmail.com> Date: Thu Mar 26 00:29:04 2026 +0800 feat: add WhatsApp integration plugin with Baileys bridge and QR pairing
181 lines
6.9 KiB
Python
181 lines
6.9 KiB
Python
import asyncio
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from plugins._whatsapp_integration.helpers import bridge_manager
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_bridge_manager_state():
|
|
bridge_manager._bridge_lock = None
|
|
bridge_manager._bridge_lock_loop = None
|
|
bridge_manager._bridge_process = None
|
|
bridge_manager._bridge_config.clear()
|
|
yield
|
|
bridge_manager._bridge_lock = None
|
|
bridge_manager._bridge_lock_loop = None
|
|
bridge_manager._bridge_process = None
|
|
bridge_manager._bridge_config.clear()
|
|
|
|
|
|
def test_get_bridge_lock_recreates_lock_when_loop_marker_changes():
|
|
async def _run():
|
|
original_lock = asyncio.Lock()
|
|
bridge_manager._bridge_lock = original_lock
|
|
bridge_manager._bridge_lock_loop = object()
|
|
|
|
refreshed_lock = bridge_manager._get_bridge_lock()
|
|
|
|
assert refreshed_lock is bridge_manager._bridge_lock
|
|
assert refreshed_lock is not original_lock
|
|
|
|
asyncio.run(_run())
|
|
|
|
|
|
def test_ensure_bridge_dependencies_reinstalls_invalid_dependency_tree(monkeypatch):
|
|
state_writes: list[dict] = []
|
|
validate_results = [False, True]
|
|
reinstall_calls: list[bool] = []
|
|
|
|
monkeypatch.setattr(bridge_manager, "NODE_MODULES_DIR", "/tmp/whatsapp-node-modules")
|
|
monkeypatch.setattr(bridge_manager.os.path, "isdir", lambda path: path == "/tmp/whatsapp-node-modules")
|
|
|
|
async def fake_build_dependency_state():
|
|
return {"package_json_hash": "a", "package_lock_hash": "b"}
|
|
|
|
async def fake_validate_bridge_dependencies():
|
|
return validate_results.pop(0)
|
|
|
|
async def fake_reinstall_bridge_dependencies():
|
|
reinstall_calls.append(True)
|
|
|
|
monkeypatch.setattr(bridge_manager, "_build_dependency_state", fake_build_dependency_state)
|
|
monkeypatch.setattr(bridge_manager, "_load_dependency_state", lambda: {"package_json_hash": "a", "package_lock_hash": "b"})
|
|
monkeypatch.setattr(bridge_manager, "_validate_bridge_dependencies", fake_validate_bridge_dependencies)
|
|
monkeypatch.setattr(bridge_manager, "_reinstall_bridge_dependencies", fake_reinstall_bridge_dependencies)
|
|
monkeypatch.setattr(bridge_manager, "_write_dependency_state", lambda state: state_writes.append(state))
|
|
|
|
asyncio.run(bridge_manager._ensure_bridge_dependencies())
|
|
|
|
assert reinstall_calls == [True]
|
|
assert state_writes == [{"package_json_hash": "a", "package_lock_hash": "b"}]
|
|
|
|
|
|
def test_ensure_bridge_dependencies_bootstraps_missing_state_without_reinstall(monkeypatch):
|
|
state_writes: list[dict] = []
|
|
reinstall_calls: list[bool] = []
|
|
|
|
monkeypatch.setattr(bridge_manager, "NODE_MODULES_DIR", "/tmp/whatsapp-node-modules")
|
|
monkeypatch.setattr(bridge_manager.os.path, "isdir", lambda path: path == "/tmp/whatsapp-node-modules")
|
|
|
|
async def fake_build_dependency_state():
|
|
return {"package_json_hash": "a", "package_lock_hash": "b"}
|
|
|
|
async def fake_validate_bridge_dependencies():
|
|
return True
|
|
|
|
async def fake_reinstall_bridge_dependencies():
|
|
reinstall_calls.append(True)
|
|
|
|
monkeypatch.setattr(bridge_manager, "_build_dependency_state", fake_build_dependency_state)
|
|
monkeypatch.setattr(bridge_manager, "_load_dependency_state", lambda: None)
|
|
monkeypatch.setattr(bridge_manager, "_validate_bridge_dependencies", fake_validate_bridge_dependencies)
|
|
monkeypatch.setattr(bridge_manager, "_reinstall_bridge_dependencies", fake_reinstall_bridge_dependencies)
|
|
monkeypatch.setattr(bridge_manager, "_write_dependency_state", lambda state: state_writes.append(state))
|
|
|
|
asyncio.run(bridge_manager._ensure_bridge_dependencies())
|
|
|
|
assert reinstall_calls == []
|
|
assert state_writes == [{"package_json_hash": "a", "package_lock_hash": "b"}]
|
|
|
|
|
|
def test_reinstall_bridge_dependencies_uses_local_npm_cache(monkeypatch):
|
|
run_calls: list[tuple[list[str], dict[str, str] | None]] = []
|
|
real_isdir = bridge_manager.os.path.isdir
|
|
|
|
monkeypatch.setattr(bridge_manager.PrintStyle, "warning", staticmethod(lambda message: None))
|
|
monkeypatch.setattr(bridge_manager.PrintStyle, "info", staticmethod(lambda message: None))
|
|
monkeypatch.setattr(bridge_manager, "NODE_MODULES_DIR", "/tmp/whatsapp-node-modules")
|
|
monkeypatch.setattr(bridge_manager, "BRIDGE_PACKAGE_LOCK", "/tmp/package-lock.json")
|
|
monkeypatch.setattr(bridge_manager, "BRIDGE_INSTALL_STATE", "/tmp/deps-state.json")
|
|
monkeypatch.setattr(bridge_manager, "BRIDGE_NPM_CACHE", "/tmp/npm-cache")
|
|
monkeypatch.setattr(
|
|
bridge_manager.os.path,
|
|
"isdir",
|
|
lambda path: path == "/tmp/whatsapp-node-modules" or real_isdir(path),
|
|
)
|
|
monkeypatch.setattr(
|
|
bridge_manager.os.path,
|
|
"isfile",
|
|
lambda path: path == "/tmp/package-lock.json",
|
|
)
|
|
monkeypatch.setattr(bridge_manager.shutil, "rmtree", lambda path, ignore_errors=True: None)
|
|
|
|
async def fake_run_subprocess(command, *, cwd, env=None):
|
|
run_calls.append((list(command), env))
|
|
return ""
|
|
|
|
monkeypatch.setattr(bridge_manager, "_run_subprocess", fake_run_subprocess)
|
|
|
|
asyncio.run(bridge_manager._reinstall_bridge_dependencies())
|
|
|
|
assert run_calls == [
|
|
(
|
|
["npm", "ci", "--omit=dev", "--no-audit", "--no-fund"],
|
|
{"npm_config_cache": "/tmp/npm-cache"},
|
|
),
|
|
]
|
|
|
|
|
|
def test_start_bridge_reinstalls_and_retries_after_dependency_failure(monkeypatch):
|
|
ensure_calls: list[bool] = []
|
|
start_attempts = [
|
|
(False, "Error: Cannot find package '@whiskeysockets/baileys'"),
|
|
(True, ""),
|
|
]
|
|
|
|
async def fake_ensure_bridge_dependencies(force_reinstall: bool = False):
|
|
ensure_calls.append(force_reinstall)
|
|
|
|
async def fake_start_bridge_once(**kwargs):
|
|
return start_attempts.pop(0)
|
|
|
|
monkeypatch.setattr(bridge_manager, "_ensure_bridge_dependencies", fake_ensure_bridge_dependencies)
|
|
monkeypatch.setattr(bridge_manager, "_start_bridge_once", fake_start_bridge_once)
|
|
|
|
started = asyncio.run(bridge_manager.start_bridge(
|
|
port=3100,
|
|
session_dir="/tmp/wa-session",
|
|
cache_dir="/tmp/wa-cache",
|
|
))
|
|
|
|
assert started is True
|
|
assert ensure_calls == [False, True]
|
|
|
|
|
|
def test_start_bridge_does_not_reinstall_on_non_dependency_failure(monkeypatch):
|
|
ensure_calls: list[bool] = []
|
|
|
|
async def fake_ensure_bridge_dependencies(force_reinstall: bool = False):
|
|
ensure_calls.append(force_reinstall)
|
|
|
|
async def fake_start_bridge_once(**kwargs):
|
|
return False, "Bridge closed unexpectedly without module resolution errors"
|
|
|
|
monkeypatch.setattr(bridge_manager, "_ensure_bridge_dependencies", fake_ensure_bridge_dependencies)
|
|
monkeypatch.setattr(bridge_manager, "_start_bridge_once", fake_start_bridge_once)
|
|
|
|
started = asyncio.run(bridge_manager.start_bridge(
|
|
port=3100,
|
|
session_dir="/tmp/wa-session",
|
|
cache_dir="/tmp/wa-cache",
|
|
))
|
|
|
|
assert started is False
|
|
assert ensure_calls == [False]
|