refactor(channels): rename moltchat integration to mochat
This commit is contained in:
parent
20b8a2fc58
commit
3779225917
12
README.md
12
README.md
@ -164,7 +164,7 @@ nanobot agent -m "Hello from my local LLM!"
|
||||
|
||||
## 💬 Chat Apps
|
||||
|
||||
Talk to your nanobot through Telegram, Discord, WhatsApp, Feishu, or Moltchat — anytime, anywhere.
|
||||
Talk to your nanobot through Telegram, Discord, WhatsApp, Feishu, or Mochat — anytime, anywhere.
|
||||
|
||||
| Channel | Setup |
|
||||
|---------|-------|
|
||||
@ -172,7 +172,7 @@ Talk to your nanobot through Telegram, Discord, WhatsApp, Feishu, or Moltchat
|
||||
| **Discord** | Easy (bot token + intents) |
|
||||
| **WhatsApp** | Medium (scan QR) |
|
||||
| **Feishu** | Medium (app credentials) |
|
||||
| **Moltchat** | Medium (claw token + websocket) |
|
||||
| **Mochat** | Medium (claw token + websocket) |
|
||||
|
||||
<details>
|
||||
<summary><b>Telegram</b> (Recommended)</summary>
|
||||
@ -207,7 +207,7 @@ nanobot gateway
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Moltchat (Claw IM)</b></summary>
|
||||
<summary><b>Mochat (Claw IM)</b></summary>
|
||||
|
||||
Uses **Socket.IO WebSocket** by default, with HTTP polling fallback.
|
||||
|
||||
@ -221,7 +221,7 @@ Uses **Socket.IO WebSocket** by default, with HTTP polling fallback.
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"moltchat": {
|
||||
"mochat": {
|
||||
"enabled": true,
|
||||
"baseUrl": "https://mochat.io",
|
||||
"socketUrl": "https://mochat.io",
|
||||
@ -244,7 +244,7 @@ nanobot gateway
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Keep `clawToken` private. It should only be sent in `X-Claw-Token` header to your Moltchat API endpoint.
|
||||
> Keep `clawToken` private. It should only be sent in `X-Claw-Token` header to your Mochat API endpoint.
|
||||
|
||||
</details>
|
||||
|
||||
@ -456,7 +456,7 @@ docker run -v ~/.nanobot:/root/.nanobot --rm nanobot onboard
|
||||
# Edit config on host to add API keys
|
||||
vim ~/.nanobot/config.json
|
||||
|
||||
# Run gateway (connects to enabled channels, e.g. Telegram/Discord/Moltchat)
|
||||
# Run gateway (connects to enabled channels, e.g. Telegram/Discord/Mochat)
|
||||
docker run -v ~/.nanobot:/root/.nanobot -p 18790:18790 nanobot gateway
|
||||
|
||||
# Or run a single command
|
||||
|
||||
@ -2,6 +2,6 @@
|
||||
|
||||
from nanobot.channels.base import BaseChannel
|
||||
from nanobot.channels.manager import ChannelManager
|
||||
from nanobot.channels.moltchat import MoltchatChannel
|
||||
from nanobot.channels.mochat import MochatChannel
|
||||
|
||||
__all__ = ["BaseChannel", "ChannelManager", "MoltchatChannel"]
|
||||
__all__ = ["BaseChannel", "ChannelManager", "MochatChannel"]
|
||||
|
||||
@ -78,17 +78,17 @@ class ChannelManager:
|
||||
except ImportError as e:
|
||||
logger.warning(f"Feishu channel not available: {e}")
|
||||
|
||||
# Moltchat channel
|
||||
if self.config.channels.moltchat.enabled:
|
||||
# Mochat channel
|
||||
if self.config.channels.mochat.enabled:
|
||||
try:
|
||||
from nanobot.channels.moltchat import MoltchatChannel
|
||||
from nanobot.channels.mochat import MochatChannel
|
||||
|
||||
self.channels["moltchat"] = MoltchatChannel(
|
||||
self.config.channels.moltchat, self.bus
|
||||
self.channels["mochat"] = MochatChannel(
|
||||
self.config.channels.mochat, self.bus
|
||||
)
|
||||
logger.info("Moltchat channel enabled")
|
||||
logger.info("Mochat channel enabled")
|
||||
except ImportError as e:
|
||||
logger.warning(f"Moltchat channel not available: {e}")
|
||||
logger.warning(f"Mochat channel not available: {e}")
|
||||
|
||||
async def start_all(self) -> None:
|
||||
"""Start WhatsApp channel and the outbound dispatcher."""
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"""Moltchat channel implementation using Socket.IO with HTTP polling fallback."""
|
||||
"""Mochat channel implementation using Socket.IO with HTTP polling fallback."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -15,7 +15,7 @@ from loguru import logger
|
||||
from nanobot.bus.events import OutboundMessage
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.channels.base import BaseChannel
|
||||
from nanobot.config.schema import MoltchatConfig
|
||||
from nanobot.config.schema import MochatConfig
|
||||
from nanobot.utils.helpers import get_data_path
|
||||
|
||||
try:
|
||||
@ -39,7 +39,7 @@ CURSOR_SAVE_DEBOUNCE_S = 0.5
|
||||
|
||||
|
||||
@dataclass
|
||||
class MoltchatBufferedEntry:
|
||||
class MochatBufferedEntry:
|
||||
"""Buffered inbound entry for delayed dispatch."""
|
||||
|
||||
raw_body: str
|
||||
@ -55,20 +55,20 @@ class MoltchatBufferedEntry:
|
||||
class DelayState:
|
||||
"""Per-target delayed message state."""
|
||||
|
||||
entries: list[MoltchatBufferedEntry] = field(default_factory=list)
|
||||
entries: list[MochatBufferedEntry] = field(default_factory=list)
|
||||
lock: asyncio.Lock = field(default_factory=asyncio.Lock)
|
||||
timer: asyncio.Task | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class MoltchatTarget:
|
||||
class MochatTarget:
|
||||
"""Outbound target resolution result."""
|
||||
|
||||
id: str
|
||||
is_panel: bool
|
||||
|
||||
|
||||
def normalize_moltchat_content(content: Any) -> str:
|
||||
def normalize_mochat_content(content: Any) -> str:
|
||||
"""Normalize content payload to text."""
|
||||
if isinstance(content, str):
|
||||
return content.strip()
|
||||
@ -80,17 +80,17 @@ def normalize_moltchat_content(content: Any) -> str:
|
||||
return str(content)
|
||||
|
||||
|
||||
def resolve_moltchat_target(raw: str) -> MoltchatTarget:
|
||||
def resolve_mochat_target(raw: str) -> MochatTarget:
|
||||
"""Resolve id and target kind from user-provided target string."""
|
||||
trimmed = (raw or "").strip()
|
||||
if not trimmed:
|
||||
return MoltchatTarget(id="", is_panel=False)
|
||||
return MochatTarget(id="", is_panel=False)
|
||||
|
||||
lowered = trimmed.lower()
|
||||
cleaned = trimmed
|
||||
forced_panel = False
|
||||
|
||||
prefixes = ["moltchat:", "mochat:", "group:", "channel:", "panel:"]
|
||||
prefixes = ["mochat:", "group:", "channel:", "panel:"]
|
||||
for prefix in prefixes:
|
||||
if lowered.startswith(prefix):
|
||||
cleaned = trimmed[len(prefix) :].strip()
|
||||
@ -99,9 +99,9 @@ def resolve_moltchat_target(raw: str) -> MoltchatTarget:
|
||||
break
|
||||
|
||||
if not cleaned:
|
||||
return MoltchatTarget(id="", is_panel=False)
|
||||
return MochatTarget(id="", is_panel=False)
|
||||
|
||||
return MoltchatTarget(id=cleaned, is_panel=forced_panel or not cleaned.startswith("session_"))
|
||||
return MochatTarget(id=cleaned, is_panel=forced_panel or not cleaned.startswith("session_"))
|
||||
|
||||
|
||||
def extract_mention_ids(value: Any) -> list[str]:
|
||||
@ -152,7 +152,7 @@ def resolve_was_mentioned(payload: dict[str, Any], agent_user_id: str) -> bool:
|
||||
|
||||
|
||||
def resolve_require_mention(
|
||||
config: MoltchatConfig,
|
||||
config: MochatConfig,
|
||||
session_id: str,
|
||||
group_id: str,
|
||||
) -> bool:
|
||||
@ -167,7 +167,7 @@ def resolve_require_mention(
|
||||
return bool(config.mention.require_in_groups)
|
||||
|
||||
|
||||
def build_buffered_body(entries: list[MoltchatBufferedEntry], is_group: bool) -> str:
|
||||
def build_buffered_body(entries: list[MochatBufferedEntry], is_group: bool) -> str:
|
||||
"""Build text body from one or more buffered entries."""
|
||||
if not entries:
|
||||
return ""
|
||||
@ -200,20 +200,20 @@ def parse_timestamp(value: Any) -> int | None:
|
||||
return None
|
||||
|
||||
|
||||
class MoltchatChannel(BaseChannel):
|
||||
"""Moltchat channel using socket.io with fallback polling workers."""
|
||||
class MochatChannel(BaseChannel):
|
||||
"""Mochat channel using socket.io with fallback polling workers."""
|
||||
|
||||
name = "moltchat"
|
||||
name = "mochat"
|
||||
|
||||
def __init__(self, config: MoltchatConfig, bus: MessageBus):
|
||||
def __init__(self, config: MochatConfig, bus: MessageBus):
|
||||
super().__init__(config, bus)
|
||||
self.config: MoltchatConfig = config
|
||||
self.config: MochatConfig = config
|
||||
self._http: httpx.AsyncClient | None = None
|
||||
self._socket: Any = None
|
||||
self._ws_connected = False
|
||||
self._ws_ready = False
|
||||
|
||||
self._state_dir = get_data_path() / "moltchat"
|
||||
self._state_dir = get_data_path() / "mochat"
|
||||
self._cursor_path = self._state_dir / "session_cursors.json"
|
||||
self._session_cursor: dict[str, int] = {}
|
||||
self._cursor_save_task: asyncio.Task | None = None
|
||||
@ -239,9 +239,9 @@ class MoltchatChannel(BaseChannel):
|
||||
self._target_locks: dict[str, asyncio.Lock] = {}
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Start Moltchat channel workers and websocket connection."""
|
||||
"""Start Mochat channel workers and websocket connection."""
|
||||
if not self.config.claw_token:
|
||||
logger.error("Moltchat claw_token not configured")
|
||||
logger.error("Mochat claw_token not configured")
|
||||
return
|
||||
|
||||
self._running = True
|
||||
@ -296,7 +296,7 @@ class MoltchatChannel(BaseChannel):
|
||||
async def send(self, msg: OutboundMessage) -> None:
|
||||
"""Send outbound message to session or panel."""
|
||||
if not self.config.claw_token:
|
||||
logger.warning("Moltchat claw_token missing, skip send")
|
||||
logger.warning("Mochat claw_token missing, skip send")
|
||||
return
|
||||
|
||||
content_parts = [msg.content.strip()] if msg.content and msg.content.strip() else []
|
||||
@ -306,9 +306,9 @@ class MoltchatChannel(BaseChannel):
|
||||
if not content:
|
||||
return
|
||||
|
||||
target = resolve_moltchat_target(msg.chat_id)
|
||||
target = resolve_mochat_target(msg.chat_id)
|
||||
if not target.id:
|
||||
logger.warning("Moltchat outbound target is empty")
|
||||
logger.warning("Mochat outbound target is empty")
|
||||
return
|
||||
|
||||
is_panel = target.is_panel or target.id in self._panel_set
|
||||
@ -330,7 +330,7 @@ class MoltchatChannel(BaseChannel):
|
||||
reply_to=msg.reply_to,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send Moltchat message: {e}")
|
||||
logger.error(f"Failed to send Mochat message: {e}")
|
||||
|
||||
def _seed_targets_from_config(self) -> None:
|
||||
sessions, self._auto_discover_sessions = self._normalize_id_list(self.config.sessions)
|
||||
@ -351,7 +351,7 @@ class MoltchatChannel(BaseChannel):
|
||||
|
||||
async def _start_socket_client(self) -> bool:
|
||||
if not SOCKETIO_AVAILABLE:
|
||||
logger.warning("python-socketio not installed, Moltchat using polling fallback")
|
||||
logger.warning("python-socketio not installed, Mochat using polling fallback")
|
||||
return False
|
||||
|
||||
serializer = "default"
|
||||
@ -385,7 +385,7 @@ class MoltchatChannel(BaseChannel):
|
||||
async def connect() -> None:
|
||||
self._ws_connected = True
|
||||
self._ws_ready = False
|
||||
logger.info("Moltchat websocket connected")
|
||||
logger.info("Mochat websocket connected")
|
||||
|
||||
subscribed = await self._subscribe_all()
|
||||
self._ws_ready = subscribed
|
||||
@ -400,13 +400,13 @@ class MoltchatChannel(BaseChannel):
|
||||
return
|
||||
self._ws_connected = False
|
||||
self._ws_ready = False
|
||||
logger.warning("Moltchat websocket disconnected")
|
||||
logger.warning("Mochat websocket disconnected")
|
||||
await self._ensure_fallback_workers()
|
||||
|
||||
@client.event
|
||||
async def connect_error(data: Any) -> None:
|
||||
message = str(data)
|
||||
logger.error(f"Moltchat websocket connect error: {message}")
|
||||
logger.error(f"Mochat websocket connect error: {message}")
|
||||
|
||||
@client.on("claw.session.events")
|
||||
async def on_session_events(payload: dict[str, Any]) -> None:
|
||||
@ -441,7 +441,7 @@ class MoltchatChannel(BaseChannel):
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect Moltchat websocket: {e}")
|
||||
logger.error(f"Failed to connect Mochat websocket: {e}")
|
||||
try:
|
||||
await client.disconnect()
|
||||
except Exception:
|
||||
@ -486,7 +486,7 @@ class MoltchatChannel(BaseChannel):
|
||||
},
|
||||
)
|
||||
if not ack.get("result"):
|
||||
logger.error(f"Moltchat subscribeSessions failed: {ack.get('message', 'unknown error')}")
|
||||
logger.error(f"Mochat subscribeSessions failed: {ack.get('message', 'unknown error')}")
|
||||
return False
|
||||
|
||||
data = ack.get("data")
|
||||
@ -516,7 +516,7 @@ class MoltchatChannel(BaseChannel):
|
||||
},
|
||||
)
|
||||
if not ack.get("result"):
|
||||
logger.error(f"Moltchat subscribePanels failed: {ack.get('message', 'unknown error')}")
|
||||
logger.error(f"Mochat subscribePanels failed: {ack.get('message', 'unknown error')}")
|
||||
return False
|
||||
|
||||
return True
|
||||
@ -544,7 +544,7 @@ class MoltchatChannel(BaseChannel):
|
||||
try:
|
||||
await self._refresh_targets(subscribe_new=self._ws_ready)
|
||||
except Exception as e:
|
||||
logger.warning(f"Moltchat refresh failed: {e}")
|
||||
logger.warning(f"Mochat refresh failed: {e}")
|
||||
|
||||
if self._fallback_mode:
|
||||
await self._ensure_fallback_workers()
|
||||
@ -560,7 +560,7 @@ class MoltchatChannel(BaseChannel):
|
||||
try:
|
||||
response = await self._list_sessions()
|
||||
except Exception as e:
|
||||
logger.warning(f"Moltchat listSessions failed: {e}")
|
||||
logger.warning(f"Mochat listSessions failed: {e}")
|
||||
return
|
||||
|
||||
sessions = response.get("sessions")
|
||||
@ -599,7 +599,7 @@ class MoltchatChannel(BaseChannel):
|
||||
try:
|
||||
response = await self._get_workspace_group()
|
||||
except Exception as e:
|
||||
logger.warning(f"Moltchat getWorkspaceGroup failed: {e}")
|
||||
logger.warning(f"Mochat getWorkspaceGroup failed: {e}")
|
||||
return
|
||||
|
||||
raw_panels = response.get("panels")
|
||||
@ -683,7 +683,7 @@ class MoltchatChannel(BaseChannel):
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(f"Moltchat watch fallback error ({session_id}): {e}")
|
||||
logger.warning(f"Mochat watch fallback error ({session_id}): {e}")
|
||||
await asyncio.sleep(max(0.1, self.config.retry_delay_ms / 1000.0))
|
||||
|
||||
async def _panel_poll_worker(self, panel_id: str) -> None:
|
||||
@ -723,7 +723,7 @@ class MoltchatChannel(BaseChannel):
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.warning(f"Moltchat panel polling error ({panel_id}): {e}")
|
||||
logger.warning(f"Mochat panel polling error ({panel_id}): {e}")
|
||||
|
||||
await asyncio.sleep(sleep_s)
|
||||
|
||||
@ -803,7 +803,7 @@ class MoltchatChannel(BaseChannel):
|
||||
if message_id and self._remember_message_id(seen_key, message_id):
|
||||
return
|
||||
|
||||
raw_body = normalize_moltchat_content(payload.get("content"))
|
||||
raw_body = normalize_mochat_content(payload.get("content"))
|
||||
if not raw_body:
|
||||
raw_body = "[empty message]"
|
||||
|
||||
@ -826,7 +826,7 @@ class MoltchatChannel(BaseChannel):
|
||||
if require_mention and not was_mentioned and not use_delay:
|
||||
return
|
||||
|
||||
entry = MoltchatBufferedEntry(
|
||||
entry = MochatBufferedEntry(
|
||||
raw_body=raw_body,
|
||||
author=author,
|
||||
sender_name=sender_name,
|
||||
@ -883,7 +883,7 @@ class MoltchatChannel(BaseChannel):
|
||||
key: str,
|
||||
target_id: str,
|
||||
target_kind: str,
|
||||
entry: MoltchatBufferedEntry,
|
||||
entry: MochatBufferedEntry,
|
||||
) -> None:
|
||||
state = self._delay_states.setdefault(key, DelayState())
|
||||
|
||||
@ -912,7 +912,7 @@ class MoltchatChannel(BaseChannel):
|
||||
target_id: str,
|
||||
target_kind: str,
|
||||
reason: str,
|
||||
entry: MoltchatBufferedEntry | None,
|
||||
entry: MochatBufferedEntry | None,
|
||||
) -> None:
|
||||
state = self._delay_states.setdefault(key, DelayState())
|
||||
|
||||
@ -944,7 +944,7 @@ class MoltchatChannel(BaseChannel):
|
||||
self,
|
||||
target_id: str,
|
||||
target_kind: str,
|
||||
entries: list[MoltchatBufferedEntry],
|
||||
entries: list[MochatBufferedEntry],
|
||||
was_mentioned: bool,
|
||||
) -> None:
|
||||
if not entries:
|
||||
@ -1092,7 +1092,7 @@ class MoltchatChannel(BaseChannel):
|
||||
try:
|
||||
data = json.loads(self._cursor_path.read_text("utf-8"))
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read Moltchat cursor file: {e}")
|
||||
logger.warning(f"Failed to read Mochat cursor file: {e}")
|
||||
return
|
||||
|
||||
cursors = data.get("cursors") if isinstance(data, dict) else None
|
||||
@ -1114,14 +1114,14 @@ class MoltchatChannel(BaseChannel):
|
||||
self._state_dir.mkdir(parents=True, exist_ok=True)
|
||||
self._cursor_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", "utf-8")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to save Moltchat cursor file: {e}")
|
||||
logger.warning(f"Failed to save Mochat cursor file: {e}")
|
||||
|
||||
def _base_url(self) -> str:
|
||||
return self.config.base_url.strip().rstrip("/")
|
||||
|
||||
async def _post_json(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
if not self._http:
|
||||
raise RuntimeError("Moltchat HTTP client not initialized")
|
||||
raise RuntimeError("Mochat HTTP client not initialized")
|
||||
|
||||
url = f"{self._base_url()}{path}"
|
||||
response = await self._http.post(
|
||||
@ -1135,7 +1135,7 @@ class MoltchatChannel(BaseChannel):
|
||||
|
||||
text = response.text
|
||||
if not response.is_success:
|
||||
raise RuntimeError(f"Moltchat HTTP {response.status_code}: {text[:200]}")
|
||||
raise RuntimeError(f"Mochat HTTP {response.status_code}: {text[:200]}")
|
||||
|
||||
parsed: Any
|
||||
try:
|
||||
@ -1146,7 +1146,7 @@ class MoltchatChannel(BaseChannel):
|
||||
if isinstance(parsed, dict) and isinstance(parsed.get("code"), int):
|
||||
if parsed["code"] != 200:
|
||||
message = str(parsed.get("message") or parsed.get("name") or "request failed")
|
||||
raise RuntimeError(f"Moltchat API error: {message} (code={parsed['code']})")
|
||||
raise RuntimeError(f"Mochat API error: {message} (code={parsed['code']})")
|
||||
data = parsed.get("data")
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
@ -376,11 +376,11 @@ def channels_status():
|
||||
fs_config
|
||||
)
|
||||
|
||||
# Moltchat
|
||||
mc = config.channels.moltchat
|
||||
# Mochat
|
||||
mc = config.channels.mochat
|
||||
mc_base = mc.base_url or "[dim]not configured[/dim]"
|
||||
table.add_row(
|
||||
"Moltchat",
|
||||
"Mochat",
|
||||
"✓" if mc.enabled else "✗",
|
||||
mc_base
|
||||
)
|
||||
|
||||
@ -39,18 +39,18 @@ class DiscordConfig(BaseModel):
|
||||
intents: int = 37377 # GUILDS + GUILD_MESSAGES + DIRECT_MESSAGES + MESSAGE_CONTENT
|
||||
|
||||
|
||||
class MoltchatMentionConfig(BaseModel):
|
||||
"""Moltchat mention behavior configuration."""
|
||||
class MochatMentionConfig(BaseModel):
|
||||
"""Mochat mention behavior configuration."""
|
||||
require_in_groups: bool = False
|
||||
|
||||
|
||||
class MoltchatGroupRule(BaseModel):
|
||||
"""Moltchat per-group mention requirement."""
|
||||
class MochatGroupRule(BaseModel):
|
||||
"""Mochat per-group mention requirement."""
|
||||
require_mention: bool = False
|
||||
|
||||
|
||||
class MoltchatConfig(BaseModel):
|
||||
"""Moltchat channel configuration."""
|
||||
class MochatConfig(BaseModel):
|
||||
"""Mochat channel configuration."""
|
||||
enabled: bool = False
|
||||
base_url: str = "http://localhost:11000"
|
||||
socket_url: str = ""
|
||||
@ -69,8 +69,8 @@ class MoltchatConfig(BaseModel):
|
||||
sessions: list[str] = Field(default_factory=list)
|
||||
panels: list[str] = Field(default_factory=list)
|
||||
allow_from: list[str] = Field(default_factory=list)
|
||||
mention: MoltchatMentionConfig = Field(default_factory=MoltchatMentionConfig)
|
||||
groups: dict[str, MoltchatGroupRule] = Field(default_factory=dict)
|
||||
mention: MochatMentionConfig = Field(default_factory=MochatMentionConfig)
|
||||
groups: dict[str, MochatGroupRule] = Field(default_factory=dict)
|
||||
reply_delay_mode: str = "non-mention" # off | non-mention
|
||||
reply_delay_ms: int = 120000
|
||||
|
||||
@ -81,7 +81,7 @@ class ChannelsConfig(BaseModel):
|
||||
telegram: TelegramConfig = Field(default_factory=TelegramConfig)
|
||||
discord: DiscordConfig = Field(default_factory=DiscordConfig)
|
||||
feishu: FeishuConfig = Field(default_factory=FeishuConfig)
|
||||
moltchat: MoltchatConfig = Field(default_factory=MoltchatConfig)
|
||||
mochat: MochatConfig = Field(default_factory=MochatConfig)
|
||||
|
||||
|
||||
class AgentDefaults(BaseModel):
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
import pytest
|
||||
|
||||
from nanobot.bus.queue import MessageBus
|
||||
from nanobot.channels.moltchat import (
|
||||
MoltchatBufferedEntry,
|
||||
MoltchatChannel,
|
||||
from nanobot.channels.mochat import (
|
||||
MochatBufferedEntry,
|
||||
MochatChannel,
|
||||
build_buffered_body,
|
||||
resolve_moltchat_target,
|
||||
resolve_mochat_target,
|
||||
resolve_require_mention,
|
||||
resolve_was_mentioned,
|
||||
)
|
||||
from nanobot.config.schema import MoltchatConfig, MoltchatGroupRule, MoltchatMentionConfig
|
||||
from nanobot.config.schema import MochatConfig, MochatGroupRule, MochatMentionConfig
|
||||
|
||||
|
||||
def test_resolve_moltchat_target_prefixes() -> None:
|
||||
t = resolve_moltchat_target("panel:abc")
|
||||
def test_resolve_mochat_target_prefixes() -> None:
|
||||
t = resolve_mochat_target("panel:abc")
|
||||
assert t.id == "abc"
|
||||
assert t.is_panel is True
|
||||
|
||||
t = resolve_moltchat_target("session_123")
|
||||
t = resolve_mochat_target("session_123")
|
||||
assert t.id == "session_123"
|
||||
assert t.is_panel is False
|
||||
|
||||
t = resolve_moltchat_target("mochat:session_456")
|
||||
t = resolve_mochat_target("mochat:session_456")
|
||||
assert t.id == "session_456"
|
||||
assert t.is_panel is False
|
||||
|
||||
@ -40,12 +40,12 @@ def test_resolve_was_mentioned_from_meta_and_text() -> None:
|
||||
|
||||
|
||||
def test_resolve_require_mention_priority() -> None:
|
||||
cfg = MoltchatConfig(
|
||||
cfg = MochatConfig(
|
||||
groups={
|
||||
"*": MoltchatGroupRule(require_mention=False),
|
||||
"group-a": MoltchatGroupRule(require_mention=True),
|
||||
"*": MochatGroupRule(require_mention=False),
|
||||
"group-a": MochatGroupRule(require_mention=True),
|
||||
},
|
||||
mention=MoltchatMentionConfig(require_in_groups=False),
|
||||
mention=MochatMentionConfig(require_in_groups=False),
|
||||
)
|
||||
|
||||
assert resolve_require_mention(cfg, session_id="panel-x", group_id="group-a") is True
|
||||
@ -55,14 +55,14 @@ def test_resolve_require_mention_priority() -> None:
|
||||
@pytest.mark.asyncio
|
||||
async def test_delay_buffer_flushes_on_mention() -> None:
|
||||
bus = MessageBus()
|
||||
cfg = MoltchatConfig(
|
||||
cfg = MochatConfig(
|
||||
enabled=True,
|
||||
claw_token="token",
|
||||
agent_user_id="bot",
|
||||
reply_delay_mode="non-mention",
|
||||
reply_delay_ms=60_000,
|
||||
)
|
||||
channel = MoltchatChannel(cfg, bus)
|
||||
channel = MochatChannel(cfg, bus)
|
||||
|
||||
first = {
|
||||
"type": "message.add",
|
||||
@ -94,7 +94,7 @@ async def test_delay_buffer_flushes_on_mention() -> None:
|
||||
assert bus.inbound_size == 1
|
||||
|
||||
msg = await bus.consume_inbound()
|
||||
assert msg.channel == "moltchat"
|
||||
assert msg.channel == "mochat"
|
||||
assert msg.chat_id == "panel-1"
|
||||
assert "user1: first" in msg.content
|
||||
assert "user2: hello <@bot>" in msg.content
|
||||
@ -106,8 +106,8 @@ async def test_delay_buffer_flushes_on_mention() -> None:
|
||||
def test_build_buffered_body_group_labels() -> None:
|
||||
body = build_buffered_body(
|
||||
entries=[
|
||||
MoltchatBufferedEntry(raw_body="a", author="u1", sender_name="Alice"),
|
||||
MoltchatBufferedEntry(raw_body="b", author="u2", sender_username="bot"),
|
||||
MochatBufferedEntry(raw_body="a", author="u1", sender_name="Alice"),
|
||||
MochatBufferedEntry(raw_body="b", author="u2", sender_username="bot"),
|
||||
],
|
||||
is_group=True,
|
||||
)
|
||||
Loading…
x
Reference in New Issue
Block a user