pythonclaw 0.3.3__tar.gz → 0.6.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.
Files changed (163) hide show
  1. {pythonclaw-0.3.3/pythonclaw.egg-info → pythonclaw-0.6.0}/PKG-INFO +63 -38
  2. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/README.md +60 -36
  3. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pyproject.toml +16 -2
  4. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/__init__.py +1 -1
  5. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/channels/discord_bot.py +72 -11
  6. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/channels/telegram_bot.py +145 -15
  7. pythonclaw-0.6.0/pythonclaw/channels/whatsapp_bot.py +304 -0
  8. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/config.py +25 -0
  9. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/agent.py +439 -92
  10. pythonclaw-0.6.0/pythonclaw/core/llm/anthropic_client.py +294 -0
  11. pythonclaw-0.6.0/pythonclaw/core/llm/base.py +55 -0
  12. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/llm/gemini_client.py +52 -1
  13. pythonclaw-0.6.0/pythonclaw/core/llm/openai_compatible.py +105 -0
  14. pythonclaw-0.6.0/pythonclaw/core/memory/manager.py +221 -0
  15. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/memory/storage.py +83 -1
  16. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/persistent_agent.py +33 -1
  17. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/session_store.py +1 -1
  18. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/skill_loader.py +93 -10
  19. pythonclaw-0.6.0/pythonclaw/core/skillhub.py +309 -0
  20. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/tools.py +37 -0
  21. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/init.py +2 -1
  22. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/main.py +21 -13
  23. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/onboard.py +62 -15
  24. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/server.py +21 -2
  25. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/session_manager.py +63 -12
  26. pythonclaw-0.6.0/pythonclaw/templates/skills/communication/CATEGORY.md +5 -0
  27. pythonclaw-0.6.0/pythonclaw/templates/skills/communication/email/SKILL.md +65 -0
  28. pythonclaw-0.6.0/pythonclaw/templates/skills/communication/slack/SKILL.md +98 -0
  29. pythonclaw-0.6.0/pythonclaw/templates/skills/communication/slack/slack_api.py +153 -0
  30. pythonclaw-0.6.0/pythonclaw/templates/skills/data/CATEGORY.md +5 -0
  31. pythonclaw-0.6.0/pythonclaw/templates/skills/data/csv_analyzer/SKILL.md +61 -0
  32. pythonclaw-0.6.0/pythonclaw/templates/skills/data/finance/SKILL.md +51 -0
  33. pythonclaw-0.6.0/pythonclaw/templates/skills/data/news/SKILL.md +52 -0
  34. pythonclaw-0.6.0/pythonclaw/templates/skills/data/pdf_reader/SKILL.md +51 -0
  35. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/data/scraper/SKILL.md +1 -0
  36. pythonclaw-0.6.0/pythonclaw/templates/skills/data/weather/SKILL.md +68 -0
  37. pythonclaw-0.6.0/pythonclaw/templates/skills/data/youtube/SKILL.md +54 -0
  38. pythonclaw-0.6.0/pythonclaw/templates/skills/dev/CATEGORY.md +5 -0
  39. pythonclaw-0.6.0/pythonclaw/templates/skills/dev/code_runner/SKILL.md +58 -0
  40. pythonclaw-0.6.0/pythonclaw/templates/skills/dev/github/SKILL.md +78 -0
  41. pythonclaw-0.6.0/pythonclaw/templates/skills/dev/http_request/SKILL.md +50 -0
  42. pythonclaw-0.6.0/pythonclaw/templates/skills/google/CATEGORY.md +5 -0
  43. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/google/workspace/SKILL.md +43 -36
  44. pythonclaw-0.6.0/pythonclaw/templates/skills/google/workspace/check_setup.sh +52 -0
  45. pythonclaw-0.6.0/pythonclaw/templates/skills/media/CATEGORY.md +5 -0
  46. pythonclaw-0.6.0/pythonclaw/templates/skills/media/image_gen/SKILL.md +99 -0
  47. pythonclaw-0.6.0/pythonclaw/templates/skills/media/image_gen/generate.py +103 -0
  48. pythonclaw-0.6.0/pythonclaw/templates/skills/media/spotify/SKILL.md +124 -0
  49. pythonclaw-0.6.0/pythonclaw/templates/skills/media/spotify/spotify_ctl.py +231 -0
  50. pythonclaw-0.6.0/pythonclaw/templates/skills/media/tts/SKILL.md +83 -0
  51. pythonclaw-0.6.0/pythonclaw/templates/skills/media/tts/speak.py +50 -0
  52. pythonclaw-0.6.0/pythonclaw/templates/skills/meta/CATEGORY.md +5 -0
  53. pythonclaw-0.6.0/pythonclaw/templates/skills/meta/skill_creator/SKILL.md +79 -0
  54. pythonclaw-0.6.0/pythonclaw/templates/skills/productivity/CATEGORY.md +5 -0
  55. pythonclaw-0.6.0/pythonclaw/templates/skills/productivity/notion/SKILL.md +99 -0
  56. pythonclaw-0.6.0/pythonclaw/templates/skills/productivity/notion/notion_api.py +185 -0
  57. pythonclaw-0.6.0/pythonclaw/templates/skills/productivity/obsidian/SKILL.md +110 -0
  58. pythonclaw-0.6.0/pythonclaw/templates/skills/productivity/obsidian/obsidian_vault.py +165 -0
  59. pythonclaw-0.6.0/pythonclaw/templates/skills/productivity/trello/SKILL.md +110 -0
  60. pythonclaw-0.6.0/pythonclaw/templates/skills/productivity/trello/trello_api.py +141 -0
  61. pythonclaw-0.6.0/pythonclaw/templates/skills/system/CATEGORY.md +5 -0
  62. pythonclaw-0.6.0/pythonclaw/templates/skills/system/change_persona/SKILL.md +47 -0
  63. pythonclaw-0.6.0/pythonclaw/templates/skills/system/change_setting/SKILL.md +69 -0
  64. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/system/change_setting/update_config.py +1 -0
  65. pythonclaw-0.6.0/pythonclaw/templates/skills/system/change_soul/SKILL.md +46 -0
  66. pythonclaw-0.6.0/pythonclaw/templates/skills/system/model_usage/SKILL.md +73 -0
  67. pythonclaw-0.6.0/pythonclaw/templates/skills/system/model_usage/usage_stats.py +73 -0
  68. pythonclaw-0.6.0/pythonclaw/templates/skills/system/onboarding/SKILL.md +58 -0
  69. pythonclaw-0.6.0/pythonclaw/templates/skills/system/random/SKILL.md +51 -0
  70. pythonclaw-0.6.0/pythonclaw/templates/skills/system/session_logs/SKILL.md +80 -0
  71. pythonclaw-0.6.0/pythonclaw/templates/skills/system/session_logs/search_sessions.py +55 -0
  72. pythonclaw-0.6.0/pythonclaw/templates/skills/system/time/SKILL.md +51 -0
  73. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/system/time/time_util.py +1 -1
  74. pythonclaw-0.6.0/pythonclaw/templates/skills/text/CATEGORY.md +5 -0
  75. pythonclaw-0.6.0/pythonclaw/templates/skills/text/translator/SKILL.md +52 -0
  76. pythonclaw-0.6.0/pythonclaw/templates/skills/web/CATEGORY.md +5 -0
  77. pythonclaw-0.6.0/pythonclaw/templates/skills/web/summarize/SKILL.md +84 -0
  78. pythonclaw-0.6.0/pythonclaw/templates/skills/web/summarize/summarize_url.py +107 -0
  79. pythonclaw-0.6.0/pythonclaw/templates/skills/web/tavily/SKILL.md +62 -0
  80. pythonclaw-0.6.0/pythonclaw/templates/tools/TOOLS.md +43 -0
  81. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/web/app.py +237 -28
  82. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/web/static/index.html +419 -85
  83. {pythonclaw-0.3.3 → pythonclaw-0.6.0/pythonclaw.egg-info}/PKG-INFO +63 -38
  84. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw.egg-info/SOURCES.txt +25 -0
  85. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw.egg-info/requires.txt +1 -0
  86. pythonclaw-0.3.3/pythonclaw/core/llm/anthropic_client.py +0 -107
  87. pythonclaw-0.3.3/pythonclaw/core/llm/base.py +0 -26
  88. pythonclaw-0.3.3/pythonclaw/core/llm/openai_compatible.py +0 -39
  89. pythonclaw-0.3.3/pythonclaw/core/memory/manager.py +0 -120
  90. pythonclaw-0.3.3/pythonclaw/core/skillhub.py +0 -291
  91. pythonclaw-0.3.3/pythonclaw/templates/skills/communication/CATEGORY.md +0 -4
  92. pythonclaw-0.3.3/pythonclaw/templates/skills/communication/email/SKILL.md +0 -54
  93. pythonclaw-0.3.3/pythonclaw/templates/skills/data/CATEGORY.md +0 -4
  94. pythonclaw-0.3.3/pythonclaw/templates/skills/data/csv_analyzer/SKILL.md +0 -51
  95. pythonclaw-0.3.3/pythonclaw/templates/skills/data/finance/SKILL.md +0 -41
  96. pythonclaw-0.3.3/pythonclaw/templates/skills/data/news/SKILL.md +0 -39
  97. pythonclaw-0.3.3/pythonclaw/templates/skills/data/pdf_reader/SKILL.md +0 -40
  98. pythonclaw-0.3.3/pythonclaw/templates/skills/data/weather/SKILL.md +0 -42
  99. pythonclaw-0.3.3/pythonclaw/templates/skills/data/youtube/SKILL.md +0 -43
  100. pythonclaw-0.3.3/pythonclaw/templates/skills/dev/CATEGORY.md +0 -4
  101. pythonclaw-0.3.3/pythonclaw/templates/skills/dev/code_runner/SKILL.md +0 -46
  102. pythonclaw-0.3.3/pythonclaw/templates/skills/dev/github/SKILL.md +0 -52
  103. pythonclaw-0.3.3/pythonclaw/templates/skills/dev/http_request/SKILL.md +0 -40
  104. pythonclaw-0.3.3/pythonclaw/templates/skills/google/CATEGORY.md +0 -4
  105. pythonclaw-0.3.3/pythonclaw/templates/skills/meta/CATEGORY.md +0 -4
  106. pythonclaw-0.3.3/pythonclaw/templates/skills/meta/skill_creator/SKILL.md +0 -151
  107. pythonclaw-0.3.3/pythonclaw/templates/skills/system/CATEGORY.md +0 -4
  108. pythonclaw-0.3.3/pythonclaw/templates/skills/system/change_persona/SKILL.md +0 -41
  109. pythonclaw-0.3.3/pythonclaw/templates/skills/system/change_setting/SKILL.md +0 -65
  110. pythonclaw-0.3.3/pythonclaw/templates/skills/system/change_soul/SKILL.md +0 -41
  111. pythonclaw-0.3.3/pythonclaw/templates/skills/system/onboarding/SKILL.md +0 -63
  112. pythonclaw-0.3.3/pythonclaw/templates/skills/system/random/SKILL.md +0 -33
  113. pythonclaw-0.3.3/pythonclaw/templates/skills/system/time/SKILL.md +0 -33
  114. pythonclaw-0.3.3/pythonclaw/templates/skills/text/CATEGORY.md +0 -4
  115. pythonclaw-0.3.3/pythonclaw/templates/skills/text/translator/SKILL.md +0 -47
  116. pythonclaw-0.3.3/pythonclaw/templates/skills/web/CATEGORY.md +0 -4
  117. pythonclaw-0.3.3/pythonclaw/templates/skills/web/tavily/SKILL.md +0 -61
  118. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/LICENSE +0 -0
  119. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/MANIFEST.in +0 -0
  120. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/__main__.py +0 -0
  121. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/__init__.py +1 -1
  122. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/compaction.py +0 -0
  123. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/knowledge/rag.py +0 -0
  124. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/llm/response.py +0 -0
  125. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/retrieval/__init__.py +1 -1
  126. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/retrieval/chunker.py +0 -0
  127. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/retrieval/dense.py +2 -2
  128. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/retrieval/fusion.py +0 -0
  129. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/retrieval/reranker.py +0 -0
  130. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/retrieval/retriever.py +1 -1
  131. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/retrieval/sparse.py +0 -0
  132. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/core/utils.py +0 -0
  133. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/daemon.py +0 -0
  134. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/scheduler/cron.py +0 -0
  135. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/scheduler/heartbeat.py +0 -0
  136. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/persona/demo_persona.md +0 -0
  137. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/communication/email/send_email.py +0 -0
  138. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/data/csv_analyzer/analyze.py +0 -0
  139. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/data/finance/fetch_quote.py +0 -0
  140. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/data/news/search_news.py +0 -0
  141. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/data/pdf_reader/read_pdf.py +0 -0
  142. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/data/scraper/scrape.py +0 -0
  143. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/data/weather/weather.py +1 -1
  144. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/data/youtube/youtube_info.py +0 -0
  145. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/dev/code_runner/run_code.py +1 -1
  146. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/dev/github/gh.py +0 -0
  147. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/dev/http_request/request.py +0 -0
  148. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/system/onboarding/write_identity.py +0 -0
  149. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/system/random/random_util.py +0 -0
  150. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/skills/text/translator/translate.py +0 -0
  151. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/templates/soul/SOUL.md +0 -0
  152. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/web/__init__.py +0 -0
  153. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/web/static/favicon.png +0 -0
  154. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw/web/static/logo.png +0 -0
  155. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw.egg-info/dependency_links.txt +0 -0
  156. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw.egg-info/entry_points.txt +0 -0
  157. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/pythonclaw.egg-info/top_level.txt +0 -0
  158. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/setup.cfg +0 -0
  159. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/tests/test_compaction.py +0 -0
  160. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/tests/test_persistence.py +0 -0
  161. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/tests/test_rag_hybrid.py +0 -0
  162. {pythonclaw-0.3.3 → pythonclaw-0.6.0}/tests/test_skills.py +0 -0
  163. {pythonclaw-0.3.3 → pythonclaw-0.6.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.3
3
+ Version: 0.6.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
@@ -77,15 +78,18 @@ Dynamic: license-file
77
78
  | | Feature | Details |
78
79
  |---|---------|---------|
79
80
  | 🧠 | **Provider-agnostic** | DeepSeek, Grok, Claude, Gemini, Kimi, GLM — or any OpenAI-compatible API |
80
- | 🛠️ | **Three-tier skills** | Progressive loading: metadata → instructions → resources. Community marketplace via [SkillHub](https://www.skillhub.club) |
81
+ | 🛠️ | **Three-tier skills** | Progressive loading: metadata → instructions → resources. Community marketplace via [ClawHub](https://clawhub.com) (13K+ free skills) |
81
82
  | 💾 | **Persistent memory** | Markdown-based long-term memory with daily logs and semantic recall |
82
83
  | 🔍 | **Hybrid RAG** | BM25 + dense embeddings + RRF fusion + LLM re-ranking |
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 ".[all]"
119
+ pip install -e .
116
120
  pythonclaw onboard
117
121
  ```
118
122
 
@@ -125,11 +129,11 @@ 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) |
132
- | `pythonclaw skill search <query>` | Search skills on [SkillHub](https://www.skillhub.club) |
136
+ | `pythonclaw skill search <query>` | Search skills on [ClawHub](https://clawhub.com) |
133
137
  | `pythonclaw skill browse` | Browse top-rated skills |
134
138
  | `pythonclaw skill install <id>` | Install a community skill |
135
139
  | `pythonclaw skill info <id>` | View skill details |
@@ -170,24 +174,25 @@ $ pythonclaw start
170
174
  ## Architecture
171
175
 
172
176
  ```
173
- ┌──────────────────────────────────────────────────────────┐
174
- PythonClaw
175
- ├──────────┬───────────┬───────────┬───────────────────────┤
176
- │ CLI │ Daemon │ Sessions │ Core
177
- │ │ │ │
178
- │ onboard │ start / │ Store(MD) │ Agent
179
- │ chat │ stop / │ Manager │ ├─ Memory (Markdown)
180
- │ skill … │ status │ ├─ RAG (Hybrid)
181
- │ │ │ ├─ Skills (3-tier)
182
- │ Web UI ◄─┤ Channels │ │ ├─ Compaction
183
- │ Voice In │ Telegram │ ├─ Soul + Persona
184
- │ │ Discord └─ Tool Execution
185
- ├──────────┴───────────┴───────────┴───────────────────────┤
186
- │ LLM Provider Abstraction Layer │
187
- DeepSeek Grok Claude │ Gemini │ Kimi │ GLM │
188
- ├──────────────────────────────────────────────────────────┤
189
- │ SkillHub Marketplace (skillhub.club) │
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
+ │ ClawHub Marketplace (clawhub.com) │
195
+ └──────────────────────────────────────────────────────────────┘
191
196
  ```
192
197
 
193
198
  ---
@@ -199,7 +204,7 @@ Start with `pythonclaw start` and open **http://localhost:7788**.
199
204
  - **Dashboard** — agent status, soul/persona preview, tool list
200
205
  - **Chat** — real-time chat with voice input (Deepgram)
201
206
  - **Skill Catalog** — browse installed skills by category
202
- - **Marketplace** — search and install skills from [SkillHub](https://www.skillhub.club)
207
+ - **Marketplace** — search and install skills from [ClawHub](https://clawhub.com)
203
208
  - **Configuration** — edit LLM provider, API keys, and settings in-browser
204
209
 
205
210
  ---
@@ -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` | `pip install pythonclaw[anthropic]` |
239
- | **Gemini (Google)** | `gemini-2.0-flash` | `pip install pythonclaw[gemini]` |
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 | — |
@@ -264,9 +272,9 @@ description: Execute Python code safely in an isolated subprocess.
264
272
  Run `python {skill_path}/run_code.py "expression"`
265
273
  ```
266
274
 
267
- ### SkillHub Marketplace
275
+ ### ClawHub Marketplace
268
276
 
269
- Browse and install 22,000+ community skills from [skillhub.club](https://www.skillhub.club):
277
+ Browse and install 13,000+ community skills from [ClawHub](https://clawhub.com) — free, no API key required:
270
278
 
271
279
  ```bash
272
280
  pythonclaw skill search "database backup"
@@ -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
  ```
@@ -326,14 +351,14 @@ PythonClaw/
326
351
  │ │ ├── agent.py # Core reasoning loop
327
352
  │ │ ├── tools.py # Tool schemas and execution
328
353
  │ │ ├── skill_loader.py # Three-tier skill system
329
- │ │ ├── skillhub.py # SkillHub marketplace client
354
+ │ │ ├── skillhub.py # ClawHub marketplace client
330
355
  │ │ ├── persistent_agent.py
331
356
  │ │ ├── compaction.py # Context compaction
332
357
  │ │ ├── llm/ # Provider adapters
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 ".[all]"
379
+ pip install -e .
355
380
  pytest tests/ -v
356
381
  ```
357
382
 
@@ -372,8 +397,8 @@ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
372
397
  | CLI | `openclaw start/stop` | `pythonclaw start/stop/status` |
373
398
  | Dashboard | Web UI | Web UI (localhost:7788) |
374
399
  | Memory | Markdown | Markdown (long-term + daily) |
375
- | Skills | Plugin system | Three-tier + SkillHub marketplace |
376
- | Channels | Discord, Telegram, WhatsApp | CLI, Web, Telegram, Discord |
400
+ | Skills | Plugin system | Three-tier + ClawHub marketplace |
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`) |
@@ -36,15 +36,18 @@
36
36
  | | Feature | Details |
37
37
  |---|---------|---------|
38
38
  | 🧠 | **Provider-agnostic** | DeepSeek, Grok, Claude, Gemini, Kimi, GLM — or any OpenAI-compatible API |
39
- | 🛠️ | **Three-tier skills** | Progressive loading: metadata → instructions → resources. Community marketplace via [SkillHub](https://www.skillhub.club) |
39
+ | 🛠️ | **Three-tier skills** | Progressive loading: metadata → instructions → resources. Community marketplace via [ClawHub](https://clawhub.com) (13K+ free skills) |
40
40
  | 💾 | **Persistent memory** | Markdown-based long-term memory with daily logs and semantic recall |
41
41
  | 🔍 | **Hybrid RAG** | BM25 + dense embeddings + RRF fusion + LLM re-ranking |
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 ".[all]"
77
+ pip install -e .
75
78
  pythonclaw onboard
76
79
  ```
77
80
 
@@ -84,11 +87,11 @@ 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) |
91
- | `pythonclaw skill search <query>` | Search skills on [SkillHub](https://www.skillhub.club) |
94
+ | `pythonclaw skill search <query>` | Search skills on [ClawHub](https://clawhub.com) |
92
95
  | `pythonclaw skill browse` | Browse top-rated skills |
93
96
  | `pythonclaw skill install <id>` | Install a community skill |
94
97
  | `pythonclaw skill info <id>` | View skill details |
@@ -129,24 +132,25 @@ $ pythonclaw start
129
132
  ## Architecture
130
133
 
131
134
  ```
132
- ┌──────────────────────────────────────────────────────────┐
133
- PythonClaw
134
- ├──────────┬───────────┬───────────┬───────────────────────┤
135
- │ CLI │ Daemon │ Sessions │ Core
136
- │ │ │ │
137
- │ onboard │ start / │ Store(MD) │ Agent
138
- │ chat │ stop / │ Manager │ ├─ Memory (Markdown)
139
- │ skill … │ status │ ├─ RAG (Hybrid)
140
- │ │ │ ├─ Skills (3-tier)
141
- │ Web UI ◄─┤ Channels │ │ ├─ Compaction
142
- │ Voice In │ Telegram │ ├─ Soul + Persona
143
- │ │ Discord └─ Tool Execution
144
- ├──────────┴───────────┴───────────┴───────────────────────┤
145
- │ LLM Provider Abstraction Layer │
146
- DeepSeek Grok Claude │ Gemini │ Kimi │ GLM │
147
- ├──────────────────────────────────────────────────────────┤
148
- │ SkillHub Marketplace (skillhub.club) │
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
+ │ ClawHub Marketplace (clawhub.com) │
153
+ └──────────────────────────────────────────────────────────────┘
150
154
  ```
151
155
 
152
156
  ---
@@ -158,7 +162,7 @@ Start with `pythonclaw start` and open **http://localhost:7788**.
158
162
  - **Dashboard** — agent status, soul/persona preview, tool list
159
163
  - **Chat** — real-time chat with voice input (Deepgram)
160
164
  - **Skill Catalog** — browse installed skills by category
161
- - **Marketplace** — search and install skills from [SkillHub](https://www.skillhub.club)
165
+ - **Marketplace** — search and install skills from [ClawHub](https://clawhub.com)
162
166
  - **Configuration** — edit LLM provider, API keys, and settings in-browser
163
167
 
164
168
  ---
@@ -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` | `pip install pythonclaw[anthropic]` |
198
- | **Gemini (Google)** | `gemini-2.0-flash` | `pip install pythonclaw[gemini]` |
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 | — |
@@ -223,9 +230,9 @@ description: Execute Python code safely in an isolated subprocess.
223
230
  Run `python {skill_path}/run_code.py "expression"`
224
231
  ```
225
232
 
226
- ### SkillHub Marketplace
233
+ ### ClawHub Marketplace
227
234
 
228
- Browse and install 22,000+ community skills from [skillhub.club](https://www.skillhub.club):
235
+ Browse and install 13,000+ community skills from [ClawHub](https://clawhub.com) — free, no API key required:
229
236
 
230
237
  ```bash
231
238
  pythonclaw skill search "database backup"
@@ -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
  ```
@@ -285,14 +309,14 @@ PythonClaw/
285
309
  │ │ ├── agent.py # Core reasoning loop
286
310
  │ │ ├── tools.py # Tool schemas and execution
287
311
  │ │ ├── skill_loader.py # Three-tier skill system
288
- │ │ ├── skillhub.py # SkillHub marketplace client
312
+ │ │ ├── skillhub.py # ClawHub marketplace client
289
313
  │ │ ├── persistent_agent.py
290
314
  │ │ ├── compaction.py # Context compaction
291
315
  │ │ ├── llm/ # Provider adapters
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 ".[all]"
337
+ pip install -e .
314
338
  pytest tests/ -v
315
339
  ```
316
340
 
@@ -331,8 +355,8 @@ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
331
355
  | CLI | `openclaw start/stop` | `pythonclaw start/stop/status` |
332
356
  | Dashboard | Web UI | Web UI (localhost:7788) |
333
357
  | Memory | Markdown | Markdown (long-term + daily) |
334
- | Skills | Plugin system | Three-tier + SkillHub marketplace |
335
- | Channels | Discord, Telegram, WhatsApp | CLI, Web, Telegram, Discord |
358
+ | Skills | Plugin system | Three-tier + ClawHub marketplace |
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.3.3"
7
+ version = "0.6.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 = ["."]
@@ -6,7 +6,7 @@ from .core.llm.base import LLMProvider
6
6
  from .core.llm.openai_compatible import OpenAICompatibleProvider
7
7
  from .init import init
8
8
 
9
- __version__ = "0.3.3"
9
+ __version__ = "0.5.0"
10
10
  __all__ = [
11
11
  "Agent",
12
12
  "LLMProvider",
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Discord channel for PythonClaw.
3
3
 
4
- Session IDs: "discord:{user_id}" (DMs) or "discord:{channel_id}" (guilds)
4
+ Session IDs: "discord:dm:{user_id}" (DMs) or "discord:{channel_id}" (guilds)
5
5
 
6
6
  Commands
7
7
  --------
@@ -9,21 +9,29 @@ Commands
9
9
  !status — show session info
10
10
  !compact [hint] — compact conversation history
11
11
  <text> — forwarded to Agent.chat(), reply sent back
12
+ <image> — image attachments sent to LLM for analysis
12
13
 
13
14
  The bot responds to:
14
15
  - Direct messages (always)
15
- - Channel mentions (@bot message) in guilds
16
- - Optionally all messages in whitelisted channels
16
+ - Channel mentions (@bot message) in guilds (when requireMention=true)
17
+ - All messages in whitelisted channels (when requireMention=false)
17
18
 
18
19
  Access control
19
20
  --------------
20
21
  Set DISCORD_ALLOWED_USERS to a comma-separated list of Discord user IDs.
21
22
  Set DISCORD_ALLOWED_CHANNELS to restrict which guild channels the bot listens in.
22
23
  Leave empty to allow all.
24
+
25
+ Group behaviour
26
+ ---------------
27
+ Set ``channels.discord.requireMention`` to ``true`` to require @bot mention
28
+ in guild channels. Default is ``false`` (respond when mentioned OR in
29
+ whitelisted channels).
23
30
  """
24
31
 
25
32
  from __future__ import annotations
26
33
 
34
+ import base64
27
35
  import logging
28
36
  from typing import TYPE_CHECKING
29
37
 
@@ -52,11 +60,13 @@ class DiscordBot:
52
60
  token: str,
53
61
  allowed_users: list[int] | None = None,
54
62
  allowed_channels: list[int] | None = None,
63
+ require_mention: bool = False,
55
64
  ) -> None:
56
65
  self._sm = session_manager
57
66
  self._token = token
58
67
  self._allowed_users: set[int] = set(allowed_users) if allowed_users else set()
59
68
  self._allowed_channels: set[int] = set(allowed_channels) if allowed_channels else set()
69
+ self._require_mention = require_mention
60
70
 
61
71
  intents = discord.Intents.default()
62
72
  intents.message_content = True
@@ -113,20 +123,27 @@ class DiscordBot:
113
123
  is_dm = isinstance(message.channel, discord.DMChannel)
114
124
  is_mentioned = client.user in message.mentions if not is_dm else False
115
125
 
116
- # In guilds, only respond to mentions or whitelisted channels
117
- if not is_dm and not is_mentioned and not self._is_allowed_channel(message.channel.id):
118
- return
126
+ if not is_dm:
127
+ if self._require_mention and not is_mentioned:
128
+ return
129
+ if not self._require_mention and not is_mentioned:
130
+ if not self._is_allowed_channel(message.channel.id):
131
+ return
119
132
 
120
133
  if not self._is_allowed_user(message.author.id):
121
134
  await message.reply("Sorry, you are not authorised to use this bot.")
122
135
  return
123
136
 
124
137
  content = message.content.strip()
125
- # Remove bot mention from the beginning
126
138
  if is_mentioned and client.user:
127
139
  content = content.replace(f"<@{client.user.id}>", "").strip()
128
140
 
129
- if not content:
141
+ has_image = any(
142
+ a.content_type and a.content_type.startswith("image/")
143
+ for a in message.attachments
144
+ )
145
+
146
+ if not content and not has_image:
130
147
  return
131
148
 
132
149
  # Command dispatch
@@ -141,7 +158,35 @@ class DiscordBot:
141
158
  await self._cmd_compact(message, is_dm, hint)
142
159
  return
143
160
 
144
- await self._handle_chat(message, content, is_dm)
161
+ chat_input = content or ""
162
+ if has_image:
163
+ chat_input = await self._build_image_input(
164
+ message, content or "What's in this image?"
165
+ )
166
+
167
+ await self._handle_chat(message, chat_input, is_dm)
168
+
169
+ # ── Image handling ────────────────────────────────────────────────────────
170
+
171
+ @staticmethod
172
+ async def _build_image_input(message: discord.Message, caption: str) -> list:
173
+ """Download image attachments and build multimodal content array."""
174
+ parts: list[dict] = [{"type": "text", "text": caption}]
175
+ for att in message.attachments:
176
+ if att.content_type and att.content_type.startswith("image/"):
177
+ try:
178
+ data = await att.read()
179
+ b64 = base64.b64encode(data).decode()
180
+ media_type = att.content_type.split(";")[0]
181
+ parts.append({
182
+ "type": "image_url",
183
+ "image_url": {
184
+ "url": f"data:{media_type};base64,{b64}",
185
+ },
186
+ })
187
+ except Exception:
188
+ logger.warning("[Discord] Failed to download attachment %s", att.filename)
189
+ return parts
145
190
 
146
191
  # ── Command implementations ───────────────────────────────────────────────
147
192
 
@@ -180,12 +225,24 @@ class DiscordBot:
180
225
  for chunk in self._split_message(result or "(no result)"):
181
226
  await message.reply(chunk)
182
227
 
183
- async def _handle_chat(self, message: discord.Message, content: str, is_dm: bool) -> None:
228
+ async def _handle_chat(
229
+ self,
230
+ message: discord.Message,
231
+ content: str | list,
232
+ is_dm: bool,
233
+ ) -> None:
184
234
  sid = self._session_id(message.author.id if is_dm else message.channel.id, is_dm)
185
235
  agent = self._sm.get_or_create(sid)
236
+
237
+ if self._sm.is_locked(sid):
238
+ await message.reply("Processing previous message\u2026")
239
+
186
240
  async with message.channel.typing():
187
241
  try:
188
- response = agent.chat(content)
242
+ async with self._sm.acquire(sid):
243
+ import asyncio
244
+ loop = asyncio.get_event_loop()
245
+ response = await loop.run_in_executor(None, agent.chat, content)
189
246
  except Exception as exc:
190
247
  logger.exception("[Discord] Agent.chat() raised an exception")
191
248
  response = f"Sorry, something went wrong: {exc}"
@@ -219,11 +276,15 @@ def create_bot(session_manager: "SessionManager") -> "DiscordBot":
219
276
  allowed_channels = config.get_int_list(
220
277
  "channels", "discord", "allowedChannels", env="DISCORD_ALLOWED_CHANNELS",
221
278
  )
279
+ require_mention = config.get_bool(
280
+ "channels", "discord", "requireMention", default=False,
281
+ )
222
282
  return DiscordBot(
223
283
  session_manager=session_manager,
224
284
  token=token,
225
285
  allowed_users=allowed_users or None,
226
286
  allowed_channels=allowed_channels or None,
287
+ require_mention=require_mention,
227
288
  )
228
289
 
229
290