Merge branch 'sipeed:main' into main
|
|
@ -61,6 +61,9 @@ linters:
|
|||
- usestdlibvars
|
||||
- usetesting
|
||||
settings:
|
||||
gomoddirectives:
|
||||
replace-allow-list:
|
||||
- github.com/bwmarrin/discordgo
|
||||
errcheck:
|
||||
check-type-assertions: true
|
||||
check-blank: true
|
||||
|
|
|
|||
13
Makefile
|
|
@ -130,14 +130,17 @@ build: generate
|
|||
build-launcher:
|
||||
@echo "Building picoclaw-launcher for $(PLATFORM)/$(ARCH)..."
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@if [ ! -f web/backend/dist/index.html ]; then \
|
||||
echo "Building frontend..."; \
|
||||
cd web/frontend && pnpm install && pnpm build:backend; \
|
||||
fi
|
||||
@$(WEB_GO) build $(GOFLAGS) -o $(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH) ./web/backend
|
||||
@$(MAKE) -C web build \
|
||||
OUTPUT="$(CURDIR)/$(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH)" \
|
||||
WEB_GO='$(WEB_GO)' \
|
||||
GO_BUILD_TAGS='$(GO_BUILD_TAGS)' \
|
||||
LDFLAGS='$(LDFLAGS)'
|
||||
@ln -sf picoclaw-launcher-$(PLATFORM)-$(ARCH) $(BUILD_DIR)/picoclaw-launcher
|
||||
@echo "Build complete: $(BUILD_DIR)/picoclaw-launcher"
|
||||
|
||||
build-launcher-frontend:
|
||||
@$(MAKE) -C web build-frontend
|
||||
|
||||
## build-launcher-tui: Build the picoclaw-launcher TUI binary
|
||||
build-launcher-tui:
|
||||
@echo "Building picoclaw-launcher-tui for $(PLATFORM)/$(ARCH)..."
|
||||
|
|
|
|||
46
README.fr.md
|
|
@ -18,7 +18,7 @@
|
|||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | **Français** | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [English](README.md)
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | **Français** | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -57,17 +57,21 @@
|
|||
|
||||
## 📢 Actualités
|
||||
|
||||
2026-03-31 📱 **Support Android !** PicoClaw fonctionne maintenant sur Android ! Téléchargez l'APK sur [picoclaw.io](https://picoclaw.io/download)
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 publiée !** Refonte de l'architecture Agent (SubTurn, Hooks, Steering, EventBus), intégration WeChat/WeCom, renforcement de la sécurité (.security.yml, filtrage des données sensibles), nouveaux providers (AWS Bedrock, Azure, Xiaomi MiMo), et 35 corrections de bugs. PicoClaw a atteint **26K Stars** !
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 publiée !** Interface system tray (Windows & Linux), requête de statut des sous-agents (`spawn_status`), rechargement à chaud expérimental du Gateway, sécurisation Cron, et 2 correctifs de sécurité. PicoClaw a atteint **25K Stars** !
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — La plus grande mise à jour à ce jour !** Support du protocole MCP, 4 nouveaux channels (Matrix/IRC/WeCom/Discord Proxy), 3 nouveaux providers (Kimi/Minimax/Avian), pipeline vision, stockage mémoire JSONL, routage de modèles.
|
||||
2026-03-09 🎉 **v0.2.1 — Plus grande mise à jour à ce jour !** Support du protocole MCP, 4 nouveaux channels (Matrix/IRC/WeCom/Discord Proxy), 3 nouveaux providers (Kimi/Minimax/Avian), pipeline vision, stockage mémoire JSONL, routage de modèles.
|
||||
|
||||
2026-02-28 📦 **v0.2.0** publiée avec support Docker Compose et Web UI Launcher.
|
||||
|
||||
2026-02-26 🎉 PicoClaw atteint **20K Stars** en seulement 17 jours ! L'orchestration automatique des channels et les interfaces de capacités sont disponibles.
|
||||
|
||||
<details>
|
||||
<summary>Actualités précédentes...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw atteint **20K Stars** en seulement 17 jours ! L'orchestration automatique des channels et les interfaces de capacités sont disponibles.
|
||||
|
||||
2026-02-16 🎉 PicoClaw dépasse 12K Stars en une semaine ! Rôles de mainteneurs communautaires et [Roadmap](ROADMAP.md) officiellement lancés.
|
||||
|
||||
2026-02-13 🎉 PicoClaw dépasse 5000 Stars en 4 jours ! Roadmap du projet et groupes de développeurs en cours.
|
||||
|
|
@ -257,6 +261,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Avertissement de sécurité au premier lancement</b></summary>
|
||||
|
||||
macOS peut bloquer `picoclaw-launcher` au premier lancement car il est téléchargé depuis Internet et n'est pas notarisé via le Mac App Store.
|
||||
|
||||
**Étape 1 :** Double-cliquez sur `picoclaw-launcher`. Un avertissement de sécurité s'affiche :
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Avertissement macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" n'a pas pu être ouvert — Apple n'a pas pu vérifier que "picoclaw-launcher" ne contient pas de logiciel malveillant susceptible de nuire à votre Mac ou de compromettre votre confidentialité.*
|
||||
|
||||
**Étape 2 :** Ouvrez **Réglages Système** → **Confidentialité et sécurité** → faites défiler jusqu'à la section **Sécurité** → cliquez sur **Ouvrir quand même** → confirmez en cliquant sur **Ouvrir quand même** dans la boîte de dialogue.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Confidentialité et sécurité — Ouvrir quand même" width="600">
|
||||
</p>
|
||||
|
||||
Après cette étape unique, `picoclaw-launcher` s'ouvrira normalement lors des lancements suivants.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Recommandé pour les environnements sans interface / SSH)
|
||||
|
||||
Le TUI (Terminal UI) Launcher fournit une interface terminal complète pour la configuration et la gestion. Idéal pour les serveurs, Raspberry Pi et autres environnements sans interface graphique.
|
||||
|
|
@ -296,9 +323,9 @@ Suivez ensuite la section Terminal Launcher ci-dessous pour terminer la configur
|
|||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Option 2 : Installation APK (bientôt disponible)**
|
||||
**Option 2 : Installation APK**
|
||||
|
||||
Un APK Android autonome avec WebUI intégré est en développement. Restez à l'écoute !
|
||||
Téléchargez l'APK depuis [picoclaw.io](https://picoclaw.io/download/) et installez-le directement. Pas besoin de Termux !
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (pour les environnements à ressources limitées)</b></summary>
|
||||
|
|
@ -371,6 +398,7 @@ PicoClaw supporte plus de 30 providers LLM via la configuration `model_list`. Ut
|
|||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Requise | Modèles hébergés NVIDIA |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Requise | Inférence rapide |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Requise | Divers modèles open |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Requise | Modèles MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Non requise | Modèles locaux, auto-hébergé |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Non requise | Déploiement local, compatible OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Variable | Proxy pour 100+ providers |
|
||||
|
|
@ -427,9 +455,7 @@ Parlez à votre PicoClaw via plus de 17 plateformes de messagerie :
|
|||
| **DingTalk** | Moyen (identifiants client) | Stream | [Guide](docs/channels/dingtalk/README.fr.md) |
|
||||
| **Feishu / Lark** | Moyen (App ID + Secret) | WebSocket/SDK | [Guide](docs/channels/feishu/README.fr.md) |
|
||||
| **LINE** | Moyen (identifiants + webhook) | Webhook | [Guide](docs/channels/line/README.fr.md) |
|
||||
| **WeCom Bot** | Moyen (URL webhook) | Webhook | [Guide](docs/channels/wecom/wecom_bot/README.fr.md) |
|
||||
| **WeCom App** | Moyen (identifiants corp) | Webhook | [Guide](docs/channels/wecom/wecom_app/README.fr.md) |
|
||||
| **WeCom AI Bot** | Moyen (token + clé AES) | WebSocket / Webhook | [Guide](docs/channels/wecom/wecom_aibot/README.fr.md) |
|
||||
| **WeCom** | Facile (QR login ou manuel) | WebSocket | [Guide](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Moyen (serveur + pseudo) | Protocole IRC | [Guide](docs/fr/chat-apps.md#irc) |
|
||||
| **OneBot** | Moyen (URL WebSocket) | OneBot v11 | [Guide](docs/channels/onebot/README.fr.md) |
|
||||
| **MaixCam** | Facile (activer) | Socket TCP | [Guide](docs/channels/maixcam/README.fr.md) |
|
||||
|
|
@ -438,6 +464,8 @@ Parlez à votre PicoClaw via plus de 17 plateformes de messagerie :
|
|||
|
||||
> Tous les channels basés sur webhook partagent un seul serveur HTTP Gateway (`gateway.host`:`gateway.port`, par défaut `127.0.0.1:18790`). Feishu utilise le mode WebSocket/SDK et n'utilise pas le serveur HTTP partagé.
|
||||
|
||||
> La verbosité des logs est contrôlée par `gateway.log_level` (par défaut : `warn`). Valeurs supportées : `debug`, `info`, `warn`, `error`, `fatal`. Peut aussi être défini via `PICOCLAW_LOG_LEVEL`. Voir [Configuration](docs/fr/configuration.md#niveau-de-log-du-gateway) pour plus de détails.
|
||||
|
||||
Pour les instructions détaillées de configuration des channels, voir [Configuration des applications de chat](docs/fr/chat-apps.md).
|
||||
|
||||
## 🔧 Outils
|
||||
|
|
|
|||
46
README.id.md
|
|
@ -18,7 +18,7 @@
|
|||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [English](README.md) | **Bahasa Indonesia**
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Malay](README.my.md) | [English](README.md) | **Bahasa Indonesia**
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -56,17 +56,21 @@
|
|||
|
||||
## 📢 Berita
|
||||
|
||||
2026-03-31 📱 **Dukungan Android!** PicoClaw sekarang berjalan di Android! Unduh APK di [picoclaw.io](https://picoclaw.io/download)
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 Dirilis!** Perombakan arsitektur Agent (SubTurn, Hooks, Steering, EventBus), integrasi WeChat/WeCom, penguatan keamanan (.security.yml, penyaringan data sensitif), provider baru (AWS Bedrock, Azure, Xiaomi MiMo), dan 35 perbaikan bug. PicoClaw telah mencapai **26K Stars**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 Dirilis!** UI system tray (Windows & Linux), pelacakan status sub-agent (`spawn_status`), eksperimental Gateway hot-reload, gerbang keamanan Cron, dan 2 perbaikan keamanan. PicoClaw telah mencapai **25K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Update terbesar sejauh ini!** Dukungan protokol MCP, 4 channel baru (Matrix/IRC/WeCom/Discord Proxy), 3 provider baru (Kimi/Minimax/Avian), pipeline vision, penyimpanan memori JSONL, routing model.
|
||||
2026-03-09 🎉 **v0.2.1 — Pembaruan terbesar sejauh ini!** Dukungan protokol MCP, 4 channel baru (Matrix/IRC/WeCom/Discord Proxy), 3 provider baru (Kimi/Minimax/Avian), pipeline visi, penyimpanan memori JSONL, perutean model.
|
||||
|
||||
2026-02-28 📦 **v0.2.0** dirilis dengan dukungan Docker Compose dan Web UI Launcher.
|
||||
|
||||
2026-02-26 🎉 PicoClaw mencapai **20K Stars** hanya dalam 17 hari! Orkestrasi channel otomatis dan antarmuka kapabilitas kini aktif.
|
||||
|
||||
<details>
|
||||
<summary>Berita sebelumnya...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw mencapai **20K Stars** hanya dalam 17 hari! Orkestrasi channel otomatis dan antarmuka kapabilitas kini aktif.
|
||||
|
||||
2026-02-16 🎉 PicoClaw menembus 12K Stars dalam satu minggu! Peran maintainer komunitas dan [Roadmap](ROADMAP.md) resmi diluncurkan.
|
||||
|
||||
2026-02-13 🎉 PicoClaw menembus 5000 Stars dalam 4 hari! Roadmap proyek dan grup pengembang sedang dalam proses.
|
||||
|
|
@ -254,6 +258,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Peringatan Keamanan saat Pertama Kali Diluncurkan</b></summary>
|
||||
|
||||
macOS mungkin memblokir `picoclaw-launcher` saat pertama kali diluncurkan karena diunduh dari internet dan tidak dinotarisasi melalui Mac App Store.
|
||||
|
||||
**Langkah 1:** Klik dua kali `picoclaw-launcher`. Anda akan melihat peringatan keamanan:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Peringatan macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" Tidak Dapat Dibuka — Apple tidak dapat memverifikasi bahwa "picoclaw-launcher" bebas dari malware yang dapat membahayakan Mac Anda atau mengancam privasi Anda.*
|
||||
|
||||
**Langkah 2:** Buka **Pengaturan Sistem** → **Privasi & Keamanan** → gulir ke bawah ke bagian **Keamanan** → klik **Tetap Buka** → konfirmasi dengan mengklik **Tetap Buka** pada dialog.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Privasi & Keamanan — Tetap Buka" width="600">
|
||||
</p>
|
||||
|
||||
Setelah langkah satu kali ini, `picoclaw-launcher` akan terbuka secara normal pada peluncuran berikutnya.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Direkomendasikan untuk Headless / SSH)
|
||||
|
||||
TUI (Terminal UI) Launcher menyediakan antarmuka terminal lengkap untuk konfigurasi dan manajemen. Ideal untuk server, Raspberry Pi, dan lingkungan headless lainnya.
|
||||
|
|
@ -293,9 +320,9 @@ Kemudian ikuti bagian Terminal Launcher di bawah untuk menyelesaikan konfigurasi
|
|||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Opsi 2: Instal APK (segera hadir)**
|
||||
**Opsi 2: Instal APK**
|
||||
|
||||
APK Android mandiri dengan WebUI bawaan sedang dalam pengembangan. Pantau terus!
|
||||
Unduh APK dari [picoclaw.io](https://picoclaw.io/download/) dan instal langsung. Tanpa Termux!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (untuk lingkungan dengan sumber daya terbatas)</b></summary>
|
||||
|
|
@ -367,6 +394,7 @@ PicoClaw mendukung 30+ provider LLM melalui konfigurasi `model_list`. Gunakan fo
|
|||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Diperlukan | Model yang di-host NVIDIA |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Diperlukan | Inferensi cepat |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Diperlukan | Berbagai model open |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Diperlukan | Model MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Tidak perlu | Model lokal, self-hosted |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Tidak perlu | Deploy lokal, kompatibel OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Bervariasi | Proxy untuk 100+ provider |
|
||||
|
|
@ -423,9 +451,7 @@ Bicara dengan PicoClaw Anda melalui 17+ platform pesan:
|
|||
| **DingTalk** | Sedang (client credentials) | Stream | [Panduan](docs/channels/dingtalk/README.md) |
|
||||
| **Feishu / Lark** | Sedang (App ID + Secret) | WebSocket/SDK | [Panduan](docs/channels/feishu/README.md) |
|
||||
| **LINE** | Sedang (credentials + webhook) | Webhook | [Panduan](docs/channels/line/README.md) |
|
||||
| **WeCom Bot** | Sedang (webhook URL) | Webhook | [Panduan](docs/channels/wecom/wecom_bot/README.md) |
|
||||
| **WeCom App** | Sedang (corp credentials) | Webhook | [Panduan](docs/channels/wecom/wecom_app/README.md) |
|
||||
| **WeCom AI Bot** | Sedang (token + AES key) | WebSocket / Webhook | [Panduan](docs/channels/wecom/wecom_aibot/README.md) |
|
||||
| **WeCom** | Mudah (login QR atau manual) | WebSocket | [Panduan](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Sedang (server + nick) | IRC protocol | [Panduan](docs/chat-apps.md#irc) |
|
||||
| **OneBot** | Sedang (WebSocket URL) | OneBot v11 | [Panduan](docs/channels/onebot/README.md) |
|
||||
| **MaixCam** | Mudah (aktifkan) | TCP socket | [Panduan](docs/channels/maixcam/README.md) |
|
||||
|
|
@ -434,6 +460,8 @@ Bicara dengan PicoClaw Anda melalui 17+ platform pesan:
|
|||
|
||||
> Semua channel berbasis webhook berbagi satu server HTTP Gateway (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu menggunakan mode WebSocket/SDK dan tidak menggunakan server HTTP bersama.
|
||||
|
||||
> Verbositas log dikontrol oleh `gateway.log_level` (default: `warn`). Nilai yang didukung: `debug`, `info`, `warn`, `error`, `fatal`. Juga dapat diatur melalui `PICOCLAW_LOG_LEVEL`. Lihat [Konfigurasi](docs/configuration.md#gateway-log-level) untuk detail.
|
||||
|
||||
Untuk instruksi pengaturan channel lengkap, lihat [Konfigurasi Aplikasi Chat](docs/chat-apps.md).
|
||||
|
||||
## 🔧 Tools
|
||||
|
|
|
|||
48
README.it.md
|
|
@ -18,7 +18,7 @@
|
|||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | **Italiano** | [Bahasa Indonesia](README.id.md) | [English](README.md)
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | **Italiano** | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -56,17 +56,21 @@
|
|||
|
||||
## 📢 Novità
|
||||
|
||||
2026-03-31 📱 **Supporto Android!** PicoClaw ora funziona su Android! Scarica l'APK su [picoclaw.io](https://picoclaw.io/download)
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 rilasciata!** Revisione dell'architettura Agent (SubTurn, Hooks, Steering, EventBus), integrazione WeChat/WeCom, rafforzamento della sicurezza (.security.yml, filtraggio dati sensibili), nuovi provider (AWS Bedrock, Azure, Xiaomi MiMo) e 35 correzioni di bug. PicoClaw raggiunge **26K Stars**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 rilasciata!** Interfaccia system tray (Windows & Linux), query sullo stato dei sub-agent (`spawn_status`), hot-reload sperimentale del Gateway, gate di sicurezza per Cron e 2 correzioni di sicurezza. PicoClaw raggiunge **25K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Il più grande aggiornamento di sempre!** Supporto al protocollo MCP, 4 nuovi canali (Matrix/IRC/WeCom/Discord Proxy), 3 nuovi provider (Kimi/Minimax/Avian), pipeline di visione, store di memoria JSONL e routing dei modelli.
|
||||
2026-03-09 🎉 **v0.2.1 — Il più grande aggiornamento di sempre!** Supporto al protocollo MCP, 4 nuovi canali (Matrix/IRC/WeCom/Discord Proxy), 3 nuovi provider (Kimi/Minimax/Avian), pipeline visiva, archivio memoria JSONL, routing dei modelli.
|
||||
|
||||
2026-02-28 📦 **v0.2.0** rilasciata con supporto Docker Compose e Web UI Launcher.
|
||||
|
||||
2026-02-26 🎉 PicoClaw raggiunge **20K stelle** in soli 17 giorni! Orchestrazione automatica dei canali e interfacce di capacità sono attive.
|
||||
|
||||
<details>
|
||||
<summary>Notizie precedenti...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw raggiunge **20K stelle** in soli 17 giorni! Orchestrazione automatica dei canali e interfacce di capacità sono attive.
|
||||
|
||||
2026-02-16 🎉 PicoClaw supera 12K stelle in una settimana! Ruoli di maintainer della community e [Roadmap](ROADMAP.md) pubblicati ufficialmente.
|
||||
|
||||
2026-02-13 🎉 PicoClaw supera 5000 stelle in 4 giorni! Roadmap del progetto e gruppi sviluppatori in fase di avvio.
|
||||
|
|
@ -254,6 +258,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Avviso di sicurezza al primo avvio</b></summary>
|
||||
|
||||
macOS potrebbe bloccare `picoclaw-launcher` al primo avvio perché è stato scaricato da internet e non è notarizzato tramite il Mac App Store.
|
||||
|
||||
**Passo 1:** Fai doppio clic su `picoclaw-launcher`. Verrà visualizzato un avviso di sicurezza:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Avviso macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" Non Aperto — Apple non è riuscita a verificare che "picoclaw-launcher" sia privo di malware che potrebbe danneggiare il Mac o compromettere la privacy.*
|
||||
|
||||
**Passo 2:** Apri **Impostazioni di Sistema** → **Privacy e sicurezza** → scorri fino alla sezione **Sicurezza** → clicca su **Apri comunque** → conferma cliccando su **Apri comunque** nella finestra di dialogo.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Privacy e sicurezza — Apri comunque" width="600">
|
||||
</p>
|
||||
|
||||
Dopo questo passaggio una tantum, `picoclaw-launcher` si aprirà normalmente ai lanci successivi.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Consigliato per Headless / SSH)
|
||||
|
||||
Il TUI (Terminal UI) Launcher fornisce un'interfaccia terminale completa per la configurazione e la gestione. Ideale per server, Raspberry Pi e altri ambienti headless.
|
||||
|
|
@ -293,9 +320,9 @@ Poi segui la sezione Terminal Launcher qui sotto per completare la configurazion
|
|||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Opzione 2: APK Install (prossimamente)**
|
||||
**Opzione 2: Installazione APK**
|
||||
|
||||
Un APK Android standalone con WebUI integrato è in sviluppo. Resta sintonizzato!
|
||||
Scarica l'APK da [picoclaw.io](https://picoclaw.io/download/) e installa direttamente. Senza Termux!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (per ambienti con risorse limitate)</b></summary>
|
||||
|
|
@ -367,6 +394,7 @@ PicoClaw supporta 30+ provider LLM tramite la configurazione `model_list`. Usa i
|
|||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Richiesta | Modelli ospitati NVIDIA |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Richiesta | Inferenza veloce |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Richiesta | Vari modelli open |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Richiesta | Modelli MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Non necessaria | Modelli locali, self-hosted |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Non necessaria | Deploy locale, compatibile OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Variabile | Proxy per 100+ provider |
|
||||
|
|
@ -423,9 +451,7 @@ Parla con il tuo PicoClaw attraverso 17+ piattaforme di messaggistica:
|
|||
| **DingTalk** | Medio (credenziali client) | Stream | [Guida](docs/channels/dingtalk/README.md) |
|
||||
| **Feishu / Lark** | Medio (App ID + Secret) | WebSocket/SDK | [Guida](docs/channels/feishu/README.md) |
|
||||
| **LINE** | Medio (credenziali + webhook) | Webhook | [Guida](docs/channels/line/README.md) |
|
||||
| **WeCom Bot** | Medio (webhook URL) | Webhook | [Guida](docs/channels/wecom/wecom_bot/README.md) |
|
||||
| **WeCom App** | Medio (credenziali aziendali) | Webhook | [Guida](docs/channels/wecom/wecom_app/README.md) |
|
||||
| **WeCom AI Bot** | Medio (token + AES key) | WebSocket / Webhook | [Guida](docs/channels/wecom/wecom_aibot/README.md) |
|
||||
| **WeCom** | Facile (login QR o manuale) | WebSocket | [Guida](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Medio (server + nick) | Protocollo IRC | [Guida](docs/chat-apps.md#irc) |
|
||||
| **OneBot** | Medio (WebSocket URL) | OneBot v11 | [Guida](docs/channels/onebot/README.md) |
|
||||
| **MaixCam** | Facile (abilita) | TCP socket | [Guida](docs/channels/maixcam/README.md) |
|
||||
|
|
@ -434,6 +460,8 @@ Parla con il tuo PicoClaw attraverso 17+ piattaforme di messaggistica:
|
|||
|
||||
> Tutti i channel basati su webhook condividono un singolo server HTTP Gateway (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu usa la modalità WebSocket/SDK e non usa il server HTTP condiviso.
|
||||
|
||||
> La verbosità dei log è controllata da `gateway.log_level` (default: `warn`). Valori supportati: `debug`, `info`, `warn`, `error`, `fatal`. Può essere impostato anche tramite `PICOCLAW_LOG_LEVEL`. Vedi [Configurazione](docs/configuration.md#gateway-log-level) per i dettagli.
|
||||
|
||||
Per istruzioni dettagliate sulla configurazione dei channel, vedi [Configurazione App di Chat](docs/chat-apps.md).
|
||||
|
||||
## 🔧 Strumenti
|
||||
|
|
@ -534,7 +562,7 @@ Connetti PicoClaw al Social Network degli Agent semplicemente inviando un singol
|
|||
| `picoclaw skills list` | Elenca le skill installate |
|
||||
| `picoclaw skills install` | Installa una skill |
|
||||
| `picoclaw migrate` | Migra i dati dalle versioni precedenti |
|
||||
| `picoclaw auth login` | Autenticazione con i provider |
|
||||
| `picoclaw auth login` | Autenticazione con i provider |
|
||||
|
||||
### ⏰ Task Pianificati / Promemoria
|
||||
|
||||
|
|
|
|||
50
README.ja.md
|
|
@ -18,7 +18,7 @@
|
|||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | **日本語** | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [English](README.md)
|
||||
[中文](README.zh.md) | **日本語** | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -56,17 +56,21 @@
|
|||
|
||||
## 📢 ニュース
|
||||
|
||||
2026-03-31 📱 **Android サポート!** PicoClawがAndroidで動作!APKは[picoclaw.io](https://picoclaw.io/download)からダウンロード
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 リリース!** Agent アーキテクチャ全面刷新(SubTurn、Hooks、Steering、EventBus)、WeChat/WeCom 統合、セキュリティ強化(.security.yml、機密データフィルタリング)、新プロバイダー(AWS Bedrock、Azure、Xiaomi MiMo)、35 件のバグ修正。PicoClaw **26K ⭐** 達成!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 リリース!** システムトレイ UI(Windows & Linux)、サブエージェントステータス追跡(`spawn_status`)、実験的 Gateway ホットリロード、cron セキュリティゲート、セキュリティ修正 2 件。PicoClaw **25K ⭐** 達成!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — 史上最大のアップデート!** MCP プロトコル対応、4 つの新 Channel(Matrix/IRC/WeCom/Discord Proxy)、3 つの新 Provider(Kimi/Minimax/Avian)、ビジョンパイプライン、JSONL メモリストア、モデルルーティング。
|
||||
2026-03-09 🎉 **v0.2.1 — 最大のアップデート!** MCP プロトコルサポート、4 つの新チャンネル (Matrix/IRC/WeCom/Discord Proxy)、3 つの新プロバイダー (Kimi/Minimax/Avian)、ビジョンパイプライン、JSONL メモリストア、モデルルーティング。
|
||||
|
||||
2026-02-28 📦 **v0.2.0** リリース — Docker Compose 対応と Web UI Launcher。
|
||||
|
||||
2026-02-26 🎉 PicoClaw がわずか 17 日で **20K スター** 達成!Channel 自動オーケストレーションとケイパビリティインターフェースが実装されました。
|
||||
2026-02-28 📦 **v0.2.0** リリース — Docker Compose と Web UI Launcher サポート。
|
||||
|
||||
<details>
|
||||
<summary>過去のニュース...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw がわずか 17 日で **20K スター** 達成!Channel 自動オーケストレーションとケイパビリティインターフェースが実装されました。
|
||||
|
||||
2026-02-16 🎉 PicoClaw が 1 週間で 12K スター達成!コミュニティメンテナーの役割と[ロードマップ](ROADMAP.md)が正式に公開されました。
|
||||
|
||||
2026-02-13 🎉 PicoClaw が 4 日間で 5000 スター達成!プロジェクトロードマップと開発者グループの準備が進行中。
|
||||
|
|
@ -254,6 +258,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — 初回起動時のセキュリティ警告</b></summary>
|
||||
|
||||
`picoclaw-launcher` はインターネットからダウンロードされ、Mac App Store を通じて公証されていないため、macOS が初回起動時にブロックする場合があります。
|
||||
|
||||
**ステップ 1:** `picoclaw-launcher` をダブルクリックすると、セキュリティ警告が表示されます:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="macOS Gatekeeper 警告" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" は開けません — "picoclaw-launcher" がMacに害を与えたりプライバシーを侵害するマルウェアを含まないことをAppleは確認できません。*
|
||||
|
||||
**ステップ 2:** **システム設定** → **プライバシーとセキュリティ** を開き、**セキュリティ** セクションまでスクロールして **このまま開く** をクリック → ダイアログで再度 **開く** をクリックします。
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS プライバシーとセキュリティ — このまま開く" width="600">
|
||||
</p>
|
||||
|
||||
この操作を一度行うと、以降の起動では警告が表示されなくなります。
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher(ヘッドレス / SSH 向け推奨)
|
||||
|
||||
TUI(Terminal UI)Launcher は設定と管理のためのフル機能ターミナルインターフェースを提供します。サーバー、Raspberry Pi、その他のヘッドレス環境に最適です。
|
||||
|
|
@ -293,9 +320,9 @@ termux-chroot ./picoclaw onboard # chroot で標準的な Linux ファイル
|
|||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**オプション 2: APK インストール(近日公開)**
|
||||
**オプション 2: APK インストール**
|
||||
|
||||
内蔵 WebUI を備えたスタンドアロン Android APK を開発中です。お楽しみに!
|
||||
[picoclaw.io](https://picoclaw.io/download/) から APK をダウンロードして直接インストール。Termux 不要!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher(リソース制約環境向け)</b></summary>
|
||||
|
|
@ -367,6 +394,7 @@ PicoClaw は `model_list` 設定を通じて 30 以上の LLM Provider をサポ
|
|||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | 必須 | NVIDIA ホスティングモデル |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | 必須 | 高速推論 |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | 必須 | 各種オープンモデル |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | 必須 | MiMo モデル |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | 不要 | ローカルモデル、セルフホスト |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | 不要 | ローカルデプロイ、OpenAI 互換 |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | 場合による | 100 以上の Provider のプロキシ |
|
||||
|
|
@ -423,9 +451,7 @@ Provider の完全な設定詳細は [Provider とモデル](docs/ja/providers.m
|
|||
| **DingTalk** | 中級(クライアント認証情報) | Stream | [ガイド](docs/channels/dingtalk/README.ja.md) |
|
||||
| **Feishu / Lark** | 中級(App ID + Secret) | WebSocket/SDK | [ガイド](docs/channels/feishu/README.ja.md) |
|
||||
| **LINE** | 中級(認証情報 + webhook) | Webhook | [ガイド](docs/channels/line/README.ja.md) |
|
||||
| **WeCom Bot** | 中級(webhook URL) | Webhook | [ガイド](docs/channels/wecom/wecom_bot/README.ja.md) |
|
||||
| **WeCom App** | 中級(corp 認証情報) | Webhook | [ガイド](docs/channels/wecom/wecom_app/README.ja.md) |
|
||||
| **WeCom AI Bot** | 中級(トークン + AES キー) | WebSocket / Webhook | [ガイド](docs/channels/wecom/wecom_aibot/README.ja.md) |
|
||||
| **WeCom** | 簡単(QR ログインまたは手動) | WebSocket | [ガイド](docs/channels/wecom/README.md) |
|
||||
| **IRC** | 中級(サーバー + nick) | IRC protocol | [ガイド](docs/ja/chat-apps.md#irc) |
|
||||
| **OneBot** | 中級(WebSocket URL) | OneBot v11 | [ガイド](docs/channels/onebot/README.ja.md) |
|
||||
| **MaixCam** | 簡単(有効化) | TCP socket | [ガイド](docs/channels/maixcam/README.ja.md) |
|
||||
|
|
@ -434,6 +460,8 @@ Provider の完全な設定詳細は [Provider とモデル](docs/ja/providers.m
|
|||
|
||||
> webhook ベースのすべての Channel は単一の Gateway HTTP サーバー(`gateway.host`:`gateway.port`、デフォルト `127.0.0.1:18790`)を共有します。Feishu は WebSocket/SDK モードを使用し、共有 HTTP サーバーを使用しません。
|
||||
|
||||
> ログの詳細度は `gateway.log_level` で制御します(デフォルト:`warn`)。サポートされる値:`debug`、`info`、`warn`、`error`、`fatal`。`PICOCLAW_LOG_LEVEL` 環境変数でも設定可能です。詳細は[設定ガイド](docs/ja/configuration.md#gateway-ログレベル)を参照してください。
|
||||
|
||||
Channel の詳細なセットアップ手順は [チャットアプリ設定](docs/ja/chat-apps.md) を参照してください。
|
||||
|
||||
## 🔧 ツール
|
||||
|
|
@ -517,7 +545,7 @@ CLI または統合チャットアプリからメッセージを 1 つ送るだ
|
|||
|
||||
## 🖥️ CLI リファレンス
|
||||
|
||||
| コマンド | 説明 |
|
||||
| コマンド | 説明 |
|
||||
| ------------------------- | ------------------------------ |
|
||||
| `picoclaw onboard` | 設定&ワークスペースの初期化 |
|
||||
| `picoclaw auth weixin` | WeChat アカウントを QR で接続 |
|
||||
|
|
|
|||
47
README.md
|
|
@ -18,7 +18,7 @@
|
|||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | **English**
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | **English**
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -56,17 +56,21 @@
|
|||
|
||||
## 📢 News
|
||||
|
||||
2026-03-31 📱 **Android Support!** PicoClaw now runs on Android! Download the APK at [picoclaw.io](https://picoclaw.io/download)
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 Released!** Agent architecture overhaul (SubTurn, Hooks, Steering, EventBus), WeChat/WeCom integration, security hardening (.security.yml, sensitive data filtering), new providers (AWS Bedrock, Azure, Xiaomi MiMo), and 35 bug fixes. PicoClaw has reached **26K Stars**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 Released!** System tray UI (Windows & Linux), sub-agent status query (`spawn_status`), experimental Gateway hot-reload, Cron security gating, and 2 security fixes. PicoClaw has reached **25K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Biggest update yet!** MCP protocol support, 4 new channels (Matrix/IRC/WeCom/Discord Proxy), 3 new providers (Kimi/Minimax/Avian), vision pipeline, JSONL memory store, model routing.
|
||||
|
||||
2026-02-28 📦 **v0.2.0** released with Docker Compose and Web UI Launcher support.
|
||||
|
||||
2026-02-26 🎉 PicoClaw hits **20K Stars** in just 17 days! Channel auto-orchestration and capability interfaces are live.
|
||||
|
||||
<details>
|
||||
<summary>Earlier news...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw hits **20K Stars** in just 17 days! Channel auto-orchestration and capability interfaces are live.
|
||||
|
||||
2026-02-16 🎉 PicoClaw breaks 12K Stars in one week! Community maintainer roles and [Roadmap](ROADMAP.md) officially launched.
|
||||
|
||||
2026-02-13 🎉 PicoClaw breaks 5000 Stars in 4 days! Project roadmap and developer groups in progress.
|
||||
|
|
@ -254,6 +258,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — First Launch Security Warning</b></summary>
|
||||
|
||||
macOS may block `picoclaw-launcher` on first launch because it is downloaded from the internet and not notarized through the Mac App Store.
|
||||
|
||||
**Step 1:** Double-click `picoclaw-launcher`. You will see a security warning:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="macOS Gatekeeper warning" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" Not Opened — Apple could not verify "picoclaw-launcher" is free of malware that may harm your Mac or compromise your privacy.*
|
||||
|
||||
**Step 2:** Open **System Settings** → **Privacy & Security** → scroll down to the **Security** section → click **Open Anyway** → confirm by clicking **Open Anyway** in the dialog.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Privacy & Security — Open Anyway" width="600">
|
||||
</p>
|
||||
|
||||
After this one-time step, `picoclaw-launcher` will open normally on subsequent launches.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Recommended for Headless / SSH)
|
||||
|
||||
The TUI (Terminal UI) Launcher provides a full-featured terminal interface for configuration and management. Ideal for servers, Raspberry Pi, and other headless environments.
|
||||
|
|
@ -293,9 +320,9 @@ Then follow the Terminal Launcher section below to complete configuration.
|
|||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Option 2: APK Install (coming soon)**
|
||||
**Option 2: APK Install**
|
||||
|
||||
A standalone Android APK with built-in WebUI is in development. Stay tuned!
|
||||
Download the APK from [picoclaw.io](https://picoclaw.io/download/) and install directly. No Termux required!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (for resource-constrained environments)</b></summary>
|
||||
|
|
@ -370,6 +397,7 @@ PicoClaw supports 30+ LLM providers through the `model_list` configuration. Use
|
|||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Required | NVIDIA hosted models |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Required | Fast inference |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Required | Various open models |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Required | MiMo models |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Not needed | Local models, self-hosted |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Not needed | Local deployment, OpenAI-compatible |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Varies | Proxy for 100+ providers |
|
||||
|
|
@ -429,9 +457,7 @@ Talk to your PicoClaw through 17+ messaging platforms:
|
|||
| **DingTalk** | Medium (client credentials) | Stream | [Guide](docs/channels/dingtalk/README.md) |
|
||||
| **Feishu / Lark** | Medium (App ID + Secret) | WebSocket/SDK | [Guide](docs/channels/feishu/README.md) |
|
||||
| **LINE** | Medium (credentials + webhook) | Webhook | [Guide](docs/channels/line/README.md) |
|
||||
| **WeCom Bot** | Medium (webhook URL) | Webhook | [Guide](docs/channels/wecom/wecom_bot/README.md) |
|
||||
| **WeCom App** | Medium (corp credentials) | Webhook | [Guide](docs/channels/wecom/wecom_app/README.md) |
|
||||
| **WeCom AI Bot** | Medium (token + AES key) | WebSocket / Webhook | [Guide](docs/channels/wecom/wecom_aibot/README.md) |
|
||||
| **WeCom** | Easy (QR login or manual) | WebSocket | [Guide](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Medium (server + nick) | IRC protocol | [Guide](docs/chat-apps.md#irc) |
|
||||
| **OneBot** | Medium (WebSocket URL) | OneBot v11 | [Guide](docs/channels/onebot/README.md) |
|
||||
| **MaixCam** | Easy (enable) | TCP socket | [Guide](docs/channels/maixcam/README.md) |
|
||||
|
|
@ -440,6 +466,8 @@ Talk to your PicoClaw through 17+ messaging platforms:
|
|||
|
||||
> All webhook-based channels share a single Gateway HTTP server (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). Feishu uses WebSocket/SDK mode and does not use the shared HTTP server.
|
||||
|
||||
> Log verbosity is controlled by `gateway.log_level` (default: `warn`). Supported values: `debug`, `info`, `warn`, `error`, `fatal`. Can also be set via `PICOCLAW_LOG_LEVEL`. See [Configuration](docs/configuration.md#gateway-log-level) for details.
|
||||
|
||||
For detailed channel setup instructions, see [Chat Apps Configuration](docs/chat-apps.md).
|
||||
|
||||
## 🔧 Tools
|
||||
|
|
@ -550,6 +578,8 @@ PicoClaw supports scheduled reminders and recurring tasks through the `cron` too
|
|||
* **Recurring tasks**: "Remind me every 2 hours" -> triggers every 2 hours
|
||||
* **Cron expressions**: "Remind me at 9am daily" -> uses cron expression
|
||||
|
||||
See [docs/cron.md](docs/cron.md) for current schedule types, execution modes, command-job gates, and persistence details.
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
For detailed guides beyond this README:
|
||||
|
|
@ -559,6 +589,7 @@ For detailed guides beyond this README:
|
|||
| [Docker & Quick Start](docs/docker.md) | Docker Compose setup, Launcher/Agent modes |
|
||||
| [Chat Apps](docs/chat-apps.md) | All 17+ channel setup guides |
|
||||
| [Configuration](docs/configuration.md) | Environment variables, workspace layout, security sandbox |
|
||||
| [Scheduled Tasks and Cron Jobs](docs/cron.md) | Cron schedule types, deliver modes, command gates, job storage |
|
||||
| [Providers & Models](docs/providers.md) | 30+ LLM providers, model routing, model_list configuration |
|
||||
| [Spawn & Async Tasks](docs/spawn-tasks.md) | Quick tasks, long tasks with spawn, async sub-agent orchestration |
|
||||
| [Hooks](docs/hooks/README.md) | Event-driven hook system: observers, interceptors, approval hooks |
|
||||
|
|
|
|||
603
README.my.md
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
<div align="center">
|
||||
<img src="assets/logo.webp" alt="PicoClaw" width="512">
|
||||
|
||||
<h1>PicoClaw: Pembantu AI Ultra-Cekap dalam Go</h1>
|
||||
|
||||
<h3>Perkakasan $10 · RAM 10MB · Boot ms · Jom, PicoClaw!</h3>
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go&logoColor=white" alt="Go">
|
||||
<img src="https://img.shields.io/badge/Arch-x86__64%2C%20ARM64%2C%20MIPS%2C%20RISC--V%2C%20LoongArch-blue" alt="Hardware">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green" alt="License">
|
||||
<br>
|
||||
<a href="https://picoclaw.io"><img src="https://img.shields.io/badge/Website-picoclaw.io-blue?style=flat&logo=google-chrome&logoColor=white" alt="Website"></a>
|
||||
<a href="https://docs.picoclaw.io/"><img src="https://img.shields.io/badge/Docs-Official-007acc?style=flat&logo=read-the-docs&logoColor=white" alt="Docs"></a>
|
||||
<a href="https://deepwiki.com/sipeed/picoclaw"><img src="https://img.shields.io/badge/Wiki-DeepWiki-FFA500?style=flat&logo=wikipedia&logoColor=white" alt="Wiki"></a>
|
||||
<br>
|
||||
<a href="https://x.com/SipeedIO"><img src="https://img.shields.io/badge/X_(Twitter)-SipeedIO-black?style=flat&logo=x&logoColor=white" alt="Twitter"></a>
|
||||
<a href="./assets/wechat.png"><img src="https://img.shields.io/badge/WeChat-Group-41d56b?style=flat&logo=wechat&logoColor=white"></a>
|
||||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | **Malay** | [English](README.md)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
> **PicoClaw** adalah projek sumber terbuka bebas yang dilancarkan oleh [Sipeed](https://sipeed.com), ditulis sepenuhnya dalam **Go** dari awal — bukan cabang OpenClaw, NanoBot, atau projek lain.
|
||||
|
||||
**PicoClaw** adalah pembantu AI peribadi ultra-ringan yang terinspirasi oleh [NanoBot](https://github.com/HKUDS/nanobot). Ia dibina semula dari awal dalam **Go** melalui proses "self-bootstrapping" — AI Agent itu sendiri yang memacu migrasi seni bina dan pengoptimuman kod.
|
||||
|
||||
**Berjalan pada perkakasan $10 dengan RAM <10MB** — 99% lebih sedikit memori daripada OpenClaw dan 98% lebih murah daripada Mac mini!
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/picoclaw_mem.gif" width="360" height="240">
|
||||
</p>
|
||||
</td>
|
||||
<td align="center" valign="top">
|
||||
<p align="center">
|
||||
<img src="assets/licheervnano.png" width="400" height="240">
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
> [!CAUTION]
|
||||
> **Notis Keselamatan**
|
||||
>
|
||||
> * **TIADA KRIPTO:** PicoClaw **tidak** mengeluarkan sebarang token atau mata wang kripto rasmi. Semua tuntutan di `pump.fun` atau platform dagangan lain adalah **penipuan**.
|
||||
> * **DOMAIN RASMI:** Satu-satunya laman web rasmi ialah **[picoclaw.io](https://picoclaw.io)**, dan laman web syarikat ialah **[sipeed.com](https://sipeed.com)**
|
||||
> * **BERHATI-HATI:** Banyak domain `.ai/.org/.com/.net/...` telah didaftarkan oleh pihak ketiga. Jangan percayai mereka.
|
||||
> * **NOTA:** PicoClaw dalam pembangunan pesat awal. Mungkin terdapat isu keselamatan yang belum diselesaikan. Jangan deploy ke pengeluaran sebelum v1.0.
|
||||
|
||||
|
||||
## 📢 Berita
|
||||
|
||||
2026-03-31 📱 **Sokongan Android!** PicoClaw sekarang berjalan di Android! Muat turun APK di [picoclaw.io](https://picoclaw.io/download)
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 Dikeluarkan!** Penstrukturan semula seni bina Agent (SubTurn, Hooks, Steering, EventBus), integrasi WeChat/WeCom, penguatan keselamatan (.security.yml, penapisan data sensitif), penyedia baharu (AWS Bedrock, Azure, Xiaomi MiMo), dan 35 pembetulan pepijat. PicoClaw mencapai **26K Stars**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 Dikeluarkan!** UI dulang sistem (Windows & Linux), pertanyaan status sub-agent (`spawn_status`), muat semula panas Gateway eksperimental, kawalan keselamatan Cron, dan 2 pembetulan keselamatan. PicoClaw mencapai **25K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Kemas kini terbesar setakat ini!** Sokongan protokol MCP, 4 saluran baharu (Matrix/IRC/WeCom/Discord Proxy), 3 penyedia baharu (Kimi/Minimax/Avian), saluran paip visi, storan memori JSONL, penghalaan model.
|
||||
|
||||
2026-02-28 📦 **v0.2.0** dikeluarkan dengan sokongan Docker Compose dan Pelancar Web UI.
|
||||
|
||||
<details>
|
||||
<summary>Berita terdahulu...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw mencapai **20K Stars** hanya dalam 17 hari! Orkestrasi saluran automatik dan antara muka keupayaan kini aktif.
|
||||
|
||||
2026-02-16 🎉 PicoClaw melepasi 12K Stars dalam seminggu! Peranan penyelenggara komuniti dan [Peta Jalan](ROADMAP.md) dilancarkan secara rasmi.
|
||||
|
||||
2026-02-13 🎉 PicoClaw melepasi 5000 Stars dalam 4 hari! Peta jalan projek dan kumpulan pembangun sedang dalam proses.
|
||||
|
||||
2026-02-09 🎉 **PicoClaw Dikeluarkan!** Dibina dalam 1 hari untuk membawa AI Agent ke perkakasan $10 dengan RAM <10MB. Jom, PicoClaw!
|
||||
|
||||
</details>
|
||||
|
||||
## ✨ Ciri-ciri
|
||||
|
||||
🪶 **Ultra-ringan**: Jejak memori teras <10MB — 99% lebih kecil daripada OpenClaw.*
|
||||
|
||||
💰 **Kos minimum**: Cukup cekap untuk berjalan pada perkakasan $10 — 98% lebih murah daripada Mac mini.
|
||||
|
||||
⚡️ **Boot kilat**: 400x lebih pantas. Boot dalam <1s walaupun pada pemproses teras tunggal 0.6GHz.
|
||||
|
||||
🌍 **Benar-benar mudah alih**: Binari tunggal merentasi seni bina RISC-V, ARM, MIPS, dan x86.
|
||||
|
||||
🤖 **Dibantu AI**: Pelaksanaan Go tulen — 95% kod teras dijana oleh Agent dan diperhalusi melalui semakan manusia.
|
||||
|
||||
🔌 **Sokongan MCP**: Integrasi [Model Context Protocol](https://modelcontextprotocol.io/) natif.
|
||||
|
||||
👁️ **Saluran paip visi**: Hantar imej dan fail terus ke Agent — pengekodan base64 automatik untuk LLM multimodal.
|
||||
|
||||
🧠 **Penghalaan pintar**: Penghalaan model berasaskan peraturan — pertanyaan mudah ke model ringan, menjimatkan kos API.
|
||||
|
||||
_*Binaan terkini mungkin menggunakan 10-20MB disebabkan penggabungan PR yang pesat. Pengoptimuman sumber dirancang. Perbandingan kelajuan boot berdasarkan penanda aras teras tunggal 0.8GHz (lihat jadual di bawah)._
|
||||
|
||||
<div align="center">
|
||||
|
||||
| | OpenClaw | NanoBot | **PicoClaw** |
|
||||
| ------------------------------ | ------------- | ------------------------ | -------------------------------------- |
|
||||
| **Bahasa** | TypeScript | Python | **Go** |
|
||||
| **RAM** | >1GB | >100MB | **< 10MB*** |
|
||||
| **Masa Boot** (teras 0.8GHz) | >500s | >30s | **<1s** |
|
||||
| **Kos** | Mac Mini $599 | Kebanyakan papan Linux ~$50 | **Mana-mana papan Linux dari $10** |
|
||||
|
||||
<img src="assets/compare.jpg" alt="PicoClaw" width="512">
|
||||
|
||||
</div>
|
||||
|
||||
> **[Senarai Keserasian Perkakasan](docs/hardware-compatibility.md)** — Lihat semua papan yang diuji, dari RISC-V $5 hingga Raspberry Pi hingga telefon Android.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/hardware-banner.jpg" alt="Keserasian Perkakasan PicoClaw" width="100%">
|
||||
</p>
|
||||
|
||||
## 🦾 Demonstrasi
|
||||
|
||||
### 🛠️ Aliran Kerja Pembantu Standard
|
||||
|
||||
<table align="center">
|
||||
<tr align="center">
|
||||
<th><p align="center">Mod Jurutera Full-Stack</p></th>
|
||||
<th><p align="center">Pengelogan & Perancangan</p></th>
|
||||
<th><p align="center">Carian Web & Pembelajaran</p></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_code.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_memory.gif" width="240" height="180"></p></td>
|
||||
<td align="center"><p align="center"><img src="assets/picoclaw_search.gif" width="240" height="180"></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">Bangun · Deploy · Skala</td>
|
||||
<td align="center">Jadual · Automatik · Ingat</td>
|
||||
<td align="center">Temui · Wawasan · Trend</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 🐜 Deployment Jejak Rendah yang Inovatif
|
||||
|
||||
PicoClaw boleh digunakan pada hampir mana-mana peranti Linux!
|
||||
|
||||
- $9.9 [LicheeRV-Nano](https://www.aliexpress.com/item/1005006519668532.html) untuk pembantu rumah minimal
|
||||
- $30~50 [NanoKVM](https://www.aliexpress.com/item/1005007369816019.html) untuk operasi pelayan automatik
|
||||
- $50 [MaixCAM](https://www.aliexpress.com/item/1005008053333693.html) untuk pengawasan pintar
|
||||
|
||||
<https://private-user-images.githubusercontent.com/83055338/547056448-e7b031ff-d6f5-4468-bcca-5726b6fecb5c.mp4>
|
||||
|
||||
🌟 Lebih Banyak Kes Deployment Menanti!
|
||||
|
||||
|
||||
## 📦 Pemasangan
|
||||
|
||||
### Muat turun dari picoclaw.io (Disyorkan)
|
||||
|
||||
Lawati **[picoclaw.io](https://picoclaw.io)** — laman web rasmi mengesan platform anda secara automatik dan menyediakan muat turun satu klik.
|
||||
|
||||
### Muat turun binari pra-kompil
|
||||
|
||||
Muat turun binari untuk platform anda dari halaman [GitHub Releases](https://github.com/sipeed/picoclaw/releases).
|
||||
|
||||
### Bina dari sumber (untuk pembangunan)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
make deps
|
||||
|
||||
# Bina binari teras
|
||||
make build
|
||||
|
||||
# Bina Pelancar Web UI (diperlukan untuk mod WebUI)
|
||||
make build-launcher
|
||||
|
||||
# Bina untuk pelbagai platform
|
||||
make build-all
|
||||
|
||||
# Bina untuk Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64)
|
||||
make build-pi-zero
|
||||
|
||||
# Bina dan pasang
|
||||
make install
|
||||
```
|
||||
|
||||
**Raspberry Pi Zero 2 W:** Gunakan binari yang sepadan dengan OS anda: Raspberry Pi OS 32-bit -> `make build-linux-arm`; 64-bit -> `make build-linux-arm64`. Atau jalankan `make build-pi-zero` untuk membina kedua-duanya.
|
||||
|
||||
## 🚀 Panduan Permulaan Pantas
|
||||
|
||||
### 🌐 Pelancar WebUI (Disyorkan untuk Desktop)
|
||||
|
||||
Pelancar WebUI menyediakan antara muka berasaskan pelayar untuk konfigurasi dan sembang. Ini adalah cara termudah untuk bermula — tiada pengetahuan baris arahan diperlukan.
|
||||
|
||||
**Pilihan 1: Klik dua kali (Desktop)**
|
||||
|
||||
Selepas memuat turun dari [picoclaw.io](https://picoclaw.io), klik dua kali `picoclaw-launcher` (atau `picoclaw-launcher.exe` pada Windows). Pelayar anda akan dibuka secara automatik di `http://localhost:18800`.
|
||||
|
||||
**Pilihan 2: Baris arahan**
|
||||
|
||||
```bash
|
||||
picoclaw-launcher
|
||||
# Buka http://localhost:18800 dalam pelayar anda
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Akses jauh / Docker / VM:** Tambah bendera `-public` untuk mendengar pada semua antara muka:
|
||||
> ```bash
|
||||
> picoclaw-launcher -public
|
||||
> ```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-webui.jpg" alt="Pelancar WebUI" width="600">
|
||||
</p>
|
||||
|
||||
**Memulakan:** Buka WebUI, kemudian: **1)** Konfigurasikan Penyedia (tambah kunci API LLM) -> **2)** Konfigurasikan Saluran (cth. Telegram) -> **3)** Mulakan Gateway -> **4)** Sembang!
|
||||
|
||||
Untuk dokumentasi WebUI terperinci, lihat [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
<details>
|
||||
<summary><b>Docker (alternatif)</b></summary>
|
||||
|
||||
```bash
|
||||
# 1. Klon repo ini
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. Jalankan pertama kali — jana docker/data/config.json secara automatik kemudian keluar
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up
|
||||
|
||||
# 3. Tetapkan kunci API anda
|
||||
vim docker/data/config.json
|
||||
|
||||
# 4. Mulakan
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
# Buka http://localhost:18800
|
||||
```
|
||||
|
||||
> **Pengguna Docker / VM:** Gateway mendengar pada `127.0.0.1` secara lalai. Tetapkan `PICOCLAW_GATEWAY_HOST=0.0.0.0` atau gunakan bendera `-public` untuk membolehkan akses dari hos.
|
||||
|
||||
```bash
|
||||
# Semak log
|
||||
docker compose -f docker/docker-compose.yml logs -f
|
||||
|
||||
# Henti
|
||||
docker compose -f docker/docker-compose.yml --profile launcher down
|
||||
|
||||
# Kemas kini
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Amaran Keselamatan Pelancaran Pertama</b></summary>
|
||||
|
||||
macOS mungkin menyekat `picoclaw-launcher` pada pelancaran pertama kerana ia dimuat turun dari internet dan tidak disahkan melalui Mac App Store.
|
||||
|
||||
**Langkah 1:** Klik dua kali `picoclaw-launcher`. Anda akan melihat amaran keselamatan:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Amaran macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" Tidak Dibuka — Apple tidak dapat mengesahkan "picoclaw-launcher" bebas daripada perisian hasad yang mungkin membahayakan Mac anda atau menjejaskan privasi anda.*
|
||||
|
||||
**Langkah 2:** Buka **Tetapan Sistem** → **Privasi & Keselamatan** → tatal ke bawah ke bahagian **Keselamatan** → klik **Buka Juga** → sahkan dengan mengklik **Buka Juga** dalam dialog.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Privasi & Keselamatan — Buka Juga" width="600">
|
||||
</p>
|
||||
|
||||
Selepas langkah sekali ini, `picoclaw-launcher` akan dibuka secara normal pada pelancaran seterusnya.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 Pelancar TUI (Disyorkan untuk Headless / SSH)
|
||||
|
||||
Pelancar TUI menyediakan antara muka terminal lengkap untuk konfigurasi dan pengurusan. Sesuai untuk pelayan, Raspberry Pi, dan persekitaran tanpa kepala lain.
|
||||
|
||||
```bash
|
||||
picoclaw-launcher-tui
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/launcher-tui.jpg" alt="Pelancar TUI" width="600">
|
||||
</p>
|
||||
|
||||
**Memulakan:**
|
||||
|
||||
Gunakan menu TUI untuk: **1)** Konfigurasikan Penyedia -> **2)** Konfigurasikan Saluran -> **3)** Mulakan Gateway -> **4)** Sembang!
|
||||
|
||||
Untuk dokumentasi TUI terperinci, lihat [docs.picoclaw.io](https://docs.picoclaw.io).
|
||||
|
||||
### 📱 Android
|
||||
|
||||
Berikan telefon lama anda kehidupan baru! Jadikannya Pembantu AI pintar dengan PicoClaw.
|
||||
|
||||
**Pilihan 1: Termux (tersedia sekarang)**
|
||||
|
||||
1. Pasang [Termux](https://github.com/termux/termux-app) (muat turun dari [GitHub Releases](https://github.com/termux/termux-app/releases), atau cari di F-Droid / Google Play)
|
||||
2. Jalankan arahan berikut:
|
||||
|
||||
```bash
|
||||
# Muat turun keluaran terkini
|
||||
wget https://github.com/sipeed/picoclaw/releases/latest/download/picoclaw_Linux_arm64.tar.gz
|
||||
tar xzf picoclaw_Linux_arm64.tar.gz
|
||||
pkg install proot
|
||||
termux-chroot ./picoclaw onboard # chroot menyediakan susun atur sistem fail Linux standard
|
||||
```
|
||||
|
||||
Kemudian ikuti bahagian Pelancar Terminal di bawah untuk melengkapkan konfigurasi.
|
||||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw pada Termux" width="512">
|
||||
|
||||
**Pilihan 2: Pasang APK**
|
||||
|
||||
Muat turun APK dari [picoclaw.io](https://picoclaw.io/download/) dan pasang secara langsung. Tiada Termux diperlukan!
|
||||
|
||||
<details>
|
||||
<summary><b>Pelancar Terminal (untuk persekitaran terhad sumber)</b></summary>
|
||||
|
||||
Untuk persekitaran minimal di mana hanya binari teras `picoclaw` tersedia (tiada UI Pelancar), anda boleh mengkonfigurasi semua melalui baris arahan dan fail konfigurasi JSON.
|
||||
|
||||
**1. Mulakan**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
Ini mencipta `~/.picoclaw/config.json` dan direktori ruang kerja.
|
||||
|
||||
**2. Konfigurasikan** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Lihat `config/config.example.json` dalam repo untuk templat konfigurasi lengkap. Nota: kunci API kini disimpan dalam `.security.yml`, bukan `config.json`.
|
||||
|
||||
**3. Sembang**
|
||||
|
||||
```bash
|
||||
picoclaw agent -m "Apa itu 2+2?"
|
||||
|
||||
# Mod interaktif
|
||||
picoclaw agent
|
||||
|
||||
# Mulakan gateway untuk integrasi aplikasi sembang
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## 🔌 Penyedia (LLM)
|
||||
|
||||
PicoClaw menyokong 30+ penyedia LLM melalui konfigurasi `model_list`. Gunakan format `protokol/model`:
|
||||
|
||||
| Penyedia | Protokol | Kunci API | Nota |
|
||||
|----------|----------|-----------|------|
|
||||
| [OpenAI](https://platform.openai.com/api-keys) | `openai/` | Diperlukan | GPT-5.4, GPT-4o, o3, dll. |
|
||||
| [Anthropic](https://console.anthropic.com/settings/keys) | `anthropic/` | Diperlukan | Claude Opus 4.6, Sonnet 4.6, dll. |
|
||||
| [Google Gemini](https://aistudio.google.com/apikey) | `gemini/` | Diperlukan | Gemini 3 Flash, 2.5 Pro, dll. |
|
||||
| [OpenRouter](https://openrouter.ai/keys) | `openrouter/` | Diperlukan | 200+ model, API bersatu |
|
||||
| [Zhipu (GLM)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | `zhipu/` | Diperlukan | GLM-4.7, GLM-5, dll. |
|
||||
| [DeepSeek](https://platform.deepseek.com/api_keys) | `deepseek/` | Diperlukan | DeepSeek-V3, DeepSeek-R1 |
|
||||
| [Volcengine](https://console.volcengine.com) | `volcengine/` | Diperlukan | Doubao, model Ark |
|
||||
| [Qwen](https://dashscope.console.aliyun.com/apiKey) | `qwen/` | Diperlukan | Qwen3, Qwen-Max, dll. |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/` | Diperlukan | Inferens pantas (Llama, Mixtral) |
|
||||
| [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | Diperlukan | Model Kimi |
|
||||
| [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | Diperlukan | Model MiniMax |
|
||||
| [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | Diperlukan | Mistral Large, Codestral |
|
||||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Diperlukan | Model hos NVIDIA |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Diperlukan | Inferens pantas |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Diperlukan | Pelbagai model terbuka |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Diperlukan | Model MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Tidak perlu | Model tempatan, self-hosted |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Tidak perlu | Deployment tempatan, serasi OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Berbeza | Proksi untuk 100+ penyedia |
|
||||
| [Azure OpenAI](https://portal.azure.com/) | `azure/` | Diperlukan | Deployment Azure perusahaan |
|
||||
| [GitHub Copilot](https://github.com/features/copilot) | `github-copilot/` | OAuth | Log masuk kod peranti |
|
||||
| [Antigravity](https://console.cloud.google.com/) | `antigravity/` | OAuth | Google Cloud AI |
|
||||
| [AWS Bedrock](https://console.aws.amazon.com/bedrock)* | `bedrock/` | Kelayakan AWS | Claude, Llama, Mistral pada AWS |
|
||||
|
||||
> \* AWS Bedrock memerlukan tag binaan: `go build -tags bedrock`. Tetapkan `api_base` kepada nama rantau (cth. `us-east-1`) untuk resolusi endpoint automatik merentasi semua partition AWS. Apabila menggunakan URL endpoint penuh, anda juga perlu mengkonfigurasi `AWS_REGION` melalui pemboleh ubah persekitaran.
|
||||
|
||||
<details>
|
||||
<summary><b>Deployment tempatan (Ollama, vLLM, dll.)</b></summary>
|
||||
|
||||
**Ollama:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-llama",
|
||||
"model": "ollama/llama3.1:8b",
|
||||
"api_base": "http://localhost:11434/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**vLLM:**
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "local-vllm",
|
||||
"model": "vllm/your-model",
|
||||
"api_base": "http://localhost:8000/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Untuk butiran konfigurasi penyedia penuh, lihat [Penyedia & Model](docs/providers.md).
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## 💬 Saluran (Aplikasi Sembang)
|
||||
|
||||
Bercakap dengan PicoClaw anda melalui 17+ platform pemesejan:
|
||||
|
||||
| Saluran | Persediaan | Protokol | Dok |
|
||||
|---------|-----------|----------|-----|
|
||||
| **Telegram** | Mudah (token bot) | Long polling | [Panduan](docs/channels/telegram/README.md) |
|
||||
| **Discord** | Mudah (token bot + intents) | WebSocket | [Panduan](docs/channels/discord/README.md) |
|
||||
| **WhatsApp** | Mudah (imbas QR atau URL jambatan) | Natif / Jambatan | [Panduan](docs/chat-apps.md#whatsapp) |
|
||||
| **Weixin** | Mudah (imbas QR natif) | iLink API | [Panduan](docs/chat-apps.md#weixin) |
|
||||
| **QQ** | Mudah (AppID + AppSecret) | WebSocket | [Panduan](docs/channels/qq/README.md) |
|
||||
| **Slack** | Mudah (token bot + app) | Socket Mode | [Panduan](docs/channels/slack/README.md) |
|
||||
| **Matrix** | Sederhana (homeserver + token) | Sync API | [Panduan](docs/channels/matrix/README.md) |
|
||||
| **DingTalk** | Sederhana (kelayakan klien) | Stream | [Panduan](docs/channels/dingtalk/README.md) |
|
||||
| **Feishu / Lark** | Sederhana (App ID + Secret) | WebSocket/SDK | [Panduan](docs/channels/feishu/README.md) |
|
||||
| **LINE** | Sederhana (kelayakan + webhook) | Webhook | [Panduan](docs/channels/line/README.md) |
|
||||
| **WeCom** | Mudah (log masuk QR atau manual) | WebSocket | [Panduan](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Sederhana (pelayan + nick) | Protokol IRC | [Panduan](docs/chat-apps.md#irc) |
|
||||
| **OneBot** | Sederhana (URL WebSocket) | OneBot v11 | [Panduan](docs/channels/onebot/README.md) |
|
||||
| **MaixCam** | Mudah (aktifkan) | TCP socket | [Panduan](docs/channels/maixcam/README.md) |
|
||||
| **Pico** | Mudah (aktifkan) | Protokol natif | Terbina dalam |
|
||||
| **Pico Client** | Mudah (URL WebSocket) | WebSocket | Terbina dalam |
|
||||
|
||||
> Semua saluran berasaskan webhook berkongsi satu pelayan HTTP Gateway (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`). Feishu menggunakan mod WebSocket/SDK dan tidak menggunakan pelayan HTTP yang dikongsi.
|
||||
|
||||
> Tahap perincian log dikawal oleh `gateway.log_level` (lalai: `warn`). Nilai yang disokong: `debug`, `info`, `warn`, `error`, `fatal`. Boleh juga ditetapkan melalui `PICOCLAW_LOG_LEVEL`. Lihat [Konfigurasi](docs/configuration.md#gateway-log-level) untuk butiran.
|
||||
|
||||
Untuk arahan persediaan saluran terperinci, lihat [Konfigurasi Aplikasi Sembang](docs/my/chat-apps.md).
|
||||
|
||||
## 🔧 Alat
|
||||
|
||||
### 🔍 Carian Web
|
||||
|
||||
PicoClaw boleh mencari web untuk menyediakan maklumat terkini. Konfigurasikan dalam `tools.web`:
|
||||
|
||||
| Enjin Carian | Kunci API | Peringkat Percuma | Pautan |
|
||||
|-------------|-----------|-------------------|--------|
|
||||
| DuckDuckGo | Tidak perlu | Tanpa had | Sandaran terbina dalam |
|
||||
| [Baidu Search](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5) | Diperlukan | 1000 pertanyaan/hari | Dikuasai AI, dioptimumkan untuk China |
|
||||
| [Tavily](https://tavily.com) | Diperlukan | 1000 pertanyaan/bulan | Dioptimumkan untuk AI Agent |
|
||||
| [Brave Search](https://brave.com/search/api) | Diperlukan | 2000 pertanyaan/bulan | Pantas dan peribadi |
|
||||
| [Perplexity](https://www.perplexity.ai) | Diperlukan | Berbayar | Carian dikuasai AI |
|
||||
| [SearXNG](https://github.com/searxng/searxng) | Tidak perlu | Self-hosted | Enjin metasearch percuma |
|
||||
| [GLM Search](https://open.bigmodel.cn/) | Diperlukan | Berbeza | Carian web Zhipu |
|
||||
|
||||
### ⚙️ Alat Lain
|
||||
|
||||
PicoClaw menyertakan alat terbina dalam untuk operasi fail, pelaksanaan kod, penjadualan, dan banyak lagi. Lihat [Konfigurasi Alat](docs/tools_configuration.md) untuk butiran.
|
||||
|
||||
## 🎯 Kemahiran
|
||||
|
||||
Kemahiran adalah keupayaan modular yang melanjutkan Agent anda. Ia dimuatkan dari fail `SKILL.md` dalam ruang kerja anda.
|
||||
|
||||
**Pasang kemahiran dari ClawHub:**
|
||||
|
||||
```bash
|
||||
picoclaw skills search "web scraping"
|
||||
picoclaw skills install <nama-kemahiran>
|
||||
```
|
||||
|
||||
**Konfigurasikan token ClawHub** (pilihan, untuk had kadar lebih tinggi):
|
||||
|
||||
Tambah ke `config.json` anda:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"skills": {
|
||||
"registries": {
|
||||
"clawhub": {
|
||||
"auth_token": "your-clawhub-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Untuk butiran lanjut, lihat [Konfigurasi Alat - Kemahiran](docs/tools_configuration.md#skills-tool).
|
||||
|
||||
## 🔗 MCP (Protokol Konteks Model)
|
||||
|
||||
PicoClaw menyokong [MCP](https://modelcontextprotocol.io/) secara natif — sambungkan mana-mana pelayan MCP untuk melanjutkan keupayaan Agent anda dengan alat dan sumber data luaran.
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"mcp": {
|
||||
"enabled": true,
|
||||
"servers": {
|
||||
"filesystem": {
|
||||
"enabled": true,
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Untuk konfigurasi MCP penuh (pengangkutan stdio, SSE, HTTP, Penemuan Alat), lihat [Konfigurasi Alat - MCP](docs/tools_configuration.md#mcp-tool).
|
||||
|
||||
## <img src="assets/clawdchat-icon.png" width="24" height="24" alt="ClawdChat"> Sertai Rangkaian Sosial Agent
|
||||
|
||||
Sambungkan PicoClaw ke Rangkaian Sosial Agent dengan menghantar satu mesej melalui CLI atau mana-mana Aplikasi Sembang yang disepadukan.
|
||||
|
||||
**Baca `https://clawdchat.ai/skill.md` dan ikuti arahan untuk menyertai [ClawdChat.ai](https://clawdchat.ai)**
|
||||
|
||||
## 🖥️ Rujukan CLI
|
||||
|
||||
| Arahan | Penerangan |
|
||||
| ------ | ---------- |
|
||||
| `picoclaw onboard` | Mulakan konfigurasi & ruang kerja |
|
||||
| `picoclaw auth weixin` | Sambungkan akaun WeChat melalui QR |
|
||||
| `picoclaw agent -m "..."` | Sembang dengan agent |
|
||||
| `picoclaw agent` | Mod sembang interaktif |
|
||||
| `picoclaw gateway` | Mulakan gateway |
|
||||
| `picoclaw status` | Tunjukkan status |
|
||||
| `picoclaw version` | Tunjukkan maklumat versi |
|
||||
| `picoclaw model` | Lihat atau tukar model lalai |
|
||||
| `picoclaw cron list` | Senaraikan semua kerja berjadual |
|
||||
| `picoclaw cron add ...` | Tambah kerja berjadual |
|
||||
| `picoclaw cron disable` | Lumpuhkan kerja berjadual |
|
||||
| `picoclaw cron remove` | Buang kerja berjadual |
|
||||
| `picoclaw skills list` | Senaraikan kemahiran yang dipasang |
|
||||
| `picoclaw skills install` | Pasang kemahiran |
|
||||
| `picoclaw migrate` | Migrasi data dari versi lama |
|
||||
| `picoclaw auth login` | Sahkan dengan penyedia |
|
||||
|
||||
### ⏰ Tugasan Berjadual / Peringatan
|
||||
|
||||
PicoClaw menyokong peringatan berjadual dan tugasan berulang melalui alat `cron`:
|
||||
|
||||
* **Peringatan sekali**: "Ingatkan saya dalam 10 minit" -> pencetus sekali selepas 10 minit
|
||||
* **Tugasan berulang**: "Ingatkan saya setiap 2 jam" -> pencetus setiap 2 jam
|
||||
* **Ungkapan Cron**: "Ingatkan saya pada pukul 9 pagi setiap hari" -> menggunakan ungkapan cron
|
||||
|
||||
## 📚 Dokumentasi
|
||||
|
||||
Untuk panduan terperinci melebihi README ini:
|
||||
|
||||
| Topik | Penerangan |
|
||||
|-------|------------|
|
||||
| [Docker & Permulaan Pantas](docs/my/docker.md) | Persediaan Docker Compose, mod Launcher/Agent |
|
||||
| [Aplikasi Sembang](docs/my/chat-apps.md) | Panduan persediaan 17+ saluran |
|
||||
| [Konfigurasi](docs/my/configuration.md) | Pemboleh ubah persekitaran, susun atur ruang kerja |
|
||||
| [Penyedia & Model](docs/providers.md) | 30+ penyedia LLM, penghalaan model |
|
||||
| [Spawn & Tugasan Async](docs/my/spawn-tasks.md) | Tugasan pantas, tugasan panjang dengan spawn |
|
||||
| [Penyelesaian Masalah](docs/my/troubleshooting.md) | Isu biasa dan penyelesaian |
|
||||
| [Konfigurasi Alat](docs/tools_configuration.md) | Aktif/nyahaktif alat, dasar exec, MCP, Kemahiran |
|
||||
| [Keserasian Perkakasan](docs/hardware-compatibility.md) | Papan yang diuji, keperluan minimum |
|
||||
|
||||
## 🤝 Sumbangan & Peta Jalan
|
||||
|
||||
PR dialu-alukan! Kod sumber sengaja dibuat kecil dan mudah dibaca.
|
||||
|
||||
Lihat [Peta Jalan Komuniti](https://github.com/sipeed/picoclaw/issues/988) dan [CONTRIBUTING.md](CONTRIBUTING.md) untuk panduan.
|
||||
|
||||
Kumpulan pembangun sedang dibina, sertai selepas PR pertama anda digabungkan!
|
||||
|
||||
Kumpulan Pengguna:
|
||||
|
||||
Discord: <https://discord.gg/V4sAZ9XWpN>
|
||||
|
||||
WeChat:
|
||||
<img src="assets/wechat.png" alt="Kod QR kumpulan WeChat" width="512">
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | **Português** | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [English](README.md)
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | **Português** | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -56,17 +56,21 @@
|
|||
|
||||
## 📢 Novidades
|
||||
|
||||
2026-03-31 📱 **Suporte Android!** PicoClaw agora roda no Android! Baixe o APK em [picoclaw.io](https://picoclaw.io/download)
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 Lançada!** Reformulação da arquitetura Agent (SubTurn, Hooks, Steering, EventBus), integração WeChat/WeCom, fortalecimento de segurança (.security.yml, filtragem de dados sensíveis), novos providers (AWS Bedrock, Azure, Xiaomi MiMo) e 35 correções de bugs. O PicoClaw atingiu **26K Stars**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 Lançada!** UI na bandeja do sistema (Windows e Linux), consulta de status de sub-agent (`spawn_status`), hot-reload experimental do Gateway, controle de segurança do Cron e 2 correções de segurança. O PicoClaw atingiu **25K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Maior atualização até agora!** Suporte ao protocolo MCP, 4 novos channels (Matrix/IRC/WeCom/Discord Proxy), 3 novos providers (Kimi/Minimax/Avian), pipeline de visão, armazenamento de memória JSONL, roteamento de modelos.
|
||||
|
||||
2026-02-28 📦 **v0.2.0** lançada com suporte a Docker Compose e Web UI Launcher.
|
||||
|
||||
2026-02-26 🎉 O PicoClaw atinge **20K Stars** em apenas 17 dias! Orquestração automática de channels e interfaces de capacidade estão disponíveis.
|
||||
|
||||
<details>
|
||||
<summary>Notícias anteriores...</summary>
|
||||
|
||||
2026-02-26 🎉 O PicoClaw atinge **20K Stars** em apenas 17 dias! Orquestração automática de channels e interfaces de capacidade estão disponíveis.
|
||||
|
||||
2026-02-16 🎉 O PicoClaw ultrapassa 12K Stars em uma semana! Funções de mantenedor da comunidade e [Roadmap](ROADMAP.md) lançados oficialmente.
|
||||
|
||||
2026-02-13 🎉 O PicoClaw ultrapassa 5000 Stars em 4 dias! Roadmap do projeto e grupos de desenvolvedores em andamento.
|
||||
|
|
@ -254,6 +258,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Aviso de segurança no primeiro lançamento</b></summary>
|
||||
|
||||
O macOS pode bloquear o `picoclaw-launcher` no primeiro lançamento porque ele foi baixado da internet e não é notarizado pela Mac App Store.
|
||||
|
||||
**Passo 1:** Dê um duplo clique em `picoclaw-launcher`. Você verá um aviso de segurança:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Aviso do macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" não foi aberto — A Apple não conseguiu verificar se "picoclaw-launcher" está livre de malware que possa prejudicar seu Mac ou comprometer sua privacidade.*
|
||||
|
||||
**Passo 2:** Abra **Configurações do Sistema** → **Privacidade e Segurança** → role até a seção **Segurança** → clique em **Abrir Mesmo Assim** → confirme clicando em **Abrir Mesmo Assim** na caixa de diálogo.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Privacidade e Segurança — Abrir Mesmo Assim" width="600">
|
||||
</p>
|
||||
|
||||
Após esta etapa única, o `picoclaw-launcher` abrirá normalmente nos lançamentos seguintes.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Recomendado para Headless / SSH)
|
||||
|
||||
O TUI (Terminal UI) Launcher fornece uma interface de terminal completa para configuração e gerenciamento. Ideal para servidores, Raspberry Pi e outros ambientes headless.
|
||||
|
|
@ -293,9 +320,9 @@ Em seguida, siga a seção Terminal Launcher abaixo para concluir a configuraç
|
|||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Opção 2: Instalação via APK (em breve)**
|
||||
**Opção 2: Instalação via APK**
|
||||
|
||||
Um APK Android independente com WebUI integrado está em desenvolvimento. Fique ligado!
|
||||
Baixe o APK de [picoclaw.io](https://picoclaw.io/download/) e instale diretamente. Sem necessidade de Termux!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (para ambientes com recursos limitados)</b></summary>
|
||||
|
|
@ -367,6 +394,7 @@ O PicoClaw suporta mais de 30 providers de LLM através da configuração `model
|
|||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Obrigatória | Modelos hospedados pela NVIDIA |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Obrigatória | Inferência rápida |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Obrigatória | Vários modelos abertos |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Obrigatória | Modelos MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Não necessária | Modelos locais, self-hosted |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Não necessária | Implantação local, compatível com OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Varia | Proxy para 100+ providers |
|
||||
|
|
@ -423,9 +451,7 @@ Converse com seu PicoClaw por meio de mais de 17 plataformas de mensagens:
|
|||
| **DingTalk** | Médio (credenciais do cliente) | Stream | [Guia](docs/channels/dingtalk/README.pt-br.md) |
|
||||
| **Feishu / Lark** | Médio (App ID + Secret) | WebSocket/SDK | [Guia](docs/channels/feishu/README.pt-br.md) |
|
||||
| **LINE** | Médio (credenciais + webhook) | Webhook | [Guia](docs/channels/line/README.pt-br.md) |
|
||||
| **WeCom Bot** | Médio (webhook URL) | Webhook | [Guia](docs/channels/wecom/wecom_bot/README.pt-br.md) |
|
||||
| **WeCom App** | Médio (credenciais corporativas) | Webhook | [Guia](docs/channels/wecom/wecom_app/README.pt-br.md) |
|
||||
| **WeCom AI Bot** | Médio (token + chave AES) | WebSocket / Webhook | [Guia](docs/channels/wecom/wecom_aibot/README.pt-br.md) |
|
||||
| **WeCom** | Fácil (login QR ou manual) | WebSocket | [Guia](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Médio (servidor + nick) | Protocolo IRC | [Guia](docs/pt-br/chat-apps.md#irc) |
|
||||
| **OneBot** | Médio (WebSocket URL) | OneBot v11 | [Guia](docs/channels/onebot/README.pt-br.md) |
|
||||
| **MaixCam** | Fácil (habilitar) | TCP socket | [Guia](docs/channels/maixcam/README.pt-br.md) |
|
||||
|
|
@ -434,6 +460,8 @@ Converse com seu PicoClaw por meio de mais de 17 plataformas de mensagens:
|
|||
|
||||
> Todos os channels baseados em webhook compartilham um único servidor HTTP do Gateway (`gateway.host`:`gateway.port`, padrão `127.0.0.1:18790`). O Feishu usa modo WebSocket/SDK e não utiliza o servidor HTTP compartilhado.
|
||||
|
||||
> A verbosidade dos logs é controlada por `gateway.log_level` (padrão: `warn`). Valores suportados: `debug`, `info`, `warn`, `error`, `fatal`. Também pode ser definido via `PICOCLAW_LOG_LEVEL`. Veja [Configuração](docs/pt-br/configuration.md#nível-de-log-do-gateway) para detalhes.
|
||||
|
||||
Para instruções detalhadas de configuração de channels, veja [Configuração de Apps de Chat](docs/pt-br/chat-apps.md).
|
||||
|
||||
## 🔧 Ferramentas
|
||||
|
|
|
|||
44
README.vi.md
|
|
@ -18,7 +18,7 @@
|
|||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | **Tiếng Việt** | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [English](README.md)
|
||||
[中文](README.zh.md) | [日本語](README.ja.md) | [Português](README.pt-br.md) | **Tiếng Việt** | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -56,17 +56,21 @@
|
|||
|
||||
## 📢 Tin tức
|
||||
|
||||
2026-03-31 📱 **Hỗ trợ Android!** PicoClaw giờ chạy trên Android! Tải APK tại [picoclaw.io](https://picoclaw.io/download)
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 đã phát hành!** Tái cấu trúc kiến trúc Agent (SubTurn, Hooks, Steering, EventBus), tích hợp WeChat/WeCom, tăng cường bảo mật (.security.yml, lọc dữ liệu nhạy cảm), provider mới (AWS Bedrock, Azure, Xiaomi MiMo) và 35 bản vá lỗi. PicoClaw đã đạt **26K Stars**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 đã phát hành!** Giao diện system tray (Windows & Linux), truy vấn trạng thái sub-agent (`spawn_status`), thử nghiệm Gateway hot-reload, bảo mật Cron, và 2 bản vá bảo mật. PicoClaw đã đạt **25K Stars**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — Bản cập nhật lớn nhất từ trước đến nay!** Hỗ trợ giao thức MCP, 4 Channel mới (Matrix/IRC/WeCom/Discord Proxy), 3 Provider mới (Kimi/Minimax/Avian), pipeline thị giác, bộ nhớ JSONL, định tuyến mô hình.
|
||||
|
||||
2026-02-28 📦 **v0.2.0** phát hành với hỗ trợ Docker Compose và Web UI Launcher.
|
||||
|
||||
2026-02-26 🎉 PicoClaw đạt **20K Stars** chỉ trong 17 ngày! Tự động điều phối Channel và giao diện khả năng đã hoạt động.
|
||||
|
||||
<details>
|
||||
<summary>Tin tức trước đó...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw đạt **20K Stars** chỉ trong 17 ngày! Tự động điều phối Channel và giao diện khả năng đã hoạt động.
|
||||
|
||||
2026-02-16 🎉 PicoClaw vượt 12K Stars trong một tuần! Vai trò người duy trì cộng đồng và [Lộ trình](ROADMAP.md) chính thức ra mắt.
|
||||
|
||||
2026-02-13 🎉 PicoClaw vượt 5000 Stars trong 4 ngày! Lộ trình dự án và nhóm nhà phát triển đang được xây dựng.
|
||||
|
|
@ -254,6 +258,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — Cảnh báo bảo mật khi khởi chạy lần đầu</b></summary>
|
||||
|
||||
macOS có thể chặn `picoclaw-launcher` khi khởi chạy lần đầu vì nó được tải từ internet và chưa được công chứng qua Mac App Store.
|
||||
|
||||
**Bước 1:** Nhấp đúp vào `picoclaw-launcher`. Bạn sẽ thấy cảnh báo bảo mật:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="Cảnh báo macOS Gatekeeper" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" Không Mở Được — Apple không thể xác minh "picoclaw-launcher" không chứa phần mềm độc hại có thể gây hại cho Mac hoặc xâm phạm quyền riêng tư của bạn.*
|
||||
|
||||
**Bước 2:** Mở **Cài đặt Hệ thống** → **Quyền riêng tư & Bảo mật** → cuộn xuống phần **Bảo mật** → nhấp **Vẫn Mở** → xác nhận bằng cách nhấp **Vẫn Mở** trong hộp thoại.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS Quyền riêng tư & Bảo mật — Vẫn Mở" width="600">
|
||||
</p>
|
||||
|
||||
Sau bước này, `picoclaw-launcher` sẽ mở bình thường trong các lần khởi chạy tiếp theo.
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher (Khuyến nghị cho Headless / SSH)
|
||||
|
||||
TUI (Terminal UI) Launcher cung cấp giao diện terminal đầy đủ tính năng để cấu hình và quản lý. Lý tưởng cho máy chủ, Raspberry Pi và các môi trường headless khác.
|
||||
|
|
@ -293,9 +320,9 @@ Sau đó làm theo phần Terminal Launcher bên dưới để hoàn tất cấu
|
|||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**Tùy chọn 2: Cài đặt APK (sắp ra mắt)**
|
||||
**Tùy chọn 2: Cài đặt APK**
|
||||
|
||||
Một APK Android độc lập với WebUI tích hợp đang được phát triển. Hãy đón chờ!
|
||||
Tải APK từ [picoclaw.io](https://picoclaw.io/download/) và cài đặt trực tiếp. Không cần Termux!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher (cho môi trường hạn chế tài nguyên)</b></summary>
|
||||
|
|
@ -367,6 +394,7 @@ PicoClaw hỗ trợ 30+ Provider LLM thông qua cấu hình `model_list`. Sử d
|
|||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Bắt buộc | Mô hình do NVIDIA lưu trữ |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Bắt buộc | Suy luận nhanh |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | Bắt buộc | Nhiều mô hình mở |
|
||||
| [Xiaomi MiMo](https://platform.xiaomimimo.com/) | `mimo/` | Bắt buộc | Mô hình MiMo |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | Không cần | Mô hình cục bộ, tự lưu trữ |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | Không cần | Triển khai cục bộ, tương thích OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | Tùy | Proxy cho 100+ provider |
|
||||
|
|
@ -423,9 +451,7 @@ Trò chuyện với PicoClaw của bạn qua 17+ nền tảng nhắn tin:
|
|||
| **DingTalk** | Trung bình (client credentials) | Stream | [Hướng dẫn](docs/channels/dingtalk/README.vi.md) |
|
||||
| **Feishu / Lark** | Trung bình (App ID + Secret) | WebSocket/SDK | [Hướng dẫn](docs/channels/feishu/README.vi.md) |
|
||||
| **LINE** | Trung bình (credentials + webhook) | Webhook | [Hướng dẫn](docs/channels/line/README.vi.md) |
|
||||
| **WeCom Bot** | Trung bình (webhook URL) | Webhook | [Hướng dẫn](docs/channels/wecom/wecom_bot/README.vi.md) |
|
||||
| **WeCom App** | Trung bình (corp credentials) | Webhook | [Hướng dẫn](docs/channels/wecom/wecom_app/README.vi.md) |
|
||||
| **WeCom AI Bot** | Trung bình (token + AES key) | WebSocket / Webhook | [Hướng dẫn](docs/channels/wecom/wecom_aibot/README.vi.md) |
|
||||
| **WeCom** | Dễ (đăng nhập QR hoặc thủ công) | WebSocket | [Hướng dẫn](docs/channels/wecom/README.md) |
|
||||
| **IRC** | Trung bình (server + nick) | IRC protocol | [Hướng dẫn](docs/vi/chat-apps.md#irc) |
|
||||
| **OneBot** | Trung bình (WebSocket URL) | OneBot v11 | [Hướng dẫn](docs/channels/onebot/README.vi.md) |
|
||||
| **MaixCam** | Dễ (bật) | TCP socket | [Hướng dẫn](docs/channels/maixcam/README.vi.md) |
|
||||
|
|
@ -434,6 +460,8 @@ Trò chuyện với PicoClaw của bạn qua 17+ nền tảng nhắn tin:
|
|||
|
||||
> Tất cả các Channel dựa trên webhook dùng chung một Gateway HTTP server (`gateway.host`:`gateway.port`, mặc định `127.0.0.1:18790`). Feishu sử dụng chế độ WebSocket/SDK và không dùng HTTP server chung.
|
||||
|
||||
> Mức độ chi tiết log được kiểm soát bởi `gateway.log_level` (mặc định: `warn`). Các giá trị được hỗ trợ: `debug`, `info`, `warn`, `error`, `fatal`. Cũng có thể đặt qua `PICOCLAW_LOG_LEVEL`. Xem [Cấu hình](docs/vi/configuration.md#mức-log-của-gateway) để biết thêm chi tiết.
|
||||
|
||||
Để biết hướng dẫn thiết lập Channel chi tiết, xem [Cấu hình Ứng dụng Chat](docs/vi/chat-apps.md).
|
||||
|
||||
## 🔧 Tools
|
||||
|
|
|
|||
44
README.zh.md
|
|
@ -18,7 +18,7 @@
|
|||
<a href="https://discord.gg/V4sAZ9XWpN"><img src="https://img.shields.io/badge/Discord-Community-4c60eb?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||
**中文** | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [English](README.md)
|
||||
**中文** | [日本語](README.ja.md) | [Português](README.pt-br.md) | [Tiếng Việt](README.vi.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Bahasa Indonesia](README.id.md) | [Malay](README.my.md) | [English](README.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -56,17 +56,21 @@
|
|||
|
||||
## 📢 新闻
|
||||
|
||||
2026-03-31 📱 **Android 支持!** PicoClaw 现可在 Android 上运行!APK 下载地址:[picoclaw.io](https://picoclaw.io/download)
|
||||
|
||||
2026-03-25 🚀 **v0.2.4 发布!** Agent 架构全面重构(SubTurn、Hook、Steering、EventBus)、微信/企业微信深度集成、安全体系升级(.security.yml、敏感数据过滤)、新增 Provider(AWS Bedrock、Azure、小米 MiMo),以及 35 项 Bug 修复。PicoClaw 已达 **26K ⭐**!
|
||||
|
||||
2026-03-17 🚀 **v0.2.3 发布!** 系统托盘 UI(Windows & Linux)、子 Agent 状态查询 (`spawn_status`)、实验性 Gateway 热重载、Cron 安全门控,以及 2 项安全修复。PicoClaw 已达 **25K ⭐**!
|
||||
|
||||
2026-03-09 🎉 **v0.2.1 — 史上最大更新!** MCP 协议支持、4 个新频道 (Matrix/IRC/WeCom/Discord Proxy)、3 个新 Provider (Kimi/Minimax/Avian)、视觉管线、JSONL 记忆存储、模型路由。
|
||||
|
||||
2026-02-28 📦 **v0.2.0** 发布,支持 Docker Compose 和 Web UI 启动器。
|
||||
|
||||
2026-02-26 🎉 PicoClaw 仅 17 天突破 **20K Stars**!频道自动编排和能力接口上线。
|
||||
|
||||
<details>
|
||||
<summary>更早的新闻...</summary>
|
||||
|
||||
2026-02-26 🎉 PicoClaw 仅 17 天突破 **20K Stars**!频道自动编排和能力接口上线。
|
||||
|
||||
2026-02-16 🎉 PicoClaw 一周内突破 12K Stars!社区维护者角色和 [路线图](ROADMAP.md) 正式发布。
|
||||
|
||||
2026-02-13 🎉 PicoClaw 4 天内突破 5000 Stars!项目路线图和开发者群组筹建中。
|
||||
|
|
@ -254,6 +258,29 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS — 首次启动安全警告</b></summary>
|
||||
|
||||
macOS 可能会在首次启动时拦截 `picoclaw-launcher`,因为它从互联网下载,未经 Mac App Store 公证。
|
||||
|
||||
**第一步:** 双击 `picoclaw-launcher`,会出现安全警告:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-warning.jpg" alt="macOS Gatekeeper 警告" width="400">
|
||||
</p>
|
||||
|
||||
> *"picoclaw-launcher" 无法打开 — Apple 无法验证 "picoclaw-launcher" 不含可能损害 Mac 或危及隐私的恶意软件。*
|
||||
|
||||
**第二步:** 打开**系统设置** → **隐私与安全性** → 向下滚动找到**安全性**部分 → 点击**仍要打开** → 在弹窗中再次点击**打开**。
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/macos-gatekeeper-allow.jpg" alt="macOS 隐私与安全性 — 仍要打开" width="600">
|
||||
</p>
|
||||
|
||||
完成这一次操作后,后续启动 `picoclaw-launcher` 将不再弹出警告。
|
||||
|
||||
</details>
|
||||
|
||||
### 💻 TUI Launcher(推荐无头环境 / SSH)
|
||||
|
||||
TUI(终端 UI)Launcher 提供功能完整的终端配置与管理界面,适合服务器、树莓派等无显示器环境。
|
||||
|
|
@ -293,9 +320,9 @@ termux-chroot ./picoclaw onboard # chroot 提供标准 Linux 文件系统布
|
|||
|
||||
<img src="assets/termux.jpg" alt="PicoClaw on Termux" width="512">
|
||||
|
||||
**方式二:APK 安装(即将推出)**
|
||||
**方式二:APK 安装**
|
||||
|
||||
内置 WebUI 的独立 Android APK 正在开发中,敬请期待!
|
||||
从 [picoclaw.io](https://picoclaw.io/download/) 下载 APK 并直接安装,无需 Termux!
|
||||
|
||||
<details>
|
||||
<summary><b>Terminal Launcher(适用于资源受限环境)</b></summary>
|
||||
|
|
@ -367,6 +394,7 @@ PicoClaw 通过 `model_list` 配置支持 30+ LLM Provider,使用 `协议/模
|
|||
| [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | 必填 | NVIDIA 托管模型 |
|
||||
| [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | 必填 | 快速推理 |
|
||||
| [Novita AI](https://novita.ai/) | `novita/` | 必填 | 多种开源模型 |
|
||||
| [小米 MiMo](https://platform.xiaomimimo.com/) | `mimo/` | 必填 | MiMo 系列模型 |
|
||||
| [Ollama](https://ollama.com/) | `ollama/` | 无需 | 本地模型,自托管 |
|
||||
| [vLLM](https://docs.vllm.ai/) | `vllm/` | 无需 | 本地部署,兼容 OpenAI |
|
||||
| [LiteLLM](https://docs.litellm.ai/) | `litellm/` | 视情况 | 100+ Provider 代理 |
|
||||
|
|
@ -423,9 +451,7 @@ PicoClaw 通过 `model_list` 配置支持 30+ LLM Provider,使用 `协议/模
|
|||
| **钉钉** | 中等(client credentials) | Stream | [指南](docs/channels/dingtalk/README.zh.md) |
|
||||
| **飞书 / Lark** | 中等(App ID + Secret) | WebSocket/SDK | [指南](docs/channels/feishu/README.zh.md) |
|
||||
| **LINE** | 中等(credentials + webhook) | Webhook | [指南](docs/channels/line/README.zh.md) |
|
||||
| **企业微信机器人** | 中等(webhook URL) | Webhook | [指南](docs/channels/wecom/wecom_bot/README.zh.md) |
|
||||
| **企业微信应用** | 中等(corp credentials) | Webhook | [指南](docs/channels/wecom/wecom_app/README.zh.md) |
|
||||
| **企业微信 AI 机器人** | 中等(token + AES key) | WebSocket / Webhook | [指南](docs/channels/wecom/wecom_aibot/README.zh.md) |
|
||||
| **企业微信** | 简单(扫码登录或手动配置) | WebSocket | [指南](docs/channels/wecom/README.zh.md) |
|
||||
| **IRC** | 中等(server + nick) | IRC 协议 | [指南](docs/zh/chat-apps.md#irc) |
|
||||
| **OneBot** | 中等(WebSocket URL) | OneBot v11 | [指南](docs/channels/onebot/README.zh.md) |
|
||||
| **MaixCam** | 简单(启用即可) | TCP socket | [指南](docs/channels/maixcam/README.zh.md) |
|
||||
|
|
@ -434,6 +460,8 @@ PicoClaw 通过 `model_list` 配置支持 30+ LLM Provider,使用 `协议/模
|
|||
|
||||
> 所有基于 Webhook 的 Channel 共用同一个 Gateway HTTP 服务器(`gateway.host`:`gateway.port`,默认 `127.0.0.1:18790`)。飞书使用 WebSocket/SDK 模式,不使用共享 HTTP 服务器。
|
||||
|
||||
> 日志详细程度通过 `gateway.log_level` 控制(默认:`warn`)。支持的值:`debug`、`info`、`warn`、`error`、`fatal`。也可通过 `PICOCLAW_LOG_LEVEL` 环境变量设置。详见[配置指南](docs/zh/configuration.md#gateway-日志等级)。
|
||||
|
||||
详细 Channel 配置说明请参阅 [聊天应用配置](docs/zh/chat-apps.md)。
|
||||
|
||||
## 🔧 Tools
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 271 KiB |
BIN
assets/macos-gatekeeper-allow.jpg
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
assets/macos-gatekeeper-warning.jpg
Normal file
|
After Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 365 KiB |
BIN
assets/wecom-qr-binding.jpg
Normal file
|
After Width: | Height: | Size: 233 KiB |
|
|
@ -7,9 +7,7 @@ package ui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -17,61 +15,30 @@ import (
|
|||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const pidFileName = "gateway.pid"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
ppid "github.com/sipeed/picoclaw/pkg/pid"
|
||||
)
|
||||
|
||||
type gatewayStatus struct {
|
||||
running bool
|
||||
pid int
|
||||
version string
|
||||
}
|
||||
|
||||
func getPidPath() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
home = "."
|
||||
}
|
||||
return filepath.Join(home, ".picoclaw", pidFileName)
|
||||
}
|
||||
|
||||
func isProcessRunning(pid int) bool {
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid))
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(string(output), strconv.Itoa(pid))
|
||||
} else if runtime.GOOS == "darwin" {
|
||||
cmd := exec.Command("ps", "aux")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(string(output), fmt.Sprintf(" %d ", pid))
|
||||
}
|
||||
// Linux
|
||||
_, err := os.Stat(fmt.Sprintf("/proc/%d", pid))
|
||||
return err == nil
|
||||
func picoHome() string {
|
||||
return config.GetHome()
|
||||
}
|
||||
|
||||
func getGatewayStatus() gatewayStatus {
|
||||
pidPath := getPidPath()
|
||||
data, err := os.ReadFile(pidPath)
|
||||
if err != nil {
|
||||
return gatewayStatus{running: false}
|
||||
}
|
||||
pid, err := strconv.Atoi(strings.TrimSpace(string(data)))
|
||||
if err != nil {
|
||||
return gatewayStatus{running: false}
|
||||
}
|
||||
if !isProcessRunning(pid) {
|
||||
os.Remove(pidPath)
|
||||
data := ppid.ReadPidFileWithCheck(picoHome())
|
||||
if data == nil {
|
||||
return gatewayStatus{running: false}
|
||||
}
|
||||
return gatewayStatus{
|
||||
running: true,
|
||||
pid: pid,
|
||||
pid: data.PID,
|
||||
version: data.Version,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,13 +48,12 @@ func startGateway() error {
|
|||
return fmt.Errorf("gateway is already running (PID: %d)", status.pid)
|
||||
}
|
||||
|
||||
pidPath := getPidPath()
|
||||
var cmd *exec.Cmd
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.Command("cmd", "/C", "start /B picoclaw gateway > NUL 2>&1")
|
||||
} else {
|
||||
cmd = exec.Command("sh", "-c", "nohup picoclaw gateway > /dev/null 2>&1 & echo $! > "+pidPath)
|
||||
cmd = exec.Command("sh", "-c", "nohup picoclaw gateway > /dev/null 2>&1 &")
|
||||
}
|
||||
|
||||
err := cmd.Start()
|
||||
|
|
@ -116,9 +82,8 @@ func startGateway() error {
|
|||
if line == "" {
|
||||
continue
|
||||
}
|
||||
pid, err := strconv.Atoi(line)
|
||||
_, err := strconv.Atoi(line)
|
||||
if err == nil {
|
||||
os.WriteFile(pidPath, []byte(strconv.Itoa(pid)), 0o600)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -141,21 +106,20 @@ func stopGateway() error {
|
|||
if runtime.GOOS == "windows" {
|
||||
err = exec.Command("taskkill", "/F", "/PID", strconv.Itoa(status.pid)).Run()
|
||||
} else {
|
||||
err = exec.Command("kill", "-9", strconv.Itoa(status.pid)).Run()
|
||||
err = exec.Command("kill", strconv.Itoa(status.pid)).Run()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 多次尝试确认进程已停止
|
||||
// Wait for process to stop (ReadPidFileWithCheck cleans up stale pid file)
|
||||
for i := 0; i < 5; i++ {
|
||||
if !isProcessRunning(status.pid) {
|
||||
if !getGatewayStatus().running {
|
||||
break
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
os.Remove(getPidPath())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +181,11 @@ func (a *App) newGatewayPage() tview.Primitive {
|
|||
updateStatus = func() {
|
||||
status := getGatewayStatus()
|
||||
if status.running {
|
||||
statusTV.SetText(fmt.Sprintf("[#39ff14::b]GATEWAY RUNNING[-]\n\nPID: %d", status.pid))
|
||||
versionInfo := ""
|
||||
if status.version != "" {
|
||||
versionInfo = fmt.Sprintf("\nVersion: %s", status.version)
|
||||
}
|
||||
statusTV.SetText(fmt.Sprintf("[#39ff14::b]GATEWAY RUNNING[-]\n\nPID: %d%s", status.pid, versionInfo))
|
||||
buttons.SetItemText(0, " [gray]START[white] ", "")
|
||||
buttons.SetItemText(1, " [red]STOP[white] ", "")
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ func TestApplyWeComAuthResult(t *testing.T) {
|
|||
|
||||
assert.True(t, cfg.Channels.WeCom.Enabled)
|
||||
assert.Equal(t, "bot-1", cfg.Channels.WeCom.BotID)
|
||||
assert.Equal(t, "secret-1", cfg.Channels.WeCom.Secret())
|
||||
assert.Equal(t, "secret-1", cfg.Channels.WeCom.Secret.String())
|
||||
assert.Equal(t, wecomDefaultWebSocketURL, cfg.Channels.WeCom.WebSocketURL)
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ func TestAuthWeComCmdWithScanner(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.True(t, cfg.Channels.WeCom.Enabled)
|
||||
assert.Equal(t, "bot-1", cfg.Channels.WeCom.BotID)
|
||||
assert.Equal(t, "secret-1", cfg.Channels.WeCom.Secret())
|
||||
assert.Equal(t, "secret-1", cfg.Channels.WeCom.Secret.String())
|
||||
assert.Equal(t, wecomDefaultWebSocketURL, cfg.Channels.WeCom.WebSocketURL)
|
||||
assert.Contains(t, output.String(), "WeCom connected.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ func newAddCommand(storePath func() string) *cobra.Command {
|
|||
message string
|
||||
every int64
|
||||
cronExp string
|
||||
deliver bool
|
||||
channel string
|
||||
to string
|
||||
)
|
||||
|
|
@ -37,7 +36,7 @@ func newAddCommand(storePath func() string) *cobra.Command {
|
|||
}
|
||||
|
||||
cs := cron.NewCronService(storePath(), nil)
|
||||
job, err := cs.AddJob(name, schedule, message, deliver, channel, to)
|
||||
job, err := cs.AddJob(name, schedule, message, channel, to)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding job: %w", err)
|
||||
}
|
||||
|
|
@ -52,7 +51,6 @@ func newAddCommand(storePath func() string) *cobra.Command {
|
|||
cmd.Flags().StringVarP(&message, "message", "m", "", "Message for agent")
|
||||
cmd.Flags().Int64VarP(&every, "every", "e", 0, "Run every N seconds")
|
||||
cmd.Flags().StringVarP(&cronExp, "cron", "c", "", "Cron expression (e.g. '0 9 * * *')")
|
||||
cmd.Flags().BoolVarP(&deliver, "deliver", "d", false, "Deliver response to channel")
|
||||
cmd.Flags().StringVar(&to, "to", "", "Recipient for delivery")
|
||||
cmd.Flags().StringVar(&channel, "channel", "", "Channel for delivery")
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ func TestNewAddSubcommand(t *testing.T) {
|
|||
|
||||
assert.NotNil(t, cmd.Flags().Lookup("every"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("cron"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("deliver"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("to"))
|
||||
assert.NotNil(t, cmd.Flags().Lookup("channel"))
|
||||
|
||||
|
|
|
|||
|
|
@ -14,11 +14,7 @@ const Logo = pkg.Logo
|
|||
// GetPicoclawHome returns the picoclaw home directory.
|
||||
// Priority: $PICOCLAW_HOME > ~/.picoclaw
|
||||
func GetPicoclawHome() string {
|
||||
if home := os.Getenv(config.EnvHome); home != "" {
|
||||
return home
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, pkg.DefaultPicoClawHome)
|
||||
return config.GetHome()
|
||||
}
|
||||
|
||||
func GetConfigPath() string {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ func listAvailableModels(cfg *config.Config) {
|
|||
if model.ModelName == defaultModel {
|
||||
marker = "> "
|
||||
}
|
||||
if model.APIKey() == "" {
|
||||
if !model.Enabled {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("%s- %s (%s)\n", marker, model.ModelName, model.Model)
|
||||
|
|
@ -92,7 +92,7 @@ func setDefaultModel(configPath string, cfg *config.Config, modelName string) er
|
|||
// Validate that the model exists in model_list
|
||||
modelFound := false
|
||||
for _, model := range cfg.ModelList {
|
||||
if model.APIKey() != "" && model.ModelName == modelName {
|
||||
if model.Enabled && model.ModelName == modelName {
|
||||
modelFound = true
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,24 +58,27 @@ func TestNewModelCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShowCurrentModel_WithDefaultModel(t *testing.T) {
|
||||
cfg := (&config.Config{
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
ModelName: "gpt-4",
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{ModelName: "gpt-4", Model: "openai/gpt-4"},
|
||||
{ModelName: "claude-3", Model: "anthropic/claude-3"},
|
||||
{
|
||||
ModelName: "gpt-4",
|
||||
Model: "openai/gpt-4",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "claude-3",
|
||||
Model: "anthropic/claude-3",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
|
||||
"gpt-4": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
"claude-3": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
output := captureStdout(func() {
|
||||
showCurrentModel(cfg)
|
||||
|
|
@ -88,20 +91,21 @@ func TestShowCurrentModel_WithDefaultModel(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShowCurrentModel_NoDefaultModel(t *testing.T) {
|
||||
cfg := (&config.Config{
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
ModelName: "",
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{ModelName: "gpt-4", Model: "openai/gpt-4"},
|
||||
{
|
||||
ModelName: "gpt-4",
|
||||
Model: "openai/gpt-4",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
|
||||
"gpt-4": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
output := captureStdout(func() {
|
||||
showCurrentModel(cfg)
|
||||
|
|
@ -124,25 +128,28 @@ func TestListAvailableModels_Empty(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestListAvailableModels_WithModels(t *testing.T) {
|
||||
cfg := (&config.Config{
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
ModelName: "gpt-4",
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{ModelName: "gpt-4", Model: "openai/gpt-4"},
|
||||
{ModelName: "claude-3", Model: "anthropic/claude-3"},
|
||||
{
|
||||
ModelName: "gpt-4",
|
||||
Model: "openai/gpt-4",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "claude-3",
|
||||
Model: "anthropic/claude-3",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{ModelName: "no-key-model", Model: "openai/test"},
|
||||
},
|
||||
}).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
|
||||
"gpt-4": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
"claude-3": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
output := captureStdout(func() {
|
||||
listAvailableModels(cfg)
|
||||
|
|
@ -157,24 +164,27 @@ func TestListAvailableModels_WithModels(t *testing.T) {
|
|||
func TestSetDefaultModel_ValidModel(t *testing.T) {
|
||||
initTest(t)
|
||||
|
||||
cfg := (&config.Config{
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
ModelName: "old-model",
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{ModelName: "new-model", Model: "openai/new-model"},
|
||||
{ModelName: "old-model", Model: "openai/old-model"},
|
||||
{
|
||||
ModelName: "new-model",
|
||||
Model: "openai/new-model",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "old-model",
|
||||
Model: "openai/old-model",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
|
||||
"new-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
"old-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
output := captureStdout(func() {
|
||||
err := setDefaultModel(configPath, cfg, "new-model")
|
||||
|
|
@ -192,20 +202,21 @@ func TestSetDefaultModel_ValidModel(t *testing.T) {
|
|||
func TestSetDefaultModel_InvalidModel(t *testing.T) {
|
||||
initTest(t)
|
||||
|
||||
cfg := (&config.Config{
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
ModelName: "existing-model",
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{ModelName: "existing-model", Model: "openai/existing"},
|
||||
{
|
||||
ModelName: "existing-model",
|
||||
Model: "openai/existing",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
|
||||
"existing-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
assert.Error(t, setDefaultModel(configPath, cfg, "nonexistent-model"))
|
||||
}
|
||||
|
|
@ -213,24 +224,22 @@ func TestSetDefaultModel_InvalidModel(t *testing.T) {
|
|||
func TestSetDefaultModel_ModelWithoutAPIKey(t *testing.T) {
|
||||
initTest(t)
|
||||
|
||||
cfg := (&config.Config{
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
ModelName: "existing-model",
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{ModelName: "existing-model", Model: "openai/existing"},
|
||||
{
|
||||
ModelName: "existing-model",
|
||||
Model: "openai/existing",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{ModelName: "no-key-model", Model: "openai/nokey"},
|
||||
},
|
||||
}).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
|
||||
"existing-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
"no-key-model": {
|
||||
APIKeys: []string{""},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
assert.Error(t, setDefaultModel(configPath, cfg, "no-key-model"))
|
||||
}
|
||||
|
|
@ -239,20 +248,21 @@ func TestSetDefaultModel_SaveConfigError(t *testing.T) {
|
|||
// Use an invalid path to trigger save error
|
||||
invalidPath := "/nonexistent/directory/config.json"
|
||||
|
||||
cfg := (&config.Config{
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
ModelName: "old-model",
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{ModelName: "new-model", Model: "openai/new-model"},
|
||||
{
|
||||
ModelName: "new-model",
|
||||
Model: "openai/new-model",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
|
||||
"new-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
err := setDefaultModel(invalidPath, cfg, "new-model")
|
||||
|
||||
|
|
@ -284,20 +294,21 @@ func TestModelCommandExecution_Show(t *testing.T) {
|
|||
initTest(t)
|
||||
|
||||
// Create a test config
|
||||
cfg := (&config.Config{
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
ModelName: "test-model",
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{ModelName: "test-model", Model: "openai/test"},
|
||||
{
|
||||
ModelName: "test-model",
|
||||
Model: "openai/test",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
|
||||
"test-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
err := config.SaveConfig(configPath, cfg)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -315,25 +326,27 @@ func TestModelCommandExecution_Show(t *testing.T) {
|
|||
func TestModelCommandExecution_Set(t *testing.T) {
|
||||
initTest(t)
|
||||
|
||||
sec := &config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
|
||||
"old-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
"new-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
}}
|
||||
cfg := (&config.Config{
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
ModelName: "old-model",
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{ModelName: "old-model", Model: "openai/old"},
|
||||
{ModelName: "new-model", Model: "openai/new"},
|
||||
{
|
||||
ModelName: "old-model",
|
||||
Model: "openai/old",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "new-model",
|
||||
Model: "openai/new",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).WithSecurity(sec)
|
||||
}
|
||||
|
||||
err := config.SaveConfig(configPath, cfg)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -357,28 +370,33 @@ func TestModelCommandExecution_TooManyArgs(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestListAvailableModels_MarkerLogic(t *testing.T) {
|
||||
cfg := (&config.Config{
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
ModelName: "middle-model",
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{ModelName: "first-model", Model: "openai/first"},
|
||||
{ModelName: "middle-model", Model: "openai/middle"},
|
||||
{ModelName: "last-model", Model: "openai/last"},
|
||||
{
|
||||
ModelName: "first-model",
|
||||
Model: "openai/first",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "middle-model",
|
||||
Model: "openai/middle",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ModelName: "last-model",
|
||||
Model: "openai/last",
|
||||
APIKeys: config.SecureStrings{config.NewSecureString("test")},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
}).WithSecurity(&config.SecurityConfig{ModelList: map[string]config.ModelSecurityEntry{
|
||||
"first-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
"middle-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
"last-model": {
|
||||
APIKeys: []string{"test"},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
output := captureStdout(func() {
|
||||
listAvailableModels(cfg)
|
||||
|
|
|
|||
|
|
@ -97,7 +97,11 @@ func onboard(encrypt bool) {
|
|||
fmt.Println("")
|
||||
fmt.Println(" See README.md for 17+ supported providers.")
|
||||
fmt.Println("")
|
||||
fmt.Println(" 3. Chat: picoclaw agent -m \"Hello!\"")
|
||||
if encrypt {
|
||||
fmt.Println(" 3. Chat: picoclaw agent -m \"Hello!\"")
|
||||
} else {
|
||||
fmt.Println(" 2. Chat: picoclaw agent -m \"Hello!\"")
|
||||
}
|
||||
}
|
||||
|
||||
// promptPassphrase reads the encryption passphrase twice from the terminal
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func NewSkillsCommand() *cobra.Command {
|
|||
d.workspace = cfg.WorkspacePath()
|
||||
installer, err := skills.NewSkillInstaller(
|
||||
d.workspace,
|
||||
cfg.Tools.Skills.Github.Token(),
|
||||
cfg.Tools.Skills.Github.Token.String(),
|
||||
cfg.Tools.Skills.Github.Proxy,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func skillsInstallFromRegistry(cfg *config.Config, registryName, slug string) er
|
|||
ClawHub: skills.ClawHubConfig{
|
||||
Enabled: clawHubConfig.Enabled,
|
||||
BaseURL: clawHubConfig.BaseURL,
|
||||
AuthToken: clawHubConfig.AuthToken(),
|
||||
AuthToken: clawHubConfig.AuthToken.String(),
|
||||
SearchPath: clawHubConfig.SearchPath,
|
||||
SkillsPath: clawHubConfig.SkillsPath,
|
||||
DownloadPath: clawHubConfig.DownloadPath,
|
||||
|
|
@ -243,7 +243,7 @@ func skillsSearchCmd(query string) {
|
|||
ClawHub: skills.ClawHubConfig{
|
||||
Enabled: clawHubConfig.Enabled,
|
||||
BaseURL: clawHubConfig.BaseURL,
|
||||
AuthToken: clawHubConfig.AuthToken(),
|
||||
AuthToken: clawHubConfig.AuthToken.String(),
|
||||
SearchPath: clawHubConfig.SearchPath,
|
||||
SkillsPath: clawHubConfig.SkillsPath,
|
||||
DownloadPath: clawHubConfig.DownloadPath,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@
|
|||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-your-deepseek-key"
|
||||
},
|
||||
{
|
||||
"model_name": "lmstudio-local",
|
||||
"model": "lmstudio/openai/gpt-oss-20b"
|
||||
},
|
||||
{
|
||||
"model_name": "longcat",
|
||||
"model": "longcat/LongCat-Flash-Thinking",
|
||||
|
|
@ -414,6 +418,9 @@
|
|||
"read_file": {
|
||||
"enabled": true
|
||||
},
|
||||
"send_tts": {
|
||||
"enabled": false
|
||||
},
|
||||
"spawn": {
|
||||
"enabled": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ services:
|
|||
picoclaw-gateway:
|
||||
image: docker.io/sipeed/picoclaw:latest
|
||||
container_name: picoclaw-gateway
|
||||
restart: on-failure
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- gateway
|
||||
# Uncomment to access host network; leave commented unless needed.
|
||||
|
|
@ -40,7 +40,7 @@ services:
|
|||
picoclaw-launcher:
|
||||
image: docker.io/sipeed/picoclaw:launcher
|
||||
container_name: picoclaw-launcher
|
||||
restart: on-failure
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- launcher
|
||||
environment:
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ Le canal Telegram utilise le long polling via l'API Bot Telegram pour une commun
|
|||
"enabled": true,
|
||||
"token": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
|
||||
"allow_from": ["123456789"],
|
||||
"proxy": ""
|
||||
"proxy": "",
|
||||
"use_markdown_v2": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Champ | Type | Requis | Description |
|
||||
| ---------- | ------ | ------ | ------------------------------------------------------------------------ |
|
||||
| enabled | bool | Oui | Activer ou non le canal Telegram |
|
||||
| token | string | Oui | Token de l'API Bot Telegram |
|
||||
| allow_from | array | Non | Liste blanche d'identifiants utilisateur ; vide signifie tous les utilisateurs |
|
||||
| proxy | string | Non | URL du proxy pour se connecter à l'API Telegram (ex. http://127.0.0.1:7890) |
|
||||
| Champ | Type | Requis | Description |
|
||||
| --------------- | ------ | ------ | ------------------------------------------------------------------------ |
|
||||
| enabled | bool | Oui | Activer ou non le canal Telegram |
|
||||
| token | string | Oui | Token de l'API Bot Telegram |
|
||||
| allow_from | array | Non | Liste blanche d'identifiants utilisateur ; vide signifie tous les utilisateurs |
|
||||
| proxy | string | Non | URL du proxy pour se connecter à l'API Telegram (ex. http://127.0.0.1:7890) |
|
||||
| use_markdown_v2 | bool | Non | Activer le formatage Telegram MarkdownV2 |
|
||||
|
||||
## Configuration initiale
|
||||
|
||||
|
|
@ -33,3 +35,20 @@ Le canal Telegram utilise le long polling via l'API Bot Telegram pour une commun
|
|||
3. Obtenir le Token de l'API HTTP
|
||||
4. Renseigner le Token dans le fichier de configuration
|
||||
5. (Optionnel) Configurer `allow_from` pour restreindre les identifiants utilisateur autorisés à interagir (les IDs peuvent être obtenus via `@userinfobot`)
|
||||
|
||||
## Formatage avancées
|
||||
|
||||
Vous pouvez définir `use_markdown_v2: true` pour activer les options de formatage améliorées. Cela permet au bot d'utiliser toutes les fonctionnalités de Telegram MarkdownV2, y compris les styles imbriqués, les spoilers et les blocs de largeur fixe personnalisés.
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"use_markdown_v2": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ Telegram チャンネルは、Telegram Bot API を使用したロングポーリ
|
|||
"enabled": true,
|
||||
"token": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
|
||||
"allow_from": ["123456789"],
|
||||
"proxy": ""
|
||||
"proxy": "",
|
||||
"use_markdown_v2": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 型 | 必須 | 説明 |
|
||||
| ---------- | ------ | ---- | ----------------------------------------------------------------- |
|
||||
| enabled | bool | はい | Telegram チャンネルを有効にするかどうか |
|
||||
| token | string | はい | Telegram Bot API トークン |
|
||||
| allow_from | array | いいえ | 許可するユーザーIDのリスト。空の場合はすべてのユーザーを許可 |
|
||||
| proxy | string | いいえ | Telegram API への接続に使用するプロキシ URL (例: http://127.0.0.1:7890) |
|
||||
| フィールド | 型 | 必須 | 説明 |
|
||||
| --------------- | ------ | ---- | ----------------------------------------------------------------- |
|
||||
| enabled | bool | はい | Telegram チャンネルを有効にするかどうか |
|
||||
| token | string | はい | Telegram Bot API トークン |
|
||||
| allow_from | array | いいえ | 許可するユーザーIDのリスト。空の場合はすべてのユーザーを許可 |
|
||||
| proxy | string | いいえ | Telegram API への接続に使用するプロキシ URL (例: http://127.0.0.1:7890) |
|
||||
| use_markdown_v2 | bool | いいえ | Telegram MarkdownV2 フォーマットを有効にする |
|
||||
|
||||
## セットアップ手順
|
||||
|
||||
|
|
@ -33,3 +35,20 @@ Telegram チャンネルは、Telegram Bot API を使用したロングポーリ
|
|||
3. HTTP API トークンを取得する
|
||||
4. 設定ファイルにトークンを入力する
|
||||
5. (任意) `allow_from` を設定して、対話を許可するユーザー ID を制限する(ID は `@userinfobot` で取得可能)
|
||||
|
||||
## 高度なフォーマット
|
||||
|
||||
`use_markdown_v2: true` を設定することで、增强されたフォーマットオプションを有効にできます。これにより、ボットは Telegram MarkdownV2 の全機能(ネストされたスタイル、スポイラー、カスタム固定幅ブロックなど)を利用できます。
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"use_markdown_v2": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ The Telegram channel uses long polling via the Telegram Bot API for bot-based co
|
|||
"enabled": true,
|
||||
"token": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
|
||||
"allow_from": ["123456789"],
|
||||
"proxy": ""
|
||||
"proxy": "",
|
||||
"use_markdown_v2": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ---------- | ------ | -------- | ------------------------------------------------------------------ |
|
||||
| enabled | bool | Yes | Whether to enable the Telegram channel |
|
||||
| token | string | Yes | Telegram Bot API Token |
|
||||
| allow_from | array | No | Allowlist of user IDs; empty means all users are allowed |
|
||||
| proxy | string | No | Proxy URL for connecting to the Telegram API (e.g. http://127.0.0.1:7890) |
|
||||
| Field | Type | Required | Description |
|
||||
| ---------------- | ------ | -------- | ------------------------------------------------------------------ |
|
||||
| enabled | bool | Yes | Whether to enable the Telegram channel |
|
||||
| token | string | Yes | Telegram Bot API Token |
|
||||
| allow_from | array | No | Allowlist of user IDs; empty means all users are allowed |
|
||||
| proxy | string | No | Proxy URL for connecting to the Telegram API (e.g. http://127.0.0.1:7890) |
|
||||
| use_markdown_v2 | bool | No | Enable Telegram MarkdownV2 formatting |
|
||||
|
||||
## Setup
|
||||
|
||||
|
|
@ -53,3 +55,20 @@ Examples:
|
|||
/use git
|
||||
explain how to squash the last 3 commits
|
||||
```
|
||||
|
||||
## Advanced Formatting
|
||||
|
||||
You can set `use_markdown_v2: true` to enable enhanced formatting options. This allows the bot to utilize the full range of Telegram MarkdownV2 features, including nested styles, spoilers, and custom fixed-width blocks.
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"use_markdown_v2": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ O canal Telegram utiliza long polling via a API de Bot do Telegram para comunica
|
|||
"enabled": true,
|
||||
"token": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
|
||||
"allow_from": ["123456789"],
|
||||
"proxy": ""
|
||||
"proxy": "",
|
||||
"use_markdown_v2": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
| ---------- | ------ | ----------- | -------------------------------------------------------------------------- |
|
||||
| enabled | bool | Sim | Se o canal Telegram deve ser habilitado |
|
||||
| token | string | Sim | Token da API de Bot do Telegram |
|
||||
| allow_from | array | Não | Lista de IDs de usuários permitidos; vazio significa todos os usuários |
|
||||
| proxy | string | Não | URL do proxy para conexão com a API do Telegram (ex. http://127.0.0.1:7890) |
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
| --------------- | ------ | ----------- | -------------------------------------------------------------------------- |
|
||||
| enabled | bool | Sim | Se o canal Telegram deve ser habilitado |
|
||||
| token | string | Sim | Token da API de Bot do Telegram |
|
||||
| allow_from | array | Não | Lista de IDs de usuários permitidos; vazio significa todos os usuários |
|
||||
| proxy | string | Não | URL do proxy para conexão com a API do Telegram (ex. http://127.0.0.1:7890) |
|
||||
| use_markdown_v2 | bool | Não | Habilitar formatação Telegram MarkdownV2 |
|
||||
|
||||
## Configuração inicial
|
||||
|
||||
|
|
@ -33,3 +35,20 @@ O canal Telegram utiliza long polling via a API de Bot do Telegram para comunica
|
|||
3. Obtenha o Token da API HTTP
|
||||
4. Preencha o Token no arquivo de configuração
|
||||
5. (Opcional) Configure `allow_from` para restringir quais IDs de usuário podem interagir (os IDs podem ser obtidos via `@userinfobot`)
|
||||
|
||||
## Formatação Avançada
|
||||
|
||||
Você pode definir `use_markdown_v2: true` para habilitar opções de formatação aprimoradas. Isso permite que o bot utilize todos os recursos do Telegram MarkdownV2, incluindo estilos aninhados, spoilers e blocos de largura fixa personalizados.
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"use_markdown_v2": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ Kênh Telegram sử dụng long polling qua Telegram Bot API để giao tiếp d
|
|||
"enabled": true,
|
||||
"token": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
|
||||
"allow_from": ["123456789"],
|
||||
"proxy": ""
|
||||
"proxy": "",
|
||||
"use_markdown_v2": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Trường | Kiểu | Bắt buộc | Mô tả |
|
||||
| ---------- | ------ | -------- | ------------------------------------------------------------------------ |
|
||||
| enabled | bool | Có | Có bật kênh Telegram hay không |
|
||||
| token | string | Có | Token API Bot Telegram |
|
||||
| allow_from | array | Không | Danh sách trắng ID người dùng; để trống nghĩa là cho phép tất cả |
|
||||
| proxy | string | Không | URL proxy để kết nối với Telegram API (ví dụ: http://127.0.0.1:7890) |
|
||||
| Trường | Kiểu | Bắt buộc | Mô tả |
|
||||
| -------------- | ------ | -------- | ------------------------------------------------------------------------ |
|
||||
| enabled | bool | Có | Có bật kênh Telegram hay không |
|
||||
| token | string | Có | Token API Bot Telegram |
|
||||
| allow_from | array | Không | Danh sách trắng ID người dùng; để trống nghĩa là cho phép tất cả |
|
||||
| proxy | string | Không | URL proxy để kết nối với Telegram API (ví dụ: http://127.0.0.1:7890) |
|
||||
| use_markdown_v2 | bool | Không | Bật định dạng Telegram MarkdownV2 |
|
||||
|
||||
## Hướng dẫn thiết lập
|
||||
|
||||
|
|
@ -33,3 +35,20 @@ Kênh Telegram sử dụng long polling qua Telegram Bot API để giao tiếp d
|
|||
3. Lấy Token API HTTP
|
||||
4. Điền Token vào file cấu hình
|
||||
5. (Tùy chọn) Cấu hình `allow_from` để giới hạn ID người dùng được phép tương tác (có thể lấy ID qua `@userinfobot`)
|
||||
|
||||
## Định dạng nâng cao
|
||||
|
||||
Bạn có thể đặt `use_markdown_v2: true` để bật các tùy chọn định dạng nâng cao. Điều này cho phép bot sử dụng toàn bộ các tính năng của Telegram MarkdownV2, bao gồm các kiểu lồng nhau, spoiler và các khối chiều rộng cố định tùy chỉnh.
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"use_markdown_v2": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -13,18 +13,20 @@ Telegram Channel 通过 Telegram 机器人 API 使用长轮询实现基于机器
|
|||
"enabled": true,
|
||||
"token": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
|
||||
"allow_from": ["123456789"],
|
||||
"proxy": ""
|
||||
"proxy": "",
|
||||
"use_markdown_v2": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 描述 |
|
||||
| ---------- | ------ | ---- | --------------------------------------------------------- |
|
||||
| enabled | bool | 是 | 是否启用 Telegram 频道 |
|
||||
| token | string | 是 | Telegram 机器人 API Token |
|
||||
| allow_from | array | 否 | 用户ID白名单,空表示允许所有用户 |
|
||||
| proxy | string | 否 | 连接 Telegram API 的代理 URL (例如 http://127.0.0.1:7890) |
|
||||
| 字段 | 类型 | 必填 | 描述 |
|
||||
| ---------------- | ------ | ---- | --------------------------------------------------------- |
|
||||
| enabled | bool | 是 | 是否启用 Telegram 频道 |
|
||||
| token | string | 是 | Telegram 机器人 API Token |
|
||||
| allow_from | array | 否 | 用户ID白名单,空表示允许所有用户 |
|
||||
| proxy | string | 否 | 连接 Telegram API 的代理 URL (例如 http://127.0.0.1:7890) |
|
||||
| use_markdown_v2 | bool | 否 | 启用 Telegram MarkdownV2 格式化 |
|
||||
|
||||
## 设置流程
|
||||
|
||||
|
|
@ -50,6 +52,23 @@ Telegram 会在启动时自动注册 PicoClaw 的顶级 Bot 命令,包括 `/st
|
|||
```text
|
||||
/list skills
|
||||
/use git explain how to squash the last 3 commits
|
||||
/use italiapersonalfinance
|
||||
dammi le ultime news
|
||||
/use git
|
||||
explain how to squash the last 3 commits
|
||||
```
|
||||
|
||||
## 高级格式化
|
||||
|
||||
您可以设置 `use_markdown_v2: true` 来启用增强的格式化选项。这允许机器人使用 Telegram MarkdownV2 的全部功能,包括嵌套样式、剧透和自定义等宽代码块。
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"use_markdown_v2": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
148
docs/channels/wecom/README.fr.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
> Retour au [README](../../../README.fr.md)
|
||||
|
||||
# WeCom
|
||||
|
||||
PicoClaw expose WeCom en tant que canal unique `channels.wecom`, basé sur l'API WebSocket officielle WeCom AI Bot.
|
||||
Ce canal remplace l'ancienne séparation `wecom`, `wecom_app` et `wecom_aibot` par un modèle de configuration unifié.
|
||||
|
||||
> Aucune URL de callback webhook publique n'est requise. PicoClaw établit une connexion WebSocket sortante vers WeCom.
|
||||
|
||||
## Fonctionnalités prises en charge
|
||||
|
||||
- Chat privé et chat de groupe
|
||||
- Réponses en streaming côté canal via le protocole WeCom AI Bot
|
||||
- Messages entrants : texte, voix, image, fichier, vidéo et messages mixtes
|
||||
- Réponses sortantes : texte et médias (`image`, `file`, `voice`, `video`)
|
||||
- Onboarding par QR code via l'interface Web ou le CLI
|
||||
- Liste blanche partagée et routage `reasoning_channel_id`
|
||||
|
||||
---
|
||||
|
||||
## Démarrage rapide
|
||||
|
||||
### Option 1 : Liaison QR via l'interface Web (recommandé)
|
||||
|
||||
Ouvrez l'interface Web, accédez à **Channels → WeCom** et cliquez sur le bouton de liaison QR. Scannez le QR code avec WeCom et confirmez dans l'application — les identifiants sont enregistrés automatiquement.
|
||||
|
||||
<p align="center">
|
||||
<img src="../../../assets/wecom-qr-binding.jpg" alt="Liaison QR WeCom dans l'interface Web" width="600">
|
||||
</p>
|
||||
|
||||
### Option 2 : Connexion QR via le CLI
|
||||
|
||||
Exécutez :
|
||||
|
||||
```bash
|
||||
picoclaw auth wecom
|
||||
```
|
||||
|
||||
La commande :
|
||||
1. Demande un QR code à WeCom et l'affiche dans le terminal
|
||||
2. Affiche également un **lien QR code** que vous pouvez ouvrir dans un navigateur si le QR du terminal est difficile à scanner
|
||||
3. Attend la confirmation — après le scan, vous devez également **confirmer la connexion dans l'application WeCom**
|
||||
4. En cas de succès, écrit `bot_id` et `secret` dans `channels.wecom` et sauvegarde la configuration
|
||||
|
||||
Le délai d'expiration par défaut est de **5 minutes**. Utilisez `--timeout` pour l'étendre :
|
||||
|
||||
```bash
|
||||
picoclaw auth wecom --timeout 10m
|
||||
```
|
||||
|
||||
> ⚠️ Scanner le QR code ne suffit pas — vous devez également appuyer sur **Confirmer** dans l'application WeCom, sinon la commande expirera.
|
||||
|
||||
### Option 3 : Configuration manuelle
|
||||
|
||||
Si vous disposez déjà d'un `bot_id` et d'un `secret` depuis la plateforme WeCom AI Bot, configurez directement :
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"bot_id": "YOUR_BOT_ID",
|
||||
"secret": "YOUR_SECRET",
|
||||
"websocket_url": "wss://openws.work.weixin.qq.com",
|
||||
"send_thinking_message": true,
|
||||
"allow_from": [],
|
||||
"reasoning_channel_id": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
| Champ | Type | Défaut | Description |
|
||||
| ----- | ---- | ------ | ----------- |
|
||||
| `enabled` | bool | `false` | Activer le canal WeCom. |
|
||||
| `bot_id` | string | — | Identifiant WeCom AI Bot. Requis lorsque le canal est activé. |
|
||||
| `secret` | string | — | Secret WeCom AI Bot. Stocké chiffré dans `.security.yml`. Requis lorsque le canal est activé. |
|
||||
| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | Point de terminaison WebSocket WeCom. |
|
||||
| `send_thinking_message` | bool | `true` | Envoyer un message `Processing...` avant le début de la réponse en streaming. |
|
||||
| `allow_from` | array | `[]` | Liste blanche des expéditeurs. Vide signifie autoriser tous les expéditeurs. |
|
||||
| `reasoning_channel_id` | string | `""` | ID de chat optionnel pour router la sortie de raisonnement vers une conversation séparée. |
|
||||
|
||||
### Variables d'environnement
|
||||
|
||||
Tous les champs peuvent être remplacés par des variables d'environnement avec le préfixe `PICOCLAW_CHANNELS_WECOM_` :
|
||||
|
||||
| Variable d'environnement | Champ correspondant |
|
||||
| ------------------------ | ------------------- |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` |
|
||||
|
||||
---
|
||||
|
||||
## Comportement à l'exécution
|
||||
|
||||
- PicoClaw maintient un tour WeCom actif pour que les réponses en streaming puissent continuer sur le même flux lorsque c'est possible.
|
||||
- Les réponses en streaming ont une durée maximale de **5,5 minutes** et un intervalle d'envoi minimum de **500 ms**.
|
||||
- Si le streaming n'est plus disponible, les réponses basculent vers la livraison par push actif.
|
||||
- Les associations de routes de chat expirent après **30 minutes** d'inactivité.
|
||||
- Les médias entrants sont téléchargés dans le stockage média local avant d'être transmis à l'agent.
|
||||
- Les médias sortants sont uploadés vers WeCom en tant que fichier temporaire, puis envoyés comme message média.
|
||||
- Les messages en double sont détectés et supprimés (tampon circulaire des 1000 derniers identifiants de messages).
|
||||
|
||||
---
|
||||
|
||||
## Migration depuis l'ancienne configuration WeCom
|
||||
|
||||
| Configuration précédente | Migration |
|
||||
| ------------------------ | --------- |
|
||||
| `channels.wecom` (bot webhook) | Remplacer par `channels.wecom` avec `bot_id` + `secret`. |
|
||||
| `channels.wecom_app` | Supprimer. Utiliser `channels.wecom` à la place. |
|
||||
| `channels.wecom_aibot` | Déplacer `bot_id` et `secret` vers `channels.wecom`. |
|
||||
| `token`, `encoding_aes_key`, `webhook_url`, `webhook_path` | Plus utilisés. Supprimer de la configuration. |
|
||||
| `corp_id`, `corp_secret`, `agent_id` | Plus utilisés. Supprimer de la configuration. |
|
||||
| `welcome_message`, `processing_message`, `max_steps` | Ne font plus partie de la configuration du canal WeCom. |
|
||||
|
||||
---
|
||||
|
||||
## Dépannage
|
||||
|
||||
### La liaison QR expire
|
||||
|
||||
- Après avoir scanné le QR code, vous devez également **confirmer la connexion dans l'application WeCom**. Le scan seul ne suffit pas.
|
||||
- Relancez avec un `--timeout` plus long : `picoclaw auth wecom --timeout 10m`
|
||||
- Si le QR code dans le terminal est difficile à scanner, utilisez le **lien QR code** affiché en dessous pour l'ouvrir dans un navigateur.
|
||||
|
||||
### QR code expiré
|
||||
|
||||
- Le QR code a une durée de validité limitée. Relancez `picoclaw auth wecom` pour en obtenir un nouveau.
|
||||
|
||||
### Échec de la connexion WebSocket
|
||||
|
||||
- Vérifiez que `bot_id` et `secret` sont corrects.
|
||||
- Confirmez que l'hôte peut atteindre `wss://openws.work.weixin.qq.com` (WebSocket sortant, aucun port entrant nécessaire).
|
||||
|
||||
### Les réponses n'arrivent pas
|
||||
|
||||
- Vérifiez si `allow_from` bloque l'expéditeur.
|
||||
- Vérifiez que `channels.wecom.bot_id` et `channels.wecom.secret` sont définis et non vides.
|
||||
148
docs/channels/wecom/README.ja.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
> [README](../../../README.ja.md) に戻る
|
||||
|
||||
# WeCom
|
||||
|
||||
PicoClaw は WeCom を公式 WeCom AI Bot WebSocket API に基づく単一の `channels.wecom` チャンネルとして公開します。
|
||||
従来の `wecom`、`wecom_app`、`wecom_aibot` の分割を統一された設定モデルに置き換えました。
|
||||
|
||||
> パブリックな Webhook コールバック URL は不要です。PicoClaw は WeCom へのアウトバウンド WebSocket 接続を確立します。
|
||||
|
||||
## サポートされる機能
|
||||
|
||||
- ダイレクトチャットとグループチャット
|
||||
- WeCom AI Bot プロトコルによるチャンネル側ストリーミング返信
|
||||
- テキスト、音声、画像、ファイル、動画、ミックスメッセージの受信
|
||||
- テキストおよびメディア返信の送信(`image`、`file`、`voice`、`video`)
|
||||
- Web UI または CLI による QR コードオンボーディング
|
||||
- 共有許可リストと `reasoning_channel_id` ルーティング
|
||||
|
||||
---
|
||||
|
||||
## クイックスタート
|
||||
|
||||
### オプション 1:Web UI QR バインディング(推奨)
|
||||
|
||||
Web UI を開き、**Channels → WeCom** に移動して、QR バインディングボタンをクリックします。WeCom で QR コードをスキャンし、アプリ内で確認すると、認証情報が自動的に保存されます。
|
||||
|
||||
<p align="center">
|
||||
<img src="../../../assets/wecom-qr-binding.jpg" alt="Web UI での WeCom QR バインディング" width="600">
|
||||
</p>
|
||||
|
||||
### オプション 2:CLI QR ログイン
|
||||
|
||||
実行:
|
||||
|
||||
```bash
|
||||
picoclaw auth wecom
|
||||
```
|
||||
|
||||
コマンドの動作:
|
||||
1. WeCom に QR コードをリクエストし、ターミナルに表示します
|
||||
2. ターミナルの QR コードがスキャンしにくい場合に備え、ブラウザで開ける **QR コードリンク** も表示します
|
||||
3. 確認をポーリングします — スキャン後、**WeCom アプリ内でログインを確認** する必要があります
|
||||
4. 成功すると、`bot_id` と `secret` を `channels.wecom` に書き込み、設定を保存します
|
||||
|
||||
デフォルトのタイムアウトは **5 分** です。`--timeout` で延長できます:
|
||||
|
||||
```bash
|
||||
picoclaw auth wecom --timeout 10m
|
||||
```
|
||||
|
||||
> ⚠️ QR コードのスキャンだけでは不十分です — WeCom アプリ内で **確認** をタップする必要があります。そうしないとコマンドがタイムアウトします。
|
||||
|
||||
### オプション 3:手動設定
|
||||
|
||||
WeCom AI Bot プラットフォームから `bot_id` と `secret` を既にお持ちの場合、直接設定できます:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"bot_id": "YOUR_BOT_ID",
|
||||
"secret": "YOUR_SECRET",
|
||||
"websocket_url": "wss://openws.work.weixin.qq.com",
|
||||
"send_thinking_message": true,
|
||||
"allow_from": [],
|
||||
"reasoning_channel_id": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 設定
|
||||
|
||||
| フィールド | 型 | デフォルト | 説明 |
|
||||
| ---------- | -- | ---------- | ---- |
|
||||
| `enabled` | bool | `false` | WeCom チャンネルを有効にする。 |
|
||||
| `bot_id` | string | — | WeCom AI Bot 識別子。有効時に必須。 |
|
||||
| `secret` | string | — | WeCom AI Bot シークレット。`.security.yml` に暗号化して保存。有効時に必須。 |
|
||||
| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | WeCom WebSocket エンドポイント。 |
|
||||
| `send_thinking_message` | bool | `true` | ストリーミング返信の開始前に `Processing...` メッセージを送信する。 |
|
||||
| `allow_from` | array | `[]` | 送信者許可リスト。空の場合はすべての送信者を許可。 |
|
||||
| `reasoning_channel_id` | string | `""` | 推論・思考出力を別の会話にルーティングするためのオプションのチャット ID。 |
|
||||
|
||||
### 環境変数
|
||||
|
||||
すべてのフィールドは `PICOCLAW_CHANNELS_WECOM_` プレフィックスの環境変数で上書きできます:
|
||||
|
||||
| 環境変数 | 対応フィールド |
|
||||
| -------- | -------------- |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` |
|
||||
|
||||
---
|
||||
|
||||
## ランタイム動作
|
||||
|
||||
- PicoClaw はアクティブな WeCom ターンを維持し、可能な限り同じストリームでストリーミング返信を継続します。
|
||||
- ストリーミング返信の最大持続時間は **5.5 分**、最小送信間隔は **500ms** です。
|
||||
- ストリーミングが利用できなくなった場合、返信はアクティブプッシュ配信にフォールバックします。
|
||||
- チャットルートの関連付けは **30 分** の非アクティブ後に期限切れになります。
|
||||
- 受信メディアはエージェントに渡される前にローカルメディアストアにダウンロードされます。
|
||||
- 送信メディアは WeCom に一時ファイルとしてアップロードされ、メディアメッセージとして送信されます。
|
||||
- 重複メッセージは検出され抑制されます(最新 1000 件のメッセージ ID のリングバッファ)。
|
||||
|
||||
---
|
||||
|
||||
## レガシー WeCom 設定からの移行
|
||||
|
||||
| 以前の設定 | 移行方法 |
|
||||
| ---------- | -------- |
|
||||
| `channels.wecom`(Webhook ボット) | `bot_id` + `secret` を使用する `channels.wecom` に置き換える。 |
|
||||
| `channels.wecom_app` | 削除して `channels.wecom` を使用する。 |
|
||||
| `channels.wecom_aibot` | `bot_id` と `secret` を `channels.wecom` に移動する。 |
|
||||
| `token`、`encoding_aes_key`、`webhook_url`、`webhook_path` | 使用されなくなりました。設定から削除してください。 |
|
||||
| `corp_id`、`corp_secret`、`agent_id` | 使用されなくなりました。設定から削除してください。 |
|
||||
| `welcome_message`、`processing_message`、`max_steps` | WeCom チャンネル設定の一部ではなくなりました。 |
|
||||
|
||||
---
|
||||
|
||||
## トラブルシューティング
|
||||
|
||||
### QR バインディングがタイムアウトする
|
||||
|
||||
- QR コードをスキャンした後、**WeCom アプリ内でログインを確認** する必要があります。スキャンだけでは不十分です。
|
||||
- より長い `--timeout` で再実行してください:`picoclaw auth wecom --timeout 10m`
|
||||
- ターミナルの QR コードがスキャンしにくい場合は、その下に表示される **QR コードリンク** を使用してブラウザで開いてください。
|
||||
|
||||
### QR コードの有効期限切れ
|
||||
|
||||
- QR コードには有効期限があります。`picoclaw auth wecom` を再実行して新しいものを取得してください。
|
||||
|
||||
### WebSocket 接続の失敗
|
||||
|
||||
- `bot_id` と `secret` が正しいことを確認してください。
|
||||
- ホストが `wss://openws.work.weixin.qq.com` に到達できることを確認してください(アウトバウンド WebSocket、インバウンドポートは不要)。
|
||||
|
||||
### 返信が届かない
|
||||
|
||||
- `allow_from` が送信者をブロックしていないか確認してください。
|
||||
- `channels.wecom.bot_id` と `channels.wecom.secret` が設定されており、空でないことを確認してください。
|
||||
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
# WeCom
|
||||
|
||||
PicoClaw now exposes WeCom as a single `channels.wecom` channel built on the official WeCom AI Bot WebSocket API.
|
||||
This replaces the legacy `wecom`, `wecom_app`, and `wecom_aibot` split with one configuration model.
|
||||
PicoClaw exposes WeCom as a single `channels.wecom` channel built on the official WeCom AI Bot WebSocket API.
|
||||
This replaces the legacy `wecom`, `wecom_app`, and `wecom_aibot` split with one unified configuration model.
|
||||
|
||||
> No public webhook callback URL is required. PicoClaw opens an outbound WebSocket connection to WeCom.
|
||||
|
||||
## What This Channel Supports
|
||||
|
||||
|
|
@ -11,14 +13,22 @@ This replaces the legacy `wecom`, `wecom_app`, and `wecom_aibot` split with one
|
|||
- Channel-side streaming replies over WeCom's AI Bot protocol
|
||||
- Incoming text, voice, image, file, video, and mixed messages
|
||||
- Outbound text and media replies (`image`, `file`, `voice`, `video`)
|
||||
- QR-based CLI onboarding with `picoclaw auth wecom`
|
||||
- QR-based onboarding via Web UI or CLI
|
||||
- Shared allowlist and `reasoning_channel_id` routing
|
||||
|
||||
> No public webhook callback URL is required for this channel. PicoClaw opens an outbound WebSocket connection to WeCom.
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Option 1: QR Login From CLI
|
||||
### Option 1: Web UI QR Binding (Recommended)
|
||||
|
||||
Open the Web UI, navigate to **Channels → WeCom**, and click the QR binding button. Scan the QR code with WeCom and confirm in the app — credentials are saved automatically.
|
||||
|
||||
<p align="center">
|
||||
<img src="../../../assets/wecom-qr-binding.jpg" alt="WeCom QR binding in Web UI" width="600">
|
||||
</p>
|
||||
|
||||
### Option 2: CLI QR Login
|
||||
|
||||
Run:
|
||||
|
||||
|
|
@ -26,16 +36,23 @@ Run:
|
|||
picoclaw auth wecom
|
||||
```
|
||||
|
||||
The command prints a QR code in the terminal, waits for confirmation in WeCom, and then writes the resulting
|
||||
`bot_id` and `secret` into `channels.wecom`.
|
||||
The command:
|
||||
1. Requests a QR code from WeCom and prints it in the terminal
|
||||
2. Also prints a **QR Code Link** you can open in a browser if the terminal QR is hard to scan
|
||||
3. Polls for confirmation — after scanning, you must also **confirm the login inside the WeCom app**
|
||||
4. On success, writes `bot_id` and `secret` into `channels.wecom` and saves the config
|
||||
|
||||
Use `--timeout` if you want to wait longer:
|
||||
The default timeout is **5 minutes**. Use `--timeout` to extend it:
|
||||
|
||||
```bash
|
||||
picoclaw auth wecom --timeout 10m
|
||||
```
|
||||
|
||||
### Option 2: Configure Manually
|
||||
> ⚠️ Scanning the QR code is not enough — you must also tap **Confirm** inside the WeCom app, otherwise the command will time out.
|
||||
|
||||
### Option 3: Configure Manually
|
||||
|
||||
If you already have a `bot_id` and `secret` from the WeCom AI Bot platform, configure directly:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -53,52 +70,79 @@ picoclaw auth wecom --timeout 10m
|
|||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ----- | ---- | -------- | ----------- |
|
||||
| `enabled` | bool | No | Enables the WeCom channel. |
|
||||
| `bot_id` | string | Yes | WeCom AI Bot identifier. Required when the channel is enabled. |
|
||||
| `secret` | string | Yes | WeCom AI Bot secret. Required when the channel is enabled. |
|
||||
| `websocket_url` | string | No | WebSocket endpoint. Defaults to `wss://openws.work.weixin.qq.com`. |
|
||||
| `send_thinking_message` | bool | No | Sends an initial `Processing...` chunk before the final streamed reply. Defaults to `true`. |
|
||||
| `allow_from` | array | No | Sender allowlist. Empty means allow all senders. |
|
||||
| `reasoning_channel_id` | string | No | Optional destination for reasoning/thinking output. |
|
||||
| Field | Type | Default | Description |
|
||||
| ----- | ---- | ------- | ----------- |
|
||||
| `enabled` | bool | `false` | Enable the WeCom channel. |
|
||||
| `bot_id` | string | — | WeCom AI Bot identifier. Required when enabled. |
|
||||
| `secret` | string | — | WeCom AI Bot secret. Stored encrypted in `.security.yml`. Required when enabled. |
|
||||
| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | WeCom WebSocket endpoint. |
|
||||
| `send_thinking_message` | bool | `true` | Send a `Processing...` message before the streamed reply begins. |
|
||||
| `allow_from` | array | `[]` | Sender allowlist. Empty means allow all senders. |
|
||||
| `reasoning_channel_id` | string | `""` | Optional chat ID to route reasoning/thinking output to a separate conversation. |
|
||||
|
||||
### Environment Variables
|
||||
|
||||
All fields can be overridden via environment variables with the prefix `PICOCLAW_CHANNELS_WECOM_`:
|
||||
|
||||
| Environment Variable | Corresponding Field |
|
||||
| -------------------- | ------------------- |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` |
|
||||
|
||||
---
|
||||
|
||||
## Runtime Behavior
|
||||
|
||||
- PicoClaw keeps the active WeCom turn so normal replies can continue the same stream when possible.
|
||||
- If streaming is no longer available, replies fall back to active push delivery to the resolved chat route.
|
||||
- Incoming media is downloaded into the media store before being handed to the agent.
|
||||
- Outbound media is uploaded to WeCom in temporary chunks and then sent as a regular media message.
|
||||
- PicoClaw maintains an active WeCom turn so streaming replies can continue on the same stream when possible.
|
||||
- Streaming replies have a maximum duration of **5.5 minutes** and a minimum send interval of **500ms**.
|
||||
- If streaming is no longer available, replies fall back to active push delivery.
|
||||
- Chat route associations expire after **30 minutes** of inactivity.
|
||||
- Incoming media is downloaded into the local media store before being passed to the agent.
|
||||
- Outbound media is uploaded to WeCom as a temporary file and then sent as a media message.
|
||||
- Duplicate messages are detected and suppressed (ring buffer of last 1000 message IDs).
|
||||
|
||||
## Migration Notes
|
||||
---
|
||||
|
||||
This branch removes the old multi-channel WeCom model.
|
||||
## Migration from Legacy WeCom Config
|
||||
|
||||
| Previous config | Now |
|
||||
| --------------- | --- |
|
||||
| `channels.wecom` webhook bot | Replace with `channels.wecom` using `bot_id` + `secret`. |
|
||||
| `channels.wecom_app` | Remove it and use `channels.wecom`. |
|
||||
| `channels.wecom_aibot` | Move the config to `channels.wecom`. |
|
||||
| `token`, `encoding_aes_key`, `webhook_url`, `webhook_path` | No longer used by the WeCom channel. |
|
||||
| `corp_id`, `corp_secret`, `agent_id` | No longer used by the WeCom channel. |
|
||||
| `welcome_message`, `processing_message`, `max_steps` under WeCom | No longer part of the WeCom channel config. |
|
||||
| Previous config | Migration |
|
||||
| --------------- | --------- |
|
||||
| `channels.wecom` (webhook bot) | Replace with `channels.wecom` using `bot_id` + `secret`. |
|
||||
| `channels.wecom_app` | Remove. Use `channels.wecom` instead. |
|
||||
| `channels.wecom_aibot` | Move `bot_id` and `secret` to `channels.wecom`. |
|
||||
| `token`, `encoding_aes_key`, `webhook_url`, `webhook_path` | No longer used. Remove from config. |
|
||||
| `corp_id`, `corp_secret`, `agent_id` | No longer used. Remove from config. |
|
||||
| `welcome_message`, `processing_message`, `max_steps` | No longer part of the WeCom channel config. |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `picoclaw auth wecom` times out
|
||||
### QR binding times out
|
||||
|
||||
- Re-run with a larger `--timeout`.
|
||||
- Make sure the QR code was confirmed inside WeCom, not only scanned.
|
||||
- After scanning the QR code, you must also **confirm the login inside the WeCom app**. Scanning alone is not enough.
|
||||
- Re-run with a larger `--timeout`: `picoclaw auth wecom --timeout 10m`
|
||||
- If the QR code in the terminal is hard to scan, use the **QR Code Link** printed below it to open in a browser.
|
||||
|
||||
### QR code expired
|
||||
|
||||
- The QR code has a limited validity. Re-run `picoclaw auth wecom` to get a fresh one.
|
||||
|
||||
### WebSocket connection fails
|
||||
|
||||
- Verify `bot_id` and `secret`.
|
||||
- Confirm the host can reach `wss://openws.work.weixin.qq.com`.
|
||||
- Verify `bot_id` and `secret` are correct.
|
||||
- Confirm the host can reach `wss://openws.work.weixin.qq.com` (outbound WebSocket, no inbound port needed).
|
||||
|
||||
### Replies do not arrive
|
||||
|
||||
- Check whether `allow_from` blocks the sender.
|
||||
- Check launcher or startup validation for missing `channels.wecom.bot_id` / `channels.wecom.secret`.
|
||||
|
||||
- Check whether `allow_from` is blocking the sender.
|
||||
- Check that `channels.wecom.bot_id` and `channels.wecom.secret` are set and non-empty.
|
||||
|
|
|
|||
148
docs/channels/wecom/README.pt-br.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
> Voltar ao [README](../../../README.pt-br.md)
|
||||
|
||||
# WeCom
|
||||
|
||||
O PicoClaw expõe o WeCom como um único canal `channels.wecom`, construído sobre a API WebSocket oficial do WeCom AI Bot.
|
||||
Isso substitui a antiga separação `wecom`, `wecom_app` e `wecom_aibot` por um modelo de configuração unificado.
|
||||
|
||||
> Nenhuma URL de callback webhook pública é necessária. O PicoClaw estabelece uma conexão WebSocket de saída para o WeCom.
|
||||
|
||||
## Funcionalidades Suportadas
|
||||
|
||||
- Chat direto e chat em grupo
|
||||
- Respostas em streaming pelo protocolo WeCom AI Bot
|
||||
- Mensagens recebidas: texto, voz, imagem, arquivo, vídeo e mensagens mistas
|
||||
- Respostas enviadas: texto e mídia (`image`, `file`, `voice`, `video`)
|
||||
- Onboarding por QR code via Web UI ou CLI
|
||||
- Lista de permissões compartilhada e roteamento `reasoning_channel_id`
|
||||
|
||||
---
|
||||
|
||||
## Início Rápido
|
||||
|
||||
### Opção 1: Vinculação QR via Web UI (Recomendado)
|
||||
|
||||
Abra a Web UI, navegue até **Channels → WeCom** e clique no botão de vinculação QR. Escaneie o QR code com o WeCom e confirme no aplicativo — as credenciais são salvas automaticamente.
|
||||
|
||||
<p align="center">
|
||||
<img src="../../../assets/wecom-qr-binding.jpg" alt="Vinculação QR do WeCom na Web UI" width="600">
|
||||
</p>
|
||||
|
||||
### Opção 2: Login QR via CLI
|
||||
|
||||
Execute:
|
||||
|
||||
```bash
|
||||
picoclaw auth wecom
|
||||
```
|
||||
|
||||
O comando:
|
||||
1. Solicita um QR code ao WeCom e o exibe no terminal
|
||||
2. Também exibe um **Link do QR Code** que você pode abrir no navegador se o QR do terminal for difícil de escanear
|
||||
3. Aguarda a confirmação — após escanear, você também deve **confirmar o login dentro do aplicativo WeCom**
|
||||
4. Em caso de sucesso, grava `bot_id` e `secret` em `channels.wecom` e salva a configuração
|
||||
|
||||
O timeout padrão é de **5 minutos**. Use `--timeout` para estendê-lo:
|
||||
|
||||
```bash
|
||||
picoclaw auth wecom --timeout 10m
|
||||
```
|
||||
|
||||
> ⚠️ Escanear o QR code não é suficiente — você também deve tocar em **Confirmar** dentro do aplicativo WeCom, caso contrário o comando expirará.
|
||||
|
||||
### Opção 3: Configuração Manual
|
||||
|
||||
Se você já possui um `bot_id` e `secret` da plataforma WeCom AI Bot, configure diretamente:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"bot_id": "YOUR_BOT_ID",
|
||||
"secret": "YOUR_SECRET",
|
||||
"websocket_url": "wss://openws.work.weixin.qq.com",
|
||||
"send_thinking_message": true,
|
||||
"allow_from": [],
|
||||
"reasoning_channel_id": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuração
|
||||
|
||||
| Campo | Tipo | Padrão | Descrição |
|
||||
| ----- | ---- | ------ | --------- |
|
||||
| `enabled` | bool | `false` | Ativar o canal WeCom. |
|
||||
| `bot_id` | string | — | Identificador do WeCom AI Bot. Obrigatório quando ativado. |
|
||||
| `secret` | string | — | Secret do WeCom AI Bot. Armazenado criptografado em `.security.yml`. Obrigatório quando ativado. |
|
||||
| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | Endpoint WebSocket do WeCom. |
|
||||
| `send_thinking_message` | bool | `true` | Enviar uma mensagem `Processing...` antes do início da resposta em streaming. |
|
||||
| `allow_from` | array | `[]` | Lista de permissões de remetentes. Vazio significa permitir todos os remetentes. |
|
||||
| `reasoning_channel_id` | string | `""` | ID de chat opcional para rotear a saída de raciocínio para uma conversa separada. |
|
||||
|
||||
### Variáveis de Ambiente
|
||||
|
||||
Todos os campos podem ser substituídos via variáveis de ambiente com o prefixo `PICOCLAW_CHANNELS_WECOM_`:
|
||||
|
||||
| Variável de Ambiente | Campo Correspondente |
|
||||
| -------------------- | -------------------- |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` |
|
||||
|
||||
---
|
||||
|
||||
## Comportamento em Tempo de Execução
|
||||
|
||||
- O PicoClaw mantém um turno WeCom ativo para que as respostas em streaming possam continuar no mesmo fluxo quando possível.
|
||||
- As respostas em streaming têm uma duração máxima de **5,5 minutos** e um intervalo mínimo de envio de **500ms**.
|
||||
- Se o streaming não estiver mais disponível, as respostas recorrem à entrega por push ativo.
|
||||
- As associações de rotas de chat expiram após **30 minutos** de inatividade.
|
||||
- A mídia recebida é baixada para o armazenamento de mídia local antes de ser passada ao agente.
|
||||
- A mídia enviada é carregada para o WeCom como um arquivo temporário e então enviada como uma mensagem de mídia.
|
||||
- Mensagens duplicadas são detectadas e suprimidas (buffer circular dos últimos 1000 IDs de mensagens).
|
||||
|
||||
---
|
||||
|
||||
## Migração da Configuração Legada do WeCom
|
||||
|
||||
| Configuração anterior | Migração |
|
||||
| --------------------- | -------- |
|
||||
| `channels.wecom` (bot webhook) | Substituir por `channels.wecom` usando `bot_id` + `secret`. |
|
||||
| `channels.wecom_app` | Remover. Usar `channels.wecom` no lugar. |
|
||||
| `channels.wecom_aibot` | Mover `bot_id` e `secret` para `channels.wecom`. |
|
||||
| `token`, `encoding_aes_key`, `webhook_url`, `webhook_path` | Não mais utilizados. Remover da configuração. |
|
||||
| `corp_id`, `corp_secret`, `agent_id` | Não mais utilizados. Remover da configuração. |
|
||||
| `welcome_message`, `processing_message`, `max_steps` | Não fazem mais parte da configuração do canal WeCom. |
|
||||
|
||||
---
|
||||
|
||||
## Solução de Problemas
|
||||
|
||||
### A vinculação QR expira
|
||||
|
||||
- Após escanear o QR code, você também deve **confirmar o login dentro do aplicativo WeCom**. Escanear sozinho não é suficiente.
|
||||
- Execute novamente com um `--timeout` maior: `picoclaw auth wecom --timeout 10m`
|
||||
- Se o QR code no terminal for difícil de escanear, use o **Link do QR Code** exibido abaixo dele para abrir no navegador.
|
||||
|
||||
### QR code expirado
|
||||
|
||||
- O QR code tem validade limitada. Execute novamente `picoclaw auth wecom` para obter um novo.
|
||||
|
||||
### Falha na conexão WebSocket
|
||||
|
||||
- Verifique se `bot_id` e `secret` estão corretos.
|
||||
- Confirme que o host pode alcançar `wss://openws.work.weixin.qq.com` (WebSocket de saída, nenhuma porta de entrada necessária).
|
||||
|
||||
### As respostas não chegam
|
||||
|
||||
- Verifique se `allow_from` está bloqueando o remetente.
|
||||
- Verifique se `channels.wecom.bot_id` e `channels.wecom.secret` estão definidos e não vazios.
|
||||
148
docs/channels/wecom/README.vi.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
> Quay lại [README](../../../README.vi.md)
|
||||
|
||||
# WeCom
|
||||
|
||||
PicoClaw cung cấp WeCom dưới dạng một kênh duy nhất `channels.wecom`, được xây dựng trên API WebSocket chính thức của WeCom AI Bot.
|
||||
Điều này thay thế việc phân tách cũ `wecom`, `wecom_app` và `wecom_aibot` bằng một mô hình cấu hình thống nhất.
|
||||
|
||||
> Không cần URL callback webhook công khai. PicoClaw thiết lập kết nối WebSocket đi ra tới WeCom.
|
||||
|
||||
## Tính năng được hỗ trợ
|
||||
|
||||
- Chat trực tiếp và chat nhóm
|
||||
- Phản hồi streaming qua giao thức WeCom AI Bot
|
||||
- Nhận tin nhắn văn bản, giọng nói, hình ảnh, tệp, video và tin nhắn hỗn hợp
|
||||
- Gửi phản hồi văn bản và phương tiện (`image`, `file`, `voice`, `video`)
|
||||
- Đăng ký qua mã QR bằng Web UI hoặc CLI
|
||||
- Danh sách cho phép chung và định tuyến `reasoning_channel_id`
|
||||
|
||||
---
|
||||
|
||||
## Bắt đầu nhanh
|
||||
|
||||
### Tùy chọn 1: Liên kết QR qua Web UI (Khuyến nghị)
|
||||
|
||||
Mở Web UI, điều hướng đến **Channels → WeCom** và nhấp vào nút liên kết QR. Quét mã QR bằng WeCom và xác nhận trong ứng dụng — thông tin đăng nhập được lưu tự động.
|
||||
|
||||
<p align="center">
|
||||
<img src="../../../assets/wecom-qr-binding.jpg" alt="Liên kết QR WeCom trong Web UI" width="600">
|
||||
</p>
|
||||
|
||||
### Tùy chọn 2: Đăng nhập QR qua CLI
|
||||
|
||||
Chạy:
|
||||
|
||||
```bash
|
||||
picoclaw auth wecom
|
||||
```
|
||||
|
||||
Lệnh thực hiện:
|
||||
1. Yêu cầu mã QR từ WeCom và hiển thị trong terminal
|
||||
2. Đồng thời in ra một **Liên kết mã QR** mà bạn có thể mở trong trình duyệt nếu mã QR trên terminal khó quét
|
||||
3. Chờ xác nhận — sau khi quét, bạn cũng phải **xác nhận đăng nhập trong ứng dụng WeCom**
|
||||
4. Khi thành công, ghi `bot_id` và `secret` vào `channels.wecom` và lưu cấu hình
|
||||
|
||||
Thời gian chờ mặc định là **5 phút**. Sử dụng `--timeout` để kéo dài:
|
||||
|
||||
```bash
|
||||
picoclaw auth wecom --timeout 10m
|
||||
```
|
||||
|
||||
> ⚠️ Quét mã QR là chưa đủ — bạn cũng phải nhấn **Xác nhận** trong ứng dụng WeCom, nếu không lệnh sẽ hết thời gian chờ.
|
||||
|
||||
### Tùy chọn 3: Cấu hình thủ công
|
||||
|
||||
Nếu bạn đã có `bot_id` và `secret` từ nền tảng WeCom AI Bot, hãy cấu hình trực tiếp:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"bot_id": "YOUR_BOT_ID",
|
||||
"secret": "YOUR_SECRET",
|
||||
"websocket_url": "wss://openws.work.weixin.qq.com",
|
||||
"send_thinking_message": true,
|
||||
"allow_from": [],
|
||||
"reasoning_channel_id": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cấu hình
|
||||
|
||||
| Trường | Kiểu | Mặc định | Mô tả |
|
||||
| ------ | ---- | -------- | ----- |
|
||||
| `enabled` | bool | `false` | Kích hoạt kênh WeCom. |
|
||||
| `bot_id` | string | — | Mã định danh WeCom AI Bot. Bắt buộc khi được kích hoạt. |
|
||||
| `secret` | string | — | Secret của WeCom AI Bot. Được lưu mã hóa trong `.security.yml`. Bắt buộc khi được kích hoạt. |
|
||||
| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | Điểm cuối WebSocket của WeCom. |
|
||||
| `send_thinking_message` | bool | `true` | Gửi tin nhắn `Processing...` trước khi phản hồi streaming bắt đầu. |
|
||||
| `allow_from` | array | `[]` | Danh sách cho phép người gửi. Để trống nghĩa là cho phép tất cả. |
|
||||
| `reasoning_channel_id` | string | `""` | ID chat tùy chọn để định tuyến đầu ra suy luận đến một cuộc hội thoại riêng. |
|
||||
|
||||
### Biến môi trường
|
||||
|
||||
Tất cả các trường có thể được ghi đè bằng biến môi trường với tiền tố `PICOCLAW_CHANNELS_WECOM_`:
|
||||
|
||||
| Biến môi trường | Trường tương ứng |
|
||||
| ---------------- | ---------------- |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` |
|
||||
|
||||
---
|
||||
|
||||
## Hành vi khi chạy
|
||||
|
||||
- PicoClaw duy trì một lượt WeCom đang hoạt động để phản hồi streaming có thể tiếp tục trên cùng một luồng khi có thể.
|
||||
- Phản hồi streaming có thời lượng tối đa **5,5 phút** và khoảng cách gửi tối thiểu **500ms**.
|
||||
- Nếu streaming không còn khả dụng, phản hồi sẽ chuyển sang gửi push chủ động.
|
||||
- Các liên kết tuyến chat hết hạn sau **30 phút** không hoạt động.
|
||||
- Phương tiện nhận được sẽ được tải xuống bộ lưu trữ phương tiện cục bộ trước khi chuyển cho agent.
|
||||
- Phương tiện gửi đi được tải lên WeCom dưới dạng tệp tạm thời, sau đó gửi dưới dạng tin nhắn phương tiện.
|
||||
- Tin nhắn trùng lặp được phát hiện và loại bỏ (bộ đệm vòng của 1000 ID tin nhắn gần nhất).
|
||||
|
||||
---
|
||||
|
||||
## Di chuyển từ cấu hình WeCom cũ
|
||||
|
||||
| Cấu hình trước đây | Di chuyển |
|
||||
| ------------------- | --------- |
|
||||
| `channels.wecom` (bot webhook) | Thay thế bằng `channels.wecom` sử dụng `bot_id` + `secret`. |
|
||||
| `channels.wecom_app` | Xóa. Sử dụng `channels.wecom` thay thế. |
|
||||
| `channels.wecom_aibot` | Di chuyển `bot_id` và `secret` sang `channels.wecom`. |
|
||||
| `token`, `encoding_aes_key`, `webhook_url`, `webhook_path` | Không còn sử dụng. Xóa khỏi cấu hình. |
|
||||
| `corp_id`, `corp_secret`, `agent_id` | Không còn sử dụng. Xóa khỏi cấu hình. |
|
||||
| `welcome_message`, `processing_message`, `max_steps` | Không còn là một phần của cấu hình kênh WeCom. |
|
||||
|
||||
---
|
||||
|
||||
## Khắc phục sự cố
|
||||
|
||||
### Liên kết QR hết thời gian chờ
|
||||
|
||||
- Sau khi quét mã QR, bạn cũng phải **xác nhận đăng nhập trong ứng dụng WeCom**. Chỉ quét là chưa đủ.
|
||||
- Chạy lại với `--timeout` lớn hơn: `picoclaw auth wecom --timeout 10m`
|
||||
- Nếu mã QR trên terminal khó quét, hãy sử dụng **Liên kết mã QR** được in bên dưới để mở trong trình duyệt.
|
||||
|
||||
### Mã QR đã hết hạn
|
||||
|
||||
- Mã QR có thời hạn hiệu lực giới hạn. Chạy lại `picoclaw auth wecom` để lấy mã mới.
|
||||
|
||||
### Kết nối WebSocket thất bại
|
||||
|
||||
- Kiểm tra xem `bot_id` và `secret` có chính xác không.
|
||||
- Xác nhận máy chủ có thể kết nối đến `wss://openws.work.weixin.qq.com` (WebSocket đi ra, không cần cổng đến).
|
||||
|
||||
### Phản hồi không đến
|
||||
|
||||
- Kiểm tra xem `allow_from` có đang chặn người gửi không.
|
||||
- Kiểm tra rằng `channels.wecom.bot_id` và `channels.wecom.secret` đã được thiết lập và không trống.
|
||||
|
|
@ -1,24 +1,34 @@
|
|||
> 返回 [README](../../../README.zh.md)
|
||||
|
||||
# 企业微信
|
||||
# 企业微信(WeCom)
|
||||
|
||||
PicoClaw 现在将企业微信统一为一个 `channels.wecom` 渠道,并基于企业微信官方 AI Bot WebSocket 协议实现。
|
||||
这取代了旧的 `wecom`、`wecom_app`、`wecom_aibot` 三套配置模型。
|
||||
PicoClaw 将企业微信整合为单一的 `channels.wecom` 渠道,基于腾讯官方企业微信 AI Bot WebSocket API 实现。
|
||||
原有的 `wecom`、`wecom_app`、`wecom_aibot` 三个独立渠道已合并为统一配置模型。
|
||||
|
||||
## 当前渠道能力
|
||||
> 本渠道无需公网 Webhook 回调地址。PicoClaw 主动向企业微信建立出站 WebSocket 连接。
|
||||
|
||||
- 支持私聊和群聊
|
||||
- 支持企业微信侧流式回复
|
||||
- 支持接收文本、语音、图片、文件、视频和 mixed 消息
|
||||
- 支持发送文本与媒体消息(`image`、`file`、`voice`、`video`)
|
||||
- 支持通过 `picoclaw auth wecom` 扫码写入配置
|
||||
- 支持统一白名单与 `reasoning_channel_id`
|
||||
## 支持的功能
|
||||
|
||||
> 这个渠道不再需要公网 webhook 回调地址。PicoClaw 会主动向企业微信发起 WebSocket 连接。
|
||||
- 单聊和群聊消息收发
|
||||
- 基于企业微信 AI Bot 协议的流式回复
|
||||
- 接收文本、语音、图片、文件、视频及混合消息
|
||||
- 发送文本及媒体消息(`image`、`file`、`voice`、`video`)
|
||||
- 通过 Web UI 或 CLI 扫码绑定
|
||||
- 发送者白名单和 `reasoning_channel_id` 路由
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 方式 1:命令行扫码登录
|
||||
### 方式一:Web UI 扫码绑定(推荐)
|
||||
|
||||
打开 Web UI,进入 **Channels → WeCom**,点击扫码绑定按钮。用企业微信扫码并在 App 内确认,凭据自动保存。
|
||||
|
||||
<p align="center">
|
||||
<img src="../../../assets/wecom-qr-binding.jpg" alt="Web UI 企业微信扫码绑定" width="600">
|
||||
</p>
|
||||
|
||||
### 方式二:CLI 扫码登录
|
||||
|
||||
运行:
|
||||
|
||||
|
|
@ -26,16 +36,23 @@ PicoClaw 现在将企业微信统一为一个 `channels.wecom` 渠道,并基
|
|||
picoclaw auth wecom
|
||||
```
|
||||
|
||||
该命令会在终端打印二维码,等待你在企业微信中确认,然后把生成的 `bot_id` 和 `secret` 写入
|
||||
`channels.wecom`。
|
||||
命令执行流程:
|
||||
1. 向企业微信请求二维码并在终端打印
|
||||
2. 同时打印一个**二维码链接**,终端二维码不清晰时可在浏览器中打开
|
||||
3. 轮询确认状态——扫码后还需要在**企业微信 App 内点击确认**
|
||||
4. 成功后将 `bot_id` 和 `secret` 写入 `channels.wecom` 并保存配置
|
||||
|
||||
如果需要更长等待时间,可以加 `--timeout`:
|
||||
默认超时为 **5 分钟**,可通过 `--timeout` 延长:
|
||||
|
||||
```bash
|
||||
picoclaw auth wecom --timeout 10m
|
||||
```
|
||||
|
||||
### 方式 2:手动配置
|
||||
> ⚠️ 仅扫描二维码还不够——必须在企业微信 App 内点击**确认**,否则命令会超时。
|
||||
|
||||
### 方式三:手动配置
|
||||
|
||||
如果已有企业微信 AI Bot 的 `bot_id` 和 `secret`,可直接配置:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -53,52 +70,79 @@ picoclaw auth wecom --timeout 10m
|
|||
}
|
||||
```
|
||||
|
||||
## 配置字段
|
||||
---
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| `enabled` | bool | 否 | 是否启用企业微信渠道。 |
|
||||
| `bot_id` | string | 是 | 企业微信 AI Bot 标识。渠道启用时必填。 |
|
||||
| `secret` | string | 是 | 企业微信 AI Bot 密钥。渠道启用时必填。 |
|
||||
| `websocket_url` | string | 否 | WebSocket 地址,默认 `wss://openws.work.weixin.qq.com`。 |
|
||||
| `send_thinking_message` | bool | 否 | 是否在流式最终回复前先发送一段 `Processing...` 开场消息,默认 `true`。 |
|
||||
| `allow_from` | array | 否 | 发送者白名单;空数组表示允许所有发送者。 |
|
||||
| `reasoning_channel_id` | string | 否 | 可选的 reasoning/thinking 输出目标。 |
|
||||
## 配置项说明
|
||||
|
||||
| 字段 | 类型 | 默认值 | 说明 |
|
||||
| ---- | ---- | ------ | ---- |
|
||||
| `enabled` | bool | `false` | 启用企业微信渠道。 |
|
||||
| `bot_id` | string | — | 企业微信 AI Bot 标识符。启用时必填。 |
|
||||
| `secret` | string | — | 企业微信 AI Bot 密钥。加密存储于 `.security.yml`。启用时必填。 |
|
||||
| `websocket_url` | string | `wss://openws.work.weixin.qq.com` | 企业微信 WebSocket 端点。 |
|
||||
| `send_thinking_message` | bool | `true` | 在流式回复开始前发送"处理中..."提示消息。 |
|
||||
| `allow_from` | array | `[]` | 发送者白名单。为空时允许所有人。 |
|
||||
| `reasoning_channel_id` | string | `""` | 可选,将推理/思考内容路由到指定会话 ID。 |
|
||||
|
||||
### 环境变量
|
||||
|
||||
所有字段均可通过 `PICOCLAW_CHANNELS_WECOM_` 前缀的环境变量覆盖:
|
||||
|
||||
| 环境变量 | 对应字段 |
|
||||
| -------- | -------- |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ENABLED` | `enabled` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_BOT_ID` | `bot_id` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SECRET` | `secret` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_WEBSOCKET_URL` | `websocket_url` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_SEND_THINKING_MESSAGE` | `send_thinking_message` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_ALLOW_FROM` | `allow_from` |
|
||||
| `PICOCLAW_CHANNELS_WECOM_REASONING_CHANNEL_ID` | `reasoning_channel_id` |
|
||||
|
||||
---
|
||||
|
||||
## 运行时行为
|
||||
|
||||
- PicoClaw 会保留当前会话对应的企业微信 turn,优先继续同一个流式回复。
|
||||
- 如果流式上下文已经失效,回复会自动回退到主动推送消息。
|
||||
- 收到的媒体会先下载到 media store,再交给 Agent 处理。
|
||||
- 发出的媒体会先按分片上传到企业微信,再作为普通媒体消息发送。
|
||||
- PicoClaw 维护活跃的企业微信 Turn,流式回复尽可能在同一流上继续。
|
||||
- 流式回复最大持续时长为 **5.5 分钟**,最小发送间隔为 **500ms**。
|
||||
- 流式不可用时,回复降级为主动推送。
|
||||
- 会话路由关联在 **30 分钟**无活动后过期。
|
||||
- 接收到的媒体文件先下载到本地媒体存储,再传递给 Agent。
|
||||
- 发送媒体时先上传为企业微信临时文件,再作为媒体消息发送。
|
||||
- 自动检测并过滤重复消息(环形缓冲区,最多记录 1000 条消息 ID)。
|
||||
|
||||
## 迁移说明
|
||||
---
|
||||
|
||||
这个分支移除了旧的多通道企业微信模型。
|
||||
## 从旧版企业微信配置迁移
|
||||
|
||||
| 旧配置 | 现在怎么做 |
|
||||
| ------ | ---------- |
|
||||
| `channels.wecom` webhook 机器人 | 改为使用 `bot_id` + `secret` 的 `channels.wecom`。 |
|
||||
| `channels.wecom_app` | 删除,统一迁移到 `channels.wecom`。 |
|
||||
| `channels.wecom_aibot` | 配置迁移到 `channels.wecom`。 |
|
||||
| `token`、`encoding_aes_key`、`webhook_url`、`webhook_path` | 企业微信渠道不再使用这些字段。 |
|
||||
| `corp_id`、`corp_secret`、`agent_id` | 企业微信渠道不再使用这些字段。 |
|
||||
| 企业微信下的 `welcome_message`、`processing_message`、`max_steps` | 不再属于企业微信渠道配置。 |
|
||||
| 旧配置 | 迁移方式 |
|
||||
| ------ | -------- |
|
||||
| `channels.wecom`(Webhook 机器人) | 改用 `channels.wecom`,填写 `bot_id` + `secret`。 |
|
||||
| `channels.wecom_app` | 删除,改用 `channels.wecom`。 |
|
||||
| `channels.wecom_aibot` | 将 `bot_id` 和 `secret` 移至 `channels.wecom`。 |
|
||||
| `token`、`encoding_aes_key`、`webhook_url`、`webhook_path` | 已废弃,从配置中删除。 |
|
||||
| `corp_id`、`corp_secret`、`agent_id` | 已废弃,从配置中删除。 |
|
||||
| `welcome_message`、`processing_message`、`max_steps` | 已不属于企业微信渠道配置,删除即可。 |
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### `picoclaw auth wecom` 超时
|
||||
### 扫码绑定超时
|
||||
|
||||
- 用更大的 `--timeout` 重新执行。
|
||||
- 确认是在企业微信里完成了确认,而不只是扫描二维码。
|
||||
- 扫码后必须在**企业微信 App 内点击确认**,仅扫码不够。
|
||||
- 使用更长的超时重试:`picoclaw auth wecom --timeout 10m`
|
||||
- 终端二维码不清晰时,使用命令打印的**二维码链接**在浏览器中打开。
|
||||
|
||||
### 二维码已过期
|
||||
|
||||
- 二维码有效期有限,重新运行 `picoclaw auth wecom` 获取新二维码。
|
||||
|
||||
### WebSocket 连接失败
|
||||
|
||||
- 检查 `bot_id` 和 `secret` 是否正确。
|
||||
- 确认运行环境可以访问 `wss://openws.work.weixin.qq.com`。
|
||||
- 确认设备可以访问 `wss://openws.work.weixin.qq.com`(出站 WebSocket,无需开放入站端口)。
|
||||
|
||||
### 消息没有回到企业微信
|
||||
|
||||
- 检查 `allow_from` 是否拦截了发送者。
|
||||
- 检查启动日志或 launcher 校验,确认 `channels.wecom.bot_id` / `channels.wecom.secret` 已填写。
|
||||
### 收不到回复
|
||||
|
||||
- 检查 `allow_from` 是否屏蔽了发送者。
|
||||
- 确认 `channels.wecom.bot_id` 和 `channels.wecom.secret` 已填写且非空。
|
||||
|
|
|
|||
|
|
@ -1,118 +0,0 @@
|
|||
> Retour au [README](../../../../README.fr.md)
|
||||
|
||||
# WeCom AI Bot
|
||||
|
||||
Le WeCom AI Bot est une méthode d'intégration de conversation IA officiellement fournie par WeCom. Il prend en charge les conversations privées et de groupe, intègre un protocole de réponse en streaming et supporte l'envoi proactif de la réponse finale via `response_url` en cas de dépassement de délai.
|
||||
|
||||
## Comparaison avec les autres canaux WeCom
|
||||
|
||||
| Fonctionnalité | WeCom Bot | WeCom App | **WeCom AI Bot** |
|
||||
|----------------|-----------|-----------|-----------------|
|
||||
| Chat privé | ✅ | ✅ | ✅ |
|
||||
| Chat de groupe | ✅ | ❌ | ✅ |
|
||||
| Sortie en streaming | ❌ | ❌ | ✅ |
|
||||
| Push proactif en cas de timeout | ❌ | ✅ | ✅ |
|
||||
| Complexité de configuration | Faible | Élevée | Moyenne |
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_aibot": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-aibot",
|
||||
"allow_from": [],
|
||||
"welcome_message": "你好!有什么可以帮助你的吗?",
|
||||
"max_steps": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Champ | Type | Requis | Description |
|
||||
| ---------------- | ------ | ------ | -------------------------------------------------- |
|
||||
| token | string | Oui | Jeton de vérification du callback, configuré sur la page de gestion de l'AI Bot |
|
||||
| encoding_aes_key | string | Oui | Clé AES de 43 caractères, générée aléatoirement sur la page de gestion de l'AI Bot |
|
||||
| webhook_path | string | Non | Chemin du webhook (par défaut : /webhook/wecom-aibot) |
|
||||
| allow_from | array | Non | Liste blanche d'ID utilisateurs ; un tableau vide autorise tous les utilisateurs |
|
||||
| welcome_message | string | Non | Message de bienvenue envoyé à l'ouverture du chat ; laisser vide pour désactiver |
|
||||
| reply_timeout | int | Non | Délai de réponse en secondes (par défaut : 5) |
|
||||
| max_steps | int | Non | Nombre maximum d'étapes d'exécution de l'agent (par défaut : 10) |
|
||||
|
||||
## Procédure de configuration
|
||||
|
||||
1. Connectez-vous à la [console d'administration WeCom](https://work.weixin.qq.com/wework_admin)
|
||||
2. Accédez à « Gestion des applications » → « AI Bot », puis créez ou sélectionnez un AI Bot
|
||||
3. Sur la page de configuration de l'AI Bot, renseignez les informations de « Réception des messages » :
|
||||
- **URL** : `http://<your-server-ip>:18790/webhook/wecom-aibot`
|
||||
- **Token** : Généré aléatoirement ou personnalisé
|
||||
- **EncodingAESKey** : Cliquez sur « Générer aléatoirement » pour obtenir une clé de 43 caractères
|
||||
4. Saisissez le Token et l'EncodingAESKey dans le fichier de configuration PicoClaw, démarrez le service, puis revenez à la console d'administration pour enregistrer (WeCom enverra une requête de vérification)
|
||||
|
||||
> [!TIP]
|
||||
> Le serveur doit être accessible par les serveurs WeCom. Si vous êtes sur un intranet ou en développement local, utilisez [ngrok](https://ngrok.com) ou frp pour le tunneling.
|
||||
|
||||
## Protocole de réponse en streaming
|
||||
|
||||
Le WeCom AI Bot utilise un protocole de « pull en streaming », différent de la réponse unique d'un webhook standard :
|
||||
|
||||
```
|
||||
L'utilisateur envoie un message
|
||||
│
|
||||
▼
|
||||
PicoClaw retourne immédiatement {finish: false} (l'agent commence le traitement)
|
||||
│
|
||||
▼
|
||||
WeCom effectue un pull environ toutes les 1 seconde avec {msgtype: "stream", stream: {id: "..."}}
|
||||
│
|
||||
├─ Agent non terminé → retourne {finish: false} (continuer à attendre)
|
||||
│
|
||||
└─ Agent terminé → retourne {finish: true, content: "contenu de la réponse"}
|
||||
```
|
||||
|
||||
**Gestion du timeout** (tâche dépassant 30 secondes) :
|
||||
|
||||
Si le traitement de l'agent dépasse environ 30 secondes (la fenêtre de polling maximale de WeCom est de 6 minutes), PicoClaw va :
|
||||
|
||||
1. Fermer immédiatement le stream et afficher à l'utilisateur : « ⏳ 正在处理中,请稍候,结果将稍后发送。 »
|
||||
2. L'agent continue de s'exécuter en arrière-plan
|
||||
3. Une fois l'agent terminé, la réponse finale est envoyée proactivement à l'utilisateur via le `response_url` inclus dans le message
|
||||
|
||||
> `response_url` est émis par WeCom, valable 1 heure, utilisable une seule fois, sans chiffrement requis — il suffit de POSTer directement le corps du message markdown.
|
||||
|
||||
## Message de bienvenue
|
||||
|
||||
Lorsque `welcome_message` est configuré, PicoClaw répond automatiquement avec ce message lorsqu'un utilisateur ouvre la fenêtre de chat avec l'AI Bot (événement `enter_chat`). Laisser vide pour ignorer silencieusement.
|
||||
|
||||
```json
|
||||
"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?"
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
### Échec de la vérification de l'URL de callback
|
||||
|
||||
- Vérifiez que le pare-feu du serveur autorise le port concerné (par défaut 18790)
|
||||
- Vérifiez que `token` et `encoding_aes_key` sont correctement renseignés
|
||||
- Consultez les logs PicoClaw pour voir si une requête GET de WeCom a été reçue
|
||||
|
||||
### Les messages ne reçoivent pas de réponse
|
||||
|
||||
- Vérifiez que `allow_from` ne restreint pas accidentellement l'expéditeur
|
||||
- Recherchez `context canceled` ou des erreurs d'agent dans les logs
|
||||
- Vérifiez que la configuration de l'agent (ex. `model_name`) est correcte
|
||||
|
||||
### Pas de push final reçu pour les tâches longues
|
||||
|
||||
- Vérifiez que le callback du message inclut `response_url` (uniquement supporté par la nouvelle version du WeCom AI Bot)
|
||||
- Vérifiez que le serveur peut effectuer des requêtes sortantes (nécessite un POST vers `response_url`)
|
||||
- Consultez les logs pour les mots-clés `response_url mode` et `Sending reply via response_url`
|
||||
|
||||
## Références
|
||||
|
||||
- [Documentation d'intégration WeCom AI Bot](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [Description du protocole de réponse en streaming](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [Réponse proactive via response_url](https://developer.work.weixin.qq.com/document/path/101138)
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
> [README](../../../../README.ja.md) に戻る
|
||||
|
||||
# 企業WeChat AIボット
|
||||
|
||||
企業WeChat AIボット(AI Bot)は、企業WeChatが公式に提供するAI会話連携方式です。プライベートチャットとグループチャットの両方をサポートし、ストリーミングレスポンスプロトコルを内蔵しており、タイムアウト後に `response_url` を通じて最終返信をプッシュする機能もサポートしています。
|
||||
|
||||
## 他のWeCom チャンネルとの比較
|
||||
|
||||
| 機能 | WeCom Bot | WeCom App | **WeCom AI Bot** |
|
||||
|------|-----------|-----------|-----------------|
|
||||
| プライベートチャット | ✅ | ✅ | ✅ |
|
||||
| グループチャット | ✅ | ❌ | ✅ |
|
||||
| ストリーミング出力 | ❌ | ❌ | ✅ |
|
||||
| タイムアウト時のプッシュ | ❌ | ✅ | ✅ |
|
||||
| 設定の複雑さ | 低 | 高 | 中 |
|
||||
|
||||
## 設定
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_aibot": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-aibot",
|
||||
"allow_from": [],
|
||||
"welcome_message": "你好!有什么可以帮助你的吗?",
|
||||
"max_steps": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 型 | 必須 | 説明 |
|
||||
| ---------------- | ------ | ---- | -------------------------------------------------- |
|
||||
| token | string | はい | コールバック検証トークン。AIボット管理ページで設定 |
|
||||
| encoding_aes_key | string | はい | 43文字のAESキー。AIボット管理ページでランダム生成 |
|
||||
| webhook_path | string | いいえ | Webhookパス(デフォルト:/webhook/wecom-aibot) |
|
||||
| allow_from | array | いいえ | ユーザーIDの許可リスト。空配列は全ユーザーを許可 |
|
||||
| welcome_message | string | いいえ | ユーザーがチャットを開いたときに送信するウェルカムメッセージ。空白の場合は送信しない |
|
||||
| reply_timeout | int | いいえ | 返信タイムアウト(秒、デフォルト:5) |
|
||||
| max_steps | int | いいえ | エージェントの最大実行ステップ数(デフォルト:10) |
|
||||
|
||||
## セットアップ手順
|
||||
|
||||
1. [企業WeChat管理コンソール](https://work.weixin.qq.com/wework_admin) にログイン
|
||||
2. 「アプリ管理」→「AIボット」に進み、AIボットを作成または選択
|
||||
3. AIボット設定ページで「メッセージ受信」情報を入力:
|
||||
- **URL**:`http://<your-server-ip>:18790/webhook/wecom-aibot`
|
||||
- **Token**:ランダム生成またはカスタム
|
||||
- **EncodingAESKey**:「ランダム生成」をクリックして43文字のキーを取得
|
||||
4. TokenとEncodingAESKeyをPicoClawの設定ファイルに入力し、サービスを起動してから管理コンソールに戻って保存(企業WeChatが検証リクエストを送信します)
|
||||
|
||||
> [!TIP]
|
||||
> サーバーは企業WeChatのサーバーからアクセス可能である必要があります。イントラネットやローカル開発環境の場合は、[ngrok](https://ngrok.com) またはfrpを使用してトンネリングしてください。
|
||||
|
||||
## ストリーミングレスポンスプロトコル
|
||||
|
||||
WeCom AIボットは「ストリーミングプル」プロトコルを使用しており、通常のWebhookの一回限りの返信とは異なります:
|
||||
|
||||
```
|
||||
ユーザーがメッセージを送信
|
||||
│
|
||||
▼
|
||||
PicoClawが即座に {finish: false} を返す(エージェントが処理開始)
|
||||
│
|
||||
▼
|
||||
企業WeChatが約1秒ごとに {msgtype: "stream", stream: {id: "..."}} でプル
|
||||
│
|
||||
├─ エージェント未完了 → {finish: false} を返す(待機継続)
|
||||
│
|
||||
└─ エージェント完了 → {finish: true, content: "返信内容"} を返す
|
||||
```
|
||||
|
||||
**タイムアウト処理**(タスクが30秒を超える場合):
|
||||
|
||||
エージェントの処理時間が約30秒を超えた場合(企業WeChatの最大ポーリングウィンドウは6分)、PicoClawは:
|
||||
|
||||
1. 即座にストリームを閉じ、ユーザーに「⏳ 正在处理中,请稍候,结果将稍后发送。」と表示
|
||||
2. エージェントはバックグラウンドで処理を継続
|
||||
3. エージェント完了後、メッセージに含まれる `response_url` を通じて最終返信をユーザーにプッシュ
|
||||
|
||||
> `response_url` は企業WeChatが発行し、有効期限は1時間、使用は1回限りで、暗号化不要。マークダウンメッセージ本文をそのままPOSTするだけです。
|
||||
|
||||
## ウェルカムメッセージ
|
||||
|
||||
`welcome_message` を設定すると、ユーザーがAIボットとのチャットウィンドウを開いたとき(`enter_chat` イベント)に、PicoClawが自動的にそのメッセージを返信します。空白の場合は無視されます。
|
||||
|
||||
```json
|
||||
"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?"
|
||||
```
|
||||
|
||||
## よくある質問
|
||||
|
||||
### コールバックURL検証の失敗
|
||||
|
||||
- サーバーのファイアウォールで該当ポートが開放されているか確認(デフォルト18790)
|
||||
- `token` と `encoding_aes_key` が正しく入力されているか確認
|
||||
- PicoClawのログに企業WeChatからのGETリクエストが届いているか確認
|
||||
|
||||
### メッセージに返信がない
|
||||
|
||||
- `allow_from` が誤って送信者を制限していないか確認
|
||||
- ログに `context canceled` またはエージェントエラーが出ていないか確認
|
||||
- エージェント設定(`model_name` など)が正しいか確認
|
||||
|
||||
### 長時間タスクで最終プッシュが届かない
|
||||
|
||||
- メッセージコールバックに `response_url` が含まれているか確認(新バージョンの企業WeChat AIボットのみ対応)
|
||||
- サーバーが外部ネットワークへのアウトバウンドリクエストを送信できるか確認(`response_url` へのPOSTが必要)
|
||||
- ログのキーワード `response_url mode` と `Sending reply via response_url` を確認
|
||||
|
||||
## 参考ドキュメント
|
||||
|
||||
- [企業WeChat AIボット連携ドキュメント](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [ストリーミングレスポンスプロトコルの説明](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [response_url によるプロアクティブ返信](https://developer.work.weixin.qq.com/document/path/101138)
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
> Back to [README](../../../../README.md)
|
||||
|
||||
# WeCom AI Bot
|
||||
|
||||
The WeCom AI Bot is an official AI conversation integration provided by WeCom. It supports both private and group chats, has a built-in streaming response protocol, and supports proactively pushing the final reply via `response_url` after a timeout.
|
||||
|
||||
## Comparison with Other WeCom Channels
|
||||
|
||||
| Feature | WeCom Bot | WeCom App | **WeCom AI Bot** |
|
||||
|---------|-----------|-----------|-----------------|
|
||||
| Private Chat | ✅ | ✅ | ✅ |
|
||||
| Group Chat | ✅ | ❌ | ✅ |
|
||||
| Streaming Output | ❌ | ❌ | ✅ |
|
||||
| Proactive Push on Timeout | ❌ | ✅ | ✅ |
|
||||
| Configuration Complexity | Low | High | Medium |
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_aibot": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-aibot",
|
||||
"allow_from": [],
|
||||
"welcome_message": "你好!有什么可以帮助你的吗?",
|
||||
"max_steps": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ---------------- | ------ | -------- | -------------------------------------------------- |
|
||||
| token | string | Yes | Callback verification token, configured on the AI Bot management page |
|
||||
| encoding_aes_key | string | Yes | 43-character AES key, randomly generated on the AI Bot management page |
|
||||
| webhook_path | string | No | Webhook path (default: /webhook/wecom-aibot) |
|
||||
| allow_from | array | No | User ID allowlist; empty array allows all users |
|
||||
| welcome_message | string | No | Welcome message sent when a user opens the chat; leave empty to disable |
|
||||
| reply_timeout | int | No | Reply timeout in seconds (default: 5) |
|
||||
| max_steps | int | No | Maximum agent execution steps (default: 10) |
|
||||
|
||||
## Setup
|
||||
|
||||
1. Log in to the [WeCom Admin Console](https://work.weixin.qq.com/wework_admin)
|
||||
2. Go to "App Management" → "AI Bot", then create or select an AI Bot
|
||||
3. On the AI Bot configuration page, fill in the "Message Reception" details:
|
||||
- **URL**: `http://<your-server-ip>:18790/webhook/wecom-aibot`
|
||||
- **Token**: Randomly generated or custom
|
||||
- **EncodingAESKey**: Click "Random Generate" to get a 43-character key
|
||||
4. Enter the Token and EncodingAESKey into the PicoClaw config file, start the service, then return to the admin console to save (WeCom will send a verification request)
|
||||
|
||||
> [!TIP]
|
||||
> The server must be accessible by WeCom's servers. If you are on an intranet or developing locally, use [ngrok](https://ngrok.com) or frp for tunneling.
|
||||
|
||||
## Streaming Response Protocol
|
||||
|
||||
WeCom AI Bot uses a "streaming pull" protocol, which differs from the one-shot reply of a standard webhook:
|
||||
|
||||
```
|
||||
User sends a message
|
||||
│
|
||||
▼
|
||||
PicoClaw immediately returns {finish: false} (Agent starts processing)
|
||||
│
|
||||
▼
|
||||
WeCom pulls approximately every 1 second with {msgtype: "stream", stream: {id: "..."}}
|
||||
│
|
||||
├─ Agent not done → returns {finish: false} (keep waiting)
|
||||
│
|
||||
└─ Agent done → returns {finish: true, content: "reply content"}
|
||||
```
|
||||
|
||||
**Timeout Handling** (task exceeds 30 seconds):
|
||||
|
||||
If the Agent takes longer than approximately 30 seconds (WeCom's maximum polling window is 6 minutes), PicoClaw will:
|
||||
|
||||
1. Immediately close the stream and show the user: "⏳ 正在处理中,请稍候,结果将稍后发送。"
|
||||
2. The Agent continues running in the background
|
||||
3. Once the Agent finishes, the final reply is proactively pushed to the user via the `response_url` included in the message
|
||||
|
||||
> `response_url` is issued by WeCom, valid for 1 hour, can only be used once, requires no encryption — just POST the markdown message body directly.
|
||||
|
||||
## Welcome Message
|
||||
|
||||
When `welcome_message` is configured, PicoClaw will automatically reply with it when a user opens the chat window with the AI Bot (`enter_chat` event). Leave it empty to silently ignore the event.
|
||||
|
||||
```json
|
||||
"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?"
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
### Callback URL Verification Failed
|
||||
|
||||
- Confirm the server firewall has the relevant port open (default 18790)
|
||||
- Confirm `token` and `encoding_aes_key` are entered correctly
|
||||
- Check PicoClaw logs to see if a GET request from WeCom was received
|
||||
|
||||
### Messages Not Getting a Reply
|
||||
|
||||
- Check whether `allow_from` is accidentally restricting the sender
|
||||
- Look for `context canceled` or Agent errors in the logs
|
||||
- Confirm the Agent configuration (e.g., `model_name`) is correct
|
||||
|
||||
### No Final Push Received for Long-Running Tasks
|
||||
|
||||
- Confirm the message callback includes `response_url` (only supported by the newer WeCom AI Bot)
|
||||
- Confirm the server can make outbound requests (needs to POST to `response_url`)
|
||||
- Check logs for keywords `response_url mode` and `Sending reply via response_url`
|
||||
|
||||
## Reference
|
||||
|
||||
- [WeCom AI Bot Integration Docs](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [Streaming Response Protocol](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [Proactive Reply via response_url](https://developer.work.weixin.qq.com/document/path/101138)
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
> Voltar ao [README](../../../../README.pt-br.md)
|
||||
|
||||
# WeCom AI Bot
|
||||
|
||||
O WeCom AI Bot é uma forma oficial de integração de conversas com IA fornecida pelo WeCom. Suporta conversas privadas e em grupo, possui um protocolo de resposta em streaming integrado e suporta o envio proativo da resposta final via `response_url` após um timeout.
|
||||
|
||||
## Comparação com outros canais WeCom
|
||||
|
||||
| Recurso | WeCom Bot | WeCom App | **WeCom AI Bot** |
|
||||
|---------|-----------|-----------|-----------------|
|
||||
| Chat privado | ✅ | ✅ | ✅ |
|
||||
| Chat em grupo | ✅ | ❌ | ✅ |
|
||||
| Saída em streaming | ❌ | ❌ | ✅ |
|
||||
| Push proativo em timeout | ❌ | ✅ | ✅ |
|
||||
| Complexidade de configuração | Baixa | Alta | Média |
|
||||
|
||||
## Configuração
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_aibot": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-aibot",
|
||||
"allow_from": [],
|
||||
"welcome_message": "你好!有什么可以帮助你的吗?",
|
||||
"max_steps": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
| ---------------- | ------ | ----------- | -------------------------------------------------- |
|
||||
| token | string | Sim | Token de verificação de callback, configurado na página de gerenciamento do AI Bot |
|
||||
| encoding_aes_key | string | Sim | Chave AES de 43 caracteres, gerada aleatoriamente na página de gerenciamento do AI Bot |
|
||||
| webhook_path | string | Não | Caminho do webhook (padrão: /webhook/wecom-aibot) |
|
||||
| allow_from | array | Não | Lista de permissão de IDs de usuários; array vazio permite todos os usuários |
|
||||
| welcome_message | string | Não | Mensagem de boas-vindas enviada quando o usuário abre o chat; deixe vazio para desativar |
|
||||
| reply_timeout | int | Não | Timeout de resposta em segundos (padrão: 5) |
|
||||
| max_steps | int | Não | Número máximo de etapas de execução do agente (padrão: 10) |
|
||||
|
||||
## Configuração passo a passo
|
||||
|
||||
1. Faça login no [Console de Administração do WeCom](https://work.weixin.qq.com/wework_admin)
|
||||
2. Acesse "Gerenciamento de Apps" → "AI Bot", depois crie ou selecione um AI Bot
|
||||
3. Na página de configuração do AI Bot, preencha as informações de "Recebimento de Mensagens":
|
||||
- **URL**: `http://<your-server-ip>:18790/webhook/wecom-aibot`
|
||||
- **Token**: Gerado aleatoriamente ou personalizado
|
||||
- **EncodingAESKey**: Clique em "Gerar Aleatoriamente" para obter uma chave de 43 caracteres
|
||||
4. Insira o Token e o EncodingAESKey no arquivo de configuração do PicoClaw, inicie o serviço e volte ao console de administração para salvar (o WeCom enviará uma requisição de verificação)
|
||||
|
||||
> [!TIP]
|
||||
> O servidor precisa ser acessível pelos servidores do WeCom. Se estiver em uma intranet ou desenvolvendo localmente, use [ngrok](https://ngrok.com) ou frp para tunelamento.
|
||||
|
||||
## Protocolo de resposta em streaming
|
||||
|
||||
O WeCom AI Bot usa um protocolo de "pull em streaming", diferente da resposta única de um webhook padrão:
|
||||
|
||||
```
|
||||
Usuário envia uma mensagem
|
||||
│
|
||||
▼
|
||||
PicoClaw retorna imediatamente {finish: false} (Agente começa a processar)
|
||||
│
|
||||
▼
|
||||
WeCom faz pull aproximadamente a cada 1 segundo com {msgtype: "stream", stream: {id: "..."}}
|
||||
│
|
||||
├─ Agente não concluído → retorna {finish: false} (continuar aguardando)
|
||||
│
|
||||
└─ Agente concluído → retorna {finish: true, content: "conteúdo da resposta"}
|
||||
```
|
||||
|
||||
**Tratamento de timeout** (tarefa excede 30 segundos):
|
||||
|
||||
Se o processamento do agente demorar mais de aproximadamente 30 segundos (a janela máxima de polling do WeCom é de 6 minutos), o PicoClaw irá:
|
||||
|
||||
1. Fechar imediatamente o stream e exibir ao usuário: "⏳ 正在处理中,请稍候,结果将稍后发送。"
|
||||
2. O agente continua executando em segundo plano
|
||||
3. Após a conclusão do agente, a resposta final é enviada proativamente ao usuário via `response_url` incluído na mensagem
|
||||
|
||||
> `response_url` é emitido pelo WeCom, válido por 1 hora, pode ser usado apenas uma vez, sem necessidade de criptografia — basta fazer um POST com o corpo da mensagem em markdown diretamente.
|
||||
|
||||
## Mensagem de boas-vindas
|
||||
|
||||
Quando `welcome_message` está configurado, o PicoClaw responde automaticamente com essa mensagem quando um usuário abre a janela de chat com o AI Bot (evento `enter_chat`). Deixe vazio para ignorar silenciosamente.
|
||||
|
||||
```json
|
||||
"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?"
|
||||
```
|
||||
|
||||
## Perguntas frequentes
|
||||
|
||||
### Falha na verificação da URL de callback
|
||||
|
||||
- Confirme que o firewall do servidor tem a porta correspondente aberta (padrão 18790)
|
||||
- Confirme que `token` e `encoding_aes_key` estão preenchidos corretamente
|
||||
- Verifique os logs do PicoClaw para ver se uma requisição GET do WeCom foi recebida
|
||||
|
||||
### Mensagens sem resposta
|
||||
|
||||
- Verifique se `allow_from` está restringindo acidentalmente o remetente
|
||||
- Procure por `context canceled` ou erros do agente nos logs
|
||||
- Confirme que a configuração do agente (ex.: `model_name`) está correta
|
||||
|
||||
### Nenhum push final recebido para tarefas longas
|
||||
|
||||
- Confirme que o callback da mensagem inclui `response_url` (suportado apenas pelo novo WeCom AI Bot)
|
||||
- Confirme que o servidor consegue fazer requisições de saída (precisa fazer POST para `response_url`)
|
||||
- Verifique nos logs as palavras-chave `response_url mode` e `Sending reply via response_url`
|
||||
|
||||
## Referências
|
||||
|
||||
- [Documentação de integração do WeCom AI Bot](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [Descrição do protocolo de resposta em streaming](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [Resposta proativa via response_url](https://developer.work.weixin.qq.com/document/path/101138)
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
> Quay lại [README](../../../../README.vi.md)
|
||||
|
||||
# WeCom AI Bot
|
||||
|
||||
WeCom AI Bot là phương thức tích hợp hội thoại AI chính thức do WeCom cung cấp. Hỗ trợ cả chat riêng tư và chat nhóm, tích hợp giao thức phản hồi streaming, và hỗ trợ chủ động đẩy phản hồi cuối cùng qua `response_url` sau khi hết thời gian chờ.
|
||||
|
||||
## So sánh với các kênh WeCom khác
|
||||
|
||||
| Tính năng | WeCom Bot | WeCom App | **WeCom AI Bot** |
|
||||
|-----------|-----------|-----------|-----------------|
|
||||
| Chat riêng tư | ✅ | ✅ | ✅ |
|
||||
| Chat nhóm | ✅ | ❌ | ✅ |
|
||||
| Đầu ra streaming | ❌ | ❌ | ✅ |
|
||||
| Đẩy chủ động khi timeout | ❌ | ✅ | ✅ |
|
||||
| Độ phức tạp cấu hình | Thấp | Cao | Trung bình |
|
||||
|
||||
## Cấu hình
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_aibot": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-aibot",
|
||||
"allow_from": [],
|
||||
"welcome_message": "你好!有什么可以帮助你的吗?",
|
||||
"max_steps": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Trường | Kiểu | Bắt buộc | Mô tả |
|
||||
| ---------------- | ------ | --------- | -------------------------------------------------- |
|
||||
| token | string | Có | Token xác minh callback, cấu hình trên trang quản lý AI Bot |
|
||||
| encoding_aes_key | string | Có | Khóa AES 43 ký tự, được tạo ngẫu nhiên trên trang quản lý AI Bot |
|
||||
| webhook_path | string | Không | Đường dẫn webhook (mặc định: /webhook/wecom-aibot) |
|
||||
| allow_from | array | Không | Danh sách cho phép ID người dùng; mảng rỗng cho phép tất cả người dùng |
|
||||
| welcome_message | string | Không | Tin nhắn chào mừng gửi khi người dùng mở chat; để trống để tắt |
|
||||
| reply_timeout | int | Không | Thời gian chờ phản hồi tính bằng giây (mặc định: 5) |
|
||||
| max_steps | int | Không | Số bước thực thi tối đa của agent (mặc định: 10) |
|
||||
|
||||
## Hướng dẫn thiết lập
|
||||
|
||||
1. Đăng nhập vào [Bảng điều khiển quản trị WeCom](https://work.weixin.qq.com/wework_admin)
|
||||
2. Vào "Quản lý ứng dụng" → "AI Bot", sau đó tạo hoặc chọn một AI Bot
|
||||
3. Trên trang cấu hình AI Bot, điền thông tin "Nhận tin nhắn":
|
||||
- **URL**: `http://<your-server-ip>:18790/webhook/wecom-aibot`
|
||||
- **Token**: Tạo ngẫu nhiên hoặc tùy chỉnh
|
||||
- **EncodingAESKey**: Nhấp "Tạo ngẫu nhiên" để lấy khóa 43 ký tự
|
||||
4. Nhập Token và EncodingAESKey vào file cấu hình PicoClaw, khởi động dịch vụ rồi quay lại bảng điều khiển quản trị để lưu (WeCom sẽ gửi yêu cầu xác minh)
|
||||
|
||||
> [!TIP]
|
||||
> Máy chủ cần có thể truy cập được từ các máy chủ WeCom. Nếu bạn đang ở mạng nội bộ hoặc phát triển cục bộ, hãy sử dụng [ngrok](https://ngrok.com) hoặc frp để tạo tunnel.
|
||||
|
||||
## Giao thức phản hồi streaming
|
||||
|
||||
WeCom AI Bot sử dụng giao thức "pull streaming", khác với phản hồi một lần của webhook thông thường:
|
||||
|
||||
```
|
||||
Người dùng gửi tin nhắn
|
||||
│
|
||||
▼
|
||||
PicoClaw trả về ngay {finish: false} (Agent bắt đầu xử lý)
|
||||
│
|
||||
▼
|
||||
WeCom pull khoảng mỗi 1 giây với {msgtype: "stream", stream: {id: "..."}}
|
||||
│
|
||||
├─ Agent chưa xong → trả về {finish: false} (tiếp tục chờ)
|
||||
│
|
||||
└─ Agent xong → trả về {finish: true, content: "nội dung phản hồi"}
|
||||
```
|
||||
|
||||
**Xử lý timeout** (tác vụ vượt quá 30 giây):
|
||||
|
||||
Nếu thời gian xử lý của agent vượt quá khoảng 30 giây (cửa sổ polling tối đa của WeCom là 6 phút), PicoClaw sẽ:
|
||||
|
||||
1. Đóng stream ngay lập tức và hiển thị cho người dùng: "⏳ 正在处理中,请稍候,结果将稍后发送。"
|
||||
2. Agent tiếp tục chạy ở nền
|
||||
3. Sau khi agent hoàn thành, phản hồi cuối cùng được chủ động đẩy đến người dùng qua `response_url` có trong tin nhắn
|
||||
|
||||
> `response_url` do WeCom cấp, có hiệu lực 1 giờ, chỉ dùng được một lần, không cần mã hóa — chỉ cần POST trực tiếp nội dung tin nhắn markdown.
|
||||
|
||||
## Tin nhắn chào mừng
|
||||
|
||||
Khi `welcome_message` được cấu hình, PicoClaw sẽ tự động phản hồi bằng tin nhắn đó khi người dùng mở cửa sổ chat với AI Bot (sự kiện `enter_chat`). Để trống để bỏ qua im lặng.
|
||||
|
||||
```json
|
||||
"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?"
|
||||
```
|
||||
|
||||
## Câu hỏi thường gặp
|
||||
|
||||
### Xác minh URL callback thất bại
|
||||
|
||||
- Xác nhận tường lửa máy chủ đã mở cổng tương ứng (mặc định 18790)
|
||||
- Xác nhận `token` và `encoding_aes_key` được điền đúng
|
||||
- Kiểm tra log PicoClaw xem có nhận được yêu cầu GET từ WeCom không
|
||||
|
||||
### Tin nhắn không nhận được phản hồi
|
||||
|
||||
- Kiểm tra xem `allow_from` có vô tình hạn chế người gửi không
|
||||
- Tìm `context canceled` hoặc lỗi agent trong log
|
||||
- Xác nhận cấu hình agent (ví dụ: `model_name`) là đúng
|
||||
|
||||
### Không nhận được push cuối cùng cho tác vụ dài
|
||||
|
||||
- Xác nhận callback tin nhắn có chứa `response_url` (chỉ hỗ trợ bởi WeCom AI Bot phiên bản mới)
|
||||
- Xác nhận máy chủ có thể thực hiện yêu cầu ra ngoài (cần POST đến `response_url`)
|
||||
- Kiểm tra log với từ khóa `response_url mode` và `Sending reply via response_url`
|
||||
|
||||
## Tài liệu tham khảo
|
||||
|
||||
- [Tài liệu tích hợp WeCom AI Bot](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [Mô tả giao thức phản hồi streaming](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [Phản hồi chủ động qua response_url](https://developer.work.weixin.qq.com/document/path/101138)
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
> 返回 [README](../../../../README.zh.md)
|
||||
|
||||
# 企业微信智能机器人 (AI Bot)
|
||||
|
||||
企业微信智能机器人(AI Bot)是企业微信官方提供的 AI 对话接入方式,支持私聊与群聊,内置流式响应协议。PicoClaw 当前同时支持两种接入模式:
|
||||
|
||||
- WebSocket 长连接模式:使用 `bot_id` + `secret`,优先级更高,推荐使用
|
||||
- Webhook 短连接模式:使用 `token` + `encoding_aes_key`,兼容传统回调,并支持超时后通过 `response_url` 主动推送最终回复
|
||||
|
||||
## 与其他 WeCom 通道的对比
|
||||
|
||||
| 特性 | WeCom Bot | WeCom App | **WeCom AI Bot** |
|
||||
|------|-----------|-----------|-----------------|
|
||||
| 私聊 | ✅ | ✅ | ✅ |
|
||||
| 群聊 | ✅ | ❌ | ✅ |
|
||||
| 流式输出 | ❌ | ❌ | ✅ |
|
||||
| 超时主动推送 | ❌ | ✅ | ✅ |
|
||||
| 配置复杂度 | 低 | 高 | 中 |
|
||||
|
||||
## 配置
|
||||
|
||||
### WebSocket 长连接模式(推荐)
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_aibot": {
|
||||
"enabled": true,
|
||||
"bot_id": "YOUR_BOT_ID",
|
||||
"secret": "YOUR_SECRET",
|
||||
"allow_from": [],
|
||||
"welcome_message": "你好!有什么可以帮助你的吗?",
|
||||
"max_steps": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Webhook 短连接模式
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_aibot": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-aibot",
|
||||
"allow_from": [],
|
||||
"welcome_message": "你好!有什么可以帮助你的吗?",
|
||||
"processing_message": "⏳ Processing, please wait. The results will be sent shortly.",
|
||||
"max_steps": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### WebSocket 模式字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 描述 |
|
||||
|--------|--------|------|--------------------------------------------|
|
||||
| bot_id | string | 是 | AI Bot 的唯一标识,在 AI Bot 管理页面配置 |
|
||||
| secret | string | 是 | AI Bot 的密钥,在 AI Bot 管理页面配置 |
|
||||
|
||||
### Webhook 模式字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 描述 |
|
||||
|------------------|--------|------|----------------------------------------------|
|
||||
| token | string | 是 | 回调验证令牌,在 AI Bot 管理页面配置 |
|
||||
| encoding_aes_key | string | 是 | 43 字符 AES 密钥,在 AI Bot 管理页面随机生成 |
|
||||
| webhook_path | string | 否 | Webhook 路径,默认 `/webhook/wecom-aibot` |
|
||||
| processing_message | string | 否 | 流式超时后返回给用户的提示语 |
|
||||
|
||||
### 通用字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 描述 |
|
||||
|-----------------|--------|------|------------------------------------------|
|
||||
| allow_from | array | 否 | 用户 ID 白名单,空数组表示允许所有用户 |
|
||||
| welcome_message | string | 否 | 用户进入聊天时发送的欢迎语,留空则不发送 |
|
||||
| reply_timeout | int | 否 | 回复超时时间(秒,默认:5) |
|
||||
| max_steps | int | 否 | Agent 最大执行步骤数(默认:10) |
|
||||
|
||||
## 模式选择
|
||||
|
||||
- 当 `bot_id` 和 `secret` 同时存在时,PicoClaw 会优先使用 WebSocket 长连接模式
|
||||
- 否则,当 `token` 和 `encoding_aes_key` 同时存在时,PicoClaw 会使用 Webhook 短连接模式
|
||||
|
||||
## 设置流程
|
||||
|
||||
### WebSocket 长连接模式
|
||||
|
||||
1. 登录 [企业微信管理后台](https://work.weixin.qq.com/wework_admin)
|
||||
2. 进入"应用管理" → "智能机器人",创建或选择一个 AI Bot
|
||||
3. 在 AI Bot 配置页面,配置 Bot 的名称、头像等信息,获取 `Bot ID` 和 `Secret`
|
||||
4. 在 PicoClaw 配置文件中添加上述配置,重启 PicoClaw
|
||||
|
||||
### Webhook 短连接模式
|
||||
|
||||
1. 登录 [企业微信管理后台](https://work.weixin.qq.com/wework_admin)
|
||||
2. 进入"应用管理" → "智能机器人",创建或选择一个 AI Bot
|
||||
3. 在 AI Bot 配置页面,填写"消息接收"信息:
|
||||
- **URL**:`http://<your-server-ip>:18790/webhook/wecom-aibot`
|
||||
- **Token**:随机生成或自定义
|
||||
- **EncodingAESKey**:点击"随机生成",得到 43 字符密钥
|
||||
4. 将 Token 和 EncodingAESKey 填入 PicoClaw 配置文件,启动服务后回到管理后台保存
|
||||
|
||||
> [!TIP]
|
||||
> 服务器需要能被企业微信服务器访问。如在内网或本地开发,可使用 [ngrok](https://ngrok.com) 或 frp 做内网穿透。
|
||||
|
||||
## Webhook 模式的流式响应协议
|
||||
|
||||
Webhook 模式使用"流式拉取"协议,区别于普通 Webhook 的一次性回复:
|
||||
|
||||
```
|
||||
用户发消息
|
||||
│
|
||||
▼
|
||||
PicoClaw 立即返回 {finish: false}(Agent 开始处理)
|
||||
│
|
||||
▼
|
||||
企业微信每隔约 1 秒拉取一次 {msgtype: "stream", stream: {id: "..."}}
|
||||
│
|
||||
├─ Agent 未完成 → 返回 {finish: false}(继续等待)
|
||||
│
|
||||
└─ Agent 完成 → 返回 {finish: true, content: "回答内容"}
|
||||
```
|
||||
|
||||
**超时处理**(任务超过约 30 秒):
|
||||
|
||||
若 Agent 处理时间超过轮询窗口,PicoClaw 会:
|
||||
|
||||
1. 立即关闭流,向用户显示 `processing_message` 提示语
|
||||
2. Agent 继续在后台运行
|
||||
3. Agent 完成后,通过消息中携带的 `response_url` 将最终回复主动推送给用户
|
||||
|
||||
> `response_url` 由企业微信颁发,有效期 1 小时,只可使用一次,无需加密,直接 POST markdown 消息体即可。
|
||||
|
||||
## 超时提示语
|
||||
|
||||
配置 `processing_message` 后,当 Webhook 模式的流式轮询超时并切换到 `response_url` 主动推送模式时,PicoClaw 会先返回这段提示语来结束当前流。
|
||||
|
||||
```json
|
||||
"processing_message": "⏳ Processing, please wait. The results will be sent shortly."
|
||||
```
|
||||
|
||||
## 欢迎语
|
||||
|
||||
配置 `welcome_message` 后,当用户打开与 AI Bot 的聊天窗口时(`enter_chat` 事件),PicoClaw 会自动回复该欢迎语。留空则静默忽略。
|
||||
|
||||
```json
|
||||
"welcome_message": "你好!我是 PicoClaw AI 助手,有什么可以帮你?"
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### WebSocket 模式无法连接
|
||||
|
||||
- 检查 `bot_id` 和 `secret` 是否填写正确
|
||||
- 查看日志中是否有 WebSocket 连接或鉴权失败信息
|
||||
- 确认服务器可以访问企业微信长连接接口
|
||||
|
||||
### 回调 URL 验证失败
|
||||
|
||||
|
||||
- 确认 `token` 与 `encoding_aes_key` 填写正确
|
||||
- 确认服务器防火墙已开放对应端口
|
||||
- 检查 PicoClaw 日志是否收到了来自企业微信的验证请求
|
||||
|
||||
### 消息没有回复
|
||||
|
||||
- 检查 `allow_from` 是否意外限制了发送者
|
||||
- 查看日志中是否出现 `context canceled` 或 Agent 错误
|
||||
- 确认 Agent 配置(`model_name` 等)正确
|
||||
|
||||
### 超长任务没有收到最终推送
|
||||
|
||||
- 确认消息回调中携带了 `response_url`
|
||||
- 确认服务器能主动访问外网
|
||||
- 查看日志关键词 `response_url mode` 和 `Sending reply via response_url`
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [企业微信 AI Bot 接入文档](https://developer.work.weixin.qq.com/document/path/101463)
|
||||
- [流式响应协议说明](https://developer.work.weixin.qq.com/document/path/100719)
|
||||
- [response_url 主动回复](https://developer.work.weixin.qq.com/document/path/101138)
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
> Retour au [README](../../../../README.fr.md)
|
||||
|
||||
# Application interne WeCom
|
||||
|
||||
Une application interne WeCom est une application créée par une entreprise au sein de WeCom, principalement destinée à un usage interne. Grâce aux applications internes WeCom, les entreprises peuvent assurer une communication et une collaboration efficaces avec leurs employés, améliorant ainsi la productivité.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_app": {
|
||||
"enabled": true,
|
||||
"corp_id": "wwxxxxxxxxxxxxxxxx",
|
||||
"corp_secret": "YOUR_CORP_SECRET",
|
||||
"agent_id": 1000002,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-app",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Champ | Type | Requis | Description |
|
||||
| ---------------- | ------ | ------ | ---------------------------------------- |
|
||||
| corp_id | string | Oui | ID de l'entreprise |
|
||||
| corp_secret | string | Oui | Secret de l'application |
|
||||
| agent_id | int | Oui | ID de l'agent de l'application |
|
||||
| token | string | Oui | Jeton de vérification du callback |
|
||||
| encoding_aes_key | string | Oui | Clé AES de 43 caractères |
|
||||
| webhook_path | string | Non | Chemin du webhook (par défaut : /webhook/wecom-app) |
|
||||
| allow_from | array | Non | Liste blanche d'ID utilisateurs |
|
||||
| reply_timeout | int | Non | Délai de réponse en secondes |
|
||||
|
||||
## Procédure de configuration
|
||||
|
||||
1. Connectez-vous à la [console d'administration WeCom](https://work.weixin.qq.com/)
|
||||
2. Accédez à « Gestion des applications » -> « Créer une application »
|
||||
3. Obtenez l'ID d'entreprise (CorpID) et le Secret de l'application
|
||||
4. Configurez « Réception des messages » dans les paramètres de l'application pour obtenir le Token et l'EncodingAESKey
|
||||
5. Définissez l'URL de callback sur `http://<your-server-ip>:<port>/webhook/wecom-app`
|
||||
6. Saisissez le CorpID, le Secret, l'AgentID et les autres informations dans le fichier de configuration
|
||||
|
||||
Remarque : PicoClaw utilise désormais un serveur HTTP Gateway partagé pour recevoir les callbacks webhook de tous les canaux. L'adresse d'écoute par défaut est 127.0.0.1:18790. Pour recevoir des callbacks depuis l'internet public, configurez un reverse proxy de votre domaine externe vers le Gateway (port par défaut 18790).
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
> [README](../../../../README.ja.md) に戻る
|
||||
|
||||
# 企業WeChat 自社開発アプリ
|
||||
|
||||
企業WeChat 自社開発アプリとは、企業が企業WeChat内で作成するアプリケーションで、主に社内利用を目的としています。企業WeChat 自社開発アプリを通じて、企業は従業員との効率的なコミュニケーションと協業を実現し、業務効率を向上させることができます。
|
||||
|
||||
## 設定
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_app": {
|
||||
"enabled": true,
|
||||
"corp_id": "wwxxxxxxxxxxxxxxxx",
|
||||
"corp_secret": "YOUR_CORP_SECRET",
|
||||
"agent_id": 1000002,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-app",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 型 | 必須 | 説明 |
|
||||
| ---------------- | ------ | ---- | ---------------------------------------- |
|
||||
| corp_id | string | はい | 企業ID |
|
||||
| corp_secret | string | はい | アプリケーションシークレット |
|
||||
| agent_id | int | はい | アプリケーションエージェントID |
|
||||
| token | string | はい | コールバック検証トークン |
|
||||
| encoding_aes_key | string | はい | 43文字のAESキー |
|
||||
| webhook_path | string | いいえ | Webhookパス(デフォルト:/webhook/wecom-app) |
|
||||
| allow_from | array | いいえ | ユーザーIDの許可リスト |
|
||||
| reply_timeout | int | いいえ | 返信タイムアウト(秒) |
|
||||
|
||||
## セットアップ手順
|
||||
|
||||
1. [企業WeChat管理コンソール](https://work.weixin.qq.com/) にログイン
|
||||
2. 「アプリ管理」→「アプリを作成」に進む
|
||||
3. 企業ID(CorpID)とアプリのSecretを取得
|
||||
4. アプリ設定で「メッセージ受信」を設定し、TokenとEncodingAESKeyを取得
|
||||
5. コールバックURLを `http://<your-server-ip>:<port>/webhook/wecom-app` に設定
|
||||
6. CorpID、Secret、AgentIDなどの情報を設定ファイルに入力
|
||||
|
||||
注意:PicoClawは現在、すべてのチャンネルのwebhookコールバックを受信するために共有のGateway HTTPサーバーを使用しています。デフォルトのリスニングアドレスは127.0.0.1:18790です。公共インターネットからコールバックを受信するには、外部ドメインをGateway(デフォルトポート18790)にリバースプロキシしてください。
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
> Back to [README](../../../../README.md)
|
||||
|
||||
# WeCom Internal App
|
||||
|
||||
A WeCom Internal App is an application created by an enterprise within WeCom, primarily intended for internal use. Through WeCom Internal Apps, enterprises can achieve efficient communication and collaboration with employees, improving productivity.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_app": {
|
||||
"enabled": true,
|
||||
"corp_id": "wwxxxxxxxxxxxxxxxx",
|
||||
"corp_secret": "YOUR_CORP_SECRET",
|
||||
"agent_id": 1000002,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-app",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ---------------- | ------ | -------- | ---------------------------------------- |
|
||||
| corp_id | string | Yes | Enterprise ID |
|
||||
| corp_secret | string | Yes | Application secret |
|
||||
| agent_id | int | Yes | Application agent ID |
|
||||
| token | string | Yes | Callback verification token |
|
||||
| encoding_aes_key | string | Yes | 43-character AES key |
|
||||
| webhook_path | string | No | Webhook path (default: /webhook/wecom-app) |
|
||||
| allow_from | array | No | User ID allowlist |
|
||||
| reply_timeout | int | No | Reply timeout in seconds |
|
||||
|
||||
## Setup
|
||||
|
||||
1. Log in to the [WeCom Admin Console](https://work.weixin.qq.com/)
|
||||
2. Go to "App Management" -> "Create App"
|
||||
3. Obtain the Enterprise ID (CorpID) and App Secret
|
||||
4. Configure "Receive Messages" in the app settings to get the Token and EncodingAESKey
|
||||
5. Set the callback URL to `http://<your-server-ip>:<port>/webhook/wecom-app`
|
||||
6. Enter the CorpID, Secret, AgentID, and other details into the config file
|
||||
|
||||
Note: PicoClaw now uses a shared Gateway HTTP server to receive webhook callbacks for all channels. The default listening address is 127.0.0.1:18790. To receive callbacks from the public internet, reverse-proxy your external domain to the Gateway (default port 18790).
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
> Voltar ao [README](../../../../README.pt-br.md)
|
||||
|
||||
# App Interno WeCom
|
||||
|
||||
Um App Interno WeCom é um aplicativo criado por uma empresa dentro do WeCom, destinado principalmente ao uso interno. Por meio dos Apps Internos WeCom, as empresas podem alcançar comunicação e colaboração eficientes com os funcionários, melhorando a produtividade.
|
||||
|
||||
## Configuração
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_app": {
|
||||
"enabled": true,
|
||||
"corp_id": "wwxxxxxxxxxxxxxxxx",
|
||||
"corp_secret": "YOUR_CORP_SECRET",
|
||||
"agent_id": 1000002,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-app",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
| ---------------- | ------ | ----------- | ---------------------------------------- |
|
||||
| corp_id | string | Sim | ID da empresa |
|
||||
| corp_secret | string | Sim | Segredo da aplicação |
|
||||
| agent_id | int | Sim | ID do agente da aplicação |
|
||||
| token | string | Sim | Token de verificação de callback |
|
||||
| encoding_aes_key | string | Sim | Chave AES de 43 caracteres |
|
||||
| webhook_path | string | Não | Caminho do webhook (padrão: /webhook/wecom-app) |
|
||||
| allow_from | array | Não | Lista de permissão de IDs de usuários |
|
||||
| reply_timeout | int | Não | Timeout de resposta em segundos |
|
||||
|
||||
## Configuração passo a passo
|
||||
|
||||
1. Faça login no [Console de Administração do WeCom](https://work.weixin.qq.com/)
|
||||
2. Acesse "Gerenciamento de Apps" -> "Criar App"
|
||||
3. Obtenha o ID da Empresa (CorpID) e o Secret do App
|
||||
4. Configure "Receber Mensagens" nas configurações do app para obter o Token e o EncodingAESKey
|
||||
5. Defina a URL de callback como `http://<your-server-ip>:<port>/webhook/wecom-app`
|
||||
6. Insira o CorpID, Secret, AgentID e outras informações no arquivo de configuração
|
||||
|
||||
Nota: O PicoClaw agora usa um servidor HTTP Gateway compartilhado para receber callbacks de webhook de todos os canais. O endereço de escuta padrão é 127.0.0.1:18790. Para receber callbacks da internet pública, configure um reverse proxy do seu domínio externo para o Gateway (porta padrão 18790).
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
> Quay lại [README](../../../../README.vi.md)
|
||||
|
||||
# Ứng dụng nội bộ WeCom
|
||||
|
||||
Ứng dụng nội bộ WeCom là ứng dụng được doanh nghiệp tạo ra trong WeCom, chủ yếu dùng cho mục đích nội bộ. Thông qua ứng dụng nội bộ WeCom, doanh nghiệp có thể thực hiện giao tiếp và cộng tác hiệu quả với nhân viên, nâng cao hiệu suất làm việc.
|
||||
|
||||
## Cấu hình
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_app": {
|
||||
"enabled": true,
|
||||
"corp_id": "wwxxxxxxxxxxxxxxxx",
|
||||
"corp_secret": "YOUR_CORP_SECRET",
|
||||
"agent_id": 1000002,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-app",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Trường | Kiểu | Bắt buộc | Mô tả |
|
||||
| ---------------- | ------ | --------- | ---------------------------------------- |
|
||||
| corp_id | string | Có | ID doanh nghiệp |
|
||||
| corp_secret | string | Có | Secret của ứng dụng |
|
||||
| agent_id | int | Có | ID agent của ứng dụng |
|
||||
| token | string | Có | Token xác minh callback |
|
||||
| encoding_aes_key | string | Có | Khóa AES 43 ký tự |
|
||||
| webhook_path | string | Không | Đường dẫn webhook (mặc định: /webhook/wecom-app) |
|
||||
| allow_from | array | Không | Danh sách cho phép ID người dùng |
|
||||
| reply_timeout | int | Không | Thời gian chờ phản hồi tính bằng giây |
|
||||
|
||||
## Hướng dẫn thiết lập
|
||||
|
||||
1. Đăng nhập vào [Bảng điều khiển quản trị WeCom](https://work.weixin.qq.com/)
|
||||
2. Vào "Quản lý ứng dụng" -> "Tạo ứng dụng"
|
||||
3. Lấy ID doanh nghiệp (CorpID) và Secret của ứng dụng
|
||||
4. Cấu hình "Nhận tin nhắn" trong cài đặt ứng dụng để lấy Token và EncodingAESKey
|
||||
5. Đặt URL callback thành `http://<your-server-ip>:<port>/webhook/wecom-app`
|
||||
6. Nhập CorpID, Secret, AgentID và các thông tin khác vào file cấu hình
|
||||
|
||||
Lưu ý: PicoClaw hiện sử dụng máy chủ HTTP Gateway dùng chung để nhận callback webhook cho tất cả các kênh. Địa chỉ lắng nghe mặc định là 127.0.0.1:18790. Để nhận callback từ internet công cộng, hãy cấu hình reverse proxy từ tên miền bên ngoài của bạn đến Gateway (cổng mặc định 18790).
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
> 返回 [README](../../../../README.zh.md)
|
||||
|
||||
# 企业微信自建应用
|
||||
|
||||
企业微信自建应用是指企业在企业微信中创建的应用,主要用于企业内部使用。通过企业微信自建应用,企业可以实现与员工的高效沟通和协作,提高工作效率。
|
||||
|
||||
## 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_app": {
|
||||
"enabled": true,
|
||||
"corp_id": "wwxxxxxxxxxxxxxxxx",
|
||||
"corp_secret": "YOUR_CORP_SECRET",
|
||||
"agent_id": 1000002,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-app",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 描述 |
|
||||
| ---------------- | ------ | ---- | ---------------------------------------- |
|
||||
| corp_id | string | 是 | 企业 ID |
|
||||
| corp_secret | string | 是 | 应用程序密钥 |
|
||||
| agent_id | int | 是 | 应用程序代理 ID |
|
||||
| token | string | 是 | 回调验证令牌 |
|
||||
| encoding_aes_key | string | 是 | 43 字符 AES 密钥 |
|
||||
| webhook_path | string | 否 | Webhook 路径(默认:/webhook/wecom-app) |
|
||||
| allow_from | array | 否 | 用户 ID 白名单 |
|
||||
| reply_timeout | int | 否 | 回复超时时间(秒) |
|
||||
|
||||
## 设置流程
|
||||
|
||||
1. 登录 [企业微信管理后台](https://work.weixin.qq.com/)
|
||||
2. 进入“应用管理” -> “创建应用”
|
||||
3. 获取企业 ID (CorpID) 和应用 Secret
|
||||
4. 在应用设置中配置“接收消息”,获取 Token 和 EncodingAESKey
|
||||
5. 设置回调 URL 为 `http://<your-server-ip>:<port>/webhook/wecom-app`
|
||||
6. 将 CorpID, Secret, AgentID 等信息填入配置文件
|
||||
|
||||
注意: PicoClaw 现在使用共享的 Gateway HTTP 服务器来接收所有渠道的 webhook 回调,默认监听地址为 127.0.0.1:18790。如需从公网接收回调,请把外部域名反向代理到 Gateway(默认端口 18790)。
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
> Retour au [README](../../../../README.fr.md)
|
||||
|
||||
# WeCom Bot
|
||||
|
||||
Le WeCom Bot est une méthode d'intégration rapide fournie par WeCom, permettant de recevoir des messages via une URL Webhook.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
|
||||
"webhook_path": "/webhook/wecom",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Champ | Type | Requis | Description |
|
||||
| ---------------- | ------ | ------ | -------------------------------------------- |
|
||||
| token | string | Oui | Jeton de vérification de signature |
|
||||
| encoding_aes_key | string | Oui | Clé AES de 43 caractères utilisée pour le déchiffrement |
|
||||
| webhook_url | string | Oui | URL Webhook du bot de groupe WeCom utilisée pour envoyer les réponses |
|
||||
| webhook_path | string | Non | Chemin de l'endpoint webhook (par défaut : /webhook/wecom) |
|
||||
| allow_from | array | Non | Liste blanche d'ID utilisateurs (vide = autoriser tous les utilisateurs) |
|
||||
| reply_timeout | int | Non | Délai de réponse en secondes (par défaut : 5) |
|
||||
|
||||
## Procédure de configuration
|
||||
|
||||
1. Ajouter un bot à un groupe WeCom
|
||||
2. Obtenir l'URL Webhook
|
||||
3. (Pour recevoir des messages) Configurer l'adresse API de réception des messages (URL de callback), le Token et l'EncodingAESKey sur la page de configuration du bot
|
||||
4. Saisir les informations pertinentes dans le fichier de configuration
|
||||
|
||||
Remarque : PicoClaw utilise désormais un serveur HTTP Gateway partagé pour recevoir les callbacks webhook de tous les canaux. L'adresse d'écoute par défaut est 127.0.0.1:18790. Pour recevoir des callbacks depuis l'internet public, configurez un reverse proxy de votre domaine externe vers le Gateway (port par défaut 18790).
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
> [README](../../../../README.ja.md) に戻る
|
||||
|
||||
# 企業WeChat ボット
|
||||
|
||||
企業WeChat ボットは、企業WeChatが提供するWebhook URLを通じてメッセージを受信できる迅速な連携方式です。
|
||||
|
||||
## 設定
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
|
||||
"webhook_path": "/webhook/wecom",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 型 | 必須 | 説明 |
|
||||
| ---------------- | ------ | ---- | -------------------------------------------- |
|
||||
| token | string | はい | 署名検証トークン |
|
||||
| encoding_aes_key | string | はい | 復号化に使用する43文字のAESキー |
|
||||
| webhook_url | string | はい | 返信送信に使用する企業WeChatグループボットのWebhook URL |
|
||||
| webhook_path | string | いいえ | Webhookエンドポイントパス(デフォルト:/webhook/wecom) |
|
||||
| allow_from | array | いいえ | ユーザーIDの許可リスト(空 = 全ユーザーを許可) |
|
||||
| reply_timeout | int | いいえ | 返信タイムアウト(秒、デフォルト:5) |
|
||||
|
||||
## セットアップ手順
|
||||
|
||||
1. 企業WeChatグループにボットを追加
|
||||
2. Webhook URLを取得
|
||||
3. (メッセージを受信する場合)ボット設定ページでメッセージ受信APIアドレス(コールバックURL)、Token、EncodingAESKeyを設定
|
||||
4. 関連情報を設定ファイルに入力
|
||||
|
||||
注意:PicoClawは現在、すべてのチャンネルのwebhookコールバックを受信するために共有のGateway HTTPサーバーを使用しています。デフォルトのリスニングアドレスは127.0.0.1:18790です。公共インターネットからコールバックを受信するには、外部ドメインをGateway(デフォルトポート18790)にリバースプロキシしてください。
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
> Back to [README](../../../../README.md)
|
||||
|
||||
# WeCom Bot
|
||||
|
||||
WeCom Bot is a quick integration method provided by WeCom that can receive messages via a Webhook URL.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
|
||||
"webhook_path": "/webhook/wecom",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ---------------- | ------ | -------- | -------------------------------------------- |
|
||||
| token | string | Yes | Signature verification token |
|
||||
| encoding_aes_key | string | Yes | 43-character AES key used for decryption |
|
||||
| webhook_url | string | Yes | WeCom group bot webhook URL used to send replies |
|
||||
| webhook_path | string | No | Webhook endpoint path (default: /webhook/wecom) |
|
||||
| allow_from | array | No | User ID allowlist (empty = allow all users) |
|
||||
| reply_timeout | int | No | Reply timeout in seconds (default: 5) |
|
||||
|
||||
## Setup
|
||||
|
||||
1. Add a bot to a WeCom group
|
||||
2. Obtain the Webhook URL
|
||||
3. (To receive messages) Configure the message receiving API address (callback URL), Token, and EncodingAESKey on the bot configuration page
|
||||
4. Enter the relevant information into the config file
|
||||
|
||||
Note: PicoClaw now uses a shared Gateway HTTP server to receive webhook callbacks for all channels. The default listening address is 127.0.0.1:18790. To receive callbacks from the public internet, reverse-proxy your external domain to the Gateway (default port 18790).
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
> Voltar ao [README](../../../../README.pt-br.md)
|
||||
|
||||
# WeCom Bot
|
||||
|
||||
O WeCom Bot é um método de integração rápida fornecido pelo WeCom que pode receber mensagens via URL de Webhook.
|
||||
|
||||
## Configuração
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
|
||||
"webhook_path": "/webhook/wecom",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Obrigatório | Descrição |
|
||||
| ---------------- | ------ | ----------- | -------------------------------------------- |
|
||||
| token | string | Sim | Token de verificação de assinatura |
|
||||
| encoding_aes_key | string | Sim | Chave AES de 43 caracteres usada para descriptografia |
|
||||
| webhook_url | string | Sim | URL do webhook do bot de grupo WeCom usada para enviar respostas |
|
||||
| webhook_path | string | Não | Caminho do endpoint webhook (padrão: /webhook/wecom) |
|
||||
| allow_from | array | Não | Lista de permissão de IDs de usuários (vazio = permitir todos) |
|
||||
| reply_timeout | int | Não | Timeout de resposta em segundos (padrão: 5) |
|
||||
|
||||
## Configuração passo a passo
|
||||
|
||||
1. Adicione um bot a um grupo WeCom
|
||||
2. Obtenha a URL do Webhook
|
||||
3. (Para receber mensagens) Configure o endereço da API de recebimento de mensagens (URL de callback), Token e EncodingAESKey na página de configuração do bot
|
||||
4. Insira as informações relevantes no arquivo de configuração
|
||||
|
||||
Nota: O PicoClaw agora usa um servidor HTTP Gateway compartilhado para receber callbacks de webhook de todos os canais. O endereço de escuta padrão é 127.0.0.1:18790. Para receber callbacks da internet pública, configure um reverse proxy do seu domínio externo para o Gateway (porta padrão 18790).
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
> Quay lại [README](../../../../README.vi.md)
|
||||
|
||||
# WeCom Bot
|
||||
|
||||
WeCom Bot là phương thức tích hợp nhanh do WeCom cung cấp, có thể nhận tin nhắn qua URL Webhook.
|
||||
|
||||
## Cấu hình
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
|
||||
"webhook_path": "/webhook/wecom",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Trường | Kiểu | Bắt buộc | Mô tả |
|
||||
| ---------------- | ------ | --------- | -------------------------------------------- |
|
||||
| token | string | Có | Token xác minh chữ ký |
|
||||
| encoding_aes_key | string | Có | Khóa AES 43 ký tự dùng để giải mã |
|
||||
| webhook_url | string | Có | URL webhook của bot nhóm WeCom dùng để gửi phản hồi |
|
||||
| webhook_path | string | Không | Đường dẫn endpoint webhook (mặc định: /webhook/wecom) |
|
||||
| allow_from | array | Không | Danh sách cho phép ID người dùng (rỗng = cho phép tất cả) |
|
||||
| reply_timeout | int | Không | Thời gian chờ phản hồi tính bằng giây (mặc định: 5) |
|
||||
|
||||
## Hướng dẫn thiết lập
|
||||
|
||||
1. Thêm bot vào một nhóm WeCom
|
||||
2. Lấy URL Webhook
|
||||
3. (Để nhận tin nhắn) Cấu hình địa chỉ API nhận tin nhắn (URL callback), Token và EncodingAESKey trên trang cấu hình bot
|
||||
4. Nhập thông tin liên quan vào file cấu hình
|
||||
|
||||
Lưu ý: PicoClaw hiện sử dụng máy chủ HTTP Gateway dùng chung để nhận callback webhook cho tất cả các kênh. Địa chỉ lắng nghe mặc định là 127.0.0.1:18790. Để nhận callback từ internet công cộng, hãy cấu hình reverse proxy từ tên miền bên ngoài của bạn đến Gateway (cổng mặc định 18790).
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
> 返回 [README](../../../../README.zh.md)
|
||||
|
||||
# 企业微信机器人
|
||||
|
||||
企业微信机器人是企业微信提供的一种快速接入方式,可以通过 Webhook URL 接收消息。
|
||||
|
||||
## 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
|
||||
"webhook_path": "/webhook/wecom",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 描述 |
|
||||
| ---------------- | ------ | ---- | -------------------------------------------- |
|
||||
| token | string | 是 | 签名验证代币 |
|
||||
| encoding_aes_key | string | 是 | 用于解密的 43 字符 AES 密钥 |
|
||||
| webhook_url | string | 是 | 用于发送回复的企业微信群聊机器人 Webhook URL |
|
||||
| webhook_path | string | 否 | Webhook 端点路径(默认:/webhook/wecom) |
|
||||
| allow_from | array | 否 | 用户 ID 白名单(空值 = 允许所有用户) |
|
||||
| reply_timeout | int | 否 | 回复超时时间(单位:秒,默认值:5) |
|
||||
|
||||
## 设置流程
|
||||
|
||||
1. 在企业微信群中添加机器人
|
||||
2. 获取 Webhook URL
|
||||
3. (如需接收消息) 在机器人配置页面设置接收消息的 API 地址(回调地址)以及 Token 和 EncodingAESKey
|
||||
4. 将相关信息填入配置文件
|
||||
|
||||
注意: PicoClaw 现在使用共享的 Gateway HTTP 服务器来接收所有渠道的 webhook 回调,默认监听地址为 127.0.0.1:18790。如需从公网接收回调,请把外部域名反向代理到 Gateway(默认端口 18790)。
|
||||
|
|
@ -11,24 +11,35 @@ PicoClaw uses a schema versioning system for `config.json` to ensure smooth upgr
|
|||
- **Changes**: Added `version` field to Config struct
|
||||
- **Migration**: No structural changes needed for existing configs
|
||||
|
||||
### Version 2
|
||||
- **Introduction**: Model enable/disable support and channel config unification
|
||||
- **Changes**:
|
||||
- Added `enabled` field to `ModelConfig` — allows disabling individual model entries without removing them
|
||||
- During V1→V2 migration, `enabled` is auto-inferred: models with API keys or the reserved `local-model` name are enabled; others default to disabled
|
||||
- Migrated legacy channel fields: Discord `mention_only` → `group_trigger.mention_only`, OneBot `group_trigger_prefix` → `group_trigger.prefixes`
|
||||
- V0 configs now migrate directly to CurrentVersion (V2) instead of going through V1
|
||||
- `makeBackup()` now uses date-only suffix (e.g., `config.json.20260330.bak`) and also backs up `.security.yml`
|
||||
|
||||
## How It Works
|
||||
|
||||
### Automatic Migration
|
||||
When you load a config file:
|
||||
1. The system first reads the `version` field from the JSON
|
||||
2. Based on the detected version, it loads the appropriate config struct (`ConfigV0`, `ConfigV1`, etc.)
|
||||
2. Based on the detected version, it loads the appropriate config struct (`configV0`, `configV1`, etc.)
|
||||
3. If the loaded version is less than the latest, migrations are applied incrementally
|
||||
4. The version number is updated automatically
|
||||
5. The migrated config is automatically saved back to disk
|
||||
4. Before saving, the system automatically creates a date-stamped backup of `config.json` and `.security.yml`
|
||||
5. The version number is updated automatically
|
||||
6. The migrated config is automatically saved back to disk
|
||||
|
||||
### Version Field
|
||||
The `version` field in `config.json` indicates the schema version:
|
||||
- `0` or missing: Legacy config (no version field)
|
||||
- `1`: Current version with versioning support
|
||||
- `1`: Previous version (will be auto-migrated to V2 on load)
|
||||
- `2`: Current version
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"agents": {...},
|
||||
...
|
||||
}
|
||||
|
|
@ -54,25 +65,25 @@ type ConfigV2 struct {
|
|||
### Step 2: Update Current Config Version
|
||||
|
||||
```go
|
||||
const CurrentConfigVersion = 2 // Increment this
|
||||
const CurrentVersion = 2 // Increment this
|
||||
```
|
||||
|
||||
### Step 3: Add a Loader Function
|
||||
|
||||
```go
|
||||
// loadConfigV2 loads a version 2 config
|
||||
func loadConfigV2(data []byte) (*Config, error) {
|
||||
// loadConfigV3 loads a version 3 config
|
||||
func loadConfigV3(data []byte) (*Config, error) {
|
||||
cfg := DefaultConfig()
|
||||
|
||||
// Parse to ConfigV2 struct
|
||||
var v2 ConfigV2
|
||||
if err := json.Unmarshal(data, &v2); err != nil {
|
||||
// Parse to ConfigV3 struct
|
||||
var v3 ConfigV3
|
||||
if err := json.Unmarshal(data, &v3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to current Config
|
||||
cfg.Version = v2.Version
|
||||
cfg.Agents = v2.Agents
|
||||
cfg.Version = v3.Version
|
||||
cfg.Agents = v3.Agents
|
||||
// ... map other fields
|
||||
|
||||
return cfg, nil
|
||||
|
|
@ -82,29 +93,12 @@ func loadConfigV2(data []byte) (*Config, error) {
|
|||
### Step 4: Add Migration Logic
|
||||
|
||||
```go
|
||||
// applyMigration applies a single migration step from fromVersion to toVersion
|
||||
func applyMigration(cfg *Config, fromVersion, toVersion int) (*Config, error) {
|
||||
switch toVersion {
|
||||
case 1:
|
||||
// Migration from version 0 to 1
|
||||
return &Config{
|
||||
Version: 1,
|
||||
Agents: cfg.Agents,
|
||||
// ... copy all fields
|
||||
}, nil
|
||||
case 2:
|
||||
// Migration from version 1 to 2
|
||||
// Example: Move or rename fields
|
||||
migrated := *cfg
|
||||
migrated.Version = 2
|
||||
// Apply structural changes
|
||||
if cfg.SomeOldField != "" {
|
||||
migrated.SomeNewField = cfg.SomeOldField
|
||||
}
|
||||
return &migrated, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported migration target version: %d", toVersion)
|
||||
}
|
||||
func (c *configV2) Migrate() (*Config, error) {
|
||||
// Apply V2→V3 structural changes here
|
||||
migrated := &c.Config
|
||||
migrated.Version = 3
|
||||
// Apply structural changes
|
||||
return migrated, nil
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -120,7 +114,9 @@ func LoadConfig(path string) (*Config, error) {
|
|||
case 1:
|
||||
cfg, err = loadConfigV1(data)
|
||||
case 2:
|
||||
cfg, err = loadConfigV2(data)
|
||||
cfg, err = loadConfig(data)
|
||||
case 3:
|
||||
cfg, err = loadConfigV3(data)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported config version: %d", versionInfo.Version)
|
||||
}
|
||||
|
|
@ -134,22 +130,22 @@ func LoadConfig(path string) (*Config, error) {
|
|||
Create a test in `config_migration_test.go`:
|
||||
|
||||
```go
|
||||
func TestMigrateV1ToV2(t *testing.T) {
|
||||
// Create a version 1 config
|
||||
v1Config := Config{
|
||||
Version: 1,
|
||||
func TestMigrateV2ToV3(t *testing.T) {
|
||||
// Create a version 2 config
|
||||
v2Config := Config{
|
||||
Version: 2,
|
||||
// ... set up test data
|
||||
}
|
||||
|
||||
// Apply migration
|
||||
migrated, err := applyMigration(&v1Config, 1, 2)
|
||||
migrated, err := v2Config.Migrate()
|
||||
if err != nil {
|
||||
t.Fatalf("Migration failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify version is updated
|
||||
if migrated.Version != 2 {
|
||||
t.Errorf("Expected version 2, got %d", migrated.Version)
|
||||
if migrated.Version != 3 {
|
||||
t.Errorf("Expected version 3, got %d", migrated.Version)
|
||||
}
|
||||
|
||||
// Verify data is preserved/transformed correctly
|
||||
|
|
@ -164,58 +160,60 @@ func TestMigrateV1ToV2(t *testing.T) {
|
|||
3. **No Data Loss**: Migrations should preserve all user settings
|
||||
4. **Idempotent**: Running the same migration multiple times should be safe
|
||||
5. **Auto-Save**: Migrated configs are automatically saved to update the user's file
|
||||
6. **Test Thoroughly**: Test with real user config files
|
||||
7. **Update Defaults**: Keep `defaults.go` in sync with the latest schema
|
||||
6. **Auto-Backup**: Before saving, the system creates a date-stamped backup of `config.json` and `.security.yml`
|
||||
7. **Test Thoroughly**: Test with real user config files
|
||||
8. **Update Defaults**: Keep `defaults.go` in sync with the latest schema
|
||||
|
||||
## Example Migration
|
||||
|
||||
### Scenario: Adding a new field with default value
|
||||
|
||||
Old config (version 1):
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"max_tokens": 32768
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Migration to version 2:
|
||||
```go
|
||||
case 2:
|
||||
migrated := *cfg
|
||||
migrated.Version = 2
|
||||
|
||||
// Add new field with default value if not set
|
||||
if migrated.Agents.Defaults.NewFeatureEnabled == false {
|
||||
// Use default value
|
||||
}
|
||||
|
||||
return &migrated, nil
|
||||
```
|
||||
|
||||
New config (version 2):
|
||||
Old config (version 2):
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"max_tokens": 32768,
|
||||
"new_feature_enabled": false
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Migration to version 3:
|
||||
```go
|
||||
func (c *configV2) Migrate() (*Config, error) {
|
||||
migrated := &c.Config
|
||||
migrated.Version = 3
|
||||
|
||||
// Add new field with default value if not set
|
||||
// ...
|
||||
|
||||
return migrated, nil
|
||||
}
|
||||
```
|
||||
|
||||
New config (version 3):
|
||||
```json
|
||||
{
|
||||
"version": 3,
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"new_option": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Config Not Upgrading
|
||||
- Check that `CurrentConfigVersion` is incremented
|
||||
- Verify migration logic in `applyMigration()` handles the target version
|
||||
- Ensure `migrateConfig()` is called in `LoadConfig()`
|
||||
- Check that `CurrentVersion` is incremented
|
||||
- Verify migration logic handles the target version
|
||||
- Ensure `Migrate()` is called in `LoadConfig()`
|
||||
|
||||
### Migration Errors
|
||||
- Check error messages for specific migration failures
|
||||
|
|
@ -227,4 +225,5 @@ New config (version 2):
|
|||
- Ensure all fields are copied during migration
|
||||
- Check that the migration doesn't overwrite values with defaults unnecessarily
|
||||
- Review the conversion logic in the loader functions
|
||||
- Check the auto-backup files (e.g., `config.json.20260330.bak`) to recover original data
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
Config file: `~/.picoclaw/config.json`
|
||||
|
||||
> **Security Configuration:** For storing API keys, tokens, and other sensitive data, see the [Security Configuration Guide](security_configuration.md).
|
||||
|
||||
### Environment Variables
|
||||
|
||||
You can override default paths using environment variables. This is useful for portable installations, containerized deployments, or running picoclaw as a system service. These variables are independent and control different paths.
|
||||
|
|
@ -38,12 +40,12 @@ PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gat
|
|||
```json
|
||||
{
|
||||
"gateway": {
|
||||
"log_level": "fatal"
|
||||
"log_level": "warn"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When omitted, the default is `fatal`. Supported values: `debug`, `info`, `warn`, `error`, `fatal`.
|
||||
When omitted, the default is `warn`. Supported values: `debug`, `info`, `warn`, `error`, `fatal`.
|
||||
|
||||
You can also override this with the environment variable `PICOCLAW_LOG_LEVEL`.
|
||||
|
||||
|
|
@ -67,6 +69,18 @@ PicoClaw stores data in your configured workspace (default: `~/.picoclaw/workspa
|
|||
|
||||
> **Note:** Changes to `AGENT.md`, `SOUL.md`, `USER.md` and `memory/MEMORY.md` are automatically detected at runtime via file modification time (mtime) tracking. You do **not** need to restart the gateway after editing these files — the agent picks up the new content on the next request.
|
||||
|
||||
### Web launcher dashboard
|
||||
|
||||
**picoclaw-launcher** serves a browser UI that requires sign-in first. By default, the **dashboard token** and **session signing key** are **generated in memory on each start** (a new random token after every restart). Set **`PICOCLAW_LAUNCHER_TOKEN`** to pin a fixed token for that process (startup logs do not print the secret when this env var is used).
|
||||
|
||||
**Where to read the token**: In **console mode** (`-console`), it is printed at startup. In **tray / GUI mode**, use the tray action **Copy dashboard token**, and check **`$PICOCLAW_HOME/logs/launcher.log`** (typically `~/.picoclaw/logs/launcher.log` if `PICOCLAW_HOME` is unset) for the random token logged on startup. The login page shows hints that match how the launcher is running (including the absolute log path); **responses do not include the token itself**.
|
||||
|
||||
- **Config file**: Same directory as `config.json` (or the file pointed to by `PICOCLAW_CONFIG`). The launcher-specific file is `launcher-config.json`.
|
||||
- **Sign-in and links**: Enter the token on the login page, or open with `?token=` when the browser is launched automatically. All responses include **`Referrer-Policy: no-referrer`** to reduce leakage of `token` via the `Referer` header.
|
||||
- **Sign-out**: Use **`POST /api/auth/logout`** with **`Content-Type: application/json`** (body may be `{}`). Do not rely on a GET URL for logout (CSRF-safe pattern).
|
||||
- **Brute-force**: **`POST /api/auth/login`** is **rate-limited per client IP per minute** (HTTP 429 when exceeded).
|
||||
- **Session lifetime**: The HttpOnly session cookie lasts about **7 days** by default; sign in again with the token after it expires.
|
||||
|
||||
### Skill Sources
|
||||
|
||||
By default, skills are loaded from:
|
||||
|
|
@ -467,8 +481,9 @@ This design also enables **multi-agent support** with flexible provider selectio
|
|||
|
||||
- **Different agents, different providers**: Each agent can use its own LLM provider
|
||||
- **Model fallbacks**: Configure primary and fallback models for resilience
|
||||
- **Load balancing**: Distribute requests across multiple endpoints
|
||||
- **Load balancing**: Distribute requests across multiple endpoints or keys
|
||||
- **Centralized configuration**: Manage all providers in one place
|
||||
- **Model enable/disable**: Use the `enabled` field to temporarily disable a model without removing its configuration
|
||||
|
||||
#### 🔒 Security Configuration (Recommended)
|
||||
|
||||
|
|
@ -548,6 +563,7 @@ For complete documentation, see [`security_configuration.md`](security_configura
|
|||
| **通义千问 (Qwen)** | `qwen/` | `https://dashscope.aliyuncs.com/compatible-mode/v1` | OpenAI | [Get Key](https://dashscope.console.aliyun.com) |
|
||||
| **NVIDIA** | `nvidia/` | `https://integrate.api.nvidia.com/v1` | OpenAI | [Get Key](https://build.nvidia.com) |
|
||||
| **Ollama** | `ollama/` | `http://localhost:11434/v1` | OpenAI | Local (no key needed) |
|
||||
| **LM Studio** | `lmstudio/` | `http://localhost:1234/v1` | OpenAI | Optional (local default: no key) |
|
||||
| **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [Get Key](https://openrouter.ai/keys) |
|
||||
| **LiteLLM Proxy** | `litellm/` | `http://localhost:4000/v1` | OpenAI | Your LiteLLM proxy key |
|
||||
| **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | Local |
|
||||
|
|
@ -569,22 +585,22 @@ For complete documentation, see [`security_configuration.md`](security_configura
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
"api_keys": ["sk-your-api-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
"api_keys": ["sk-your-openai-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-zhipu-key"
|
||||
"api_keys": ["your-zhipu-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -595,7 +611,9 @@ For complete documentation, see [`security_configuration.md`](security_configura
|
|||
}
|
||||
```
|
||||
|
||||
> **Security Note**: You can remove `api_key` fields from your config and store them in `.security.yml` instead. See [Security Configuration](#-security-configuration-recommended) above for details.
|
||||
> **Security Note**: You can remove `api_keys` fields from your config and store them in `.security.yml` instead. See [Security Configuration](#-security-configuration-recommended) above for details.
|
||||
>
|
||||
> **Note**: The `enabled` field can be set to `false` to disable a model entry without removing it. When omitted, it defaults to `true` during migration for models that have API keys.
|
||||
|
||||
#### Vendor-Specific Examples
|
||||
|
||||
|
|
@ -672,7 +690,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
|
|||
{
|
||||
"model_name": "claude-opus-4-6",
|
||||
"model": "anthropic-messages/claude-opus-4-6",
|
||||
"api_key": "sk-ant-your-key",
|
||||
"api_keys": ["sk-ant-your-key"],
|
||||
"api_base": "https://api.anthropic.com"
|
||||
}
|
||||
```
|
||||
|
|
@ -693,6 +711,21 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>LM Studio (local)</b></summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"model_name": "lmstudio-local",
|
||||
"model": "lmstudio/openai/gpt-oss-20b"
|
||||
}
|
||||
```
|
||||
|
||||
`api_base` defaults to `http://localhost:1234/v1`. API key is optional unless your LM Studio server enables authentication.<br/>
|
||||
PicoClaw sends OpenAI-compatible requests to LM Studio, and strips the `lmstudio/` prefix before sending requests, so `lmstudio/openai/gpt-oss-20b` sends `openai/gpt-oss-20b` to the LM Studio server.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Custom Proxy / LiteLLM</b></summary>
|
||||
|
||||
|
|
@ -747,13 +780,13 @@ model_list:
|
|||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
"api_keys": ["sk-key1"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
"api_keys": ["sk-key2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -761,7 +794,7 @@ model_list:
|
|||
|
||||
#### Migration from Legacy `providers` Config
|
||||
|
||||
The old `providers` configuration is **deprecated** but still supported for backward compatibility. See [docs/migration/model-list-migration.md](../migration/model-list-migration.md) for the full guide.
|
||||
The old `providers` configuration is **deprecated** and has been removed in V2. Existing V0/V1 configs are auto-migrated. See [docs/migration/model-list-migration.md](../migration/model-list-migration.md) for the full guide.
|
||||
|
||||
### Provider Architecture
|
||||
|
||||
|
|
@ -771,7 +804,7 @@ PicoClaw routes providers by protocol family:
|
|||
- **Anthropic**: Claude-native API behavior.
|
||||
- **Codex/OAuth**: OpenAI OAuth/token authentication route.
|
||||
|
||||
This keeps the runtime lightweight while making new OpenAI-compatible backends mostly a config operation (`api_base` + `api_key`).
|
||||
This keeps the runtime lightweight while making new OpenAI-compatible backends mostly a config operation (`api_base` + `api_keys`).
|
||||
|
||||
<details>
|
||||
<summary><b>Zhipu (legacy providers format)</b></summary>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Credential Encryption
|
||||
|
||||
PicoClaw supports encrypting `api_key` values in `model_list` configuration entries.
|
||||
PicoClaw supports encrypting `api_key`/`api_keys` values in `model_list` configuration entries.
|
||||
Encrypted keys are stored as `enc://<base64>` strings and decrypted automatically at startup.
|
||||
|
||||
---
|
||||
|
|
@ -42,6 +42,8 @@ enc://AAAA...base64...
|
|||
|
||||
## Supported `api_key` Formats
|
||||
|
||||
The same formats apply to both `api_key` (singular) and individual elements in the `api_keys` (array) field:
|
||||
|
||||
| Format | Example | Behaviour |
|
||||
|--------|---------|-----------|
|
||||
| Plaintext | `sk-abc123` | Used as-is |
|
||||
|
|
|
|||
125
docs/cron.md
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# Scheduled Tasks and Cron Jobs
|
||||
|
||||
> Back to [README](../README.md)
|
||||
|
||||
PicoClaw stores scheduled jobs in the current workspace and can run them either as reminders, full agent turns, or shell commands.
|
||||
|
||||
## Schedule Types
|
||||
|
||||
PicoClaw currently uses three schedule forms in the cron tool:
|
||||
|
||||
- `at_seconds`: one-time job, relative to now. After it runs, the job is removed from the store.
|
||||
- `every_seconds`: recurring interval, in seconds.
|
||||
- `cron_expr`: recurring cron expression such as `0 9 * * *`.
|
||||
|
||||
The CLI command `picoclaw cron add` currently supports recurring jobs only:
|
||||
|
||||
- `--every <seconds>`
|
||||
- `--cron '<expr>'`
|
||||
|
||||
There is no CLI flag for a one-time `at` job today.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
picoclaw cron add --name "Daily summary" --message "Summarize today's logs" --cron "0 18 * * *"
|
||||
picoclaw cron add --name "Ping" --message "heartbeat" --every 300 --deliver
|
||||
```
|
||||
|
||||
## Execution Modes
|
||||
|
||||
Jobs are stored with a message payload and can execute in three stable user-facing modes:
|
||||
|
||||
### `deliver: false`
|
||||
|
||||
This is the default for the cron tool.
|
||||
|
||||
When the job fires, PicoClaw sends the saved message back through the agent loop as a new agent turn. Use this for scheduled work that may need reasoning, tools, or a generated reply.
|
||||
|
||||
### `deliver: true`
|
||||
|
||||
When the job fires, PicoClaw publishes the saved message directly to the target channel and recipient without agent processing.
|
||||
|
||||
The CLI `picoclaw cron add --deliver` flag uses this mode.
|
||||
|
||||
### `command`
|
||||
|
||||
When a cron-tool job includes `command`, PicoClaw runs that shell command through the `exec` tool and publishes the command output back to the channel.
|
||||
|
||||
For command jobs, `deliver` is forced to `false` when the job is created. The saved `message` becomes descriptive text only; the scheduled action is the shell command.
|
||||
|
||||
The current CLI `picoclaw cron add` command does not expose a `command` flag.
|
||||
|
||||
## Config and Security Gates
|
||||
|
||||
### `tools.cron`
|
||||
|
||||
`tools.cron.enabled` controls whether the agent-facing `cron` tool is registered. Default: `true`.
|
||||
|
||||
If you disable `tools.cron`, users can no longer create or manage jobs through the agent tool. The gateway still starts `CronService`, but it does not install the job execution callback. As a result, due jobs do not actually run; one-time jobs may be deleted and recurring jobs may be rescheduled without executing their payload. The CLI still uses the same job store.
|
||||
|
||||
`tools.cron.exec_timeout_minutes` sets the timeout used for scheduled command execution. Default: `5`. Set `0` for no timeout.
|
||||
|
||||
### `tools.exec`
|
||||
|
||||
Scheduled command jobs depend on `tools.exec.enabled`. Default: `true`.
|
||||
|
||||
If `tools.exec.enabled` is `false`:
|
||||
|
||||
- new command jobs are rejected by the cron tool
|
||||
- existing command jobs publish a `command execution is disabled` error when they fire
|
||||
|
||||
`tools.exec.allow_remote` is still enforced by the exec tool, but cron command scheduling already requires an internal channel when the job is created. In practice, reminder jobs can be scheduled from remote channels, while scheduled command jobs are limited to internal channels.
|
||||
|
||||
### `allow_command`
|
||||
|
||||
`tools.cron.allow_command` defaults to `true`.
|
||||
|
||||
This is not a hard disable switch. If you set `allow_command` to `false`, PicoClaw still allows a command job when the caller explicitly passes `command_confirm: true`.
|
||||
|
||||
Command jobs also require an internal channel. Non-command reminders do not have that restriction.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"cron": {
|
||||
"enabled": true,
|
||||
"exec_timeout_minutes": 5,
|
||||
"allow_command": true
|
||||
},
|
||||
"exec": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Persistence and Location
|
||||
|
||||
Cron jobs are stored in:
|
||||
|
||||
```text
|
||||
<workspace>/cron/jobs.json
|
||||
```
|
||||
|
||||
By default, the workspace is:
|
||||
|
||||
```text
|
||||
~/.picoclaw/workspace
|
||||
```
|
||||
|
||||
If `PICOCLAW_HOME` is set, the default workspace becomes:
|
||||
|
||||
```text
|
||||
$PICOCLAW_HOME/workspace
|
||||
```
|
||||
|
||||
Both the gateway and `picoclaw cron` CLI subcommands use the same `cron/jobs.json` file.
|
||||
|
||||
Notes:
|
||||
|
||||
- one-time `at_seconds` jobs are deleted after they run
|
||||
- recurring jobs stay in the store until removed
|
||||
- disabled jobs stay in the store and still appear in `picoclaw cron list`
|
||||
|
|
@ -48,7 +48,7 @@ docker compose -f docker/docker-compose.yml --profile launcher up -d
|
|||
Open http://localhost:18800 in your browser. The launcher manages the gateway process automatically.
|
||||
|
||||
> [!WARNING]
|
||||
> The web console does not yet support authentication. Avoid exposing it to the public internet.
|
||||
> The web console uses a dashboard token (in-memory per run unless `PICOCLAW_LAUNCHER_TOKEN` is set). **Do not** expose the launcher to untrusted networks or the public internet. See [Web launcher dashboard](configuration.md#web-launcher-dashboard) in the Configuration Guide.
|
||||
|
||||
### Agent Mode (One-shot)
|
||||
|
||||
|
|
@ -95,19 +95,19 @@ picoclaw onboard
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key",
|
||||
"api_keys": ["sk-your-api-key"],
|
||||
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "your-api-key",
|
||||
"api_keys": ["your-api-key"],
|
||||
"request_timeout": 300
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "your-anthropic-key"
|
||||
"api_keys": ["your-anthropic-key"]
|
||||
}
|
||||
],
|
||||
"tools": {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,22 @@ PICOCLAW_HOME=/opt/picoclaw picoclaw agent
|
|||
PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway
|
||||
```
|
||||
|
||||
### Niveau de Log du Gateway
|
||||
|
||||
`gateway.log_level` contrôle la verbosité des logs du Gateway, configurable dans `config.json` :
|
||||
|
||||
```json
|
||||
{
|
||||
"gateway": {
|
||||
"log_level": "warn"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
La valeur par défaut est `warn`. Valeurs supportées : `debug`, `info`, `warn`, `error`, `fatal`.
|
||||
|
||||
Peut également être surchargé via la variable d'environnement : `PICOCLAW_LOG_LEVEL=info`
|
||||
|
||||
### Structure du Workspace
|
||||
|
||||
PicoClaw stocke les données dans votre workspace configuré (par défaut : `~/.picoclaw/workspace`) :
|
||||
|
|
@ -318,15 +334,15 @@ Configurez plusieurs endpoints pour le même nom de modèle — PicoClaw effectu
|
|||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" },
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" }
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] },
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Migration depuis l'ancienne config `providers`
|
||||
|
||||
L'ancienne configuration `providers` est **dépréciée** mais toujours supportée. Voir [docs/migration/model-list-migration.md](../migration/model-list-migration.md).
|
||||
L'ancienne configuration `providers` est **dépréciée** et a été supprimée dans V2. Les configs V0/V1 existantes sont auto-migrées. Voir [docs/migration/model-list-migration.md](../migration/model-list-migration.md).
|
||||
|
||||
### Architecture des Providers
|
||||
|
||||
|
|
|
|||
|
|
@ -92,19 +92,19 @@ picoclaw onboard
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key",
|
||||
"api_keys": ["sk-your-api-key"],
|
||||
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "your-api-key",
|
||||
"api_keys": ["your-api-key"],
|
||||
"request_timeout": 300
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "your-anthropic-key"
|
||||
"api_keys": ["your-anthropic-key"]
|
||||
}
|
||||
],
|
||||
"tools": {
|
||||
|
|
|
|||
|
|
@ -73,22 +73,22 @@ Cette conception permet également le **support multi-agents** avec une sélecti
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
"api_keys": ["sk-your-api-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
"api_keys": ["sk-your-openai-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-zhipu-key"
|
||||
"api_keys": ["your-zhipu-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -107,7 +107,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti
|
|||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti
|
|||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti
|
|||
{
|
||||
"model_name": "deepseek-chat",
|
||||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ Cette conception permet également le **support multi-agents** avec une sélecti
|
|||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ Pour l'accès direct à l'API Anthropic ou les endpoints personnalisés qui ne p
|
|||
{
|
||||
"model_name": "claude-opus-4-6",
|
||||
"model": "anthropic-messages/claude-opus-4-6",
|
||||
"api_key": "sk-ant-your-key",
|
||||
"api_keys": ["sk-ant-your-key"],
|
||||
"api_base": "https://api.anthropic.com"
|
||||
}
|
||||
```
|
||||
|
|
@ -189,7 +189,7 @@ Pour l'accès direct à l'API Anthropic ou les endpoints personnalisés qui ne p
|
|||
"model_name": "my-custom-model",
|
||||
"model": "openai/custom-model",
|
||||
"api_base": "https://my-proxy.com/v1",
|
||||
"api_key": "sk-...",
|
||||
"api_keys": ["sk-..."],
|
||||
"request_timeout": 300
|
||||
}
|
||||
```
|
||||
|
|
@ -201,7 +201,7 @@ Pour l'accès direct à l'API Anthropic ou les endpoints personnalisés qui ne p
|
|||
"model_name": "lite-gpt4",
|
||||
"model": "litellm/lite-gpt4",
|
||||
"api_base": "http://localhost:4000/v1",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -218,13 +218,13 @@ Configurez plusieurs endpoints pour le même nom de modèle — PicoClaw effectu
|
|||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
"api_keys": ["sk-key1"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
"api_keys": ["sk-key2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ Configurez plusieurs endpoints pour le même nom de modèle — PicoClaw effectu
|
|||
|
||||
#### Migration depuis l'Ancienne Configuration `providers`
|
||||
|
||||
L'ancienne configuration `providers` est **dépréciée** mais toujours prise en charge pour la compatibilité ascendante.
|
||||
L'ancienne configuration `providers` est **dépréciée** et a été supprimée dans V2. Les configs V0/V1 existantes sont auto-migrées.
|
||||
|
||||
**Ancienne configuration (dépréciée) :**
|
||||
|
||||
|
|
@ -257,11 +257,12 @@ L'ancienne configuration `providers` est **dépréciée** mais toujours prise en
|
|||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,22 @@ PICOCLAW_HOME=/opt/picoclaw picoclaw agent
|
|||
PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway
|
||||
```
|
||||
|
||||
### Gateway ログレベル
|
||||
|
||||
`gateway.log_level` は Gateway のログ詳細度を制御します。`config.json` で設定できます:
|
||||
|
||||
```json
|
||||
{
|
||||
"gateway": {
|
||||
"log_level": "warn"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
デフォルト値は `warn` です。サポートされる値:`debug`、`info`、`warn`、`error`、`fatal`。
|
||||
|
||||
環境変数でも上書き可能です:`PICOCLAW_LOG_LEVEL=info`
|
||||
|
||||
### ワークスペースレイアウト
|
||||
|
||||
PicoClaw は設定されたワークスペース(デフォルト: `~/.picoclaw/workspace`)にデータを保存します:
|
||||
|
|
@ -319,15 +335,15 @@ HEARTBEAT_OK を返信 ユーザーが直接結果を受信
|
|||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" },
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" }
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] },
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 旧 `providers` 設定からの移行
|
||||
|
||||
旧 `providers` 設定は**非推奨**ですが後方互換性のためサポートされています。[docs/migration/model-list-migration.md](../migration/model-list-migration.md) を参照してください。
|
||||
旧 `providers` 設定は**非推奨**となり、V2 で削除されました。既存の V0/V1 設定は自動的に移行されます。[docs/migration/model-list-migration.md](../migration/model-list-migration.md) を参照してください。
|
||||
|
||||
### Provider アーキテクチャ
|
||||
|
||||
|
|
|
|||
|
|
@ -94,19 +94,19 @@ picoclaw onboard
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key",
|
||||
"api_keys": ["sk-your-api-key"],
|
||||
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "your-api-key",
|
||||
"api_keys": ["your-api-key"],
|
||||
"request_timeout": 300
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "your-anthropic-key"
|
||||
"api_keys": ["your-anthropic-key"]
|
||||
}
|
||||
],
|
||||
"tools": {
|
||||
|
|
|
|||
|
|
@ -73,22 +73,22 @@
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
"api_keys": ["sk-your-api-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
"api_keys": ["sk-your-openai-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-zhipu-key"
|
||||
"api_keys": ["your-zhipu-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -117,7 +117,7 @@
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -127,7 +127,18 @@
|
|||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
**LiteLLM Proxy**
|
||||
|
||||
```json
|
||||
{
|
||||
"model_name": "lite-gpt4",
|
||||
"model": "litellm/lite-gpt4",
|
||||
"api_base": "http://localhost:4000/v1",
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -137,7 +148,7 @@
|
|||
{
|
||||
"model_name": "deepseek-chat",
|
||||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -147,7 +158,7 @@
|
|||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -161,7 +172,7 @@ Anthropic API への直接アクセスや、Anthropic のネイティブメッ
|
|||
{
|
||||
"model_name": "claude-opus-4-6",
|
||||
"model": "anthropic-messages/claude-opus-4-6",
|
||||
"api_key": "sk-ant-your-key",
|
||||
"api_keys": ["sk-ant-your-key"],
|
||||
"api_base": "https://api.anthropic.com"
|
||||
}
|
||||
```
|
||||
|
|
@ -189,7 +200,7 @@ Anthropic API への直接アクセスや、Anthropic のネイティブメッ
|
|||
"model_name": "my-custom-model",
|
||||
"model": "openai/custom-model",
|
||||
"api_base": "https://my-proxy.com/v1",
|
||||
"api_key": "sk-...",
|
||||
"api_keys": ["sk-..."],
|
||||
"request_timeout": 300
|
||||
}
|
||||
```
|
||||
|
|
@ -201,7 +212,7 @@ Anthropic API への直接アクセスや、Anthropic のネイティブメッ
|
|||
"model_name": "lite-gpt4",
|
||||
"model": "litellm/lite-gpt4",
|
||||
"api_base": "http://localhost:4000/v1",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -218,13 +229,13 @@ PicoClaw はリクエスト送信前に外側の `litellm/` プレフィック
|
|||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
"api_keys": ["sk-key1"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
"api_keys": ["sk-key2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -232,7 +243,7 @@ PicoClaw はリクエスト送信前に外側の `litellm/` プレフィック
|
|||
|
||||
#### レガシー `providers` 設定からの移行
|
||||
|
||||
旧 `providers` 設定形式は**非推奨**ですが、後方互換性のためまだサポートされています。
|
||||
旧 `providers` 設定形式は**非推奨**となり、V2 で削除されました。既存の V0/V1 設定は自動的に移行されます。
|
||||
|
||||
**旧設定(非推奨):**
|
||||
|
||||
|
|
@ -257,11 +268,12 @@ PicoClaw はリクエスト送信前に外側の `litellm/` プレフィック
|
|||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -282,7 +294,7 @@ PicoClaw はプロトコルファミリーごとに Provider をルーティン
|
|||
- Anthropic プロトコル:Claude ネイティブ API 動作。
|
||||
- Codex/OAuth パス:OpenAI OAuth/Token 認証ルート。
|
||||
|
||||
これによりランタイムを軽量に保ちつつ、新しい OpenAI 互換バックエンドの追加をほぼ設定操作(`api_base` + `api_key`)のみで実現しています。
|
||||
これによりランタイムを軽量に保ちつつ、新しい OpenAI 互換バックエンドの追加をほぼ設定操作(`api_base` + `api_keys`)のみで実現しています。
|
||||
|
||||
<details>
|
||||
<summary><b>Zhipu 設定例</b></summary>
|
||||
|
|
|
|||
|
|
@ -50,22 +50,23 @@ The new `model_list` configuration offers several advantages:
|
|||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key",
|
||||
"api_keys": ["sk-your-openai-key"],
|
||||
"api_base": "https://api.openai.com/v1"
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "deepseek",
|
||||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-your-deepseek-key"
|
||||
"api_keys": ["sk-your-deepseek-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -76,6 +77,8 @@ The new `model_list` configuration offers several advantages:
|
|||
}
|
||||
```
|
||||
|
||||
> **Note**: The `enabled` field can be omitted — during V1→V2 migration it is auto-inferred (models with API keys or the `local-model` name are enabled by default). For new configs, you can explicitly set `"enabled": false` to disable a model entry without removing it.
|
||||
|
||||
## Protocol Prefixes
|
||||
|
||||
The `model` field uses a protocol prefix format: `[protocol/]model-identifier`
|
||||
|
|
@ -111,7 +114,8 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier`
|
|||
| `model_name` | Yes | User-facing alias for the model |
|
||||
| `model` | Yes | Protocol and model identifier (e.g., `openai/gpt-5.4`) |
|
||||
| `api_base` | No | API endpoint URL |
|
||||
| `api_key` | No* | API authentication key |
|
||||
| `api_keys` | No | API authentication keys (array; supports multiple keys for load balancing) |
|
||||
| `enabled` | No | Whether this model entry is active. Defaults to `true` during migration for models with API keys or named `local-model`. Set to `false` to disable. |
|
||||
| `proxy` | No | HTTP proxy URL |
|
||||
| `auth_method` | No | Authentication method: `oauth`, `token` |
|
||||
| `connect_mode` | No | Connection mode for CLI providers: `stdio`, `grpc` |
|
||||
|
|
@ -119,11 +123,13 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier`
|
|||
| `max_tokens_field` | No | Field name for max tokens |
|
||||
| `request_timeout` | No | HTTP request timeout in seconds; `<=0` uses default `120s` |
|
||||
|
||||
*`api_key` is required for HTTP-based protocols unless `api_base` points to a local server.
|
||||
> **Note**: `api_key` (singular) has been **removed** in V2 configs. Only `api_keys` (array) is supported. During migration from V0/V1, both `api_key` and `api_keys` are automatically merged into the new `api_keys` array.
|
||||
|
||||
## Load Balancing
|
||||
|
||||
Configure multiple endpoints for the same model to distribute load:
|
||||
There are two ways to configure load balancing:
|
||||
|
||||
### Option 1: Multiple API Keys in `api_keys` (Recommended)
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -131,19 +137,45 @@ Configure multiple endpoints for the same model to distribute load:
|
|||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-key1",
|
||||
"api_keys": ["sk-key1", "sk-key2", "sk-key3"],
|
||||
"api_base": "https://api.openai.com/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Or via `.security.yml`:
|
||||
|
||||
```yaml
|
||||
model_list:
|
||||
gpt4:
|
||||
api_keys:
|
||||
- "sk-key1"
|
||||
- "sk-key2"
|
||||
- "sk-key3"
|
||||
```
|
||||
|
||||
### Option 2: Multiple Model Entries
|
||||
|
||||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_keys": ["sk-key1"],
|
||||
"api_base": "https://api1.example.com/v1"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-key2",
|
||||
"api_keys": ["sk-key2"],
|
||||
"api_base": "https://api2.example.com/v1"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-key3",
|
||||
"api_keys": ["sk-key3"],
|
||||
"api_base": "https://api3.example.com/v1"
|
||||
}
|
||||
]
|
||||
|
|
@ -162,7 +194,7 @@ With `model_list`, adding a new provider requires zero code changes:
|
|||
{
|
||||
"model_name": "my-custom-llm",
|
||||
"model": "openai/my-model-v1",
|
||||
"api_key": "your-api-key",
|
||||
"api_keys": ["your-api-key"],
|
||||
"api_base": "https://api.your-provider.com/v1"
|
||||
}
|
||||
]
|
||||
|
|
@ -173,11 +205,12 @@ Just specify `openai/` as the protocol (or omit it for the default), and provide
|
|||
|
||||
## Backward Compatibility
|
||||
|
||||
During the migration period, your existing `providers` configuration will continue to work:
|
||||
During the migration period, your existing V0/V1 config will be auto-migrated to V2:
|
||||
|
||||
1. If `model_list` is empty and `providers` has data, the system auto-converts internally
|
||||
2. A deprecation warning is logged: `"providers config is deprecated, please migrate to model_list"`
|
||||
3. All existing functionality remains unchanged
|
||||
2. Both `api_key` (singular) and `api_keys` (array) in V0/V1 configs are merged into the new `api_keys` array
|
||||
3. A deprecation warning is logged: `"providers config is deprecated, please migrate to model_list"`
|
||||
4. All existing functionality remains unchanged
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
|
|
@ -212,7 +245,7 @@ unknown protocol "xxx" in model "xxx/model-name"
|
|||
api_key or api_base is required for HTTP-based protocol "xxx"
|
||||
```
|
||||
|
||||
**Solution**: Provide `api_key` and/or `api_base` for HTTP-based providers.
|
||||
**Solution**: Provide `api_keys` and/or `api_base` for HTTP-based providers.
|
||||
|
||||
## Need Help?
|
||||
|
||||
|
|
|
|||
431
docs/my/chat-apps.md
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
# 💬 Konfigurasi Aplikasi Sembang
|
||||
|
||||
> Kembali ke [README](../../README.my.md)
|
||||
|
||||
## 💬 Aplikasi Sembang
|
||||
|
||||
Berbual dengan picoclaw anda melalui Telegram, Discord, WhatsApp, Matrix, QQ, DingTalk, LINE, WeCom, Feishu, Slack, IRC, OneBot, MaixCam, atau Pico (protokol asli)
|
||||
|
||||
> **Nota**: Semua saluran berasaskan webhook (LINE, WeCom, dan sebagainya) diservis pada satu pelayan HTTP Gateway yang dikongsi (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`). Tiada port khusus per saluran untuk dikonfigurasikan. Nota: Feishu menggunakan mod WebSocket/SDK dan tidak menggunakan pelayan HTTP webhook yang dikongsi.
|
||||
|
||||
| Saluran | Penyediaan |
|
||||
| ---------------- | ------------------------------------------ |
|
||||
| **Telegram** | Mudah (hanya token) |
|
||||
| **Discord** | Mudah (token bot + intents) |
|
||||
| **WhatsApp** | Mudah (asli: imbas QR; atau bridge URL) |
|
||||
| **Matrix** | Sederhana (homeserver + access token bot) |
|
||||
| **QQ** | Mudah (AppID + AppSecret) |
|
||||
| **DingTalk** | Sederhana (kelayakan aplikasi) |
|
||||
| **LINE** | Sederhana (kelayakan + webhook URL) |
|
||||
| **WeCom AI Bot** | Sederhana (Token + kunci AES) |
|
||||
| **Feishu** | Sederhana (App ID + Secret, mod WebSocket) |
|
||||
| **Slack** | Sederhana (Bot token + App token) |
|
||||
| **IRC** | Sederhana (pelayan + konfigurasi TLS) |
|
||||
| **OneBot** | Sederhana (QQ melalui protokol OneBot) |
|
||||
| **MaixCam** | Mudah (integrasi perkakasan Sipeed) |
|
||||
| **Pico** | Protokol PicoClaw asli |
|
||||
|
||||
<details>
|
||||
<summary><b>Telegram</b> (Disyorkan)</summary>
|
||||
|
||||
**1. Cipta bot**
|
||||
|
||||
* Buka Telegram, cari `@BotFather`
|
||||
* Hantar `/newbot`, ikut arahan
|
||||
* Salin token
|
||||
|
||||
**2. Konfigurasi**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"],
|
||||
"use_markdown_v2": false,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Dapatkan user ID anda daripada `@userinfobot` di Telegram.
|
||||
|
||||
**3. Jalankan**
|
||||
|
||||
```bash
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
**4. Menu arahan Telegram (auto-register semasa startup)**
|
||||
|
||||
PicoClaw kini menyimpan definisi arahan dalam satu registry bersama. Semasa startup, Telegram akan mendaftarkan arahan bot yang disokong secara automatik (contohnya `/start`, `/help`, `/show`, `/list`) supaya menu arahan dan tingkah laku runtime sentiasa selari.
|
||||
Pendaftaran menu arahan Telegram kekal sebagai UX penemuan setempat saluran; pelaksanaan arahan generik dikendalikan secara berpusat dalam gelung agen melalui commands executor.
|
||||
|
||||
Jika pendaftaran arahan gagal (ralat sementara rangkaian/API), saluran tetap akan bermula dan PicoClaw akan mencuba semula pendaftaran di latar belakang.
|
||||
|
||||
**4. Pemformatan Lanjutan**
|
||||
Anda boleh menetapkan `use_markdown_v2: true` untuk mengaktifkan pilihan pemformatan yang lebih maju. Ini membolehkan bot menggunakan keseluruhan set ciri Telegram MarkdownV2, termasuk gaya bersarang, spoiler, dan blok lebar tetap tersuai.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Discord</b></summary>
|
||||
|
||||
**1. Cipta bot**
|
||||
|
||||
* Pergi ke <https://discord.com/developers/applications>
|
||||
* Cipta aplikasi → Bot → Add Bot
|
||||
* Salin token bot
|
||||
|
||||
**2. Aktifkan intents**
|
||||
|
||||
* Dalam tetapan Bot, aktifkan **MESSAGE CONTENT INTENT**
|
||||
* (Pilihan) Aktifkan **SERVER MEMBERS INTENT** jika anda bercadang menggunakan allow list berasaskan data ahli
|
||||
|
||||
**3. Dapatkan User ID anda**
|
||||
* Discord Settings → Advanced → aktifkan **Developer Mode**
|
||||
* Klik kanan avatar anda → **Copy User ID**
|
||||
|
||||
**4. Konfigurasi**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"discord": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_BOT_TOKEN",
|
||||
"allow_from": ["YOUR_USER_ID"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**5. Jemput bot**
|
||||
|
||||
* OAuth2 → URL Generator
|
||||
* Scopes: `bot`
|
||||
* Bot Permissions: `Send Messages`, `Read Message History`
|
||||
* Buka URL jemputan yang dijana dan tambahkan bot ke pelayan anda
|
||||
|
||||
**Pilihan: Mod trigger kumpulan**
|
||||
|
||||
Secara lalai bot membalas semua mesej dalam saluran pelayan. Untuk mengehadkan balasan kepada @mention sahaja, tambah:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"discord": {
|
||||
"group_trigger": { "mention_only": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Anda juga boleh mencetuskan dengan awalan kata kunci (contohnya `!bot`):
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"discord": {
|
||||
"group_trigger": { "prefixes": ["!bot"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**6. Jalankan**
|
||||
|
||||
```bash
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>WhatsApp</b> (asli melalui whatsmeow)</summary>
|
||||
|
||||
PicoClaw boleh menyambung ke WhatsApp dalam dua cara:
|
||||
|
||||
- **Asli (disyorkan):** Dalam proses menggunakan [whatsmeow](https://github.com/tulir/whatsmeow). Tiada bridge berasingan. Tetapkan `"use_native": true` dan biarkan `bridge_url` kosong. Pada larian pertama, imbas kod QR dengan WhatsApp (Linked Devices). Sesi disimpan di bawah workspace anda (contohnya `workspace/whatsapp/`). Saluran asli ini adalah **pilihan** untuk memastikan binari lalai kekal kecil; bina dengan `-tags whatsapp_native` (contohnya `make build-whatsapp-native` atau `go build -tags whatsapp_native ./cmd/...`).
|
||||
- **Bridge:** Sambung ke bridge WebSocket luaran. Tetapkan `bridge_url` (contohnya `ws://localhost:3001`) dan biarkan `use_native` sebagai false.
|
||||
|
||||
**Konfigurasi (asli)**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"whatsapp": {
|
||||
"enabled": true,
|
||||
"use_native": true,
|
||||
"session_store_path": "",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Jika `session_store_path` kosong, sesi akan disimpan dalam `<workspace>/whatsapp/`. Jalankan `picoclaw gateway`; pada larian pertama, imbas kod QR yang dipaparkan dalam terminal menggunakan WhatsApp → Linked Devices.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>QQ</b></summary>
|
||||
|
||||
**1. Cipta bot**
|
||||
|
||||
- Pergi ke [QQ Open Platform](https://q.qq.com/#)
|
||||
- Cipta aplikasi → Dapatkan **AppID** dan **AppSecret**
|
||||
|
||||
**2. Konfigurasi**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"qq": {
|
||||
"enabled": true,
|
||||
"app_id": "YOUR_APP_ID",
|
||||
"app_secret": "YOUR_APP_SECRET",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Tetapkan `allow_from` kepada kosong untuk membenarkan semua pengguna, atau nyatakan nombor QQ untuk mengehadkan akses.
|
||||
|
||||
**3. Jalankan**
|
||||
|
||||
```bash
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>DingTalk</b></summary>
|
||||
|
||||
**1. Cipta bot**
|
||||
|
||||
* Pergi ke [Open Platform](https://open.dingtalk.com/)
|
||||
* Cipta aplikasi dalaman
|
||||
* Salin Client ID dan Client Secret
|
||||
|
||||
**2. Konfigurasi**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"dingtalk": {
|
||||
"enabled": true,
|
||||
"client_id": "YOUR_CLIENT_ID",
|
||||
"client_secret": "YOUR_CLIENT_SECRET",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Tetapkan `allow_from` kepada kosong untuk membenarkan semua pengguna, atau nyatakan user ID DingTalk untuk mengehadkan akses.
|
||||
|
||||
**3. Jalankan**
|
||||
|
||||
```bash
|
||||
picoclaw gateway
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Matrix</b></summary>
|
||||
|
||||
**1. Sediakan akaun bot**
|
||||
|
||||
* Gunakan homeserver pilihan anda (contohnya `https://matrix.org` atau self-hosted)
|
||||
* Cipta pengguna bot dan dapatkan access tokennya
|
||||
|
||||
**2. Konfigurasi**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"matrix": {
|
||||
"enabled": true,
|
||||
"homeserver": "https://matrix.org",
|
||||
"user_id": "@your-bot:matrix.org",
|
||||
"access_token": "YOUR_MATRIX_ACCESS_TOKEN",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. Jalankan**
|
||||
|
||||
```bash
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
Untuk pilihan penuh (`device_id`, `join_on_invite`, `group_trigger`, `placeholder`, `reasoning_channel_id`), lihat [Panduan Konfigurasi Saluran Matrix](docs/channels/matrix/README.md).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>LINE</b></summary>
|
||||
|
||||
**1. Cipta Akaun Rasmi LINE**
|
||||
|
||||
- Pergi ke [LINE Developers Console](https://developers.line.biz/)
|
||||
- Cipta provider → Cipta saluran Messaging API
|
||||
- Salin **Channel Secret** dan **Channel Access Token**
|
||||
|
||||
**2. Konfigurasi**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"line": {
|
||||
"enabled": true,
|
||||
"channel_secret": "YOUR_CHANNEL_SECRET",
|
||||
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
|
||||
"webhook_path": "/webhook/line",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Webhook LINE diservis pada pelayan Gateway yang dikongsi (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`).
|
||||
|
||||
**3. Tetapkan Webhook URL**
|
||||
|
||||
LINE memerlukan HTTPS untuk webhook. Gunakan reverse proxy atau tunnel:
|
||||
|
||||
```bash
|
||||
# Contoh dengan ngrok (port lalai gateway ialah 18790)
|
||||
ngrok http 18790
|
||||
```
|
||||
|
||||
Kemudian tetapkan Webhook URL dalam LINE Developers Console kepada `https://your-domain/webhook/line` dan aktifkan **Use webhook**.
|
||||
|
||||
**4. Jalankan**
|
||||
|
||||
```bash
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
> Dalam sembang kumpulan, bot hanya membalas apabila @disebut. Balasan akan memetik mesej asal.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>WeCom (企业微信)</b></summary>
|
||||
|
||||
PicoClaw menyokong tiga jenis integrasi WeCom:
|
||||
|
||||
**Pilihan 1: WeCom Bot (Bot)** - Penyediaan lebih mudah, menyokong sembang kumpulan
|
||||
**Pilihan 2: WeCom App (Custom App)** - Lebih banyak ciri, pemesejan proaktif, sembang peribadi sahaja
|
||||
**Pilihan 3: WeCom AI Bot (AI Bot)** - AI Bot rasmi, balasan streaming, menyokong sembang kumpulan & peribadi
|
||||
|
||||
Lihat [Panduan Konfigurasi WeCom AI Bot](docs/channels/wecom/wecom_aibot/README.zh.md) untuk arahan penyediaan terperinci.
|
||||
|
||||
**Quick Setup - WeCom Bot:**
|
||||
|
||||
**1. Cipta bot**
|
||||
|
||||
* Pergi ke WeCom Admin Console → Group Chat → Add Group Bot
|
||||
* Salin webhook URL (format: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx`)
|
||||
|
||||
**2. Konfigurasi**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
|
||||
"webhook_path": "/webhook/wecom",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Webhook WeCom diservis pada pelayan Gateway yang dikongsi (`gateway.host`:`gateway.port`, lalai `127.0.0.1:18790`).
|
||||
|
||||
**Quick Setup - WeCom App:**
|
||||
|
||||
**1. Cipta aplikasi**
|
||||
|
||||
* Pergi ke WeCom Admin Console → App Management → Create App
|
||||
* Salin **AgentId** dan **Secret**
|
||||
* Pergi ke halaman "My Company", salin **CorpID**
|
||||
|
||||
**2. Konfigurasi penerimaan mesej**
|
||||
|
||||
* Dalam butiran aplikasi, klik "Receive Message" → "Set API"
|
||||
* Tetapkan URL kepada `http://your-server:18790/webhook/wecom-app`
|
||||
* Jana **Token** dan **EncodingAESKey**
|
||||
|
||||
**3. Konfigurasi**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_app": {
|
||||
"enabled": true,
|
||||
"corp_id": "wwxxxxxxxxxxxxxxxx",
|
||||
"corp_secret": "YOUR_CORP_SECRET",
|
||||
"agent_id": 1000002,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-app",
|
||||
"allow_from": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**4. Jalankan**
|
||||
|
||||
```bash
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
> **Nota**: Callback webhook WeCom diservis pada port Gateway (lalai 18790). Gunakan reverse proxy untuk HTTPS.
|
||||
|
||||
**Quick Setup - WeCom AI Bot:**
|
||||
|
||||
**1. Cipta AI Bot**
|
||||
|
||||
* Pergi ke WeCom Admin Console → App Management → AI Bot
|
||||
* Dalam tetapan AI Bot, konfigurasikan callback URL: `http://your-server:18791/webhook/wecom-aibot`
|
||||
* Salin **Token** dan klik "Random Generate" untuk **EncodingAESKey**
|
||||
|
||||
**2. Konfigurasi**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom_aibot": {
|
||||
"enabled": true,
|
||||
"token": "YOUR_TOKEN",
|
||||
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
|
||||
"webhook_path": "/webhook/wecom-aibot",
|
||||
"allow_from": [],
|
||||
"welcome_message": "Hello! How can I help you?"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. Jalankan**
|
||||
|
||||
```bash
|
||||
picoclaw gateway
|
||||
```
|
||||
|
||||
> **Nota**: WeCom AI Bot menggunakan protokol streaming pull — tiada isu timeout balasan. Tugasan panjang (>30 saat) akan bertukar secara automatik kepada penghantaran push `response_url`.
|
||||
|
||||
</details>
|
||||
216
docs/my/configuration.md
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
# ⚙️ Panduan Konfigurasi
|
||||
|
||||
> Kembali ke [README](../../README.my.md)
|
||||
|
||||
## ⚙️ Konfigurasi
|
||||
|
||||
Fail konfigurasi: `~/.picoclaw/config.json`
|
||||
|
||||
### Pemboleh Ubah Persekitaran
|
||||
|
||||
Anda boleh menggantikan laluan lalai menggunakan pemboleh ubah persekitaran. Ini berguna untuk pemasangan mudah alih, deployment dalam container, atau menjalankan picoclaw sebagai system service. Pemboleh ubah ini saling bebas dan mengawal laluan yang berbeza.
|
||||
|
||||
| Pemboleh Ubah | Penerangan | Laluan Lalai |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
|
||||
| `PICOCLAW_CONFIG` | Menindih laluan ke fail konfigurasi. Ini memberitahu picoclaw secara terus fail `config.json` yang perlu dimuatkan, dengan mengabaikan lokasi lain. | `~/.picoclaw/config.json` |
|
||||
| `PICOCLAW_HOME` | Menindih direktori root untuk data picoclaw. Ini mengubah lokasi lalai bagi `workspace` dan direktori data lain. | `~/.picoclaw` |
|
||||
|
||||
**Contoh:**
|
||||
|
||||
```bash
|
||||
# Jalankan picoclaw menggunakan fail config tertentu
|
||||
# Laluan workspace akan dibaca daripada fail config tersebut
|
||||
PICOCLAW_CONFIG=/etc/picoclaw/production.json picoclaw gateway
|
||||
|
||||
# Jalankan picoclaw dengan semua data disimpan di /opt/picoclaw
|
||||
# Config akan dimuatkan dari lalai ~/.picoclaw/config.json
|
||||
# Workspace akan dicipta di /opt/picoclaw/workspace
|
||||
PICOCLAW_HOME=/opt/picoclaw picoclaw agent
|
||||
|
||||
# Gunakan kedua-duanya untuk setup yang disesuaikan sepenuhnya
|
||||
PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway
|
||||
```
|
||||
|
||||
### Susun Atur Workspace
|
||||
|
||||
PicoClaw menyimpan data dalam workspace yang dikonfigurasikan (lalai: `~/.picoclaw/workspace`):
|
||||
|
||||
```
|
||||
~/.picoclaw/workspace/
|
||||
├── sessions/ # Sesi perbualan dan sejarah
|
||||
├── memory/ # Memori jangka panjang (MEMORY.md)
|
||||
├── state/ # Keadaan persisten (saluran terakhir, dll.)
|
||||
├── cron/ # Pangkalan data job berjadual
|
||||
├── skills/ # Skill tersuai
|
||||
├── AGENTS.md # Panduan tingkah laku agen
|
||||
├── HEARTBEAT.md # Prompt tugasan berkala (disemak setiap 30 minit)
|
||||
├── IDENTITY.md # Identiti agen
|
||||
├── SOUL.md # Jiwa agen
|
||||
└── USER.md # Keutamaan pengguna
|
||||
```
|
||||
|
||||
### Sumber Skill
|
||||
|
||||
Secara lalai, skill dimuatkan daripada:
|
||||
|
||||
1. `~/.picoclaw/workspace/skills` (workspace)
|
||||
2. `~/.picoclaw/skills` (global)
|
||||
3. `<current-working-directory>/skills` (builtin)
|
||||
|
||||
Untuk setup lanjutan/ujian, anda boleh menindih root builtin skills dengan:
|
||||
|
||||
```bash
|
||||
export PICOCLAW_BUILTIN_SKILLS=/path/to/skills
|
||||
```
|
||||
|
||||
### Polisi Pelaksanaan Arahan Bersepadu
|
||||
|
||||
- Generic slash command dilaksanakan melalui satu laluan dalam `pkg/agent/loop.go` melalui `commands.Executor`.
|
||||
- Adapter saluran tidak lagi menggunakan generic command secara setempat; ia memajukan teks masuk ke laluan bus/agent. Telegram masih auto-register arahan yang disokong semasa startup.
|
||||
- Slash command yang tidak dikenali (contohnya `/foo`) akan diteruskan ke pemprosesan LLM biasa.
|
||||
- Arahan yang didaftarkan tetapi tidak disokong pada saluran semasa (contohnya `/show` di WhatsApp) akan memulangkan ralat yang jelas kepada pengguna dan menghentikan pemprosesan lanjut.
|
||||
|
||||
### 🔒 Security Sandbox
|
||||
|
||||
PicoClaw berjalan dalam persekitaran bersandbox secara lalai. Agen hanya boleh mengakses fail dan melaksanakan arahan dalam workspace yang dikonfigurasikan.
|
||||
|
||||
#### Konfigurasi Lalai
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"workspace": "~/.picoclaw/workspace",
|
||||
"restrict_to_workspace": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
| ----------------------- | ----------------------- | ----------------------------------------- |
|
||||
| `workspace` | `~/.picoclaw/workspace` | Direktori kerja untuk agen |
|
||||
| `restrict_to_workspace` | `true` | Hadkan akses fail/arahan kepada workspace |
|
||||
|
||||
#### Tools yang Dilindungi
|
||||
|
||||
Apabila `restrict_to_workspace: true`, tools berikut disandboxkan:
|
||||
|
||||
| Tool | Fungsi | Sekatan |
|
||||
| ------------- | ----------------- | ----------------------------------- |
|
||||
| `read_file` | Baca fail | Hanya fail dalam workspace |
|
||||
| `write_file` | Tulis fail | Hanya fail dalam workspace |
|
||||
| `list_dir` | Senarai direktori | Hanya direktori dalam workspace |
|
||||
| `edit_file` | Edit fail | Hanya fail dalam workspace |
|
||||
| `append_file` | Tambah ke fail | Hanya fail dalam workspace |
|
||||
| `exec` | Jalankan arahan | Laluan arahan mesti dalam workspace |
|
||||
|
||||
#### Perlindungan Exec Tambahan
|
||||
|
||||
Walaupun dengan `restrict_to_workspace: false`, tool `exec` menyekat arahan berbahaya berikut:
|
||||
|
||||
* `rm -rf`, `del /f`, `rmdir /s` — Pemadaman pukal
|
||||
* `format`, `mkfs`, `diskpart` — Pemformatan cakera
|
||||
* `dd if=` — Pengimejan cakera
|
||||
* Menulis ke `/dev/sd[a-z]` — Tulis terus ke cakera
|
||||
* `shutdown`, `reboot`, `poweroff` — Penutupan sistem
|
||||
* Fork bomb `:(){ :|:& };:`
|
||||
|
||||
### Kawalan Akses Fail
|
||||
|
||||
| Kunci Config | Jenis | Lalai | Penerangan |
|
||||
| ------------------------- | -------- | ----- | --------------------------------------------------------------- |
|
||||
| `tools.allow_read_paths` | string[] | `[]` | Laluan tambahan yang dibenarkan untuk dibaca di luar workspace |
|
||||
| `tools.allow_write_paths` | string[] | `[]` | Laluan tambahan yang dibenarkan untuk ditulis di luar workspace |
|
||||
|
||||
### Keselamatan Exec
|
||||
|
||||
| Kunci Config | Jenis | Lalai | Penerangan |
|
||||
| ---------------------------------- | -------- | ------- | ------------------------------------------------------------ |
|
||||
| `tools.exec.allow_remote` | bool | `false` | Benarkan tool exec dari saluran jauh (Telegram/Discord dll.) |
|
||||
| `tools.exec.enable_deny_patterns` | bool | `true` | Aktifkan pemintasan arahan berbahaya |
|
||||
| `tools.exec.custom_deny_patterns` | string[] | `[]` | Corak regex tersuai untuk disekat |
|
||||
| `tools.exec.custom_allow_patterns` | string[] | `[]` | Corak regex tersuai untuk dibenarkan |
|
||||
|
||||
> **Nota Keselamatan:** Perlindungan symlink diaktifkan secara lalai — semua laluan fail akan diselesaikan melalui `filepath.EvalSymlinks` sebelum dipadankan dengan whitelist, bagi mengelakkan serangan melarikan diri melalui symlink.
|
||||
|
||||
#### Had yang Diketahui: Proses Anak Daripada Build Tools
|
||||
|
||||
Pengawal keselamatan exec hanya memeriksa baris arahan yang PicoClaw lancarkan secara terus. Ia tidak memeriksa secara rekursif proses anak yang dilancarkan oleh tools pembangun yang dibenarkan seperti `make`, `go run`, `cargo`, `npm run`, atau skrip build tersuai.
|
||||
|
||||
Ini bermakna arahan peringkat atas masih boleh mengkompil atau melancarkan binari lain selepas ia melepasi semakan awal pengawal. Dalam amalan, anggap build script, Makefile, package script, dan binari terjana sebagai kod boleh laksana yang memerlukan tahap semakan yang sama seperti arahan shell terus.
|
||||
|
||||
Untuk persekitaran yang lebih berisiko:
|
||||
|
||||
* Semak build script sebelum pelaksanaan.
|
||||
* Utamakan kelulusan/semakan manual untuk aliran kerja compile-and-run.
|
||||
* Jalankan PicoClaw dalam container atau VM jika anda memerlukan pengasingan yang lebih kuat daripada pengawal terbina dalam.
|
||||
|
||||
#### Contoh Ralat
|
||||
|
||||
```
|
||||
[ERROR] tool: Tool execution failed
|
||||
{tool=exec, error=Command blocked by safety guard (path outside working dir)}
|
||||
```
|
||||
|
||||
```
|
||||
[ERROR] tool: Tool execution failed
|
||||
{tool=exec, error=Command blocked by safety guard (dangerous pattern detected)}
|
||||
```
|
||||
|
||||
#### Menyahaktifkan Sekatan (Risiko Keselamatan)
|
||||
|
||||
Jika anda perlu membenarkan agen mengakses laluan di luar workspace:
|
||||
|
||||
**Kaedah 1: Fail config**
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"restrict_to_workspace": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Kaedah 2: Pemboleh ubah persekitaran**
|
||||
|
||||
```bash
|
||||
export PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE=false
|
||||
```
|
||||
|
||||
> ⚠️ **Amaran**: Menyahaktifkan sekatan ini membenarkan agen mengakses mana-mana laluan pada sistem anda. Gunakan dengan berhati-hati hanya dalam persekitaran terkawal.
|
||||
|
||||
#### Ketekalan Sempadan Keselamatan
|
||||
|
||||
Tetapan `restrict_to_workspace` digunakan secara konsisten merentas semua laluan pelaksanaan:
|
||||
|
||||
| Execution Path | Security Boundary |
|
||||
| ---------------- | --------------------------- |
|
||||
| Main Agent | `restrict_to_workspace` ✅ |
|
||||
| Subagent / Spawn | Inherits same restriction ✅ |
|
||||
| Heartbeat tasks | Inherits same restriction ✅ |
|
||||
|
||||
Semua laluan berkongsi sekatan workspace yang sama — tiada cara untuk memintas sempadan keselamatan melalui subagent atau tugasan berjadual.
|
||||
|
||||
### Heartbeat (Tugasan Berkala)
|
||||
|
||||
PicoClaw boleh melaksanakan tugasan berkala secara automatik. Cipta fail `HEARTBEAT.md` dalam workspace anda:
|
||||
|
||||
```markdown
|
||||
# Periodic Tasks
|
||||
|
||||
- Check my email for important messages
|
||||
- Review my calendar for upcoming events
|
||||
- Check the weather forecast
|
||||
```
|
||||
|
||||
Agen akan membaca fail ini setiap 30 minit (boleh dikonfigurasi) dan melaksanakan sebarang tugasan menggunakan tools yang tersedia.
|
||||
|
||||
#### Tugasan Async dengan Spawn
|
||||
|
||||
Untuk tugasan yang berjalan lama (carian web, panggilan API), gunakan tool `spawn` untuk mencipta **subagent**:
|
||||
|
||||
```markdown
|
||||
# Periodic Tasks
|
||||
33
docs/my/debug.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Penyahpepijatan PicoClaw
|
||||
|
||||
PicoClaw melakukan pelbagai interaksi kompleks di sebalik tabir untuk setiap permintaan yang diterimanya, daripada menghala mesej dan menilai kerumitan, hinggalah melaksanakan tools dan menyesuaikan diri dengan kegagalan model. Keupayaan melihat dengan tepat apa yang sedang berlaku sangat penting, bukan sahaja untuk menyelesaikan masalah, malah untuk benar-benar memahami cara agen ini beroperasi.
|
||||
## Memulakan PicoClaw dalam Mod Debug
|
||||
|
||||
Untuk mendapatkan maklumat terperinci tentang apa yang sedang dilakukan oleh agen (permintaan LLM, panggilan tool, penghalaan mesej), anda boleh memulakan gateway PicoClaw dengan flag debug:
|
||||
|
||||
```bash
|
||||
picoclaw gateway --debug
|
||||
# or
|
||||
picoclaw gateway -d
|
||||
```
|
||||
|
||||
Dalam mod ini, sistem akan memformat log dengan lebih terperinci dan memaparkan pratonton system prompt serta hasil pelaksanaan tool.
|
||||
|
||||
## Menyahaktifkan Pemotongan Log (Log Penuh)
|
||||
|
||||
Secara lalai, PicoClaw memotong rentetan yang sangat panjang (seperti *System Prompt* atau hasil output JSON yang besar) dalam log debug supaya konsol kekal mudah dibaca.
|
||||
|
||||
Jika anda perlu memeriksa output penuh sesuatu arahan atau payload tepat yang dihantar kepada model LLM, anda boleh menggunakan flag `--no-truncate`.
|
||||
|
||||
**Nota:** Flag ini *hanya* berfungsi apabila digabungkan dengan mod `--debug`.
|
||||
|
||||
```bash
|
||||
picoclaw gateway --debug --no-truncate
|
||||
|
||||
```
|
||||
|
||||
Apabila flag ini aktif, fungsi pemotongan global dinyahaktifkan. Ini sangat berguna untuk:
|
||||
|
||||
* Mengesahkan sintaks tepat mesej yang dihantar kepada penyedia.
|
||||
* Membaca output lengkap daripada tools seperti `exec`, `web_fetch`, atau `read_file`.
|
||||
* Menyahpepijat sejarah sesi yang disimpan dalam memori.
|
||||
166
docs/my/docker.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# 🐳 Panduan Docker & Quick Start
|
||||
|
||||
> Kembali ke [README](../../README.my.md)
|
||||
|
||||
## 🐳 Docker Compose
|
||||
|
||||
Anda juga boleh menjalankan PicoClaw menggunakan Docker Compose tanpa memasang apa-apa secara setempat.
|
||||
|
||||
```bash
|
||||
# 1. Clone repo ini
|
||||
git clone https://github.com/sipeed/picoclaw.git
|
||||
cd picoclaw
|
||||
|
||||
# 2. Larian pertama — jana docker/data/config.json secara automatik kemudian keluar
|
||||
docker compose -f docker/docker-compose.yml --profile gateway up
|
||||
# Container akan memaparkan "First-run setup complete." dan berhenti.
|
||||
|
||||
# 3. Tetapkan kunci API anda
|
||||
vim docker/data/config.json # Tetapkan API key penyedia, token bot, dan sebagainya.
|
||||
|
||||
# 4. Mula
|
||||
docker compose -f docker/docker-compose.yml --profile gateway up -d
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Pengguna Docker**: Secara lalai, Gateway mendengar pada `127.0.0.1` yang tidak boleh diakses dari host. Jika anda perlu mengakses health endpoint atau mendedahkan port, tetapkan `PICOCLAW_GATEWAY_HOST=0.0.0.0` dalam persekitaran anda atau kemas kini `config.json`.
|
||||
|
||||
```bash
|
||||
# 5. Semak log
|
||||
docker compose -f docker/docker-compose.yml logs -f picoclaw-gateway
|
||||
|
||||
# 6. Hentikan
|
||||
docker compose -f docker/docker-compose.yml --profile gateway down
|
||||
```
|
||||
|
||||
### Mod Launcher (Konsol Web)
|
||||
|
||||
Imej `launcher` merangkumi ketiga-tiga binari (`picoclaw`, `picoclaw-launcher`, `picoclaw-launcher-tui`) dan memulakan konsol web secara lalai, yang menyediakan UI berasaskan pelayar untuk konfigurasi dan sembang.
|
||||
|
||||
```bash
|
||||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
Buka http://localhost:18800 dalam pelayar anda. Launcher mengurus proses gateway secara automatik.
|
||||
|
||||
> [!WARNING]
|
||||
> Konsol web belum menyokong autentikasi. Elakkan mendedahkannya ke internet awam.
|
||||
|
||||
### Mod Agent (One-shot)
|
||||
|
||||
```bash
|
||||
# Tanyakan soalan
|
||||
docker compose -f docker/docker-compose.yml run --rm picoclaw-agent -m "What is 2+2?"
|
||||
|
||||
# Mod interaktif
|
||||
docker compose -f docker/docker-compose.yml run --rm picoclaw-agent
|
||||
```
|
||||
|
||||
### Kemas kini
|
||||
|
||||
```bash
|
||||
docker compose -f docker/docker-compose.yml pull
|
||||
docker compose -f docker/docker-compose.yml --profile gateway up -d
|
||||
```
|
||||
|
||||
### 🚀 Quick Start
|
||||
|
||||
> [!TIP]
|
||||
> Tetapkan API Key anda dalam `~/.picoclaw/config.json`. Dapatkan API Key: [Volcengine (CodingPlan)](https://www.volcengine.com/activity/codingplan?utm_campaign=PicoClaw&utm_content=PicoClaw&utm_medium=devrel&utm_source=OWO&utm_term=PicoClaw) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM). Carian web adalah pilihan — dapatkan [Tavily API](https://tavily.com) percuma (1000 pertanyaan percuma/bulan) atau [Brave Search API](https://brave.com/search/api) (2000 pertanyaan percuma/bulan).
|
||||
|
||||
**1. Inisialisasi**
|
||||
|
||||
```bash
|
||||
picoclaw onboard
|
||||
```
|
||||
|
||||
**2. Konfigurasi** (`~/.picoclaw/config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"workspace": "~/.picoclaw/workspace",
|
||||
"model_name": "gpt-5.4",
|
||||
"max_tokens": 8192,
|
||||
"temperature": 0.7,
|
||||
"max_tool_iterations": 20
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_keys": ["sk-your-api-key"],
|
||||
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_keys": ["your-api-key"],
|
||||
"request_timeout": 300
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_keys": ["your-anthropic-key"]
|
||||
}
|
||||
],
|
||||
"tools": {
|
||||
"web": {
|
||||
"enabled": true,
|
||||
"fetch_limit_bytes": 10485760,
|
||||
"format": "plaintext",
|
||||
"brave": {
|
||||
"enabled": false,
|
||||
"api_key": "YOUR_BRAVE_API_KEY",
|
||||
"max_results": 5
|
||||
},
|
||||
"tavily": {
|
||||
"enabled": false,
|
||||
"api_key": "YOUR_TAVILY_API_KEY",
|
||||
"max_results": 5
|
||||
},
|
||||
"duckduckgo": {
|
||||
"enabled": true,
|
||||
"max_results": 5
|
||||
},
|
||||
"perplexity": {
|
||||
"enabled": false,
|
||||
"api_key": "YOUR_PERPLEXITY_API_KEY",
|
||||
"max_results": 5
|
||||
},
|
||||
"searxng": {
|
||||
"enabled": false,
|
||||
"base_url": "http://your-searxng-instance:8888",
|
||||
"max_results": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Baharu**: Format konfigurasi `model_list` membolehkan penambahan penyedia tanpa perubahan kod. Lihat [Konfigurasi Model](#konfigurasi-model-model_list) untuk butiran.
|
||||
> `request_timeout` adalah pilihan dan menggunakan saat. Jika diabaikan atau ditetapkan kepada `<= 0`, PicoClaw menggunakan timeout lalai (120s).
|
||||
|
||||
**3. Dapatkan API Key**
|
||||
|
||||
* **Penyedia LLM**: [OpenRouter](https://openrouter.ai/keys) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) · [Anthropic](https://console.anthropic.com) · [OpenAI](https://platform.openai.com) · [Gemini](https://aistudio.google.com/api-keys)
|
||||
* **Carian Web** (pilihan):
|
||||
* [Brave Search](https://brave.com/search/api) - Berbayar ($5/1000 pertanyaan, ~$5-6/bulan)
|
||||
* [Perplexity](https://www.perplexity.ai) - Carian berkuasa AI dengan antara muka sembang
|
||||
* [SearXNG](https://github.com/searxng/searxng) - Enjin meta-carian hos kendiri (percuma, tidak perlu API key)
|
||||
* [Tavily](https://tavily.com) - Dioptimumkan untuk AI Agents (1000 permintaan/bulan)
|
||||
* DuckDuckGo - Fallback terbina dalam (tidak memerlukan API key)
|
||||
|
||||
> **Nota**: Lihat `config.example.json` untuk templat konfigurasi penuh.
|
||||
|
||||
**4. Sembang**
|
||||
|
||||
```bash
|
||||
picoclaw agent -m "What is 2+2?"
|
||||
```
|
||||
|
||||
Itu sahaja! Anda kini mempunyai pembantu AI yang berfungsi dalam masa 2 minit.
|
||||
|
||||
---
|
||||
61
docs/my/spawn-tasks.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# 🔄 Spawn & Tugasan Async
|
||||
|
||||
> Kembali ke [README](../../README.my.md)
|
||||
|
||||
## Tugasan Cepat (balas terus)
|
||||
|
||||
- Laporkan masa semasa
|
||||
|
||||
## Tugasan Panjang (guna spawn untuk async)
|
||||
|
||||
- Cari berita AI di web dan ringkaskan
|
||||
- Semak e-mel dan laporkan mesej penting
|
||||
```
|
||||
|
||||
**Tingkah laku utama:**
|
||||
|
||||
| Feature | Description |
|
||||
| ----------------------- | --------------------------------------------------------- |
|
||||
| **spawn** | Mencipta sub-agen async, tidak menyekat heartbeat |
|
||||
| **Independent context** | Sub-agen mempunyai konteks sendiri, tiada sejarah sesi |
|
||||
| **message tool** | Sub-agen berkomunikasi terus dengan pengguna melalui message tool |
|
||||
| **Non-blocking** | Selepas spawn, heartbeat terus ke tugasan seterusnya |
|
||||
|
||||
#### Cara Komunikasi Sub-agen Berfungsi
|
||||
|
||||
```
|
||||
Heartbeat dicetuskan
|
||||
↓
|
||||
Agen membaca HEARTBEAT.md
|
||||
↓
|
||||
Untuk tugasan panjang: spawn sub-agen
|
||||
↓ ↓
|
||||
Terus ke tugasan seterusnya Sub-agen bekerja secara bebas
|
||||
↓ ↓
|
||||
Semua tugasan selesai Sub-agen menggunakan tool "message"
|
||||
↓ ↓
|
||||
Balas HEARTBEAT_OK Pengguna menerima hasil secara terus
|
||||
```
|
||||
|
||||
Sub-agen mempunyai akses kepada tools (message, web_search, dan sebagainya) dan boleh berkomunikasi dengan pengguna secara bebas tanpa melalui agen utama.
|
||||
|
||||
**Konfigurasi:**
|
||||
|
||||
```json
|
||||
{
|
||||
"heartbeat": {
|
||||
"enabled": true,
|
||||
"interval": 30
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
| ---------- | ------- | ---------------------------------------- |
|
||||
| `enabled` | `true` | Hidupkan/matikan heartbeat |
|
||||
| `interval` | `30` | Selang semakan dalam minit (minimum: 5) |
|
||||
|
||||
**Pemboleh ubah persekitaran:**
|
||||
|
||||
* `PICOCLAW_HEARTBEAT_ENABLED=false` untuk nyahaktifkan
|
||||
* `PICOCLAW_HEARTBEAT_INTERVAL=60` untuk menukar selang
|
||||
43
docs/my/troubleshooting.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Penyelesaian Masalah
|
||||
|
||||
## "model ... not found in model_list" atau OpenRouter "free is not a valid model ID"
|
||||
|
||||
**Gejala:** Anda akan melihat salah satu daripada mesej berikut:
|
||||
|
||||
- `Error creating provider: model "openrouter/free" not found in model_list`
|
||||
- OpenRouter memulangkan 400: `"free is not a valid model ID"`
|
||||
|
||||
**Punca:** Medan `model` dalam entri `model_list` anda ialah nilai yang dihantar ke API. Untuk OpenRouter, anda mesti menggunakan ID model **penuh**, bukan bentuk singkatan.
|
||||
|
||||
- **Salah:** `"model": "free"` → OpenRouter menerima `free` dan menolaknya.
|
||||
- **Betul:** `"model": "openrouter/free"` → OpenRouter menerima `openrouter/free` (routing auto free-tier).
|
||||
|
||||
**Penyelesaian:** Dalam `~/.picoclaw/config.json` (atau laluan config anda):
|
||||
|
||||
1. **agents.defaults.model** mesti sepadan dengan `model_name` dalam `model_list` (contohnya `"openrouter-free"`).
|
||||
2. Medan **model** bagi entri tersebut mesti merupakan ID model OpenRouter yang sah, contohnya:
|
||||
- `"openrouter/free"` – auto free-tier
|
||||
- `"google/gemini-2.0-flash-exp:free"`
|
||||
- `"meta-llama/llama-3.1-8b-instruct:free"`
|
||||
|
||||
Example snippet:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": "openrouter-free"
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "openrouter-free",
|
||||
"model": "openrouter/free",
|
||||
"api_key": "sk-or-v1-YOUR_OPENROUTER_KEY",
|
||||
"api_base": "https://openrouter.ai/api/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Dapatkan kunci anda di [OpenRouter Keys](https://openrouter.ai/keys).
|
||||
|
|
@ -56,6 +56,7 @@ This design also enables **multi-agent support** with flexible provider selectio
|
|||
| **通义千问 (Qwen)** | `qwen/` | `https://dashscope.aliyuncs.com/compatible-mode/v1` | OpenAI | [Get Key](https://dashscope.console.aliyun.com) |
|
||||
| **NVIDIA** | `nvidia/` | `https://integrate.api.nvidia.com/v1` | OpenAI | [Get Key](https://build.nvidia.com) |
|
||||
| **Ollama** | `ollama/` | `http://localhost:11434/v1` | OpenAI | Local (no key needed) |
|
||||
| **LM Studio** | `lmstudio/` | `http://localhost:1234/v1` | OpenAI | Optional (local default: no key) |
|
||||
| **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [Get Key](https://openrouter.ai/keys) |
|
||||
| **LiteLLM Proxy** | `litellm/` | `http://localhost:4000/v1` | OpenAI | Your LiteLLM proxy key |
|
||||
| **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | Local |
|
||||
|
|
@ -79,22 +80,22 @@ This design also enables **multi-agent support** with flexible provider selectio
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
"api_keys": ["sk-your-api-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
"api_keys": ["sk-your-openai-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-zhipu-key"
|
||||
"api_keys": ["your-zhipu-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -117,7 +118,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
|
|||
{
|
||||
"model_name": "voice-gemini",
|
||||
"model": "gemini/gemini-2.5-flash",
|
||||
"api_key": "your-gemini-key"
|
||||
"api_keys": ["your-gemini-key"]
|
||||
}
|
||||
],
|
||||
"voice": {
|
||||
|
|
@ -140,7 +141,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
|
|||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -150,7 +151,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -160,7 +161,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
|
|||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -170,7 +171,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
|
|||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "openai/glm-4.7",
|
||||
"api_key": "your-z.ai-key"
|
||||
"api_keys": ["your-z.ai-key"],
|
||||
"api_base": "https://api.z.ai/api/coding/paas/v4"
|
||||
}
|
||||
```
|
||||
|
|
@ -181,7 +182,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
|
|||
{
|
||||
"model_name": "deepseek-chat",
|
||||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -191,7 +192,7 @@ If `voice.model_name` is not configured, PicoClaw will continue to fall back to
|
|||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -205,7 +206,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
|
|||
{
|
||||
"model_name": "claude-opus-4-6",
|
||||
"model": "anthropic-messages/claude-opus-4-6",
|
||||
"api_key": "sk-ant-your-key",
|
||||
"api_keys": ["sk-ant-your-key"],
|
||||
"api_base": "https://api.anthropic.com"
|
||||
}
|
||||
```
|
||||
|
|
@ -226,6 +227,18 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
|
|||
}
|
||||
```
|
||||
|
||||
**LM Studio (local)**
|
||||
|
||||
```json
|
||||
{
|
||||
"model_name": "lmstudio-local",
|
||||
"model": "lmstudio/openai/gpt-oss-20b"
|
||||
}
|
||||
```
|
||||
|
||||
`api_base` defaults to `http://localhost:1234/v1`. API key is optional unless your LM Studio server enables authentication.<br/>
|
||||
PicoClaw sends OpenAI-compatible requests to LM Studio, and strips the `lmstudio/` prefix before sending requests, so `lmstudio/openai/gpt-oss-20b` sends `openai/gpt-oss-20b` to the LM Studio server.
|
||||
|
||||
**Custom Proxy/API**
|
||||
|
||||
```json
|
||||
|
|
@ -233,7 +246,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
|
|||
"model_name": "my-custom-model",
|
||||
"model": "openai/custom-model",
|
||||
"api_base": "https://my-proxy.com/v1",
|
||||
"api_key": "sk-...",
|
||||
"api_keys": ["sk-..."],
|
||||
"request_timeout": 300
|
||||
}
|
||||
```
|
||||
|
|
@ -245,7 +258,7 @@ For direct Anthropic API access or custom endpoints that only support Anthropic'
|
|||
"model_name": "lite-gpt4",
|
||||
"model": "litellm/lite-gpt4",
|
||||
"api_base": "http://localhost:4000/v1",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -259,7 +272,7 @@ If the standard Zhipu endpoint (`https://open.bigmodel.cn/api/paas/v4`) returns
|
|||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "openai/glm-4.7",
|
||||
"api_key": "your-zhipu-api-key",
|
||||
"api_keys": ["your-zhipu-api-key"],
|
||||
"api_base": "https://api.z.ai/api/coding/paas/v4"
|
||||
}
|
||||
```
|
||||
|
|
@ -277,13 +290,13 @@ Configure multiple endpoints for the same model name—PicoClaw will automatical
|
|||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
"api_keys": ["sk-key1"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
"api_keys": ["sk-key2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -302,17 +315,17 @@ It also applies cooldown tracking per candidate to avoid immediately retrying a
|
|||
"model_name": "qwen-main",
|
||||
"model": "openai/qwen3.5:cloud",
|
||||
"api_base": "https://api.example.com/v1",
|
||||
"api_key": "sk-main"
|
||||
"api_keys": ["sk-main"]
|
||||
},
|
||||
{
|
||||
"model_name": "deepseek-backup",
|
||||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-backup-1"
|
||||
"api_keys": ["sk-backup-1"]
|
||||
},
|
||||
{
|
||||
"model_name": "gemini-backup",
|
||||
"model": "gemini/gemini-2.5-flash",
|
||||
"api_key": "sk-backup-2"
|
||||
"api_keys": ["sk-backup-2"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -330,7 +343,7 @@ If you use key-level failover for the same model, PicoClaw can chain through add
|
|||
|
||||
#### Migration from Legacy `providers` Config
|
||||
|
||||
The old `providers` configuration is **deprecated** but still supported for backward compatibility.
|
||||
The old `providers` configuration is **deprecated** and has been removed in V2. Existing V0/V1 configs are auto-migrated.
|
||||
|
||||
**Old Config (deprecated):**
|
||||
|
||||
|
|
@ -355,11 +368,12 @@ The old `providers` configuration is **deprecated** but still supported for back
|
|||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,22 @@ PICOCLAW_HOME=/opt/picoclaw picoclaw agent
|
|||
PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway
|
||||
```
|
||||
|
||||
### Nível de Log do Gateway
|
||||
|
||||
`gateway.log_level` controla a verbosidade dos logs do Gateway, configurável em `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"gateway": {
|
||||
"log_level": "warn"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
O valor padrão é `warn`. Valores suportados: `debug`, `info`, `warn`, `error`, `fatal`.
|
||||
|
||||
Também pode ser substituído pela variável de ambiente: `PICOCLAW_LOG_LEVEL=info`
|
||||
|
||||
### Layout do Workspace
|
||||
|
||||
O PicoClaw armazena dados no seu workspace configurado (padrão: `~/.picoclaw/workspace`):
|
||||
|
|
@ -319,15 +335,15 @@ Configure múltiplos endpoints para o mesmo nome de modelo — PicoClaw fará ro
|
|||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" },
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" }
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] },
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Migração da Configuração Legada `providers`
|
||||
|
||||
A configuração antiga `providers` está **depreciada** mas ainda é suportada. Veja [docs/migration/model-list-migration.md](../migration/model-list-migration.md).
|
||||
A configuração antiga `providers` está **depreciada** e foi removida no V2. Configs V0/V1 existentes são auto-migradas. Veja [docs/migration/model-list-migration.md](../migration/model-list-migration.md).
|
||||
|
||||
### Arquitetura de Providers
|
||||
|
||||
|
|
|
|||
|
|
@ -92,19 +92,19 @@ picoclaw onboard
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key",
|
||||
"api_keys": ["sk-your-api-key"],
|
||||
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "your-api-key",
|
||||
"api_keys": ["your-api-key"],
|
||||
"request_timeout": 300
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "your-anthropic-key"
|
||||
"api_keys": ["your-anthropic-key"]
|
||||
}
|
||||
],
|
||||
"tools": {
|
||||
|
|
|
|||
|
|
@ -73,22 +73,22 @@ Este design também permite **suporte multi-agente** com seleção flexível de
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
"api_keys": ["sk-your-api-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
"api_keys": ["sk-your-openai-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-zhipu-key"
|
||||
"api_keys": ["your-zhipu-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -107,7 +107,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de
|
|||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de
|
|||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de
|
|||
{
|
||||
"model_name": "deepseek-chat",
|
||||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ Este design também permite **suporte multi-agente** com seleção flexível de
|
|||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ Para acesso direto à API Anthropic ou endpoints personalizados que suportam ape
|
|||
{
|
||||
"model_name": "claude-opus-4-6",
|
||||
"model": "anthropic-messages/claude-opus-4-6",
|
||||
"api_key": "sk-ant-your-key",
|
||||
"api_keys": ["sk-ant-your-key"],
|
||||
"api_base": "https://api.anthropic.com"
|
||||
}
|
||||
```
|
||||
|
|
@ -189,7 +189,7 @@ Para acesso direto à API Anthropic ou endpoints personalizados que suportam ape
|
|||
"model_name": "my-custom-model",
|
||||
"model": "openai/custom-model",
|
||||
"api_base": "https://my-proxy.com/v1",
|
||||
"api_key": "sk-...",
|
||||
"api_keys": ["sk-..."],
|
||||
"request_timeout": 300
|
||||
}
|
||||
```
|
||||
|
|
@ -201,7 +201,7 @@ Para acesso direto à API Anthropic ou endpoints personalizados que suportam ape
|
|||
"model_name": "lite-gpt4",
|
||||
"model": "litellm/lite-gpt4",
|
||||
"api_base": "http://localhost:4000/v1",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -218,13 +218,13 @@ Configure múltiplos endpoints para o mesmo nome de modelo — o PicoClaw fará
|
|||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
"api_keys": ["sk-key1"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
"api_keys": ["sk-key2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ Configure múltiplos endpoints para o mesmo nome de modelo — o PicoClaw fará
|
|||
|
||||
#### Migração da Configuração Legacy `providers`
|
||||
|
||||
A configuração antiga `providers` está **descontinuada** mas ainda é suportada para compatibilidade retroativa.
|
||||
A configuração antiga `providers` está **descontinuada** e foi removida no V2. Configs V0/V1 existentes são auto-migradas.
|
||||
|
||||
**Configuração Antiga (descontinuada):**
|
||||
|
||||
|
|
@ -257,11 +257,12 @@ A configuração antiga `providers` está **descontinuada** mas ainda é suporta
|
|||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -282,7 +283,7 @@ O PicoClaw roteia provedores por família de protocolo:
|
|||
- Protocolo Anthropic: Comportamento nativo da API Claude.
|
||||
- Caminho Codex/OAuth: Rota de autenticação OAuth/token da OpenAI.
|
||||
|
||||
Isso mantém o runtime leve enquanto torna novos backends compatíveis com OpenAI basicamente uma operação de configuração (`api_base` + `api_key`).
|
||||
Isso mantém o runtime leve enquanto torna novos backends compatíveis com OpenAI basicamente uma operação de configuração (`api_base` + `api_keys`).
|
||||
|
||||
<details>
|
||||
<summary><b>Zhipu</b></summary>
|
||||
|
|
|
|||
|
|
@ -401,7 +401,7 @@ The pattern is: `PICOCLAW_<SECTION>_<KEY>_<FIELD>` with underscores separating p
|
|||
3. **Set file permissions**: `chmod 600 ~/.picoclaw/.security.yml`
|
||||
4. **Use different keys** for different environments (dev, staging, production)
|
||||
5. **Rotate keys regularly** and update `.security.yml`
|
||||
6. **Backup securely**: Encrypt backups containing `.security.yml`
|
||||
6. **Backup securely**: Encrypt backups containing `.security.yml`. Note that config migrations automatically create date-stamped backups (e.g., `config.json.20260330.bak` and `.security.yml.20260330.bak`)
|
||||
7. **Review access**: Ensure only authorized users have read access to the file
|
||||
|
||||
## API
|
||||
|
|
@ -444,7 +444,7 @@ Returns the path to `.security.yml` relative to the config file.
|
|||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"workspace": "~/picoclaw-workspace",
|
||||
|
|
@ -557,6 +557,8 @@ go test ./pkg/config -run TestSecurityConfig
|
|||
|
||||
### Step 1: Backup your config
|
||||
|
||||
The system automatically creates a date-stamped backup before saving a migrated config (e.g., `config.json.20260330.bak` and `.security.yml.20260330.bak`). If you prefer a manual backup:
|
||||
|
||||
```bash
|
||||
cp ~/.picoclaw/config.json ~/.picoclaw/config.json.backup
|
||||
```
|
||||
|
|
@ -597,9 +599,11 @@ Test your models and channels to ensure everything works correctly.
|
|||
|
||||
### Step 8: Clean up (optional)
|
||||
|
||||
If everything works, you can delete the backup:
|
||||
If everything works, you can delete the backups:
|
||||
```bash
|
||||
rm ~/.picoclaw/config.json.backup
|
||||
# Also remove auto-generated date-stamped backups if desired:
|
||||
rm ~/.picoclaw/config.json.20*.bak ~/.picoclaw/.security.yml.20*.bak
|
||||
```
|
||||
|
||||
## Advanced: Encrypted API Keys
|
||||
|
|
|
|||
|
|
@ -70,12 +70,12 @@ General settings for fetching and processing webpage content.
|
|||
|
||||
Baidu Search uses the [Qianfan AI Search API](https://cloud.baidu.com/doc/qianfan-api/s/Wmbq4z7e5), which is AI-powered and optimized for Chinese-language queries.
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
|---------------|--------|------------------------------------------------------------------|---------------------------|
|
||||
| `enabled` | bool | false | Enable Baidu Search |
|
||||
| `api_key` | string | - | Qianfan API key |
|
||||
| `base_url` | string | `https://qianfan.baidubce.com/v2/ai_search/web_search` | Baidu Search API URL |
|
||||
| `max_results` | int | 10 | Maximum number of results |
|
||||
| Config | Type | Default | Description |
|
||||
|---------------|--------|--------------------------------------------------------|---------------------------|
|
||||
| `enabled` | bool | false | Enable Baidu Search |
|
||||
| `api_key` | string | - | Qianfan API key |
|
||||
| `base_url` | string | `https://qianfan.baidubce.com/v2/ai_search/web_search` | Baidu Search API URL |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -107,25 +107,25 @@ Baidu Search uses the [Qianfan AI Search API](https://cloud.baidu.com/doc/qianfa
|
|||
| `enabled` | bool | false | Enable Tavily search |
|
||||
| `api_key` | string | - | Tavily API key |
|
||||
| `base_url` | string | - | Custom Tavily API base URL |
|
||||
| `max_results` | int | 0 | Maximum number of results (0 = default) |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
||||
### SearXNG
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
|---------------|--------|--------------------------|---------------------------|
|
||||
| `enabled` | bool | false | Enable SearXNG search |
|
||||
| `base_url` | string | `http://localhost:8888` | SearXNG instance URL |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
| Config | Type | Default | Description |
|
||||
|---------------|--------|-------------------------|---------------------------|
|
||||
| `enabled` | bool | false | Enable SearXNG search |
|
||||
| `base_url` | string | `http://localhost:8888` | SearXNG instance URL |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
||||
### GLM Search
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
|-----------------|--------|------------------------------------------------------|---------------------------|
|
||||
| `enabled` | bool | false | Enable GLM Search |
|
||||
| `api_key` | string | - | GLM API key |
|
||||
| `base_url` | string | `https://open.bigmodel.cn/api/paas/v4/web_search` | GLM Search API URL |
|
||||
| `search_engine` | string | `search_std` | Search engine type |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
| Config | Type | Default | Description |
|
||||
|-----------------|--------|---------------------------------------------------|---------------------------|
|
||||
| `enabled` | bool | false | Enable GLM Search |
|
||||
| `api_key` | string | - | GLM API key |
|
||||
| `base_url` | string | `https://open.bigmodel.cn/api/paas/v4/web_search` | GLM Search API URL |
|
||||
| `search_engine` | string | `search_std` | Search engine type |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
||||
### Additional Web Settings
|
||||
|
||||
|
|
@ -134,6 +134,28 @@ Baidu Search uses the [Qianfan AI Search API](https://cloud.baidu.com/doc/qianfa
|
|||
| `prefer_native` | bool | true | Prefer provider's native search over configured search engines |
|
||||
| `private_host_whitelist` | string[] | `[]` | Private/internal hosts allowed for web fetching |
|
||||
|
||||
### `web_search` Tool Parameters
|
||||
|
||||
At runtime, the `web_search` tool accepts the following parameters:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `query` | string | yes | Search query string |
|
||||
| `count` | integer | no | Number of results to return. Default: `10`, max: `10` |
|
||||
| `range` | string | no | Optional time filter: `d` (day), `w` (week), `m` (month), `y` (year) |
|
||||
|
||||
If `range` is omitted, PicoClaw performs an unrestricted search.
|
||||
|
||||
### Example `web_search` Call
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "ai agent news",
|
||||
"count": 10,
|
||||
"range": "w"
|
||||
}
|
||||
```
|
||||
|
||||
## Exec Tool
|
||||
|
||||
The exec tool is used to execute shell commands.
|
||||
|
|
@ -226,8 +248,11 @@ The cron tool is used for scheduling periodic tasks.
|
|||
|
||||
| Config | Type | Default | Description |
|
||||
|------------------------|------|---------|------------------------------------------------|
|
||||
| `enabled` | bool | true | Register the agent-facing cron tool |
|
||||
| `allow_command` | bool | true | Allow command jobs without extra confirmation |
|
||||
| `exec_timeout_minutes` | int | 5 | Execution timeout in minutes, 0 means no limit |
|
||||
| `allow_command` | bool | false | Allow cron tasks to execute shell commands |
|
||||
|
||||
For schedule types, execution modes (`deliver`, agent turn, and command jobs), persistence, and the current command-security gates, see [Scheduled Tasks and Cron Jobs](cron.md).
|
||||
|
||||
## MCP Tool
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,22 @@ PICOCLAW_HOME=/opt/picoclaw picoclaw agent
|
|||
PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway
|
||||
```
|
||||
|
||||
### Mức Log của Gateway
|
||||
|
||||
`gateway.log_level` kiểm soát mức độ chi tiết của log Gateway, có thể cấu hình trong `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"gateway": {
|
||||
"log_level": "warn"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Giá trị mặc định là `warn`. Các giá trị được hỗ trợ: `debug`, `info`, `warn`, `error`, `fatal`.
|
||||
|
||||
Cũng có thể ghi đè bằng biến môi trường: `PICOCLAW_LOG_LEVEL=info`
|
||||
|
||||
### Bố Cục Workspace
|
||||
|
||||
PicoClaw lưu trữ dữ liệu trong workspace đã cấu hình (mặc định: `~/.picoclaw/workspace`):
|
||||
|
|
@ -319,15 +335,15 @@ Cấu hình nhiều endpoint cho cùng tên mô hình — PicoClaw sẽ tự đ
|
|||
```json
|
||||
{
|
||||
"model_list": [
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_key": "sk-key1" },
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" }
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api1.example.com/v1", "api_keys": ["sk-key1"] },
|
||||
{ "model_name": "gpt-5.4", "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_keys": ["sk-key2"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Di Chuyển Từ Cấu Hình `providers` Cũ
|
||||
|
||||
Cấu hình `providers` cũ đã **bị deprecated** nhưng vẫn được hỗ trợ. Xem [docs/migration/model-list-migration.md](../migration/model-list-migration.md).
|
||||
Cấu hình `providers` cũ đã **bị deprecated** và đã được loại bỏ trong V2. Các cấu hình V0/V1 hiện có sẽ được tự động migrate. Xem [docs/migration/model-list-migration.md](../migration/model-list-migration.md).
|
||||
|
||||
### Kiến Trúc Provider
|
||||
|
||||
|
|
|
|||
|
|
@ -92,19 +92,19 @@ picoclaw onboard
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key",
|
||||
"api_keys": ["sk-your-api-key"],
|
||||
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "your-api-key",
|
||||
"api_keys": ["your-api-key"],
|
||||
"request_timeout": 300
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "your-anthropic-key"
|
||||
"api_keys": ["your-anthropic-key"]
|
||||
}
|
||||
],
|
||||
"tools": {
|
||||
|
|
|
|||
|
|
@ -73,22 +73,22 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
"api_keys": ["sk-your-api-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
"api_keys": ["sk-your-openai-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-zhipu-key"
|
||||
"api_keys": ["your-zhipu-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -107,7 +107,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
|
|||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
|
|||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
|
|||
{
|
||||
"model_name": "deepseek-chat",
|
||||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
|
|||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
|
|||
{
|
||||
"model_name": "claude-opus-4-6",
|
||||
"model": "anthropic-messages/claude-opus-4-6",
|
||||
"api_key": "sk-ant-your-key",
|
||||
"api_keys": ["sk-ant-your-key"],
|
||||
"api_base": "https://api.anthropic.com"
|
||||
}
|
||||
```
|
||||
|
|
@ -189,7 +189,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
|
|||
"model_name": "my-custom-model",
|
||||
"model": "openai/custom-model",
|
||||
"api_base": "https://my-proxy.com/v1",
|
||||
"api_key": "sk-...",
|
||||
"api_keys": ["sk-..."],
|
||||
"request_timeout": 300
|
||||
}
|
||||
```
|
||||
|
|
@ -201,7 +201,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa agent** với lựa chọn pr
|
|||
"model_name": "lite-gpt4",
|
||||
"model": "litellm/lite-gpt4",
|
||||
"api_base": "http://localhost:4000/v1",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -218,13 +218,13 @@ Cấu hình nhiều endpoint cho cùng tên mô hình — PicoClaw sẽ tự đ
|
|||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
"api_keys": ["sk-key1"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
"api_keys": ["sk-key2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ Cấu hình nhiều endpoint cho cùng tên mô hình — PicoClaw sẽ tự đ
|
|||
|
||||
#### Di Chuyển Từ Cấu Hình Legacy `providers`
|
||||
|
||||
Cấu hình `providers` cũ đã **ngừng hỗ trợ** nhưng vẫn được hỗ trợ để tương thích ngược.
|
||||
Cấu hình `providers` cũ đã **bị deprecated** và đã được loại bỏ trong V2. Các cấu hình V0/V1 hiện có sẽ được tự động migrate.
|
||||
|
||||
**Cấu hình cũ (ngừng hỗ trợ):**
|
||||
|
||||
|
|
@ -257,11 +257,12 @@ Cấu hình `providers` cũ đã **ngừng hỗ trợ** nhưng vẫn được h
|
|||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -282,7 +283,7 @@ PicoClaw định tuyến provider theo họ giao thức:
|
|||
- Giao thức Anthropic: Hành vi API native của Claude.
|
||||
- Đường dẫn Codex/OAuth: Tuyến xác thực OAuth/token của OpenAI.
|
||||
|
||||
Điều này giữ runtime nhẹ trong khi làm cho backend tương thích OpenAI mới chủ yếu là thao tác cấu hình (`api_base` + `api_key`).
|
||||
Điều này giữ runtime nhẹ trong khi làm cho backend tương thích OpenAI mới chủ yếu là thao tác cấu hình (`api_base` + `api_keys`).
|
||||
|
||||
<details>
|
||||
<summary><b>Zhipu</b></summary>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,22 @@ PICOCLAW_HOME=/opt/picoclaw picoclaw agent
|
|||
PICOCLAW_HOME=/srv/picoclaw PICOCLAW_CONFIG=/srv/picoclaw/main.json picoclaw gateway
|
||||
```
|
||||
|
||||
### Gateway 日志等级
|
||||
|
||||
`gateway.log_level` 控制 Gateway 的日志详细程度,可在 `config.json` 中配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"gateway": {
|
||||
"log_level": "warn"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
默认值为 `warn`。支持的值:`debug`、`info`、`warn`、`error`、`fatal`。
|
||||
|
||||
也可通过环境变量覆盖:`PICOCLAW_LOG_LEVEL=info`
|
||||
|
||||
### 工作区布局 (Workspace Layout)
|
||||
|
||||
PicoClaw 将数据存储在您配置的工作区中(默认:`~/.picoclaw/workspace`):
|
||||
|
|
@ -51,6 +67,18 @@ PicoClaw 将数据存储在您配置的工作区中(默认:`~/.picoclaw/work
|
|||
|
||||
> **提示:** 对 `AGENT.md`、`SOUL.md`、`USER.md` 和 `memory/MEMORY.md` 的修改会通过文件修改时间(mtime)在运行时自动检测。**无需重启 gateway**,Agent 将在下一次请求时自动加载最新内容。
|
||||
|
||||
### Web 启动器控制台
|
||||
|
||||
用 **picoclaw-launcher** 打开浏览器控制台前需要先登录。**访问口令**与 **会话签名密钥**默认在**每次启动时在内存中生成**(重启后随机口令会变)。若设置环境变量 **`PICOCLAW_LAUNCHER_TOKEN`**,则该进程使用固定口令(启动日志中不会打印具体口令值)。
|
||||
|
||||
**到哪里找口令**:**控制台模式**(`-console`)请看启动时的终端输出;**托盘 / GUI 模式**可使用托盘菜单中的「复制控制台口令」,并在 **`$PICOCLAW_HOME/logs/launcher.log`**(未设置 `PICOCLAW_HOME` 时一般为 `~/.picoclaw/logs/launcher.log`)中查看本次启动写入的随机口令。登录页在未登录时会根据当前运行方式展示提示(含日志文件绝对路径等;**接口与页面均不会返回口令本身**)。
|
||||
|
||||
- **配置文件**:与 `config.json` 同一目录(若设置了 `PICOCLAW_CONFIG`,则与它所指的文件同目录)。启动器专用文件名为 `launcher-config.json`。
|
||||
- **登录与链接**:在登录页输入口令;自动打开浏览器时可在 URL 上使用 `?token=`。全站响应携带 **`Referrer-Policy: no-referrer`**,减轻 `token` 经 `Referer` 头泄露的风险。
|
||||
- **退出登录**:应使用 **`POST /api/auth/logout`**,且请求头为 **`Content-Type: application/json`**(请求体可为 `{}`),勿使用可被第三方页面触发的 GET 链接登出。
|
||||
- **暴力尝试**:`POST /api/auth/login` 对同一远程地址有 **每分钟尝试次数上限**(超限返回 HTTP 429)。
|
||||
- **会话时长**:登录后的 HttpOnly 会话 Cookie 默认约 **7 天**有效,到期需重新用口令登录。
|
||||
|
||||
### 技能来源 (Skill Sources)
|
||||
|
||||
默认情况下,技能会按以下顺序加载:
|
||||
|
|
@ -337,6 +365,7 @@ Agent 读取 HEARTBEAT.md
|
|||
| **通义千问 (Qwen)** | `qwen/` | `https://dashscope.aliyuncs.com/compatible-mode/v1` | OpenAI | [获取](https://dashscope.console.aliyun.com) |
|
||||
| **NVIDIA** | `nvidia/` | `https://integrate.api.nvidia.com/v1` | OpenAI | [获取](https://build.nvidia.com) |
|
||||
| **Ollama** | `ollama/` | `http://localhost:11434/v1` | OpenAI | 本地(无需 Key) |
|
||||
| **LM Studio** | `lmstudio/` | `http://localhost:1234/v1` | OpenAI | 可选(本地默认无需密钥) |
|
||||
| **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [获取](https://openrouter.ai/keys) |
|
||||
| **LiteLLM Proxy** | `litellm/` | `http://localhost:4000/v1` | OpenAI | 你的 LiteLLM 代理 Key |
|
||||
| **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | 本地 |
|
||||
|
|
@ -358,22 +387,22 @@ Agent 读取 HEARTBEAT.md
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
"api_keys": ["sk-your-api-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
"api_keys": ["sk-your-openai-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-zhipu-key"
|
||||
"api_keys": ["your-zhipu-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -393,7 +422,7 @@ Agent 读取 HEARTBEAT.md
|
|||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -406,7 +435,7 @@ Agent 读取 HEARTBEAT.md
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -419,7 +448,7 @@ Agent 读取 HEARTBEAT.md
|
|||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -432,7 +461,7 @@ Agent 读取 HEARTBEAT.md
|
|||
{
|
||||
"model_name": "deepseek-chat",
|
||||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -445,7 +474,7 @@ Agent 读取 HEARTBEAT.md
|
|||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -457,7 +486,7 @@ Agent 读取 HEARTBEAT.md
|
|||
{
|
||||
"model_name": "claude-opus-4-6",
|
||||
"model": "anthropic-messages/claude-opus-4-6",
|
||||
"api_key": "sk-ant-your-key",
|
||||
"api_keys": ["sk-ant-your-key"],
|
||||
"api_base": "https://api.anthropic.com"
|
||||
}
|
||||
```
|
||||
|
|
@ -478,6 +507,21 @@ Agent 读取 HEARTBEAT.md
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>LM Studio(本地)</b></summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"model_name": "lmstudio-local",
|
||||
"model": "lmstudio/openai/gpt-oss-20b"
|
||||
}
|
||||
```
|
||||
|
||||
`api_base` 默认是 `http://localhost:1234/v1`。除非你在 LM Studio 侧启用了认证,否则不需要配置 API Key。
|
||||
PicoClaw 向 LM Studio 的 OpenAI 兼容终结点发送请求,且将移除首个 `lmstudio/` 前缀,因此 `lmstudio/openai/gpt-oss-20b` 会发送 `openai/gpt-oss-20b`。
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>自定义代理 / LiteLLM</b></summary>
|
||||
|
||||
|
|
@ -486,7 +530,7 @@ Agent 读取 HEARTBEAT.md
|
|||
"model_name": "my-custom-model",
|
||||
"model": "openai/custom-model",
|
||||
"api_base": "https://my-proxy.com/v1",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -505,13 +549,13 @@ PicoClaw 只剥离最外层的 `litellm/` 前缀再发送请求,因此 `litell
|
|||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
"api_keys": ["sk-key1"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
"api_keys": ["sk-key2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -519,7 +563,7 @@ PicoClaw 只剥离最外层的 `litellm/` 前缀再发送请求,因此 `litell
|
|||
|
||||
#### 从旧版 `providers` 配置迁移
|
||||
|
||||
旧版 `providers` 配置**已废弃**,但仍向后兼容。完整迁移指南见 [docs/migration/model-list-migration.md](../migration/model-list-migration.md)。
|
||||
旧版 `providers` 配置**已废弃**,V2 中已移除。现有 V0/V1 配置会自动迁移。完整迁移指南见 [docs/migration/model-list-migration.md](../migration/model-list-migration.md)。
|
||||
|
||||
### Provider 架构
|
||||
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ docker compose -f docker/docker-compose.yml --profile gateway down
|
|||
docker compose -f docker/docker-compose.yml --profile launcher up -d
|
||||
```
|
||||
|
||||
在浏览器中打开 http://localhost:18800。Launcher 会自动管理 Gateway 进程。
|
||||
在浏览器中打开 <http://localhost:18800>。Launcher 会自动管理 Gateway 进程。
|
||||
|
||||
> [!WARNING]
|
||||
> Web 控制台尚不支持身份验证。请勿将其暴露到公网。
|
||||
> Web 控制台通过 dashboard 令牌鉴权(默认每次启动在内存中生成;可用 `PICOCLAW_LAUNCHER_TOKEN` 固定)。**不要**将启动器暴露到不可信网络或公网。完整说明见 [配置指南](configuration.md) 中的「Web 启动器控制台」一节。
|
||||
|
||||
### Agent 模式 (一次性运行)
|
||||
|
||||
|
|
@ -94,19 +94,19 @@ picoclaw onboard
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key",
|
||||
"api_keys": ["sk-your-api-key"],
|
||||
"api_base":"https://ark.cn-beijing.volces.com/api/coding/v3"
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "your-api-key",
|
||||
"api_keys": ["your-api-key"],
|
||||
"request_timeout": 300
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "your-anthropic-key"
|
||||
"api_keys": ["your-anthropic-key"]
|
||||
}
|
||||
],
|
||||
"tools": {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@
|
|||
| **通义千问 (Qwen)** | `qwen/` | `https://dashscope.aliyuncs.com/compatible-mode/v1` | OpenAI | [获取密钥](https://dashscope.console.aliyun.com) |
|
||||
| **NVIDIA** | `nvidia/` | `https://integrate.api.nvidia.com/v1` | OpenAI | [获取密钥](https://build.nvidia.com) |
|
||||
| **Ollama** | `ollama/` | `http://localhost:11434/v1` | OpenAI | 本地(无需密钥) |
|
||||
| **LM Studio** | `lmstudio/` | `http://localhost:1234/v1` | OpenAI | 可选(本地默认无需密钥) |
|
||||
| **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [获取密钥](https://openrouter.ai/keys) |
|
||||
| **LiteLLM Proxy** | `litellm/` | `http://localhost:4000/v1` | OpenAI | 你的 LiteLLM 代理密钥 |
|
||||
| **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | 本地 |
|
||||
|
|
@ -75,22 +76,22 @@
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-your-api-key"
|
||||
"api_keys": ["sk-your-api-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-your-openai-key"
|
||||
"api_keys": ["sk-your-openai-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "claude-sonnet-4.6",
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"api_key": "sk-ant-your-key"
|
||||
"api_keys": ["sk-ant-your-key"]
|
||||
},
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-zhipu-key"
|
||||
"api_keys": ["your-zhipu-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -113,7 +114,7 @@
|
|||
{
|
||||
"model_name": "voice-gemini",
|
||||
"model": "gemini/gemini-2.5-flash",
|
||||
"api_key": "your-gemini-key"
|
||||
"api_keys": ["your-gemini-key"]
|
||||
}
|
||||
],
|
||||
"voice": {
|
||||
|
|
@ -136,7 +137,7 @@
|
|||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -146,7 +147,7 @@
|
|||
{
|
||||
"model_name": "ark-code-latest",
|
||||
"model": "volcengine/ark-code-latest",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -156,7 +157,7 @@
|
|||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -166,7 +167,7 @@
|
|||
{
|
||||
"model_name": "deepseek-chat",
|
||||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -190,7 +191,7 @@
|
|||
{
|
||||
"model_name": "claude-opus-4-6",
|
||||
"model": "anthropic-messages/claude-opus-4-6",
|
||||
"api_key": "sk-ant-your-key",
|
||||
"api_keys": ["sk-ant-your-key"],
|
||||
"api_base": "https://api.anthropic.com"
|
||||
}
|
||||
```
|
||||
|
|
@ -211,6 +212,18 @@
|
|||
}
|
||||
```
|
||||
|
||||
**LM Studio(本地)**
|
||||
|
||||
```json
|
||||
{
|
||||
"model_name": "lmstudio-local",
|
||||
"model": "lmstudio/openai/gpt-oss-20b"
|
||||
}
|
||||
```
|
||||
|
||||
`api_base` 默认是 `http://localhost:1234/v1`。除非你在 LM Studio 侧启用了认证,否则不需要配置 API Key。
|
||||
PicoClaw 向 LM Studio 的 OpenAI 兼容终结点发送请求,且将移除首个 `lmstudio/` 前缀,因此 `lmstudio/openai/gpt-oss-20b` 会发送 `openai/gpt-oss-20b`。
|
||||
|
||||
**自定义代理/API**
|
||||
|
||||
```json
|
||||
|
|
@ -218,7 +231,7 @@
|
|||
"model_name": "my-custom-model",
|
||||
"model": "openai/custom-model",
|
||||
"api_base": "https://my-proxy.com/v1",
|
||||
"api_key": "sk-...",
|
||||
"api_keys": ["sk-..."],
|
||||
"request_timeout": 300
|
||||
}
|
||||
```
|
||||
|
|
@ -230,7 +243,7 @@
|
|||
"model_name": "lite-gpt4",
|
||||
"model": "litellm/lite-gpt4",
|
||||
"api_base": "http://localhost:4000/v1",
|
||||
"api_key": "sk-..."
|
||||
"api_keys": ["sk-..."]
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -247,13 +260,13 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l
|
|||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api1.example.com/v1",
|
||||
"api_key": "sk-key1"
|
||||
"api_keys": ["sk-key1"]
|
||||
},
|
||||
{
|
||||
"model_name": "gpt-5.4",
|
||||
"model": "openai/gpt-5.4",
|
||||
"api_base": "https://api2.example.com/v1",
|
||||
"api_key": "sk-key2"
|
||||
"api_keys": ["sk-key2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -272,17 +285,17 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l
|
|||
"model_name": "qwen-main",
|
||||
"model": "openai/qwen3.5:cloud",
|
||||
"api_base": "https://api.example.com/v1",
|
||||
"api_key": "sk-main"
|
||||
"api_keys": ["sk-main"]
|
||||
},
|
||||
{
|
||||
"model_name": "deepseek-backup",
|
||||
"model": "deepseek/deepseek-chat",
|
||||
"api_key": "sk-backup-1"
|
||||
"api_keys": ["sk-backup-1"]
|
||||
},
|
||||
{
|
||||
"model_name": "gemini-backup",
|
||||
"model": "gemini/gemini-2.5-flash",
|
||||
"api_key": "sk-backup-2"
|
||||
"api_keys": ["sk-backup-2"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
@ -300,7 +313,7 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l
|
|||
|
||||
#### 从旧的 `providers` 配置迁移
|
||||
|
||||
旧的 `providers` 配置格式**已弃用**,但为向后兼容仍支持。
|
||||
旧的 `providers` 配置格式**已弃用**,V2 中已移除。现有 V0/V1 配置会自动迁移。
|
||||
|
||||
**旧配置(已弃用):**
|
||||
|
||||
|
|
@ -325,11 +338,12 @@ PicoClaw 在发送请求前仅去除外层 `litellm/` 前缀,因此 `litellm/l
|
|||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "glm-4.7",
|
||||
"model": "zhipu/glm-4.7",
|
||||
"api_key": "your-key"
|
||||
"api_keys": ["your-key"]
|
||||
}
|
||||
],
|
||||
"agents": {
|
||||
|
|
|
|||
31
go.mod
|
|
@ -7,9 +7,10 @@ require (
|
|||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/adhocore/gronx v1.19.6
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.4
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.2
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.4
|
||||
github.com/bwmarrin/discordgo v0.29.0
|
||||
github.com/caarlos0/env/v11 v11.4.0
|
||||
github.com/creack/pty v1.1.24
|
||||
|
|
@ -26,6 +27,8 @@ require (
|
|||
github.com/mymmrac/telego v1.7.0
|
||||
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1
|
||||
github.com/openai/openai-go/v3 v3.22.0
|
||||
github.com/pion/rtp v1.8.7
|
||||
github.com/pion/webrtc/v3 v3.3.6
|
||||
github.com/rivo/tview v0.42.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/slack-go/slack v0.17.3
|
||||
|
|
@ -36,21 +39,21 @@ require (
|
|||
go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/term v0.41.0
|
||||
golang.org/x/time v0.14.0
|
||||
golang.org/x/time v0.15.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
maunium.net/go/mautrix v0.26.4
|
||||
modernc.org/sqlite v1.46.1
|
||||
modernc.org/sqlite v1.47.0
|
||||
rsc.io/qr v0.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect
|
||||
|
|
@ -60,11 +63,14 @@ require (
|
|||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/beeper/argo-go v1.1.2 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/coder/websocket v1.8.14 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
||||
github.com/gdamore/encoding v1.0.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
|
|
@ -73,6 +79,7 @@ require (
|
|||
github.com/mattn/go-sqlite3 v1.14.34 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
|
|
@ -81,9 +88,13 @@ require (
|
|||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
|
||||
go.mau.fi/libsignal v0.2.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
modernc.org/libc v1.67.6 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
|
@ -94,7 +105,7 @@ require (
|
|||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/github/copilot-sdk/go v0.1.32
|
||||
github.com/github/copilot-sdk/go v0.2.0
|
||||
github.com/go-resty/resty/v2 v2.17.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/jsonschema-go v0.4.2 // indirect
|
||||
|
|
@ -116,3 +127,5 @@ require (
|
|||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0
|
||||
)
|
||||
|
||||
replace github.com/bwmarrin/discordgo => github.com/yeongaori/discordgo-fork v0.0.0-20260319072544-e8e546f5d532
|
||||
|
|
|
|||
82
go.sum
|
|
@ -17,24 +17,26 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo
|
|||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 h1:3kGOqnh1pPeddVa/E37XNTaWJ8W6vrbYV9lJEkCnhuY=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.2 h1:x0eGAWpd1B5I/vMtrB4Q4Zuc3CXWI8wjHfPPqBSrKmM=
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.2/go.mod h1:V9oTWSDC2MtS1DR71hbNET/bZ8psQp022amEBe1grJc=
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.4 h1:W6tKfa/s37faUnwJ71pGqsBO7/wfUX1L7tVprupQGo4=
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.4/go.mod h1:BZ+9thH0QOTDUwE8KAv/ZwUzsNC7CSMJXj/wtnZMs5k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y=
|
||||
|
|
@ -51,8 +53,6 @@ github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
|||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/beeper/argo-go v1.1.2 h1:UQI2G8F+NLfGTOmTUI0254pGKx/HUU/etbUGTJv91Fs=
|
||||
github.com/beeper/argo-go v1.1.2/go.mod h1:M+LJAnyowKVQ6Rdj6XYGEn+qcVFkb3R/MUpqkGR0hM4=
|
||||
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
|
||||
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
|
|
@ -63,6 +63,8 @@ github.com/caarlos0/env/v11 v11.4.0 h1:Kcb6t5kIIr4XkoQC9AF2j+8E1Jsrl3Wz/hhm1LtoG
|
|||
github.com/caarlos0/env/v11 v11.4.0/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
|
|
@ -92,8 +94,13 @@ github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uh
|
|||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU=
|
||||
github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
|
||||
github.com/github/copilot-sdk/go v0.1.32 h1:wc9SFWwxXhJts6vyzzboPLJqcEJGnHE8rMCAY1RrUgo=
|
||||
github.com/github/copilot-sdk/go v0.1.32/go.mod h1:qc2iEF7hdO8kzSvbyGvrcGhuk2fzdW4xTtT0+1EH2ts=
|
||||
github.com/github/copilot-sdk/go v0.2.0 h1:RnrIIirmtp4wGgqSQFJ2k9phbeveIxOtYZqDogoNEa0=
|
||||
github.com/github/copilot-sdk/go v0.2.0/go.mod h1:uGWkjVYcp2DV9DgtqYihh5tEoJjNqxIFaUNnrwY4FxM=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
||||
github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4=
|
||||
|
|
@ -155,8 +162,9 @@ github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzh
|
|||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
|
|
@ -196,6 +204,12 @@ github.com/openai/openai-go/v3 v3.22.0 h1:6MEoNoV8sbjOVmXdvhmuX3BjVbVdcExbVyGixi
|
|||
github.com/openai/openai-go/v3 v3.22.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
||||
github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE=
|
||||
github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtp v1.8.7 h1:qslKkG8qxvQ7hqaxkmL7Pl0XcUm+/Er7nMnu6Vq+ZxM=
|
||||
github.com/pion/rtp v1.8.7/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
||||
github.com/pion/webrtc/v3 v3.3.6 h1:7XAh4RPtlY1Vul6/GmZrv7z+NnxKA6If0KStXBI2ZLE=
|
||||
github.com/pion/webrtc/v3 v3.3.6/go.mod h1:zyN7th4mZpV27eXybfR/cnUf3J2DRy8zw/mdjD9JTNM=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
|
@ -207,8 +221,9 @@ github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoX
|
|||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
|
|
@ -264,6 +279,8 @@ github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTd
|
|||
github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yeongaori/discordgo-fork v0.0.0-20260319072544-e8e546f5d532 h1:gxFHYeUDGziRb0zXYEqBFohC+NJbIW9L0tddaXMWr2o=
|
||||
github.com/yeongaori/discordgo-fork v0.0.0-20260319072544-e8e546f5d532/go.mod h1:A0FcMFJKJ9fRjgSuZ2o+pIQ6mPS81SVuiLN2vYTa7Ao=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
|
@ -275,6 +292,14 @@ go.mau.fi/util v0.9.7 h1:AWGNbJfz1zRcQOKeOEYhKUG2fT+/26Gy6kyqcH8tnBg=
|
|||
go.mau.fi/util v0.9.7/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE=
|
||||
go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4 h1:hsmlwsM+VqfF70cpdZEeIUKer2XWCQmQPK0u0tHy3ZQ=
|
||||
go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
|
|
@ -283,7 +308,6 @@ golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
|
|
@ -361,8 +385,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
|
|
@ -405,18 +429,18 @@ maunium.net/go/mautrix v0.26.4 h1:enHSnkf0L2V9+VnfJfNhKSReSW6pBKS/x3Su+v+Vovs=
|
|||
maunium.net/go/mautrix v0.26.4/go.mod h1:YWw8NWTszsbyFAznboicBObwHPgTSLcuTbVX2kY7U2M=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
|
||||
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
||||
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
|
|
@ -425,8 +449,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
|||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk=
|
||||
modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
|
|
@ -59,14 +58,7 @@ func (cb *ContextBuilder) WithSplitOnMarker(enabled bool) *ContextBuilder {
|
|||
}
|
||||
|
||||
func getGlobalConfigDir() string {
|
||||
if home := os.Getenv(config.EnvHome); home != "" {
|
||||
return home
|
||||
}
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(home, pkg.DefaultPicoClawHome)
|
||||
return config.GetHome()
|
||||
}
|
||||
|
||||
func NewContextBuilder(workspace string) *ContextBuilder {
|
||||
|
|
|
|||
|
|
@ -90,14 +90,29 @@ func findSafeBoundary(history []providers.Message, targetIndex int) int {
|
|||
// including Content, ReasoningContent, ToolCalls arguments, ToolCallID
|
||||
// metadata, and Media items. Uses a heuristic of 2.5 characters per token.
|
||||
func estimateMessageTokens(msg providers.Message) int {
|
||||
chars := utf8.RuneCountInString(msg.Content)
|
||||
contentChars := utf8.RuneCountInString(msg.Content)
|
||||
|
||||
// ReasoningContent (extended thinking / chain-of-thought) can be
|
||||
// substantial and is stored in session history via AddFullMessage.
|
||||
if msg.ReasoningContent != "" {
|
||||
chars += utf8.RuneCountInString(msg.ReasoningContent)
|
||||
// SystemParts are structured system blocks used for cache-aware adapters.
|
||||
// They carry the same content as Content, but in multiple blocks.
|
||||
// We estimate them as an alternative representation, not additive.
|
||||
systemPartsChars := 0
|
||||
if len(msg.SystemParts) > 0 {
|
||||
for _, part := range msg.SystemParts {
|
||||
systemPartsChars += utf8.RuneCountInString(part.Text)
|
||||
}
|
||||
// Per-part overhead for JSON structure (type, text, cache_control).
|
||||
const perPartOverhead = 20
|
||||
systemPartsChars += len(msg.SystemParts) * perPartOverhead
|
||||
}
|
||||
|
||||
// Use the larger of the two representations to stay conservative.
|
||||
chars := contentChars
|
||||
if systemPartsChars > chars {
|
||||
chars = systemPartsChars
|
||||
}
|
||||
|
||||
chars += utf8.RuneCountInString(msg.ReasoningContent)
|
||||
|
||||
for _, tc := range msg.ToolCalls {
|
||||
chars += len(tc.ID) + len(tc.Type)
|
||||
if tc.Function != nil {
|
||||
|
|
|
|||
|
|
@ -529,6 +529,26 @@ func TestEstimateMessageTokens_MediaItems(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEstimateMessageTokens_SystemParts(t *testing.T) {
|
||||
plain := providers.Message{Role: "system", Content: "instructions"}
|
||||
withParts := providers.Message{
|
||||
Role: "system",
|
||||
Content: "instructions",
|
||||
SystemParts: []providers.ContentBlock{
|
||||
{Type: "text", Text: "some more system context"},
|
||||
{Type: "text", Text: "even more cached blocks"},
|
||||
},
|
||||
}
|
||||
|
||||
plainTokens := estimateMessageTokens(plain)
|
||||
partsTokens := estimateMessageTokens(withParts)
|
||||
|
||||
if partsTokens <= plainTokens {
|
||||
t.Errorf("system message with SystemParts (%d) should exceed plain message (%d)",
|
||||
partsTokens, plainTokens)
|
||||
}
|
||||
}
|
||||
|
||||
// --- estimateToolDefsTokens tests ---
|
||||
|
||||
func TestEstimateToolDefsTokens(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ type AgentInstance struct {
|
|||
// LightCandidates holds the resolved provider candidates for the light model.
|
||||
// Pre-computed at agent creation to avoid repeated model_list lookups at runtime.
|
||||
LightCandidates []providers.FallbackCandidate
|
||||
// LightProvider is the concrete provider instance for the configured light model.
|
||||
// It is only used when routing selects the light tier for a turn.
|
||||
LightProvider providers.LLMProvider
|
||||
}
|
||||
|
||||
// NewAgentInstance creates an agent instance from config.
|
||||
|
|
@ -171,14 +174,28 @@ func NewAgentInstance(
|
|||
// to avoid repeated model_list lookups on every incoming message.
|
||||
var router *routing.Router
|
||||
var lightCandidates []providers.FallbackCandidate
|
||||
var lightProvider providers.LLMProvider
|
||||
if rc := defaults.Routing; rc != nil && rc.Enabled && rc.LightModel != "" {
|
||||
resolved := resolveModelCandidates(cfg, defaults.Provider, rc.LightModel, nil)
|
||||
if len(resolved) > 0 {
|
||||
router = routing.New(routing.RouterConfig{
|
||||
LightModel: rc.LightModel,
|
||||
Threshold: rc.Threshold,
|
||||
})
|
||||
lightCandidates = resolved
|
||||
lightModelCfg, err := resolvedModelConfig(cfg, rc.LightModel, workspace)
|
||||
if err != nil {
|
||||
logger.WarnCF("agent", "Routing light model config invalid; routing disabled",
|
||||
map[string]any{"light_model": rc.LightModel, "agent_id": agentID, "error": err.Error()})
|
||||
} else {
|
||||
lp, _, err := providers.CreateProviderFromConfig(lightModelCfg)
|
||||
if err != nil {
|
||||
logger.WarnCF("agent", "Routing light model provider init failed; routing disabled",
|
||||
map[string]any{"light_model": rc.LightModel, "agent_id": agentID, "error": err.Error()})
|
||||
} else {
|
||||
router = routing.New(routing.RouterConfig{
|
||||
LightModel: rc.LightModel,
|
||||
Threshold: rc.Threshold,
|
||||
})
|
||||
lightCandidates = resolved
|
||||
lightProvider = lp
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.WarnCF("agent", "Routing light model not found; routing disabled",
|
||||
map[string]any{"light_model": rc.LightModel, "agent_id": agentID})
|
||||
|
|
@ -207,6 +224,7 @@ func NewAgentInstance(
|
|||
Candidates: candidates,
|
||||
Router: router,
|
||||
LightCandidates: lightCandidates,
|
||||
LightProvider: lightProvider,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/audio/asr"
|
||||
"github.com/sipeed/picoclaw/pkg/audio/tts"
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/channels"
|
||||
"github.com/sipeed/picoclaw/pkg/commands"
|
||||
|
|
@ -31,7 +33,6 @@ import (
|
|||
"github.com/sipeed/picoclaw/pkg/state"
|
||||
"github.com/sipeed/picoclaw/pkg/tools"
|
||||
"github.com/sipeed/picoclaw/pkg/utils"
|
||||
"github.com/sipeed/picoclaw/pkg/voice"
|
||||
)
|
||||
|
||||
type AgentLoop struct {
|
||||
|
|
@ -51,7 +52,7 @@ type AgentLoop struct {
|
|||
fallback *providers.FallbackChain
|
||||
channelManager *channels.Manager
|
||||
mediaStore media.MediaStore
|
||||
transcriber voice.Transcriber
|
||||
transcriber asr.Transcriber
|
||||
cmdRegistry *commands.Registry
|
||||
mcp mcpRuntime
|
||||
hookRuntime hookRuntime
|
||||
|
|
@ -75,6 +76,8 @@ type processOptions struct {
|
|||
SessionKey string // Session identifier for history/context
|
||||
Channel string // Target channel for tool execution
|
||||
ChatID string // Target chat ID for tool execution
|
||||
MessageID string // Current inbound platform message ID
|
||||
ReplyToMessageID string // Current inbound reply target message ID
|
||||
SenderID string // Current sender ID for dynamic context
|
||||
SenderDisplayName string // Current sender display name for dynamic context
|
||||
UserMessage string // User message content (may include prefix)
|
||||
|
|
@ -104,6 +107,7 @@ const (
|
|||
metadataKeyAccountID = "account_id"
|
||||
metadataKeyGuildID = "guild_id"
|
||||
metadataKeyTeamID = "team_id"
|
||||
metadataKeyReplyToMessage = "reply_to_message_id"
|
||||
metadataKeyParentPeerKind = "parent_peer_kind"
|
||||
metadataKeyParentPeerID = "parent_peer_id"
|
||||
)
|
||||
|
|
@ -156,6 +160,13 @@ func registerSharedTools(
|
|||
provider providers.LLMProvider,
|
||||
) {
|
||||
allowReadPaths := buildAllowReadPatterns(cfg)
|
||||
var ttsProvider tts.TTSProvider
|
||||
if cfg.Tools.IsToolEnabled("send_tts") {
|
||||
ttsProvider = tts.DetectTTS(cfg)
|
||||
if ttsProvider == nil {
|
||||
logger.WarnCF("voice-tts", "send_tts enabled but no TTS provider configured", nil)
|
||||
}
|
||||
}
|
||||
|
||||
for _, agentID := range registry.ListAgentIDs() {
|
||||
agent, ok := registry.GetAgent(agentID)
|
||||
|
|
@ -165,33 +176,27 @@ func registerSharedTools(
|
|||
|
||||
if cfg.Tools.IsToolEnabled("web") {
|
||||
searchTool, err := tools.NewWebSearchTool(tools.WebSearchToolOptions{
|
||||
BraveAPIKeys: config.MergeAPIKeys(cfg.Tools.Web.Brave.APIKey(), cfg.Tools.Web.Brave.APIKeys()),
|
||||
BraveMaxResults: cfg.Tools.Web.Brave.MaxResults,
|
||||
BraveEnabled: cfg.Tools.Web.Brave.Enabled,
|
||||
TavilyAPIKeys: config.MergeAPIKeys(
|
||||
cfg.Tools.Web.Tavily.APIKey(),
|
||||
cfg.Tools.Web.Tavily.APIKeys(),
|
||||
),
|
||||
TavilyBaseURL: cfg.Tools.Web.Tavily.BaseURL,
|
||||
TavilyMaxResults: cfg.Tools.Web.Tavily.MaxResults,
|
||||
TavilyEnabled: cfg.Tools.Web.Tavily.Enabled,
|
||||
DuckDuckGoMaxResults: cfg.Tools.Web.DuckDuckGo.MaxResults,
|
||||
DuckDuckGoEnabled: cfg.Tools.Web.DuckDuckGo.Enabled,
|
||||
PerplexityAPIKeys: config.MergeAPIKeys(
|
||||
cfg.Tools.Web.Perplexity.APIKey(),
|
||||
cfg.Tools.Web.Perplexity.APIKeys(),
|
||||
),
|
||||
BraveAPIKeys: cfg.Tools.Web.Brave.APIKeys.Values(),
|
||||
BraveMaxResults: cfg.Tools.Web.Brave.MaxResults,
|
||||
BraveEnabled: cfg.Tools.Web.Brave.Enabled,
|
||||
TavilyAPIKeys: cfg.Tools.Web.Tavily.APIKeys.Values(),
|
||||
TavilyBaseURL: cfg.Tools.Web.Tavily.BaseURL,
|
||||
TavilyMaxResults: cfg.Tools.Web.Tavily.MaxResults,
|
||||
TavilyEnabled: cfg.Tools.Web.Tavily.Enabled,
|
||||
DuckDuckGoMaxResults: cfg.Tools.Web.DuckDuckGo.MaxResults,
|
||||
DuckDuckGoEnabled: cfg.Tools.Web.DuckDuckGo.Enabled,
|
||||
PerplexityAPIKeys: cfg.Tools.Web.Perplexity.APIKeys.Values(),
|
||||
PerplexityMaxResults: cfg.Tools.Web.Perplexity.MaxResults,
|
||||
PerplexityEnabled: cfg.Tools.Web.Perplexity.Enabled,
|
||||
SearXNGBaseURL: cfg.Tools.Web.SearXNG.BaseURL,
|
||||
SearXNGMaxResults: cfg.Tools.Web.SearXNG.MaxResults,
|
||||
SearXNGEnabled: cfg.Tools.Web.SearXNG.Enabled,
|
||||
GLMSearchAPIKey: cfg.Tools.Web.GLMSearch.APIKey(),
|
||||
GLMSearchAPIKey: cfg.Tools.Web.GLMSearch.APIKey.String(),
|
||||
GLMSearchBaseURL: cfg.Tools.Web.GLMSearch.BaseURL,
|
||||
GLMSearchEngine: cfg.Tools.Web.GLMSearch.SearchEngine,
|
||||
GLMSearchMaxResults: cfg.Tools.Web.GLMSearch.MaxResults,
|
||||
GLMSearchEnabled: cfg.Tools.Web.GLMSearch.Enabled,
|
||||
BaiduSearchAPIKey: cfg.Tools.Web.BaiduSearch.APIKey(),
|
||||
BaiduSearchAPIKey: cfg.Tools.Web.BaiduSearch.APIKey.String(),
|
||||
BaiduSearchBaseURL: cfg.Tools.Web.BaiduSearch.BaseURL,
|
||||
BaiduSearchMaxResults: cfg.Tools.Web.BaiduSearch.MaxResults,
|
||||
BaiduSearchEnabled: cfg.Tools.Web.BaiduSearch.Enabled,
|
||||
|
|
@ -228,17 +233,37 @@ func registerSharedTools(
|
|||
// Message tool
|
||||
if cfg.Tools.IsToolEnabled("message") {
|
||||
messageTool := tools.NewMessageTool()
|
||||
messageTool.SetSendCallback(func(channel, chatID, content string) error {
|
||||
messageTool.SetSendCallback(func(channel, chatID, content, replyToMessageID string) error {
|
||||
pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer pubCancel()
|
||||
return msgBus.PublishOutbound(pubCtx, bus.OutboundMessage{
|
||||
Channel: channel,
|
||||
ChatID: chatID,
|
||||
Content: content,
|
||||
Channel: channel,
|
||||
ChatID: chatID,
|
||||
Content: content,
|
||||
ReplyToMessageID: replyToMessageID,
|
||||
})
|
||||
})
|
||||
agent.Tools.Register(messageTool)
|
||||
}
|
||||
if cfg.Tools.IsToolEnabled("reaction") {
|
||||
reactionTool := tools.NewReactionTool()
|
||||
reactionTool.SetReactionCallback(func(ctx context.Context, channel, chatID, messageID string) error {
|
||||
if al.channelManager == nil {
|
||||
return fmt.Errorf("channel manager not configured")
|
||||
}
|
||||
ch, ok := al.channelManager.GetChannel(channel)
|
||||
if !ok {
|
||||
return fmt.Errorf("channel %s not found", channel)
|
||||
}
|
||||
rc, ok := ch.(channels.ReactionCapable)
|
||||
if !ok {
|
||||
return fmt.Errorf("channel %s does not support reactions", channel)
|
||||
}
|
||||
_, err := rc.ReactToMessage(ctx, chatID, messageID)
|
||||
return err
|
||||
})
|
||||
agent.Tools.Register(reactionTool)
|
||||
}
|
||||
|
||||
// Send file tool (outbound media via MediaStore — store injected later by SetMediaStore)
|
||||
if cfg.Tools.IsToolEnabled("send_file") {
|
||||
|
|
@ -252,6 +277,21 @@ func registerSharedTools(
|
|||
agent.Tools.Register(sendFileTool)
|
||||
}
|
||||
|
||||
if ttsProvider != nil {
|
||||
agent.Tools.Register(tools.NewSendTTSTool(ttsProvider, nil))
|
||||
}
|
||||
|
||||
if cfg.Tools.IsToolEnabled("load_image") {
|
||||
loadImageTool := tools.NewLoadImageTool(
|
||||
agent.Workspace,
|
||||
cfg.Agents.Defaults.RestrictToWorkspace,
|
||||
cfg.Agents.Defaults.GetMaxMediaSize(),
|
||||
nil,
|
||||
allowReadPaths,
|
||||
)
|
||||
agent.Tools.Register(loadImageTool)
|
||||
}
|
||||
|
||||
// Skill discovery and installation tools
|
||||
skills_enabled := cfg.Tools.IsToolEnabled("skills")
|
||||
find_skills_enable := cfg.Tools.IsToolEnabled("find_skills")
|
||||
|
|
@ -263,7 +303,7 @@ func registerSharedTools(
|
|||
ClawHub: skills.ClawHubConfig{
|
||||
Enabled: clawHubConfig.Enabled,
|
||||
BaseURL: clawHubConfig.BaseURL,
|
||||
AuthToken: clawHubConfig.AuthToken(),
|
||||
AuthToken: clawHubConfig.AuthToken.String(),
|
||||
SearchPath: clawHubConfig.SearchPath,
|
||||
SkillsPath: clawHubConfig.SkillsPath,
|
||||
DownloadPath: clawHubConfig.DownloadPath,
|
||||
|
|
@ -294,6 +334,14 @@ func registerSharedTools(
|
|||
subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Workspace)
|
||||
subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature)
|
||||
|
||||
// Inject a media resolver so the legacy RunToolLoop fallback path can
|
||||
// resolve media:// refs in the same way the main AgentLoop does.
|
||||
// This keeps subagent vision support working even when the optimized
|
||||
// sub-turn spawner path is unavailable.
|
||||
subagentManager.SetMediaResolver(func(msgs []providers.Message) []providers.Message {
|
||||
return resolveMediaRefs(msgs, al.mediaStore, cfg.Agents.Defaults.GetMaxMediaSize())
|
||||
})
|
||||
|
||||
// Set the spawner that links into AgentLoop's turnState
|
||||
subagentManager.SetSpawner(func(
|
||||
ctx context.Context,
|
||||
|
|
@ -393,10 +441,17 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
for al.running.Load() {
|
||||
idleTicker := time.NewTicker(100 * time.Millisecond)
|
||||
defer idleTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-idleTicker.C:
|
||||
if !al.running.Load() {
|
||||
return nil
|
||||
}
|
||||
case msg, ok := <-al.bus.InboundChan():
|
||||
if !ok {
|
||||
return nil
|
||||
|
|
@ -460,7 +515,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
|||
if target == nil {
|
||||
cancelDrain()
|
||||
if finalResponse != "" {
|
||||
al.publishResponseIfNeeded(ctx, msg.Channel, msg.ChatID, finalResponse)
|
||||
al.PublishResponseIfNeeded(ctx, msg.Channel, msg.ChatID, finalResponse)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -520,15 +575,11 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
|||
}
|
||||
|
||||
if finalResponse != "" {
|
||||
al.publishResponseIfNeeded(ctx, target.Channel, target.ChatID, finalResponse)
|
||||
al.PublishResponseIfNeeded(ctx, target.Channel, target.ChatID, finalResponse)
|
||||
}
|
||||
}()
|
||||
default:
|
||||
time.Sleep(time.Microsecond * 200)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// drainBusToSteering consumes inbound messages and redirects messages from the
|
||||
|
|
@ -606,7 +657,7 @@ func (al *AgentLoop) Stop() {
|
|||
al.running.Store(false)
|
||||
}
|
||||
|
||||
func (al *AgentLoop) publishResponseIfNeeded(ctx context.Context, channel, chatID, response string) {
|
||||
func (al *AgentLoop) PublishResponseIfNeeded(ctx context.Context, channel, chatID, response string) {
|
||||
if response == "" {
|
||||
return
|
||||
}
|
||||
|
|
@ -1039,10 +1090,15 @@ func (al *AgentLoop) SetMediaStore(s media.MediaStore) {
|
|||
agent.Tools.SetMediaStore(s)
|
||||
}
|
||||
}
|
||||
registry.ForEachTool("send_tts", func(t tools.Tool) {
|
||||
if st, ok := t.(*tools.SendTTSTool); ok {
|
||||
st.SetMediaStore(s)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// SetTranscriber injects a voice transcriber for agent-level audio transcription.
|
||||
func (al *AgentLoop) SetTranscriber(t voice.Transcriber) {
|
||||
func (al *AgentLoop) SetTranscriber(t asr.Transcriber) {
|
||||
al.transcriber = t
|
||||
}
|
||||
|
||||
|
|
@ -1063,19 +1119,23 @@ func (al *AgentLoop) transcribeAudioInMessage(ctx context.Context, msg bus.Inbou
|
|||
|
||||
// Transcribe each audio media ref in order.
|
||||
var transcriptions []string
|
||||
var keptMedia []string
|
||||
for _, ref := range msg.Media {
|
||||
path, meta, err := al.mediaStore.ResolveWithMeta(ref)
|
||||
if err != nil {
|
||||
logger.WarnCF("voice", "Failed to resolve media ref", map[string]any{"ref": ref, "error": err})
|
||||
keptMedia = append(keptMedia, ref)
|
||||
continue
|
||||
}
|
||||
if !utils.IsAudioFile(meta.Filename, meta.ContentType) {
|
||||
keptMedia = append(keptMedia, ref)
|
||||
continue
|
||||
}
|
||||
result, err := al.transcriber.Transcribe(ctx, path)
|
||||
if err != nil {
|
||||
logger.WarnCF("voice", "Transcription failed", map[string]any{"ref": ref, "error": err})
|
||||
transcriptions = append(transcriptions, "")
|
||||
keptMedia = append(keptMedia, ref)
|
||||
continue
|
||||
}
|
||||
transcriptions = append(transcriptions, result.Text)
|
||||
|
|
@ -1095,15 +1155,21 @@ func (al *AgentLoop) transcribeAudioInMessage(ctx context.Context, msg bus.Inbou
|
|||
}
|
||||
text := transcriptions[idx]
|
||||
idx++
|
||||
if text == "" {
|
||||
return match
|
||||
}
|
||||
return "[voice: " + text + "]"
|
||||
})
|
||||
|
||||
// Append any remaining transcriptions not matched by an annotation.
|
||||
for ; idx < len(transcriptions); idx++ {
|
||||
newContent += "\n[voice: " + transcriptions[idx] + "]"
|
||||
if transcriptions[idx] != "" {
|
||||
newContent += "\n[voice: " + transcriptions[idx] + "]"
|
||||
}
|
||||
}
|
||||
|
||||
msg.Content = newContent
|
||||
msg.Media = keptMedia
|
||||
return msg, true
|
||||
}
|
||||
|
||||
|
|
@ -1318,6 +1384,8 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
|||
SessionKey: sessionKey,
|
||||
Channel: msg.Channel,
|
||||
ChatID: msg.ChatID,
|
||||
MessageID: msg.MessageID,
|
||||
ReplyToMessageID: inboundMetadata(msg, metadataKeyReplyToMessage),
|
||||
SenderID: msg.SenderID,
|
||||
SenderDisplayName: msg.Sender.DisplayName,
|
||||
UserMessage: msg.Content,
|
||||
|
|
@ -1686,7 +1754,11 @@ func (al *AgentLoop) runTurn(ctx context.Context, ts *turnState) (turnResult, er
|
|||
ts.recordPersistedMessage(rootMsg)
|
||||
}
|
||||
|
||||
activeCandidates, activeModel := al.selectCandidates(ts.agent, ts.userMessage, messages)
|
||||
activeCandidates, activeModel, usedLight := al.selectCandidates(ts.agent, ts.userMessage, messages)
|
||||
activeProvider := ts.agent.Provider
|
||||
if usedLight && ts.agent.LightProvider != nil {
|
||||
activeProvider = ts.agent.LightProvider
|
||||
}
|
||||
pendingMessages := append([]providers.Message(nil), ts.opts.InitialSteeringMessages...)
|
||||
var finalContent string
|
||||
|
||||
|
|
@ -1808,6 +1880,14 @@ turnLoop:
|
|||
providerToolDefs = filtered
|
||||
}
|
||||
|
||||
// Resolve media:// refs produced by tool results (e.g. load_image).
|
||||
// Skipped on iteration 1 because inbound user media is already resolved
|
||||
// before entering the loop; only subsequent iterations can contain new
|
||||
// tool-generated media refs that need base64 encoding.
|
||||
if iteration > 1 {
|
||||
messages = resolveMediaRefs(messages, al.mediaStore, maxMediaSize)
|
||||
}
|
||||
|
||||
callMessages := messages
|
||||
if gracefulTerminal {
|
||||
callMessages = append(append([]providers.Message(nil), messages...), ts.interruptHintMessage())
|
||||
|
|
@ -1908,7 +1988,7 @@ turnLoop:
|
|||
providerCtx,
|
||||
activeCandidates,
|
||||
func(ctx context.Context, provider, model string) (*providers.LLMResponse, error) {
|
||||
return ts.agent.Provider.Chat(ctx, messagesForCall, toolDefsForCall, model, llmOpts)
|
||||
return activeProvider.Chat(ctx, messagesForCall, toolDefsForCall, model, llmOpts)
|
||||
},
|
||||
)
|
||||
if fbErr != nil {
|
||||
|
|
@ -1924,7 +2004,7 @@ turnLoop:
|
|||
}
|
||||
return fbResult.Response, nil
|
||||
}
|
||||
return ts.agent.Provider.Chat(providerCtx, messagesForCall, toolDefsForCall, llmModel, llmOpts)
|
||||
return activeProvider.Chat(providerCtx, messagesForCall, toolDefsForCall, llmModel, llmOpts)
|
||||
}
|
||||
|
||||
var response *providers.LLMResponse
|
||||
|
|
@ -2116,16 +2196,21 @@ turnLoop:
|
|||
},
|
||||
)
|
||||
|
||||
logger.DebugCF("agent", "LLM response",
|
||||
map[string]any{
|
||||
"agent_id": ts.agent.ID,
|
||||
"iteration": iteration,
|
||||
"content_chars": len(response.Content),
|
||||
"tool_calls": len(response.ToolCalls),
|
||||
"reasoning": response.Reasoning,
|
||||
"target_channel": al.targetReasoningChannelID(ts.channel),
|
||||
"channel": ts.channel,
|
||||
})
|
||||
llmResponseFields := map[string]any{
|
||||
"agent_id": ts.agent.ID,
|
||||
"iteration": iteration,
|
||||
"content_chars": len(response.Content),
|
||||
"tool_calls": len(response.ToolCalls),
|
||||
"reasoning": response.Reasoning,
|
||||
"target_channel": al.targetReasoningChannelID(ts.channel),
|
||||
"channel": ts.channel,
|
||||
}
|
||||
if response.Usage != nil {
|
||||
llmResponseFields["prompt_tokens"] = response.Usage.PromptTokens
|
||||
llmResponseFields["completion_tokens"] = response.Usage.CompletionTokens
|
||||
llmResponseFields["total_tokens"] = response.Usage.TotalTokens
|
||||
}
|
||||
logger.DebugCF("agent", "LLM response", llmResponseFields)
|
||||
|
||||
if len(response.ToolCalls) == 0 || gracefulTerminal {
|
||||
responseContent := response.Content
|
||||
|
|
@ -2378,8 +2463,15 @@ turnLoop:
|
|||
}
|
||||
|
||||
toolStart := time.Now()
|
||||
toolResult := ts.agent.Tools.ExecuteWithContext(
|
||||
execCtx := tools.WithToolInboundContext(
|
||||
turnCtx,
|
||||
ts.channel,
|
||||
ts.chatID,
|
||||
ts.opts.MessageID,
|
||||
ts.opts.ReplyToMessageID,
|
||||
)
|
||||
toolResult := ts.agent.Tools.ExecuteWithContext(
|
||||
execCtx,
|
||||
toolName,
|
||||
toolArgs,
|
||||
ts.channel,
|
||||
|
|
@ -2426,6 +2518,28 @@ turnLoop:
|
|||
if toolResult == nil {
|
||||
toolResult = tools.ErrorResult("hook returned nil tool result")
|
||||
}
|
||||
|
||||
// Send ForUser if not silent and has content.
|
||||
// For ResponseHandled tools, send regardless of SendResponse setting,
|
||||
// since they've already handled the response (e.g., send_tts, send_file).
|
||||
shouldSendForUser := !toolResult.Silent && toolResult.ForUser != "" &&
|
||||
(ts.opts.SendResponse || toolResult.ResponseHandled)
|
||||
if shouldSendForUser {
|
||||
al.bus.PublishOutbound(ctx, bus.OutboundMessage{
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
Content: toolResult.ForUser,
|
||||
Metadata: map[string]string{
|
||||
"is_tool_call": "true",
|
||||
},
|
||||
})
|
||||
logger.DebugCF("agent", "Sent tool result to user",
|
||||
map[string]any{
|
||||
"tool": toolName,
|
||||
"content_len": len(toolResult.ForUser),
|
||||
})
|
||||
}
|
||||
|
||||
if len(toolResult.Media) > 0 && toolResult.ResponseHandled {
|
||||
parts := make([]bus.MediaPart, 0, len(toolResult.Media))
|
||||
for _, ref := range toolResult.Media {
|
||||
|
|
@ -2464,6 +2578,13 @@ turnLoop:
|
|||
}
|
||||
|
||||
if len(toolResult.Media) > 0 && !toolResult.ResponseHandled {
|
||||
// For tools like load_image that produce media refs without sending them
|
||||
// to the user channel (ResponseHandled == false), both Media and ArtifactTags
|
||||
// coexist on the result:
|
||||
// - Media: carries media:// refs that resolveMediaRefs will base64-encode
|
||||
// into image_url parts in the next LLM iteration (enabling vision).
|
||||
// - ArtifactTags: exposes the local file path as a structured [file:…] tag
|
||||
// in the tool result text, so the LLM knows an artifact was produced.
|
||||
toolResult.ArtifactTags = buildArtifactTags(al.mediaStore, toolResult.Media)
|
||||
}
|
||||
|
||||
|
|
@ -2471,19 +2592,6 @@ turnLoop:
|
|||
allResponsesHandled = false
|
||||
}
|
||||
|
||||
if !toolResult.Silent && toolResult.ForUser != "" && ts.opts.SendResponse {
|
||||
al.bus.PublishOutbound(ctx, bus.OutboundMessage{
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
Content: toolResult.ForUser,
|
||||
})
|
||||
logger.DebugCF("agent", "Sent tool result to user",
|
||||
map[string]any{
|
||||
"tool": toolName,
|
||||
"content_len": len(toolResult.ForUser),
|
||||
})
|
||||
}
|
||||
|
||||
contentForLLM := toolResult.ContentForLLM()
|
||||
|
||||
// Filter sensitive data (API keys, tokens, secrets) before sending to LLM
|
||||
|
|
@ -2496,6 +2604,9 @@ turnLoop:
|
|||
Content: contentForLLM,
|
||||
ToolCallID: toolCallID,
|
||||
}
|
||||
if len(toolResult.Media) > 0 && !toolResult.ResponseHandled {
|
||||
toolResultMsg.Media = append(toolResultMsg.Media, toolResult.Media...)
|
||||
}
|
||||
al.emitEvent(
|
||||
EventKindToolExecEnd,
|
||||
ts.eventMeta("runTurn", "turn.tool.end"),
|
||||
|
|
@ -2748,9 +2859,9 @@ func (al *AgentLoop) selectCandidates(
|
|||
agent *AgentInstance,
|
||||
userMsg string,
|
||||
history []providers.Message,
|
||||
) (candidates []providers.FallbackCandidate, model string) {
|
||||
) (candidates []providers.FallbackCandidate, model string, usedLight bool) {
|
||||
if agent.Router == nil || len(agent.LightCandidates) == 0 {
|
||||
return agent.Candidates, resolvedCandidateModel(agent.Candidates, agent.Model)
|
||||
return agent.Candidates, resolvedCandidateModel(agent.Candidates, agent.Model), false
|
||||
}
|
||||
|
||||
_, usedLight, score := agent.Router.SelectModel(userMsg, history, agent.Model)
|
||||
|
|
@ -2761,7 +2872,7 @@ func (al *AgentLoop) selectCandidates(
|
|||
"score": score,
|
||||
"threshold": agent.Router.Threshold(),
|
||||
})
|
||||
return agent.Candidates, resolvedCandidateModel(agent.Candidates, agent.Model)
|
||||
return agent.Candidates, resolvedCandidateModel(agent.Candidates, agent.Model), false
|
||||
}
|
||||
|
||||
logger.InfoCF("agent", "Model routing: light model selected",
|
||||
|
|
@ -2771,7 +2882,7 @@ func (al *AgentLoop) selectCandidates(
|
|||
"score": score,
|
||||
"threshold": agent.Router.Threshold(),
|
||||
})
|
||||
return agent.LightCandidates, resolvedCandidateModel(agent.LightCandidates, agent.Router.LightModel())
|
||||
return agent.LightCandidates, resolvedCandidateModel(agent.LightCandidates, agent.Router.LightModel()), true
|
||||
}
|
||||
|
||||
// maybeSummarize triggers summarization if the session history exceeds thresholds.
|
||||
|
|
|
|||
|
|
@ -25,23 +25,25 @@ import (
|
|||
|
||||
type fakeChannel struct{ id string }
|
||||
|
||||
func (f *fakeChannel) Name() string { return "fake" }
|
||||
func (f *fakeChannel) Start(ctx context.Context) error { return nil }
|
||||
func (f *fakeChannel) Stop(ctx context.Context) error { return nil }
|
||||
func (f *fakeChannel) Send(ctx context.Context, msg bus.OutboundMessage) error { return nil }
|
||||
func (f *fakeChannel) IsRunning() bool { return true }
|
||||
func (f *fakeChannel) IsAllowed(string) bool { return true }
|
||||
func (f *fakeChannel) IsAllowedSender(sender bus.SenderInfo) bool { return true }
|
||||
func (f *fakeChannel) ReasoningChannelID() string { return f.id }
|
||||
func (f *fakeChannel) Name() string { return "fake" }
|
||||
func (f *fakeChannel) Start(ctx context.Context) error { return nil }
|
||||
func (f *fakeChannel) Stop(ctx context.Context) error { return nil }
|
||||
func (f *fakeChannel) Send(ctx context.Context, msg bus.OutboundMessage) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeChannel) IsRunning() bool { return true }
|
||||
func (f *fakeChannel) IsAllowed(string) bool { return true }
|
||||
func (f *fakeChannel) IsAllowedSender(sender bus.SenderInfo) bool { return true }
|
||||
func (f *fakeChannel) ReasoningChannelID() string { return f.id }
|
||||
|
||||
type fakeMediaChannel struct {
|
||||
fakeChannel
|
||||
sentMedia []bus.OutboundMediaMessage
|
||||
}
|
||||
|
||||
func (f *fakeMediaChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) error {
|
||||
func (f *fakeMediaChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) ([]string, error) {
|
||||
f.sentMedia = append(f.sentMedia, msg)
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func newStartedTestChannelManager(
|
||||
|
|
@ -531,6 +533,20 @@ func TestToolContext_Updates(t *testing.T) {
|
|||
if got := tools.ToolChannel(context.Background()); got != "" {
|
||||
t.Errorf("expected empty channel from bare context, got %q", got)
|
||||
}
|
||||
|
||||
inboundCtx := tools.WithToolInboundContext(
|
||||
context.Background(),
|
||||
"telegram",
|
||||
"chat-42",
|
||||
"msg-123",
|
||||
"msg-100",
|
||||
)
|
||||
if got := tools.ToolMessageID(inboundCtx); got != "msg-123" {
|
||||
t.Errorf("expected messageID 'msg-123', got %q", got)
|
||||
}
|
||||
if got := tools.ToolReplyToMessageID(inboundCtx); got != "msg-100" {
|
||||
t.Errorf("expected replyToMessageID 'msg-100', got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestToolRegistry_GetDefinitions verifies tool definitions can be retrieved
|
||||
|
|
@ -1296,6 +1312,46 @@ func newChatCompletionTestServer(
|
|||
}))
|
||||
}
|
||||
|
||||
func newStrictChatCompletionTestServer(
|
||||
t *testing.T,
|
||||
label string,
|
||||
expectedModel string,
|
||||
response string,
|
||||
calls *int,
|
||||
) *httptest.Server {
|
||||
t.Helper()
|
||||
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/chat/completions" {
|
||||
t.Fatalf("%s server path = %q, want /chat/completions", label, r.URL.Path)
|
||||
}
|
||||
*calls = *calls + 1
|
||||
defer r.Body.Close()
|
||||
|
||||
var req struct {
|
||||
Model string `json:"model"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
t.Fatalf("decode %s request: %v", label, err)
|
||||
}
|
||||
if req.Model != expectedModel {
|
||||
t.Fatalf("%s server model = %q, want %q", label, req.Model, expectedModel)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(map[string]any{
|
||||
"choices": []map[string]any{
|
||||
{
|
||||
"message": map[string]any{"content": response},
|
||||
"finish_reason": "stop",
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("encode %s response: %v", label, err)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func (h testHelper) executeAndGetResponse(tb testing.TB, ctx context.Context, msg bus.InboundMessage) string {
|
||||
// Use a short timeout to avoid hanging
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, responseTimeout)
|
||||
|
|
@ -1467,24 +1523,16 @@ func TestProcessMessage_SwitchModelShowModelConsistency(t *testing.T) {
|
|||
ModelName: "local",
|
||||
Model: "openai/local-model",
|
||||
APIBase: "https://local.example.invalid/v1",
|
||||
APIKeys: config.SimpleSecureStrings("test-key"),
|
||||
},
|
||||
{
|
||||
ModelName: "deepseek",
|
||||
Model: "openrouter/deepseek/deepseek-v3.2",
|
||||
APIBase: "https://openrouter.ai/api/v1",
|
||||
APIKeys: config.SimpleSecureStrings("test-key"),
|
||||
},
|
||||
},
|
||||
}
|
||||
cfg.WithSecurity(&config.SecurityConfig{
|
||||
ModelList: map[string]config.ModelSecurityEntry{
|
||||
"local": {
|
||||
APIKeys: []string{"test-key"},
|
||||
},
|
||||
"deepseek": {
|
||||
APIKeys: []string{"test-key"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
msgBus := bus.NewMessageBus()
|
||||
provider := &countingMockProvider{response: "LLM reply"}
|
||||
|
|
@ -1546,16 +1594,10 @@ func TestProcessMessage_SwitchModelRejectsUnknownAlias(t *testing.T) {
|
|||
ModelName: "local",
|
||||
Model: "openai/local-model",
|
||||
APIBase: "https://local.example.invalid/v1",
|
||||
APIKeys: config.SimpleSecureStrings("test-key"),
|
||||
},
|
||||
},
|
||||
}
|
||||
cfg.WithSecurity(&config.SecurityConfig{
|
||||
ModelList: map[string]config.ModelSecurityEntry{
|
||||
"local": {
|
||||
APIKeys: []string{"test-key"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
msgBus := bus.NewMessageBus()
|
||||
provider := &countingMockProvider{response: "LLM reply"}
|
||||
|
|
@ -1627,24 +1669,16 @@ func TestProcessMessage_SwitchModelRoutesSubsequentRequestsToSelectedProvider(t
|
|||
ModelName: "local",
|
||||
Model: "openai/Qwen3.5-35B-A3B",
|
||||
APIBase: localServer.URL,
|
||||
APIKeys: config.SimpleSecureStrings("local-key"),
|
||||
},
|
||||
{
|
||||
ModelName: "deepseek",
|
||||
Model: "openrouter/deepseek/deepseek-v3.2",
|
||||
APIBase: remoteServer.URL,
|
||||
APIKeys: config.SimpleSecureStrings("remote-key"),
|
||||
},
|
||||
},
|
||||
}
|
||||
cfg.WithSecurity(&config.SecurityConfig{
|
||||
ModelList: map[string]config.ModelSecurityEntry{
|
||||
"local": {
|
||||
APIKeys: []string{"local-key"},
|
||||
},
|
||||
"deepseek": {
|
||||
APIKeys: []string{"remote-key"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
msgBus := bus.NewMessageBus()
|
||||
provider, _, err := providers.CreateProvider(cfg)
|
||||
|
|
@ -1719,6 +1753,92 @@ func TestProcessMessage_SwitchModelRoutesSubsequentRequestsToSelectedProvider(t
|
|||
}
|
||||
}
|
||||
|
||||
func TestProcessMessage_ModelRoutingUsesLightProvider(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
heavyCalls := 0
|
||||
heavyServer := newStrictChatCompletionTestServer(
|
||||
t,
|
||||
"heavy",
|
||||
"gemini-2.5-flash",
|
||||
"heavy reply",
|
||||
&heavyCalls,
|
||||
)
|
||||
defer heavyServer.Close()
|
||||
|
||||
lightCalls := 0
|
||||
lightServer := newStrictChatCompletionTestServer(
|
||||
t,
|
||||
"light",
|
||||
"qwen2.5:0.5b",
|
||||
"light reply",
|
||||
&lightCalls,
|
||||
)
|
||||
defer lightServer.Close()
|
||||
|
||||
cfg := &config.Config{
|
||||
Agents: config.AgentsConfig{
|
||||
Defaults: config.AgentDefaults{
|
||||
Workspace: tmpDir,
|
||||
ModelName: "gemini-main",
|
||||
MaxTokens: 4096,
|
||||
MaxToolIterations: 10,
|
||||
Routing: &config.RoutingConfig{
|
||||
Enabled: true,
|
||||
LightModel: "qwen-light",
|
||||
Threshold: 0.99,
|
||||
},
|
||||
},
|
||||
},
|
||||
ModelList: []*config.ModelConfig{
|
||||
{
|
||||
ModelName: "gemini-main",
|
||||
Model: "gemini/gemini-2.5-flash",
|
||||
APIBase: heavyServer.URL,
|
||||
APIKeys: config.SimpleSecureStrings("heavy-key"),
|
||||
},
|
||||
{
|
||||
ModelName: "qwen-light",
|
||||
Model: "ollama/qwen2.5:0.5b",
|
||||
APIBase: lightServer.URL,
|
||||
APIKeys: config.SimpleSecureStrings("light-key"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
msgBus := bus.NewMessageBus()
|
||||
provider, _, err := providers.CreateProvider(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateProvider() error = %v", err)
|
||||
}
|
||||
al := NewAgentLoop(cfg, msgBus, provider)
|
||||
helper := testHelper{al: al}
|
||||
|
||||
resp := helper.executeAndGetResponse(t, context.Background(), bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "hi",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if resp != "light reply" {
|
||||
t.Fatalf("response = %q, want %q", resp, "light reply")
|
||||
}
|
||||
if heavyCalls != 0 {
|
||||
t.Fatalf("heavy calls = %d, want 0", heavyCalls)
|
||||
}
|
||||
if lightCalls != 1 {
|
||||
t.Fatalf("light calls = %d, want 1", lightCalls)
|
||||
}
|
||||
}
|
||||
|
||||
// TestToolResult_SilentToolDoesNotSendUserMessage verifies silent tools don't trigger outbound
|
||||
func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||
|
|
@ -2343,7 +2463,7 @@ func TestProcessMessage_PublishesReasoningContentToReasoningChannel(t *testing.T
|
|||
if outbound.Content != "thinking trace" {
|
||||
t.Fatalf("reasoning content = %q, want %q", outbound.Content, "thinking trace")
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("expected reasoning content to be published to reasoning channel")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
166
pkg/audio/asr/README.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# ASR (Automatic Speech Recognition)
|
||||
|
||||
This package handles speech-to-text for PicoClaw voice input.
|
||||
|
||||
If you are new to ASR setup, the simplest mental model is:
|
||||
|
||||
1. Add one or more ASR-capable entries to `model_list`.
|
||||
2. Point `voice.model_name` at the one you want to use.
|
||||
3. Put the API key in `.security.yml`.
|
||||
|
||||
## Quick Recommendation
|
||||
|
||||
For most new users, start with one of these:
|
||||
|
||||
| Provider | Example model | Why start here |
|
||||
| --- | --- | --- |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/whisper-large-v3-turbo` | Fast Whisper-style transcription and a straightforward OpenAI-compatible API. Groq currently advertises a free tier plan for 2000 reqs/day. |
|
||||
| [ElevenLabs](https://elevenlabs.io/pricing) | `elevenlabs/scribe_v1` | Easy setup and strong speech-to-text quality. ElevenLabs currently advertises a free plan that includes speech-to-text usage. |
|
||||
|
||||
Pricing and free-plan limits can change, so check the linked pricing pages before depending on them in production.
|
||||
|
||||
## How ASR Configuration Works
|
||||
|
||||
PicoClaw does not keep ASR API keys inside the `voice` section.
|
||||
|
||||
Instead:
|
||||
|
||||
- `voice.model_name` chooses a named entry from `model_list`.
|
||||
- The matching `model_list` entry describes the actual provider and model.
|
||||
- `.security.yml` stores the API key for that named model entry.
|
||||
|
||||
This is the recommended pattern because it is explicit, reusable, and consistent with the rest of PicoClaw's model configuration.
|
||||
|
||||
## Recommended Setup
|
||||
|
||||
### Option A: Groq Whisper
|
||||
|
||||
`config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"voice": {
|
||||
"model_name": "groq-asr",
|
||||
"echo_transcription": true
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "groq-asr",
|
||||
"model": "groq/whisper-large-v3-turbo"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`.security.yml`
|
||||
|
||||
```yaml
|
||||
model_list:
|
||||
groq-asr:
|
||||
api_keys:
|
||||
- "gsk_your_groq_key"
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- You can omit `api_base` and PicoClaw will use Groq's default API base automatically.
|
||||
- If you set `api_base` manually for Groq Whisper, both of these forms work:
|
||||
- `https://api.groq.com/openai/v1`
|
||||
- `https://api.groq.com/openai/v1/audio/transcriptions`
|
||||
- Any OpenAI-compatible Whisper model name containing `whisper` can use the Whisper transcription path, not only `whisper-large-v3-turbo`.
|
||||
|
||||
### Option B: ElevenLabs
|
||||
|
||||
`config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"voice": {
|
||||
"model_name": "elevenlabs-asr",
|
||||
"echo_transcription": true
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "elevenlabs-asr",
|
||||
"model": "elevenlabs/scribe_v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`.security.yml`
|
||||
|
||||
```yaml
|
||||
model_list:
|
||||
elevenlabs-asr:
|
||||
api_keys:
|
||||
- "sk-elevenlabs-your-key"
|
||||
```
|
||||
|
||||
### Option C: OpenAI Whisper
|
||||
|
||||
`config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"voice": {
|
||||
"model_name": "openai-asr"
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "openai-asr",
|
||||
"model": "openai/whisper-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`.security.yml`
|
||||
|
||||
```yaml
|
||||
model_list:
|
||||
openai-asr:
|
||||
api_keys:
|
||||
- "sk-openai-your-key"
|
||||
```
|
||||
|
||||
## Other ASR-Capable Model Types
|
||||
|
||||
PicoClaw currently supports three main ASR routes:
|
||||
|
||||
| Route | Example models | Behavior |
|
||||
| --- | --- | --- |
|
||||
| ElevenLabs ASR | `elevenlabs/scribe_v1` | Uses the ElevenLabs transcription API. |
|
||||
| Whisper endpoint models | `openai/whisper-1`, `groq/whisper-large-v3` | Uses an OpenAI-compatible `/audio/transcriptions` endpoint. |
|
||||
| Audio-capable chat models **(Under construction)** | `openai/gpt-4o-audio-preview`, `gemini/gemini-2.5-flash` | Sends audio to a multimodal chat model and asks it to transcribe. |
|
||||
|
||||
If you are unsure which one to pick, choose Groq Whisper or ElevenLabs first.
|
||||
|
||||
## How PicoClaw Chooses a Transcriber
|
||||
|
||||
`DetectTranscriber` resolves ASR in this order:
|
||||
|
||||
1. **Preferred path**: resolve `voice.model_name` against `model_list`.
|
||||
2. If that resolved model is:
|
||||
- `elevenlabs/...`, PicoClaw uses the ElevenLabs transcriber.
|
||||
- an OpenAI-compatible Whisper model, PicoClaw uses the Whisper transcriber.
|
||||
- an audio-capable chat model, PicoClaw uses `AudioModelTranscriber`.
|
||||
3. **Fallback path**: if `voice.model_name` is not set, PicoClaw performs a compatibility scan through `model_list` for legacy auto-detected ASR entries.
|
||||
|
||||
Fallback scanning exists for backward compatibility. New configurations should set `voice.model_name` explicitly.
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
- Defining an ASR model in `model_list` but forgetting to set `voice.model_name`.
|
||||
- Putting the API key in `voice` instead of `.security.yml`.
|
||||
- Using a non-ASR model and expecting Whisper-style transcription behavior.
|
||||
- Setting a custom `api_base` that points to the wrong provider endpoint.
|
||||
|
||||
## Minimal Checklist
|
||||
|
||||
Before testing voice input, make sure:
|
||||
|
||||
- `voice.model_name` matches a `model_list[].model_name`.
|
||||
- The matching `.security.yml` entry contains a valid API key.
|
||||
- The selected model is actually ASR-capable.
|
||||
- Voice input is enabled for the channel you are using.
|
||||
166
pkg/audio/asr/README_zh.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# ASR(自动语音识别)
|
||||
|
||||
这个目录负责 PicoClaw 的语音转文字能力。
|
||||
|
||||
如果你是第一次配置 ASR,可以参考如下步骤:
|
||||
|
||||
1. 在 `model_list` 里添加一个或多个支持 ASR 的模型条目。
|
||||
2. 用 `voice.model_name` 指向你想使用的那个条目。
|
||||
3. 在 `.security.yml` 里配置对应的 API Key。
|
||||
|
||||
## 快速推荐
|
||||
|
||||
对于大多数新用户,建议先从下面两种开始:
|
||||
|
||||
| 提供商 | 示例模型 | 推荐理由 |
|
||||
| --- | --- | --- |
|
||||
| [Groq](https://console.groq.com/keys) | `groq/whisper-large-v3-turbo` | Whisper 风格转录速度快,并且提供 OpenAI 兼容接口,配置比较直接。Groq 目前官方提供2000请求每日的免费套餐。 |
|
||||
| [ElevenLabs](https://elevenlabs.io/pricing) | `elevenlabs/scribe_v1` | 上手简单,语音转文字质量也不错。ElevenLabs 目前官方免费套餐包含 STT 用量。 |
|
||||
|
||||
价格和免费额度可能会变化,正式使用前请以官网定价页为准。
|
||||
|
||||
## ASR 配置是如何工作的
|
||||
|
||||
PicoClaw 不会把 ASR 的 API Key 放在 `voice` 配置里。
|
||||
|
||||
推荐的方式是:
|
||||
|
||||
- `voice.model_name` 用来选择 `model_list` 里的某个命名模型。
|
||||
- `model_list` 条目描述真实的提供商和模型。
|
||||
- `.security.yml` 负责保存该模型条目的 API Key。
|
||||
|
||||
这种方式更明确、更安全,也和 PicoClaw 其他模型配置方式保持一致。
|
||||
|
||||
## 推荐配置方式
|
||||
|
||||
### 方案 A:Groq Whisper
|
||||
|
||||
`config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"voice": {
|
||||
"model_name": "groq-asr",
|
||||
"echo_transcription": true
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "groq-asr",
|
||||
"model": "groq/whisper-large-v3-turbo"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`.security.yml`
|
||||
|
||||
```yaml
|
||||
model_list:
|
||||
groq-asr:
|
||||
api_keys:
|
||||
- "gsk_your_groq_key"
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 你可以不写 `api_base`,PicoClaw 会自动使用 Groq 默认接口地址。
|
||||
- 如果你手动设置 Groq Whisper 的 `api_base`,下面两种写法都可以:
|
||||
- `https://api.groq.com/openai/v1`
|
||||
- `https://api.groq.com/openai/v1/audio/transcriptions`
|
||||
- 只要是 OpenAI 兼容、并且模型名里包含 `whisper` 的模型,都可以走 Whisper 转录路径,不仅限于 `whisper-large-v3-turbo`。
|
||||
|
||||
### 方案 B:ElevenLabs
|
||||
|
||||
`config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"voice": {
|
||||
"model_name": "elevenlabs-asr",
|
||||
"echo_transcription": true
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "elevenlabs-asr",
|
||||
"model": "elevenlabs/scribe_v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`.security.yml`
|
||||
|
||||
```yaml
|
||||
model_list:
|
||||
elevenlabs-asr:
|
||||
api_keys:
|
||||
- "sk-elevenlabs-your-key"
|
||||
```
|
||||
|
||||
### 方案 C:OpenAI Whisper
|
||||
|
||||
`config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"voice": {
|
||||
"model_name": "openai-asr"
|
||||
},
|
||||
"model_list": [
|
||||
{
|
||||
"model_name": "openai-asr",
|
||||
"model": "openai/whisper-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`.security.yml`
|
||||
|
||||
```yaml
|
||||
model_list:
|
||||
openai-asr:
|
||||
api_keys:
|
||||
- "sk-openai-your-key"
|
||||
```
|
||||
|
||||
## 其他支持 ASR 的模型类型
|
||||
|
||||
PicoClaw 目前主要支持三种 ASR 路径:
|
||||
|
||||
| 路径 | 示例模型 | 行为说明 |
|
||||
| --- | --- | --- |
|
||||
| ElevenLabs ASR | `elevenlabs/scribe_v1` | 使用 ElevenLabs 的语音转录接口。 |
|
||||
| Whisper 接口模型 | `openai/whisper-1`、`groq/whisper-large-v3` | 使用 OpenAI 兼容的 `/audio/transcriptions` 接口。 |
|
||||
| 支持音频的聊天模型 **(重构中)** | `openai/gpt-4o-audio-preview`、`gemini/gemini-2.5-flash` | 把音频发给多模态聊天模型,并要求它返回转录结果。 |
|
||||
|
||||
如果你不确定该选哪种,建议优先使用 Groq Whisper 或 ElevenLabs。
|
||||
|
||||
## PicoClaw 如何选择转录器
|
||||
|
||||
`DetectTranscriber` 会按下面顺序选择 ASR:
|
||||
|
||||
1. **首选路径**:根据 `voice.model_name` 在 `model_list` 中找到对应模型。
|
||||
2. 如果找到的模型属于以下类型:
|
||||
- `elevenlabs/...`,则使用 ElevenLabs transcriber。
|
||||
- OpenAI 兼容的 Whisper 模型,则使用 Whisper transcriber。
|
||||
- 支持音频输入的聊天模型,则使用 `AudioModelTranscriber`。
|
||||
3. **回退路径**:如果没有设置 `voice.model_name`,PicoClaw 会为了兼容旧配置,扫描 `model_list` 中可自动识别的 ASR 条目。
|
||||
|
||||
回退扫描只是为了兼容旧行为。新配置建议始终显式设置 `voice.model_name`。
|
||||
|
||||
## 常见错误
|
||||
|
||||
- 在 `model_list` 里定义了 ASR 模型,但忘了设置 `voice.model_name`。
|
||||
- 把 API Key 写进了 `voice`,而不是 `.security.yml`。
|
||||
- 选择了不支持 ASR 的模型,却期望得到 Whisper 风格的转录结果。
|
||||
- 自定义了错误的 `api_base`,导致请求打到错误的接口地址。
|
||||
|
||||
## 最小检查清单
|
||||
|
||||
在测试语音输入前,请确认:
|
||||
|
||||
- `voice.model_name` 能正确匹配某个 `model_list[].model_name`。
|
||||
- `.security.yml` 中对应条目已经配置了有效 API Key。
|
||||
- 你选择的模型确实支持 ASR。
|
||||
- 你当前使用的频道已经启用了语音输入能力。
|
||||
252
pkg/audio/asr/agent.go
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
package asr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3/pkg/media/oggwriter"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
)
|
||||
|
||||
type speechAccumulator struct {
|
||||
writer *oggwriter.OggWriter
|
||||
file string
|
||||
lastAudioAt time.Time
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
chatID string
|
||||
speakerID string
|
||||
sessionID string
|
||||
channel string
|
||||
}
|
||||
|
||||
func (a *speechAccumulator) Push(chunk bus.AudioChunk) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
if a.closed {
|
||||
return
|
||||
}
|
||||
|
||||
a.lastAudioAt = time.Now()
|
||||
|
||||
pkt := &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
SequenceNumber: uint16(chunk.Sequence),
|
||||
Timestamp: chunk.Timestamp,
|
||||
SSRC: 1, // Stable arbitrary dummy
|
||||
},
|
||||
Payload: chunk.Data,
|
||||
}
|
||||
|
||||
if err := a.writer.WriteRTP(pkt); err != nil {
|
||||
logger.ErrorCF("voice-agent", "Failed to write RTP", map[string]any{"error": err})
|
||||
}
|
||||
}
|
||||
|
||||
func (a *speechAccumulator) Close() {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if !a.closed {
|
||||
a.writer.Close()
|
||||
a.closed = true
|
||||
}
|
||||
}
|
||||
|
||||
type Agent struct {
|
||||
bus *bus.MessageBus
|
||||
transcriber Transcriber
|
||||
|
||||
mu sync.Mutex
|
||||
sessions map[string]*speechAccumulator // keyed by sessionID_speakerID
|
||||
}
|
||||
|
||||
func NewAgent(mb *bus.MessageBus, t Transcriber) *Agent {
|
||||
return &Agent{
|
||||
bus: mb,
|
||||
transcriber: t,
|
||||
sessions: make(map[string]*speechAccumulator),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) Start(ctx context.Context) {
|
||||
logger.InfoCF("voice-agent", "Started Voice Agent orchestrator", nil)
|
||||
go a.listenChunks(ctx)
|
||||
go a.vadTick(ctx)
|
||||
|
||||
// Cleanup sessions on shutdown
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
a.mu.Lock()
|
||||
for key, acc := range a.sessions {
|
||||
acc.Close()
|
||||
os.Remove(acc.file)
|
||||
delete(a.sessions, key)
|
||||
}
|
||||
a.mu.Unlock()
|
||||
logger.InfoCF("voice-agent", "Cleaned up voice sessions on shutdown", nil)
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *Agent) listenChunks(ctx context.Context) {
|
||||
chunks := a.bus.AudioChunksChan()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case chunk, ok := <-chunks:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
a.handleChunk(chunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) handleChunk(chunk bus.AudioChunk) {
|
||||
// Only accept Opus-encoded audio
|
||||
if chunk.Format != "opus" {
|
||||
logger.DebugCF("voice-agent", "Ignoring unsupported audio format", map[string]any{"format": chunk.Format})
|
||||
return
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s_%s", chunk.SessionID, chunk.SpeakerID)
|
||||
|
||||
a.mu.Lock()
|
||||
acc, exists := a.sessions[key]
|
||||
if !exists {
|
||||
filename := filepath.Join(os.TempDir(), fmt.Sprintf("voice_%s_%d.ogg", key, time.Now().UnixNano()))
|
||||
writer, err := oggwriter.New(filename, uint32(chunk.SampleRate), uint16(chunk.Channels))
|
||||
if err != nil {
|
||||
a.mu.Unlock()
|
||||
logger.ErrorCF("voice-agent", "Failed to create OggWriter", map[string]any{"error": err})
|
||||
return
|
||||
}
|
||||
|
||||
acc = &speechAccumulator{
|
||||
writer: writer,
|
||||
file: filename,
|
||||
lastAudioAt: time.Now(),
|
||||
chatID: chunk.ChatID,
|
||||
speakerID: chunk.SpeakerID,
|
||||
sessionID: chunk.SessionID,
|
||||
channel: chunk.Channel,
|
||||
}
|
||||
a.sessions[key] = acc
|
||||
logger.DebugCF("voice-agent", "Started accumulating voice", map[string]any{"key": key, "file": filename})
|
||||
}
|
||||
a.mu.Unlock()
|
||||
|
||||
acc.Push(chunk)
|
||||
}
|
||||
|
||||
func (a *Agent) vadTick(ctx context.Context) {
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
a.checkSilence(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) checkSilence(ctx context.Context) {
|
||||
a.mu.Lock()
|
||||
now := time.Now()
|
||||
var finished []*speechAccumulator
|
||||
|
||||
for key, acc := range a.sessions {
|
||||
acc.mu.Lock()
|
||||
last := acc.lastAudioAt
|
||||
acc.mu.Unlock()
|
||||
|
||||
if now.Sub(last) > 1500*time.Millisecond {
|
||||
acc.Close()
|
||||
delete(a.sessions, key)
|
||||
finished = append(finished, acc)
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
|
||||
for _, acc := range finished {
|
||||
go a.processUtterance(ctx, acc)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) processUtterance(ctx context.Context, acc *speechAccumulator) {
|
||||
defer os.Remove(acc.file)
|
||||
|
||||
logger.InfoCF("voice-agent", "User finished speaking, transcribing...", map[string]any{"file": acc.file})
|
||||
|
||||
if a.transcriber == nil {
|
||||
logger.ErrorCF("voice-agent", "No STT configured!", nil)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := a.transcriber.Transcribe(ctx, acc.file)
|
||||
if err != nil {
|
||||
logger.ErrorCF("voice-agent", "Transcription failed", map[string]any{"error": err})
|
||||
return
|
||||
}
|
||||
|
||||
if res.Text == "" {
|
||||
logger.DebugCF("voice-agent", "Ignored empty transcription", map[string]any{"file": acc.file})
|
||||
return
|
||||
}
|
||||
|
||||
logger.InfoCF("voice-agent", "Transcription result", map[string]any{"text": res.Text, "duration": res.Duration})
|
||||
|
||||
channelType := acc.channel
|
||||
if channelType == "" {
|
||||
channelType = "discord" // fallback for legacy chunks
|
||||
}
|
||||
|
||||
text := strings.ToLower(strings.TrimSpace(res.Text))
|
||||
if strings.Contains(text, "leave the voice channel") || strings.Contains(text, "leave voice") ||
|
||||
strings.Contains(text, "disconnect voice") || strings.Contains(text, "leave the channel") ||
|
||||
strings.Contains(text, "leave channel") {
|
||||
logger.InfoCF("voice-agent", "Voice command triggered: leave", nil)
|
||||
if err := a.bus.PublishVoiceControl(ctx, bus.VoiceControl{
|
||||
SessionID: acc.sessionID,
|
||||
Type: "command",
|
||||
Action: "leave",
|
||||
}); err != nil {
|
||||
logger.ErrorCF("voice-agent", "Failed to publish leave control", map[string]any{"error": err})
|
||||
}
|
||||
if err := a.bus.PublishOutbound(ctx, bus.OutboundMessage{
|
||||
Channel: channelType,
|
||||
ChatID: acc.chatID,
|
||||
Content: "Goodbye! Leaving the voice channel.",
|
||||
}); err != nil {
|
||||
logger.ErrorCF("voice-agent", "Failed to publish goodbye message", map[string]any{"error": err})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
oralPrompt := "\n\n[SYSTEM]: The user just spoke this to you over voice chat. Please reply in a highly concise, conversational, oral style suitable for text-to-speech. Do not use markdown, emojis, asterisks, or code blocks. Speak naturally."
|
||||
|
||||
if err := a.bus.PublishInbound(ctx, bus.InboundMessage{
|
||||
Channel: channelType,
|
||||
SenderID: acc.speakerID,
|
||||
ChatID: acc.chatID,
|
||||
Content: res.Text + oralPrompt,
|
||||
Peer: bus.Peer{Kind: "channel", ID: acc.chatID},
|
||||
Metadata: map[string]string{
|
||||
"is_voice": "true",
|
||||
},
|
||||
}); err != nil {
|
||||
logger.ErrorCF("voice-agent", "Failed to publish inbound message", map[string]any{"error": err})
|
||||
}
|
||||
}
|
||||
196
pkg/audio/asr/agent_test.go
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
package asr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pion/webrtc/v3/pkg/media/oggwriter"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
)
|
||||
|
||||
type fakeTranscriber struct {
|
||||
text string
|
||||
err error
|
||||
lastPath string
|
||||
}
|
||||
|
||||
func (f *fakeTranscriber) Name() string { return "fake" }
|
||||
|
||||
func (f *fakeTranscriber) Transcribe(ctx context.Context, audioFilePath string) (*TranscriptionResponse, error) {
|
||||
f.lastPath = audioFilePath
|
||||
if f.err != nil {
|
||||
return nil, f.err
|
||||
}
|
||||
return &TranscriptionResponse{Text: f.text}, nil
|
||||
}
|
||||
|
||||
func waitForFileRemoval(t *testing.T, path string, timeout time.Duration) {
|
||||
t.Helper()
|
||||
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
t.Fatalf("expected file to be removed: %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentHandleChunkCreatesSession(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mb := bus.NewMessageBus()
|
||||
defer mb.Close()
|
||||
|
||||
agent := NewAgent(mb, &fakeTranscriber{})
|
||||
|
||||
chunk := bus.AudioChunk{
|
||||
SessionID: "sess",
|
||||
SpeakerID: "speaker",
|
||||
ChatID: "chat",
|
||||
Channel: "discord",
|
||||
Sequence: 1,
|
||||
Timestamp: 1,
|
||||
SampleRate: 48000,
|
||||
Channels: 2,
|
||||
Format: "opus",
|
||||
Data: []byte{0xF8, 0xFF, 0xFE},
|
||||
}
|
||||
|
||||
agent.handleChunk(chunk)
|
||||
|
||||
key := "sess_speaker"
|
||||
agent.mu.Lock()
|
||||
acc, ok := agent.sessions[key]
|
||||
agent.mu.Unlock()
|
||||
if !ok {
|
||||
t.Fatal("expected session to be created")
|
||||
}
|
||||
|
||||
acc.Close()
|
||||
_ = os.Remove(acc.file)
|
||||
}
|
||||
|
||||
func TestAgentHandleChunkIgnoresUnsupportedFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mb := bus.NewMessageBus()
|
||||
defer mb.Close()
|
||||
|
||||
agent := NewAgent(mb, &fakeTranscriber{})
|
||||
|
||||
chunk := bus.AudioChunk{Format: "pcm"}
|
||||
agent.handleChunk(chunk)
|
||||
|
||||
agent.mu.Lock()
|
||||
count := len(agent.sessions)
|
||||
agent.mu.Unlock()
|
||||
if count != 0 {
|
||||
t.Fatalf("expected no sessions, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentProcessUtteranceLeaveCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mb := bus.NewMessageBus()
|
||||
defer mb.Close()
|
||||
|
||||
tr := &fakeTranscriber{text: "please leave the voice channel now"}
|
||||
agent := NewAgent(mb, tr)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
filePath := filepath.Join(tmpDir, "voice.ogg")
|
||||
if err := os.WriteFile(filePath, []byte("data"), 0o600); err != nil {
|
||||
t.Fatalf("write temp file: %v", err)
|
||||
}
|
||||
|
||||
acc := &speechAccumulator{
|
||||
file: filePath,
|
||||
chatID: "chat",
|
||||
speakerID: "speaker",
|
||||
sessionID: "sess",
|
||||
channel: "discord",
|
||||
}
|
||||
|
||||
agent.processUtterance(context.Background(), acc)
|
||||
|
||||
select {
|
||||
case ctrl := <-mb.VoiceControlsChan():
|
||||
if ctrl.Action != "leave" || ctrl.Type != "command" || ctrl.SessionID != "sess" {
|
||||
t.Fatalf("unexpected voice control: %#v", ctrl)
|
||||
}
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
t.Fatal("expected voice control publish")
|
||||
}
|
||||
|
||||
select {
|
||||
case out := <-mb.OutboundChan():
|
||||
if !strings.Contains(out.Content, "Leaving the voice channel") {
|
||||
t.Fatalf("unexpected outbound content: %q", out.Content)
|
||||
}
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
t.Fatal("expected outbound publish")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filePath); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected temp file to be removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentCheckSilencePublishesInboundAndCleansUp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mb := bus.NewMessageBus()
|
||||
defer mb.Close()
|
||||
|
||||
tr := &fakeTranscriber{text: "hello there"}
|
||||
agent := NewAgent(mb, tr)
|
||||
|
||||
filePath := filepath.Join(t.TempDir(), "voice.ogg")
|
||||
writer, err := oggwriter.New(filePath, 48000, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("create ogg writer: %v", err)
|
||||
}
|
||||
|
||||
acc := &speechAccumulator{
|
||||
writer: writer,
|
||||
file: filePath,
|
||||
lastAudioAt: time.Now().Add(-2 * time.Second),
|
||||
chatID: "chat",
|
||||
speakerID: "speaker",
|
||||
sessionID: "sess",
|
||||
channel: "slack",
|
||||
}
|
||||
|
||||
agent.mu.Lock()
|
||||
agent.sessions["sess_speaker"] = acc
|
||||
agent.mu.Unlock()
|
||||
|
||||
agent.checkSilence(context.Background())
|
||||
|
||||
select {
|
||||
case msg := <-mb.InboundChan():
|
||||
if msg.Channel != "slack" {
|
||||
t.Fatalf("unexpected inbound channel: %q", msg.Channel)
|
||||
}
|
||||
if !strings.Contains(msg.Content, "hello there") {
|
||||
t.Fatalf("unexpected inbound content: %q", msg.Content)
|
||||
}
|
||||
if msg.Metadata["is_voice"] != "true" {
|
||||
t.Fatalf("expected is_voice metadata, got %#v", msg.Metadata)
|
||||
}
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
t.Fatal("expected inbound publish")
|
||||
}
|
||||
|
||||
waitForFileRemoval(t, filePath, 500*time.Millisecond)
|
||||
}
|
||||