Merge pull request #6 from Athemis/feat/matrix-improvements
feat(matrix): E2E, typing, markdown/HTML, group policy, inbound+outbound media, thread replies
This commit is contained in:
67
README.md
67
README.md
@@ -301,6 +301,73 @@ nanobot gateway
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Matrix (Element)</b></summary>
|
||||||
|
|
||||||
|
Uses Matrix sync via `matrix-nio` (inbound media + outbound file attachments).
|
||||||
|
|
||||||
|
**1. Create/choose a Matrix account**
|
||||||
|
|
||||||
|
- Create or reuse a Matrix account on your homeserver (for example `matrix.org`).
|
||||||
|
- Confirm you can log in with Element.
|
||||||
|
|
||||||
|
**2. Get credentials**
|
||||||
|
|
||||||
|
- You need:
|
||||||
|
- `userId` (example: `@nanobot:matrix.org`)
|
||||||
|
- `accessToken`
|
||||||
|
- `deviceId` (recommended so sync tokens can be restored across restarts)
|
||||||
|
- You can obtain these from your homeserver login API (`/_matrix/client/v3/login`) or from your client's advanced session settings.
|
||||||
|
|
||||||
|
**3. Configure**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channels": {
|
||||||
|
"matrix": {
|
||||||
|
"enabled": true,
|
||||||
|
"homeserver": "https://matrix.org",
|
||||||
|
"userId": "@nanobot:matrix.org",
|
||||||
|
"accessToken": "syt_xxx",
|
||||||
|
"deviceId": "NANOBOT01",
|
||||||
|
"e2eeEnabled": true,
|
||||||
|
"allowFrom": [],
|
||||||
|
"groupPolicy": "open",
|
||||||
|
"groupAllowFrom": [],
|
||||||
|
"allowRoomMentions": false,
|
||||||
|
"maxMediaBytes": 20971520
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> `allowFrom`: Empty allows all senders; set user IDs to restrict access.
|
||||||
|
> `groupPolicy`: `open`, `mention`, or `allowlist`.
|
||||||
|
> `groupAllowFrom`: Room allowlist used when `groupPolicy` is `allowlist`.
|
||||||
|
> `allowRoomMentions`: If `true`, accepts `@room` (`m.mentions.room`) in mention mode.
|
||||||
|
> `e2eeEnabled`: Enables Matrix E2EE support (default `true`); set `false` only for plaintext-only setups.
|
||||||
|
> `maxMediaBytes`: Max attachment size in bytes (default `20MB`) for inbound and outbound media handling; set to `0` to block all inbound and outbound attachment uploads.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Matrix E2EE implications:
|
||||||
|
>
|
||||||
|
> - Keep a persistent `matrix-store` and stable `deviceId`; otherwise encrypted session state can be lost after restart.
|
||||||
|
> - In newly joined encrypted rooms, initial messages may fail until Olm/Megolm sessions are established.
|
||||||
|
> - With `e2eeEnabled=false`, encrypted room messages may be undecryptable and E2EE send safeguards are not applied.
|
||||||
|
> - With `e2eeEnabled=true`, the bot sends with `ignore_unverified_devices=true` (more compatible, less strict than verified-only sending).
|
||||||
|
> - Changing `accessToken`/`deviceId` effectively creates a new device and may require session re-establishment.
|
||||||
|
> - Outbound attachments are sent from `OutboundMessage.media`.
|
||||||
|
> - Effective media limit (inbound + outbound) uses the stricter value of local `maxMediaBytes` and homeserver `m.upload.size` (if advertised).
|
||||||
|
> - If `tools.restrictToWorkspace=true`, Matrix outbound attachments are limited to files inside the workspace.
|
||||||
|
|
||||||
|
**4. Run**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nanobot gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>WhatsApp</b></summary>
|
<summary><b>WhatsApp</b></summary>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
|||||||
"""Configuration schema using Pydantic."""
|
"""Configuration schema using Pydantic."""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, ConfigDict
|
from pydantic import BaseModel, Field, ConfigDict
|
||||||
from pydantic.alias_generators import to_camel
|
from pydantic.alias_generators import to_camel
|
||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
@@ -60,6 +62,26 @@ class DiscordConfig(Base):
|
|||||||
intents: int = 37377 # GUILDS + GUILD_MESSAGES + DIRECT_MESSAGES + MESSAGE_CONTENT
|
intents: int = 37377 # GUILDS + GUILD_MESSAGES + DIRECT_MESSAGES + MESSAGE_CONTENT
|
||||||
|
|
||||||
|
|
||||||
|
class MatrixConfig(Base):
|
||||||
|
"""Matrix (Element) channel configuration."""
|
||||||
|
|
||||||
|
enabled: bool = False
|
||||||
|
homeserver: str = "https://matrix.org"
|
||||||
|
access_token: str = ""
|
||||||
|
user_id: str = "" # @bot:matrix.org
|
||||||
|
device_id: str = ""
|
||||||
|
# Enable Matrix E2EE support (encryption + encrypted room handling).
|
||||||
|
e2ee_enabled: bool = True
|
||||||
|
# Max seconds to wait for sync_forever to stop gracefully before cancellation fallback.
|
||||||
|
sync_stop_grace_seconds: int = 2
|
||||||
|
# Max attachment size accepted for Matrix media handling (inbound + outbound).
|
||||||
|
max_media_bytes: int = 20 * 1024 * 1024
|
||||||
|
allow_from: list[str] = Field(default_factory=list)
|
||||||
|
group_policy: Literal["open", "mention", "allowlist"] = "open"
|
||||||
|
group_allow_from: list[str] = Field(default_factory=list)
|
||||||
|
allow_room_mentions: bool = False
|
||||||
|
|
||||||
|
|
||||||
class EmailConfig(Base):
|
class EmailConfig(Base):
|
||||||
"""Email channel configuration (IMAP inbound + SMTP outbound)."""
|
"""Email channel configuration (IMAP inbound + SMTP outbound)."""
|
||||||
|
|
||||||
@@ -176,6 +198,7 @@ class ChannelsConfig(Base):
|
|||||||
email: EmailConfig = Field(default_factory=EmailConfig)
|
email: EmailConfig = Field(default_factory=EmailConfig)
|
||||||
slack: SlackConfig = Field(default_factory=SlackConfig)
|
slack: SlackConfig = Field(default_factory=SlackConfig)
|
||||||
qq: QQConfig = Field(default_factory=QQConfig)
|
qq: QQConfig = Field(default_factory=QQConfig)
|
||||||
|
matrix: MatrixConfig = Field(default_factory=MatrixConfig)
|
||||||
|
|
||||||
|
|
||||||
class AgentDefaults(Base):
|
class AgentDefaults(Base):
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ dependencies = [
|
|||||||
"prompt-toolkit>=3.0.50,<4.0.0",
|
"prompt-toolkit>=3.0.50,<4.0.0",
|
||||||
"mcp>=1.26.0,<2.0.0",
|
"mcp>=1.26.0,<2.0.0",
|
||||||
"json-repair>=0.57.0,<1.0.0",
|
"json-repair>=0.57.0,<1.0.0",
|
||||||
|
"matrix-nio[e2e]>=0.25.2",
|
||||||
|
"mistune>=3.0.0,<4.0.0",
|
||||||
|
"nh3>=0.2.17,<1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
1279
tests/test_matrix_channel.py
Normal file
1279
tests/test_matrix_channel.py
Normal file
File diff suppressed because it is too large
Load Diff
10
tests/test_message_tool.py
Normal file
10
tests/test_message_tool.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from nanobot.agent.tools.message import MessageTool
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_message_tool_returns_error_when_no_target_context() -> None:
|
||||||
|
tool = MessageTool()
|
||||||
|
result = await tool.execute(content="test")
|
||||||
|
assert result == "Error: No target channel/chat specified"
|
||||||
Reference in New Issue
Block a user