feat: rebuild AI commands and migrate config to TOML
This commit is contained in:
90
AGENTS.md
Normal file
90
AGENTS.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# AGENTS.md
|
||||
|
||||
Repository guidance for automated coding tools and contributors.
|
||||
|
||||
## Running the bot
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python main.py
|
||||
```
|
||||
|
||||
Logs go to `bot.log` by default (rotating, 10 MB, 3 backups). Override with `LOG_FILE=<path>`.
|
||||
|
||||
Run the test suite with:
|
||||
|
||||
```bash
|
||||
python -m unittest discover -s tests -v
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Copy `config.example.toml` to `config.toml` and fill in credentials, or use environment variables (`.env.example` shows the env-var form). Both sources are merged at startup — env vars take precedence.
|
||||
|
||||
Key env vars: `TG_API_ID`, `TG_API_HASH`, `SESSION_DIR`, `ACCOUNTS_JSON` (JSON list) or `ACCOUNT_SESSIONS` (comma-separated session names), `BOT_CONFIG` (path to config file), and `BOT_DOTENV_PATH` (path to `.env`).
|
||||
|
||||
The optional GitHub webhook uses `GITHUB_WEBHOOK_ENABLED`, `GITHUB_WEBHOOK_HOST`, `GITHUB_WEBHOOK_PORT`, and `GITHUB_WEBHOOK_SECRET`.
|
||||
|
||||
The `.txt` transcription command uses `[elevenlabs]` settings or `ELEVENLABS_API_KEY`, `ELEVENLABS_MODEL_ID`, `ELEVENLABS_LANGUAGE_CODE`, and `ELEVENLABS_REQUEST_TIMEOUT_SECONDS`.
|
||||
|
||||
The `.ai` agent command uses `[ai.glm]` by default and `[ai.deepseek]` with the `.ds` flag. Provider environment variables are `GLM_API_URL`, `GLM_API_KEY`, `GLM_MODEL`, `DEEPSEEK_API_URL`, `DEEPSEEK_API_KEY`, and `DEEPSEEK_MODEL`. `.th` enables reasoning. Brave Search is available in every AI session and requires `BRAVE_SEARCH_API_KEY` or `ai.brave_api_key`.
|
||||
|
||||
AI prompt templates are always loaded from `prompts/MASTER.txt` and `prompts/USER.txt` for every invocation. C-style `/* ... */` blocks and `//` comments (at line start or following whitespace) are removed before rendering. Both `{name}` and `{{name}}` placeholders are supported, including dotted values such as `caller_user.id`, `caller_user.name`, `owner.id`, `owner.name`, `is_owner`, `text`, `chat.*`, `message.*`, `reply.*`, `account.*`, `provider`, `thinking`, and `context_messages`. Unknown placeholders fail the command with 👎.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Boot path
|
||||
`main.py` → `load_app_config()` → `AccountManager` → one Pyrogram (`kurigram`) `Client` per account → `register_common_handlers()` → `pyrogram.idle()`.
|
||||
|
||||
### Command system (`app/commands/`)
|
||||
|
||||
- **Dispatcher** (`dispatcher.py`): parses `.command` (owner prefix) or `..command` (shared/guest prefix) from message text, resolves to a `CommandDefinition`, runs pre-checks, calls handler.
|
||||
- **Registry** (`core.py`): `CommandRegistry` holds `CommandDefinition` objects. `DEFAULT_REGISTRY` is the singleton used at runtime; commands self-register via decorator.
|
||||
- **Built-in commands** (`builtins.py`): `.ping`, `.status`, `.restart`, `.id`, `.help`, `.rights`/`.acl`, `.allow`, `.deny` (access), `.echo` (shared), `.txt` (shared ElevenLabs transcription), and `.ai` (shared agent loop). Add new commands here using the `@command_registry.command(...)` decorator pattern.
|
||||
- **Check system** (`core.py`): `CommandCheck` functions (e.g. `owner_only_check()`, `shared_access_check()`, `require_reply_check()`, `require_args_check()`) gate handler execution. Chain them with `.check(...)` or `.owner_only()` / `.require_reply()` / `.require_args()` on the builder.
|
||||
- **ACL** (`acl.py`): `RuntimeAclStore` persists per-session `<session>.acl.json` allow/deny rules. Only shared (`.is_shared`) commands can be granted to guests.
|
||||
|
||||
### GitHub webhook (`app/github_webhook.py`)
|
||||
|
||||
When enabled, the `aiohttp` server accepts signed GitHub events at `/github/{account}/{chat_id}` and forwards formatted messages through the selected running account.
|
||||
|
||||
### Transcription (`app/transcription.py`)
|
||||
|
||||
`.txt` / `..txt` must reply to a voice message, video note, or video. The command downloads the Telegram media temporarily and sends it to ElevenLabs Speech to Text. Invalid usage and transcription failures react to the command message with 👎 and send no error reply. Guests need an ACL grant for `txt`.
|
||||
|
||||
### Agent loop (`app/agentic.py`)
|
||||
|
||||
`.ai [count] [.ds] [.th] <prompt>` runs `micro-agents` with GLM by default. `.ds` selects DeepSeek and `.th` enables reasoning. Every session exposes the `micro-agents-tools` Brave Search tool. Unknown leading dot-arguments are ignored. The optional count adds preceding Telegram messages; a replied message is always injected once. Media at or below 5 MiB is attached. Invalid syntax and runtime/API failures only react with 👎. Guests need an ACL grant for `ai` and invoke it as `..ai`.
|
||||
|
||||
The master and user prompt callbacks must render `prompts/MASTER.txt` and `prompts/USER.txt`; do not add a config or hard-coded fallback that bypasses those files.
|
||||
|
||||
Every historical or replied Telegram message is a `UserMessage` rendered through `USER.txt`; `message.id`, `message.text`, `message.date`, `message.user.*`, and `message.is_reply` describe that specific message. Filesystem and Python execution tools are registered through `AgentToolManager` but exposed and executable only when `is_owner` is true. Telegram `send_message`, `send_image`, and `send_file` tools target the current chat. Owner file sends are confined to the repository root; guest file sends are confined to the current session's temporary media directory to prevent repository data disclosure.
|
||||
|
||||
Each account keeps a bounded in-memory `AgentSessionStore`. Replies to Telegram messages previously emitted as final `.ai` answers reuse the corresponding `micro-agents` session. Session reuse is checked only inside the `.ai` command handler; ordinary replies without `.ai`/`..ai` never trigger the agent. All chunks of a long final answer are mapped to the same session, and session workspaces are cleaned up when evicted or when the account manager stops.
|
||||
|
||||
### Key types
|
||||
|
||||
- `CommandContext` — per-invocation context passed to every handler; holds `client`, `manager`, `account`, `message`, parsed `args`/`argv`, and convenience properties (`actor_id`, `owner_id`, `is_owner`, `replied_user`).
|
||||
- `AccountState` — runtime state per account (client, ACL store, status, `me` object).
|
||||
|
||||
## Adding a new command
|
||||
|
||||
```python
|
||||
@command_registry.command(
|
||||
"mycommand",
|
||||
"Short description",
|
||||
usage=".mycommand <arg>",
|
||||
category="general",
|
||||
).shared() # omit for owner-only; add .require_args() / .require_reply() as needed
|
||||
async def mycommand(ctx: "CommandContext") -> None:
|
||||
await ctx.reply(ctx.args)
|
||||
```
|
||||
|
||||
Shared commands use `..mycommand` for guests and `.mycommand` for the owner. Owner-only commands use `.mycommand` only.
|
||||
|
||||
## Change guidelines
|
||||
|
||||
- Preserve unrelated local and untracked changes.
|
||||
- Register commands through `command_registry`; do not add separate Pyrogram handlers for individual commands.
|
||||
- Keep shared commands behind `shared_access_check()` and persist ACL changes through `RuntimeAclStore`.
|
||||
- Add or update focused `unittest` coverage for behavioral changes.
|
||||
Reference in New Issue
Block a user