pythonclaw 0.3.3__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pythonclaw-0.3.3/pythonclaw.egg-info → pythonclaw-0.5.0}/PKG-INFO +56 -31
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/README.md +53 -29
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pyproject.toml +16 -2
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/__init__.py +1 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/channels/discord_bot.py +8 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/channels/telegram_bot.py +58 -3
- pythonclaw-0.5.0/pythonclaw/channels/whatsapp_bot.py +252 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/config.py +25 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/agent.py +144 -16
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/memory/manager.py +39 -13
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/memory/storage.py +0 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/persistent_agent.py +6 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/skill_loader.py +90 -7
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/skillhub.py +29 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/init.py +2 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/main.py +6 -3
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/onboard.py +48 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/server.py +21 -2
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/session_manager.py +63 -12
- pythonclaw-0.5.0/pythonclaw/templates/skills/communication/CATEGORY.md +5 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/communication/email/SKILL.md +65 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/communication/slack/SKILL.md +98 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/communication/slack/slack_api.py +153 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/data/CATEGORY.md +5 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/data/csv_analyzer/SKILL.md +61 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/data/finance/SKILL.md +51 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/data/news/SKILL.md +52 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/data/pdf_reader/SKILL.md +51 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/data/scraper/SKILL.md +1 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/data/weather/SKILL.md +68 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/data/youtube/SKILL.md +54 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/dev/CATEGORY.md +5 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/dev/code_runner/SKILL.md +58 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/dev/github/SKILL.md +78 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/dev/http_request/SKILL.md +50 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/google/CATEGORY.md +5 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/google/workspace/SKILL.md +43 -36
- pythonclaw-0.5.0/pythonclaw/templates/skills/google/workspace/check_setup.sh +52 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/media/CATEGORY.md +5 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/media/image_gen/SKILL.md +99 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/media/image_gen/generate.py +103 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/media/spotify/SKILL.md +124 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/media/spotify/spotify_ctl.py +231 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/media/tts/SKILL.md +83 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/media/tts/speak.py +50 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/meta/CATEGORY.md +5 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/meta/skill_creator/SKILL.md +79 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/productivity/CATEGORY.md +5 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/productivity/notion/SKILL.md +99 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/productivity/notion/notion_api.py +185 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/productivity/obsidian/SKILL.md +110 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/productivity/obsidian/obsidian_vault.py +165 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/productivity/trello/SKILL.md +110 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/productivity/trello/trello_api.py +141 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/CATEGORY.md +5 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/change_persona/SKILL.md +47 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/change_setting/SKILL.md +69 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/system/change_setting/update_config.py +1 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/change_soul/SKILL.md +46 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/model_usage/SKILL.md +73 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/model_usage/usage_stats.py +73 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/onboarding/SKILL.md +58 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/random/SKILL.md +51 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/session_logs/SKILL.md +80 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/session_logs/search_sessions.py +55 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/system/time/SKILL.md +51 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/system/time/time_util.py +1 -1
- pythonclaw-0.5.0/pythonclaw/templates/skills/text/CATEGORY.md +5 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/text/translator/SKILL.md +52 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/web/CATEGORY.md +5 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/web/summarize/SKILL.md +84 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/web/summarize/summarize_url.py +107 -0
- pythonclaw-0.5.0/pythonclaw/templates/skills/web/tavily/SKILL.md +62 -0
- pythonclaw-0.5.0/pythonclaw/templates/tools/TOOLS.md +43 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/web/app.py +116 -12
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/web/static/index.html +198 -33
- {pythonclaw-0.3.3 → pythonclaw-0.5.0/pythonclaw.egg-info}/PKG-INFO +56 -31
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw.egg-info/SOURCES.txt +25 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw.egg-info/requires.txt +1 -0
- pythonclaw-0.3.3/pythonclaw/templates/skills/communication/CATEGORY.md +0 -4
- pythonclaw-0.3.3/pythonclaw/templates/skills/communication/email/SKILL.md +0 -54
- pythonclaw-0.3.3/pythonclaw/templates/skills/data/CATEGORY.md +0 -4
- pythonclaw-0.3.3/pythonclaw/templates/skills/data/csv_analyzer/SKILL.md +0 -51
- pythonclaw-0.3.3/pythonclaw/templates/skills/data/finance/SKILL.md +0 -41
- pythonclaw-0.3.3/pythonclaw/templates/skills/data/news/SKILL.md +0 -39
- pythonclaw-0.3.3/pythonclaw/templates/skills/data/pdf_reader/SKILL.md +0 -40
- pythonclaw-0.3.3/pythonclaw/templates/skills/data/weather/SKILL.md +0 -42
- pythonclaw-0.3.3/pythonclaw/templates/skills/data/youtube/SKILL.md +0 -43
- pythonclaw-0.3.3/pythonclaw/templates/skills/dev/CATEGORY.md +0 -4
- pythonclaw-0.3.3/pythonclaw/templates/skills/dev/code_runner/SKILL.md +0 -46
- pythonclaw-0.3.3/pythonclaw/templates/skills/dev/github/SKILL.md +0 -52
- pythonclaw-0.3.3/pythonclaw/templates/skills/dev/http_request/SKILL.md +0 -40
- pythonclaw-0.3.3/pythonclaw/templates/skills/google/CATEGORY.md +0 -4
- pythonclaw-0.3.3/pythonclaw/templates/skills/meta/CATEGORY.md +0 -4
- pythonclaw-0.3.3/pythonclaw/templates/skills/meta/skill_creator/SKILL.md +0 -151
- pythonclaw-0.3.3/pythonclaw/templates/skills/system/CATEGORY.md +0 -4
- pythonclaw-0.3.3/pythonclaw/templates/skills/system/change_persona/SKILL.md +0 -41
- pythonclaw-0.3.3/pythonclaw/templates/skills/system/change_setting/SKILL.md +0 -65
- pythonclaw-0.3.3/pythonclaw/templates/skills/system/change_soul/SKILL.md +0 -41
- pythonclaw-0.3.3/pythonclaw/templates/skills/system/onboarding/SKILL.md +0 -63
- pythonclaw-0.3.3/pythonclaw/templates/skills/system/random/SKILL.md +0 -33
- pythonclaw-0.3.3/pythonclaw/templates/skills/system/time/SKILL.md +0 -33
- pythonclaw-0.3.3/pythonclaw/templates/skills/text/CATEGORY.md +0 -4
- pythonclaw-0.3.3/pythonclaw/templates/skills/text/translator/SKILL.md +0 -47
- pythonclaw-0.3.3/pythonclaw/templates/skills/web/CATEGORY.md +0 -4
- pythonclaw-0.3.3/pythonclaw/templates/skills/web/tavily/SKILL.md +0 -61
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/LICENSE +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/MANIFEST.in +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/__main__.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/__init__.py +1 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/compaction.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/knowledge/rag.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/llm/anthropic_client.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/llm/base.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/llm/gemini_client.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/llm/openai_compatible.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/llm/response.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/retrieval/__init__.py +1 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/retrieval/chunker.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/retrieval/dense.py +2 -2
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/retrieval/fusion.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/retrieval/reranker.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/retrieval/retriever.py +1 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/retrieval/sparse.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/session_store.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/tools.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/core/utils.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/daemon.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/scheduler/cron.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/scheduler/heartbeat.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/persona/demo_persona.md +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/communication/email/send_email.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/data/csv_analyzer/analyze.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/data/finance/fetch_quote.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/data/news/search_news.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/data/pdf_reader/read_pdf.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/data/scraper/scrape.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/data/weather/weather.py +1 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/data/youtube/youtube_info.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/dev/code_runner/run_code.py +1 -1
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/dev/github/gh.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/dev/http_request/request.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/system/onboarding/write_identity.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/system/random/random_util.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/skills/text/translator/translate.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/templates/soul/SOUL.md +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/web/__init__.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/web/static/favicon.png +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw/web/static/logo.png +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw.egg-info/dependency_links.txt +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw.egg-info/entry_points.txt +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/pythonclaw.egg-info/top_level.txt +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/setup.cfg +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/tests/test_compaction.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/tests/test_persistence.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/tests/test_rag_hybrid.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/tests/test_skills.py +0 -0
- {pythonclaw-0.3.3 → pythonclaw-0.5.0}/tests/test_soul.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonclaw
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: OpenClaw reimagined in pure Python — autonomous AI agent with memory, RAG, skills, web dashboard, and multi-channel support.
|
|
5
5
|
Author-email: Eric Wang <wangchen2007915@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://github.com/ericwang915/PythonClaw
|
|
|
8
8
|
Project-URL: Documentation, https://github.com/ericwang915/PythonClaw#readme
|
|
9
9
|
Project-URL: Repository, https://github.com/ericwang915/PythonClaw
|
|
10
10
|
Project-URL: Issues, https://github.com/ericwang915/PythonClaw/issues
|
|
11
|
-
Keywords: llm,agent,ai,autonomous,memory,rag,skills,telegram,chatbot,framework
|
|
11
|
+
Keywords: llm,agent,ai,autonomous,memory,rag,skills,telegram,discord,whatsapp,chatbot,framework
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
@@ -27,6 +27,7 @@ Requires-Dist: anthropic
|
|
|
27
27
|
Requires-Dist: google-generativeai
|
|
28
28
|
Requires-Dist: python-telegram-bot[job-queue]>=20.0
|
|
29
29
|
Requires-Dist: discord.py>=2.3
|
|
30
|
+
Requires-Dist: pywa[fastapi]>=2.8
|
|
30
31
|
Requires-Dist: apscheduler>=3.10
|
|
31
32
|
Requires-Dist: pyyaml>=6.0
|
|
32
33
|
Requires-Dist: fastapi>=0.100
|
|
@@ -83,9 +84,12 @@ Dynamic: license-file
|
|
|
83
84
|
| 🌐 | **Web dashboard** | Browser UI for chat, config, skill catalog, identity editing, and marketplace |
|
|
84
85
|
| 🎙️ | **Voice input** | Deepgram speech-to-text in the web chat |
|
|
85
86
|
| ⏰ | **Cron jobs** | Schedule tasks via YAML or let the agent create its own |
|
|
86
|
-
| 📡 | **Multi-channel** | CLI, Web, Telegram, Discord — same agent, different interfaces |
|
|
87
|
+
| 📡 | **Multi-channel** | CLI, Web, Telegram, Discord, WhatsApp — same agent, different interfaces |
|
|
87
88
|
| 🔄 | **Daemon mode** | PID-managed background process with `start` / `stop` / `status` |
|
|
88
89
|
| 🧬 | **Soul + Persona** | Separate core identity from swappable role presentation |
|
|
90
|
+
| 🔧 | **TOOLS.md** | Local environment notes — your cheat sheet for the agent |
|
|
91
|
+
| 🔒 | **Per-group isolation** | Each chat session gets its own memory (optional) |
|
|
92
|
+
| 🔁 | **Concurrency control** | Per-session locks + global semaphore prevent interleaving |
|
|
89
93
|
|
|
90
94
|
---
|
|
91
95
|
|
|
@@ -112,7 +116,7 @@ pythonclaw stop
|
|
|
112
116
|
```bash
|
|
113
117
|
git clone https://github.com/ericwang915/PythonClaw.git
|
|
114
118
|
cd PythonClaw
|
|
115
|
-
pip install -e
|
|
119
|
+
pip install -e .
|
|
116
120
|
pythonclaw onboard
|
|
117
121
|
```
|
|
118
122
|
|
|
@@ -125,7 +129,7 @@ pythonclaw onboard
|
|
|
125
129
|
| `pythonclaw onboard` | Interactive setup wizard — choose LLM provider, enter API key |
|
|
126
130
|
| `pythonclaw start` | Start the agent as a background daemon |
|
|
127
131
|
| `pythonclaw start -f` | Start in foreground (no daemonize) |
|
|
128
|
-
| `pythonclaw start --channels telegram discord` | Start with messaging channels |
|
|
132
|
+
| `pythonclaw start --channels telegram discord whatsapp` | Start with messaging channels |
|
|
129
133
|
| `pythonclaw stop` | Stop the running daemon |
|
|
130
134
|
| `pythonclaw status` | Show daemon status (PID, uptime, port) |
|
|
131
135
|
| `pythonclaw chat` | Interactive CLI chat (foreground REPL) |
|
|
@@ -170,24 +174,25 @@ $ pythonclaw start
|
|
|
170
174
|
## Architecture
|
|
171
175
|
|
|
172
176
|
```
|
|
173
|
-
|
|
174
|
-
│
|
|
175
|
-
|
|
176
|
-
│ CLI │ Daemon
|
|
177
|
-
│ │
|
|
178
|
-
│ onboard │ start /
|
|
179
|
-
│ chat │ stop /
|
|
180
|
-
│ skill … │ status
|
|
181
|
-
│ │
|
|
182
|
-
│ Web UI ◄─┤ Channels
|
|
183
|
-
│ Voice In │ Telegram
|
|
184
|
-
│ │ Discord
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
│
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
177
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
178
|
+
│ PythonClaw │
|
|
179
|
+
├──────────┬────────────┬───────────┬──────────────────────────┤
|
|
180
|
+
│ CLI │ Daemon │ Sessions │ Core │
|
|
181
|
+
│ │ │ │ │
|
|
182
|
+
│ onboard │ start / │ Store(MD) │ Agent │
|
|
183
|
+
│ chat │ stop / │ Manager │ ├─ Memory (Markdown) │
|
|
184
|
+
│ skill … │ status │ Locks + │ ├─ RAG (Hybrid) │
|
|
185
|
+
│ │ │ Semaphore │ ├─ Skills (3-tier) │
|
|
186
|
+
│ Web UI ◄─┤ Channels │ │ ├─ Compaction │
|
|
187
|
+
│ Voice In │ Telegram │ Per-group │ ├─ Soul + Persona │
|
|
188
|
+
│ │ Discord │ Isolation │ ├─ Group Context │
|
|
189
|
+
│ │ WhatsApp │ │ └─ Tool Execution │
|
|
190
|
+
├──────────┴────────────┴───────────┴──────────────────────────┤
|
|
191
|
+
│ LLM Provider Abstraction Layer │
|
|
192
|
+
│ DeepSeek │ Grok │ Claude │ Gemini │ Kimi │ GLM │
|
|
193
|
+
├──────────────────────────────────────────────────────────────┤
|
|
194
|
+
│ SkillHub Marketplace (skillhub.club) │
|
|
195
|
+
└──────────────────────────────────────────────────────────────┘
|
|
191
196
|
```
|
|
192
197
|
|
|
193
198
|
---
|
|
@@ -220,8 +225,11 @@ See [`pythonclaw.example.json`](pythonclaw.example.json) for the full template.
|
|
|
220
225
|
"web": { "host": "0.0.0.0", "port": 7788 },
|
|
221
226
|
"channels": {
|
|
222
227
|
"telegram": { "token": "" },
|
|
223
|
-
"discord": { "token": "" }
|
|
224
|
-
|
|
228
|
+
"discord": { "token": "" },
|
|
229
|
+
"whatsapp": { "phoneNumberId": "", "token": "", "verifyToken": "pythonclaw_verify" }
|
|
230
|
+
},
|
|
231
|
+
"isolation": { "perGroup": false },
|
|
232
|
+
"concurrency": { "maxAgents": 4 }
|
|
225
233
|
}
|
|
226
234
|
```
|
|
227
235
|
|
|
@@ -235,8 +243,8 @@ Environment variables (e.g. `DEEPSEEK_API_KEY`, `TAVILY_API_KEY`) override JSON
|
|
|
235
243
|
|----------|---------------|---------------|
|
|
236
244
|
| **DeepSeek** | `deepseek-chat` | — |
|
|
237
245
|
| **Grok (xAI)** | `grok-3` | — |
|
|
238
|
-
| **Claude (Anthropic)** | `claude-sonnet-4-20250514` |
|
|
239
|
-
| **Gemini (Google)** | `gemini-2.0-flash` |
|
|
246
|
+
| **Claude (Anthropic)** | `claude-sonnet-4-20250514` | — (included) |
|
|
247
|
+
| **Gemini (Google)** | `gemini-2.0-flash` | — (included) |
|
|
240
248
|
| **Kimi (Moonshot)** | `moonshot-v1-128k` | — |
|
|
241
249
|
| **GLM (Zhipu)** | `glm-4-flash` | — |
|
|
242
250
|
| Any OpenAI-compatible | Custom | — |
|
|
@@ -282,11 +290,28 @@ Also accessible from the web dashboard **Marketplace** tab.
|
|
|
282
290
|
### Markdown Memory
|
|
283
291
|
|
|
284
292
|
```
|
|
285
|
-
context/memory/
|
|
293
|
+
~/.pythonclaw/context/memory/
|
|
286
294
|
├── MEMORY.md # Curated long-term memory
|
|
287
295
|
└── 2026-02-23.md # Daily append-only log
|
|
288
296
|
```
|
|
289
297
|
|
|
298
|
+
When **per-group isolation** is enabled (`"isolation": { "perGroup": true }` in config),
|
|
299
|
+
each session (Telegram chat, Discord channel, etc.) gets its own `memory/`, `persona/`,
|
|
300
|
+
and `soul/` under `~/.pythonclaw/context/groups/<session-id>/`, while global memories
|
|
301
|
+
remain accessible via read-through fallback.
|
|
302
|
+
|
|
303
|
+
### TOOLS.md — Local Notes
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
~/.pythonclaw/context/tools/
|
|
307
|
+
└── TOOLS.md # Your environment-specific cheat sheet
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Skills define *how* tools work. `TOOLS.md` stores *your* specifics — SSH hosts, device
|
|
311
|
+
nicknames, project paths, preferred defaults, API endpoints. Keeping them apart means
|
|
312
|
+
you can update skills without losing your notes, and share skills without leaking your
|
|
313
|
+
infrastructure. Editable from the web dashboard.
|
|
314
|
+
|
|
290
315
|
### Hybrid RAG Pipeline
|
|
291
316
|
|
|
292
317
|
```
|
|
@@ -333,7 +358,7 @@ PythonClaw/
|
|
|
333
358
|
│ │ ├── memory/ # Markdown memory
|
|
334
359
|
│ │ ├── knowledge/ # Knowledge-base RAG
|
|
335
360
|
│ │ └── retrieval/ # BM25 + dense + fusion + reranker
|
|
336
|
-
│ ├── channels/ # Telegram, Discord
|
|
361
|
+
│ ├── channels/ # Telegram, Discord, WhatsApp
|
|
337
362
|
│ ├── scheduler/ # Cron jobs, heartbeat
|
|
338
363
|
│ ├── web/ # FastAPI dashboard + static assets
|
|
339
364
|
│ └── templates/ # Built-in skill templates
|
|
@@ -351,7 +376,7 @@ PythonClaw/
|
|
|
351
376
|
git clone https://github.com/ericwang915/PythonClaw.git
|
|
352
377
|
cd PythonClaw
|
|
353
378
|
python -m venv .venv && source .venv/bin/activate
|
|
354
|
-
pip install -e
|
|
379
|
+
pip install -e .
|
|
355
380
|
pytest tests/ -v
|
|
356
381
|
```
|
|
357
382
|
|
|
@@ -373,7 +398,7 @@ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
|
373
398
|
| Dashboard | Web UI | Web UI (localhost:7788) |
|
|
374
399
|
| Memory | Markdown | Markdown (long-term + daily) |
|
|
375
400
|
| Skills | Plugin system | Three-tier + SkillHub marketplace |
|
|
376
|
-
| Channels | Discord, Telegram, WhatsApp | CLI, Web, Telegram, Discord |
|
|
401
|
+
| Channels | Discord, Telegram, WhatsApp | CLI, Web, Telegram, Discord, WhatsApp |
|
|
377
402
|
| Voice | — | Deepgram STT |
|
|
378
403
|
| LLM Providers | OpenAI, Anthropic, Gemini | DeepSeek, Grok, Claude, Gemini, Kimi, GLM |
|
|
379
404
|
| Daemon | Background process | PID-managed (`start`/`stop`/`status`) |
|
|
@@ -42,9 +42,12 @@
|
|
|
42
42
|
| 🌐 | **Web dashboard** | Browser UI for chat, config, skill catalog, identity editing, and marketplace |
|
|
43
43
|
| 🎙️ | **Voice input** | Deepgram speech-to-text in the web chat |
|
|
44
44
|
| ⏰ | **Cron jobs** | Schedule tasks via YAML or let the agent create its own |
|
|
45
|
-
| 📡 | **Multi-channel** | CLI, Web, Telegram, Discord — same agent, different interfaces |
|
|
45
|
+
| 📡 | **Multi-channel** | CLI, Web, Telegram, Discord, WhatsApp — same agent, different interfaces |
|
|
46
46
|
| 🔄 | **Daemon mode** | PID-managed background process with `start` / `stop` / `status` |
|
|
47
47
|
| 🧬 | **Soul + Persona** | Separate core identity from swappable role presentation |
|
|
48
|
+
| 🔧 | **TOOLS.md** | Local environment notes — your cheat sheet for the agent |
|
|
49
|
+
| 🔒 | **Per-group isolation** | Each chat session gets its own memory (optional) |
|
|
50
|
+
| 🔁 | **Concurrency control** | Per-session locks + global semaphore prevent interleaving |
|
|
48
51
|
|
|
49
52
|
---
|
|
50
53
|
|
|
@@ -71,7 +74,7 @@ pythonclaw stop
|
|
|
71
74
|
```bash
|
|
72
75
|
git clone https://github.com/ericwang915/PythonClaw.git
|
|
73
76
|
cd PythonClaw
|
|
74
|
-
pip install -e
|
|
77
|
+
pip install -e .
|
|
75
78
|
pythonclaw onboard
|
|
76
79
|
```
|
|
77
80
|
|
|
@@ -84,7 +87,7 @@ pythonclaw onboard
|
|
|
84
87
|
| `pythonclaw onboard` | Interactive setup wizard — choose LLM provider, enter API key |
|
|
85
88
|
| `pythonclaw start` | Start the agent as a background daemon |
|
|
86
89
|
| `pythonclaw start -f` | Start in foreground (no daemonize) |
|
|
87
|
-
| `pythonclaw start --channels telegram discord` | Start with messaging channels |
|
|
90
|
+
| `pythonclaw start --channels telegram discord whatsapp` | Start with messaging channels |
|
|
88
91
|
| `pythonclaw stop` | Stop the running daemon |
|
|
89
92
|
| `pythonclaw status` | Show daemon status (PID, uptime, port) |
|
|
90
93
|
| `pythonclaw chat` | Interactive CLI chat (foreground REPL) |
|
|
@@ -129,24 +132,25 @@ $ pythonclaw start
|
|
|
129
132
|
## Architecture
|
|
130
133
|
|
|
131
134
|
```
|
|
132
|
-
|
|
133
|
-
│
|
|
134
|
-
|
|
135
|
-
│ CLI │ Daemon
|
|
136
|
-
│ │
|
|
137
|
-
│ onboard │ start /
|
|
138
|
-
│ chat │ stop /
|
|
139
|
-
│ skill … │ status
|
|
140
|
-
│ │
|
|
141
|
-
│ Web UI ◄─┤ Channels
|
|
142
|
-
│ Voice In │ Telegram
|
|
143
|
-
│ │ Discord
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
│
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
135
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
136
|
+
│ PythonClaw │
|
|
137
|
+
├──────────┬────────────┬───────────┬──────────────────────────┤
|
|
138
|
+
│ CLI │ Daemon │ Sessions │ Core │
|
|
139
|
+
│ │ │ │ │
|
|
140
|
+
│ onboard │ start / │ Store(MD) │ Agent │
|
|
141
|
+
│ chat │ stop / │ Manager │ ├─ Memory (Markdown) │
|
|
142
|
+
│ skill … │ status │ Locks + │ ├─ RAG (Hybrid) │
|
|
143
|
+
│ │ │ Semaphore │ ├─ Skills (3-tier) │
|
|
144
|
+
│ Web UI ◄─┤ Channels │ │ ├─ Compaction │
|
|
145
|
+
│ Voice In │ Telegram │ Per-group │ ├─ Soul + Persona │
|
|
146
|
+
│ │ Discord │ Isolation │ ├─ Group Context │
|
|
147
|
+
│ │ WhatsApp │ │ └─ Tool Execution │
|
|
148
|
+
├──────────┴────────────┴───────────┴──────────────────────────┤
|
|
149
|
+
│ LLM Provider Abstraction Layer │
|
|
150
|
+
│ DeepSeek │ Grok │ Claude │ Gemini │ Kimi │ GLM │
|
|
151
|
+
├──────────────────────────────────────────────────────────────┤
|
|
152
|
+
│ SkillHub Marketplace (skillhub.club) │
|
|
153
|
+
└──────────────────────────────────────────────────────────────┘
|
|
150
154
|
```
|
|
151
155
|
|
|
152
156
|
---
|
|
@@ -179,8 +183,11 @@ See [`pythonclaw.example.json`](pythonclaw.example.json) for the full template.
|
|
|
179
183
|
"web": { "host": "0.0.0.0", "port": 7788 },
|
|
180
184
|
"channels": {
|
|
181
185
|
"telegram": { "token": "" },
|
|
182
|
-
"discord": { "token": "" }
|
|
183
|
-
|
|
186
|
+
"discord": { "token": "" },
|
|
187
|
+
"whatsapp": { "phoneNumberId": "", "token": "", "verifyToken": "pythonclaw_verify" }
|
|
188
|
+
},
|
|
189
|
+
"isolation": { "perGroup": false },
|
|
190
|
+
"concurrency": { "maxAgents": 4 }
|
|
184
191
|
}
|
|
185
192
|
```
|
|
186
193
|
|
|
@@ -194,8 +201,8 @@ Environment variables (e.g. `DEEPSEEK_API_KEY`, `TAVILY_API_KEY`) override JSON
|
|
|
194
201
|
|----------|---------------|---------------|
|
|
195
202
|
| **DeepSeek** | `deepseek-chat` | — |
|
|
196
203
|
| **Grok (xAI)** | `grok-3` | — |
|
|
197
|
-
| **Claude (Anthropic)** | `claude-sonnet-4-20250514` |
|
|
198
|
-
| **Gemini (Google)** | `gemini-2.0-flash` |
|
|
204
|
+
| **Claude (Anthropic)** | `claude-sonnet-4-20250514` | — (included) |
|
|
205
|
+
| **Gemini (Google)** | `gemini-2.0-flash` | — (included) |
|
|
199
206
|
| **Kimi (Moonshot)** | `moonshot-v1-128k` | — |
|
|
200
207
|
| **GLM (Zhipu)** | `glm-4-flash` | — |
|
|
201
208
|
| Any OpenAI-compatible | Custom | — |
|
|
@@ -241,11 +248,28 @@ Also accessible from the web dashboard **Marketplace** tab.
|
|
|
241
248
|
### Markdown Memory
|
|
242
249
|
|
|
243
250
|
```
|
|
244
|
-
context/memory/
|
|
251
|
+
~/.pythonclaw/context/memory/
|
|
245
252
|
├── MEMORY.md # Curated long-term memory
|
|
246
253
|
└── 2026-02-23.md # Daily append-only log
|
|
247
254
|
```
|
|
248
255
|
|
|
256
|
+
When **per-group isolation** is enabled (`"isolation": { "perGroup": true }` in config),
|
|
257
|
+
each session (Telegram chat, Discord channel, etc.) gets its own `memory/`, `persona/`,
|
|
258
|
+
and `soul/` under `~/.pythonclaw/context/groups/<session-id>/`, while global memories
|
|
259
|
+
remain accessible via read-through fallback.
|
|
260
|
+
|
|
261
|
+
### TOOLS.md — Local Notes
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
~/.pythonclaw/context/tools/
|
|
265
|
+
└── TOOLS.md # Your environment-specific cheat sheet
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Skills define *how* tools work. `TOOLS.md` stores *your* specifics — SSH hosts, device
|
|
269
|
+
nicknames, project paths, preferred defaults, API endpoints. Keeping them apart means
|
|
270
|
+
you can update skills without losing your notes, and share skills without leaking your
|
|
271
|
+
infrastructure. Editable from the web dashboard.
|
|
272
|
+
|
|
249
273
|
### Hybrid RAG Pipeline
|
|
250
274
|
|
|
251
275
|
```
|
|
@@ -292,7 +316,7 @@ PythonClaw/
|
|
|
292
316
|
│ │ ├── memory/ # Markdown memory
|
|
293
317
|
│ │ ├── knowledge/ # Knowledge-base RAG
|
|
294
318
|
│ │ └── retrieval/ # BM25 + dense + fusion + reranker
|
|
295
|
-
│ ├── channels/ # Telegram, Discord
|
|
319
|
+
│ ├── channels/ # Telegram, Discord, WhatsApp
|
|
296
320
|
│ ├── scheduler/ # Cron jobs, heartbeat
|
|
297
321
|
│ ├── web/ # FastAPI dashboard + static assets
|
|
298
322
|
│ └── templates/ # Built-in skill templates
|
|
@@ -310,7 +334,7 @@ PythonClaw/
|
|
|
310
334
|
git clone https://github.com/ericwang915/PythonClaw.git
|
|
311
335
|
cd PythonClaw
|
|
312
336
|
python -m venv .venv && source .venv/bin/activate
|
|
313
|
-
pip install -e
|
|
337
|
+
pip install -e .
|
|
314
338
|
pytest tests/ -v
|
|
315
339
|
```
|
|
316
340
|
|
|
@@ -332,7 +356,7 @@ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
|
332
356
|
| Dashboard | Web UI | Web UI (localhost:7788) |
|
|
333
357
|
| Memory | Markdown | Markdown (long-term + daily) |
|
|
334
358
|
| Skills | Plugin system | Three-tier + SkillHub marketplace |
|
|
335
|
-
| Channels | Discord, Telegram, WhatsApp | CLI, Web, Telegram, Discord |
|
|
359
|
+
| Channels | Discord, Telegram, WhatsApp | CLI, Web, Telegram, Discord, WhatsApp |
|
|
336
360
|
| Voice | — | Deepgram STT |
|
|
337
361
|
| LLM Providers | OpenAI, Anthropic, Gemini | DeepSeek, Grok, Claude, Gemini, Kimi, GLM |
|
|
338
362
|
| Daemon | Background process | PID-managed (`start`/`stop`/`status`) |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pythonclaw"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "OpenClaw reimagined in pure Python — autonomous AI agent with memory, RAG, skills, web dashboard, and multi-channel support."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -12,7 +12,7 @@ requires-python = ">=3.10"
|
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "Eric Wang", email = "wangchen2007915@gmail.com"},
|
|
14
14
|
]
|
|
15
|
-
keywords = ["llm", "agent", "ai", "autonomous", "memory", "rag", "skills", "telegram", "chatbot", "framework"]
|
|
15
|
+
keywords = ["llm", "agent", "ai", "autonomous", "memory", "rag", "skills", "telegram", "discord", "whatsapp", "chatbot", "framework"]
|
|
16
16
|
classifiers = [
|
|
17
17
|
"Development Status :: 4 - Beta",
|
|
18
18
|
"Intended Audience :: Developers",
|
|
@@ -31,6 +31,7 @@ dependencies = [
|
|
|
31
31
|
"google-generativeai",
|
|
32
32
|
"python-telegram-bot[job-queue]>=20.0",
|
|
33
33
|
"discord.py>=2.3",
|
|
34
|
+
"pywa[fastapi]>=2.8",
|
|
34
35
|
"apscheduler>=3.10",
|
|
35
36
|
"pyyaml>=6.0",
|
|
36
37
|
"fastapi>=0.100",
|
|
@@ -58,6 +59,7 @@ include = ["pythonclaw*"]
|
|
|
58
59
|
[tool.setuptools.package-data]
|
|
59
60
|
pythonclaw = [
|
|
60
61
|
"templates/**/*.py",
|
|
62
|
+
"templates/**/*.sh",
|
|
61
63
|
"templates/**/*.yaml",
|
|
62
64
|
"templates/**/*.md",
|
|
63
65
|
"web/static/**/*",
|
|
@@ -65,3 +67,15 @@ pythonclaw = [
|
|
|
65
67
|
|
|
66
68
|
[tool.setuptools.exclude-package-data]
|
|
67
69
|
"*" = ["__pycache__", "*.pyc", "*.pyo"]
|
|
70
|
+
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
target-version = "py310"
|
|
73
|
+
line-length = 120
|
|
74
|
+
|
|
75
|
+
[tool.ruff.lint]
|
|
76
|
+
select = ["E", "F", "W", "I"]
|
|
77
|
+
ignore = ["E501"]
|
|
78
|
+
|
|
79
|
+
[tool.pytest.ini_options]
|
|
80
|
+
testpaths = ["tests"]
|
|
81
|
+
pythonpath = ["."]
|
|
@@ -183,9 +183,16 @@ class DiscordBot:
|
|
|
183
183
|
async def _handle_chat(self, message: discord.Message, content: str, is_dm: bool) -> None:
|
|
184
184
|
sid = self._session_id(message.author.id if is_dm else message.channel.id, is_dm)
|
|
185
185
|
agent = self._sm.get_or_create(sid)
|
|
186
|
+
|
|
187
|
+
if self._sm.is_locked(sid):
|
|
188
|
+
await message.reply("Processing previous message…")
|
|
189
|
+
|
|
186
190
|
async with message.channel.typing():
|
|
187
191
|
try:
|
|
188
|
-
|
|
192
|
+
async with self._sm.acquire(sid):
|
|
193
|
+
import asyncio
|
|
194
|
+
loop = asyncio.get_event_loop()
|
|
195
|
+
response = await loop.run_in_executor(None, agent.chat, content)
|
|
189
196
|
except Exception as exc:
|
|
190
197
|
logger.exception("[Discord] Agent.chat() raised an exception")
|
|
191
198
|
response = f"Sorry, something went wrong: {exc}"
|
|
@@ -23,10 +23,11 @@ IDs to restrict access. Leave empty (or unset) to allow all users.
|
|
|
23
23
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
|
+
import asyncio
|
|
26
27
|
import logging
|
|
27
28
|
from typing import TYPE_CHECKING
|
|
28
29
|
|
|
29
|
-
from telegram import Update
|
|
30
|
+
from telegram import BotCommand, ReactionTypeEmoji, Update
|
|
30
31
|
from telegram.ext import (
|
|
31
32
|
Application,
|
|
32
33
|
CommandHandler,
|
|
@@ -158,17 +159,61 @@ class TelegramBot:
|
|
|
158
159
|
return
|
|
159
160
|
sid = self._session_id(update.effective_chat.id)
|
|
160
161
|
agent = self._sm.get_or_create(sid)
|
|
161
|
-
|
|
162
|
+
|
|
163
|
+
if self._sm.is_locked(sid):
|
|
164
|
+
await update.message.reply_text("⏳ Processing previous message…")
|
|
165
|
+
|
|
166
|
+
# React to the message so the user knows the bot saw it
|
|
167
|
+
try:
|
|
168
|
+
await update.message.set_reaction([ReactionTypeEmoji("👀")])
|
|
169
|
+
except Exception:
|
|
170
|
+
pass # reaction API may fail on older bot API or in groups
|
|
171
|
+
|
|
172
|
+
# Keep "typing…" visible for the entire duration of processing.
|
|
173
|
+
# Telegram's typing indicator expires after ~5 s, so we resend it
|
|
174
|
+
# on a loop until the agent finishes.
|
|
175
|
+
typing_task = asyncio.create_task(
|
|
176
|
+
self._keep_typing(update.message.chat_id)
|
|
177
|
+
)
|
|
162
178
|
try:
|
|
163
|
-
|
|
179
|
+
async with self._sm.acquire(sid):
|
|
180
|
+
loop = asyncio.get_event_loop()
|
|
181
|
+
response = await loop.run_in_executor(None, agent.chat, user_text)
|
|
164
182
|
except Exception as exc:
|
|
165
183
|
logger.exception("[Telegram] Agent.chat() raised an exception")
|
|
166
184
|
response = f"Sorry, something went wrong: {exc}"
|
|
185
|
+
finally:
|
|
186
|
+
typing_task.cancel()
|
|
187
|
+
|
|
188
|
+
# Clear the "seen" reaction once we reply
|
|
189
|
+
try:
|
|
190
|
+
await update.message.set_reaction([])
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
|
|
167
194
|
for chunk in _split_message(response or "(no response)"):
|
|
168
195
|
await update.message.reply_text(chunk)
|
|
169
196
|
|
|
197
|
+
async def _keep_typing(self, chat_id: int) -> None:
|
|
198
|
+
"""Re-send the 'typing' chat action every 4 s until cancelled."""
|
|
199
|
+
try:
|
|
200
|
+
while True:
|
|
201
|
+
await self._app.bot.send_chat_action(chat_id=chat_id, action="typing")
|
|
202
|
+
await asyncio.sleep(4)
|
|
203
|
+
except asyncio.CancelledError:
|
|
204
|
+
pass
|
|
205
|
+
except Exception:
|
|
206
|
+
logger.debug("[Telegram] _keep_typing stopped unexpectedly", exc_info=True)
|
|
207
|
+
|
|
170
208
|
# ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
171
209
|
|
|
210
|
+
_BOT_COMMANDS = [
|
|
211
|
+
BotCommand("start", "Show welcome message"),
|
|
212
|
+
BotCommand("reset", "Start a fresh session"),
|
|
213
|
+
BotCommand("status", "Show session info"),
|
|
214
|
+
BotCommand("compact", "Compact conversation history"),
|
|
215
|
+
]
|
|
216
|
+
|
|
172
217
|
def build_application(self) -> Application:
|
|
173
218
|
app = Application.builder().token(self._token).build()
|
|
174
219
|
app.add_handler(CommandHandler("start", self._cmd_start))
|
|
@@ -179,10 +224,19 @@ class TelegramBot:
|
|
|
179
224
|
self._app = app
|
|
180
225
|
return app
|
|
181
226
|
|
|
227
|
+
async def _register_commands(self) -> None:
|
|
228
|
+
"""Register slash-commands with Telegram so they appear in the menu."""
|
|
229
|
+
try:
|
|
230
|
+
await self._app.bot.set_my_commands(self._BOT_COMMANDS)
|
|
231
|
+
logger.info("[Telegram] Registered %d bot commands", len(self._BOT_COMMANDS))
|
|
232
|
+
except Exception:
|
|
233
|
+
logger.warning("[Telegram] Failed to register bot commands", exc_info=True)
|
|
234
|
+
|
|
182
235
|
def run_polling(self) -> None:
|
|
183
236
|
"""Blocking call — starts the bot using long polling (for standalone use)."""
|
|
184
237
|
app = self.build_application()
|
|
185
238
|
logger.info("[Telegram] Starting bot (polling mode)...")
|
|
239
|
+
app.post_init = lambda _app: self._register_commands()
|
|
186
240
|
app.run_polling(drop_pending_updates=True)
|
|
187
241
|
|
|
188
242
|
async def start_async(self) -> None:
|
|
@@ -191,6 +245,7 @@ class TelegramBot:
|
|
|
191
245
|
logger.info("[Telegram] Initialising bot (async mode)...")
|
|
192
246
|
await app.initialize()
|
|
193
247
|
await app.start()
|
|
248
|
+
await self._register_commands()
|
|
194
249
|
await app.updater.start_polling(drop_pending_updates=True)
|
|
195
250
|
|
|
196
251
|
async def stop_async(self) -> None:
|