From 9caa2376396d52afb231e720c19f5e305b40533f Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:46:34 +0900 Subject: [PATCH] refactor: adopt upstream versions for docs, config, and low-diff Go files Replace 30+ files with upstream's exact versions to eliminate merge conflicts. Fork-only additions (interfaces, struct fields, methods) are re-added at non-conflicting positions or in separate _ext.go files. Files replaced: READMEs, CI configs, docs, Makefile, and ~20 Go source files with <30 line diffs from upstream. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/pr.yml | 23 +-- .github/workflows/release.yml | 13 ++ .gitignore | 12 +- .goreleaser.yaml | 73 +++++++-- Makefile | 20 ++- README.fr.md | 63 +++++--- README.ja.md | 63 +++++--- README.md | 78 ++++++--- README.pt-br.md | 63 +++++--- README.vi.md | 61 ++++--- README.zh.md | 65 +++++--- .../internal/ui/channel.go | 5 - .../internal/ui/style.go | 12 ++ cmd/picoclaw/internal/agent/command.go | 12 +- cmd/picoclaw/internal/agent/helpers.go | 7 +- cmd/picoclaw/internal/auth/helpers.go | 14 +- cmd/picoclaw/internal/gateway/command.go | 29 +++- cmd/picoclaw/internal/helpers.go | 27 +--- cmd/picoclaw/internal/onboard/command.go | 10 +- cmd/picoclaw/internal/status/helpers.go | 5 +- cmd/picoclaw/internal/version/command.go | 5 +- cmd/picoclaw/main.go | 5 +- config/config.example.json | 50 ++++-- docker/docker-compose.yml | 20 ++- docs/channels/matrix/README.md | 7 +- docs/design/provider-refactoring.md | 12 +- docs/migration/model-list-migration.md | 14 +- docs/tools_configuration.md | 151 ++++++++++++++---- pkg/channels/base.go | 12 +- pkg/channels/dingtalk/dingtalk.go | 4 + pkg/channels/line/line.go | 11 +- pkg/commands/builtin.go | 1 + pkg/commands/runtime.go | 1 + pkg/config/config.go | 18 ++- pkg/config/migration.go | 17 ++ pkg/health/server.go | 28 +--- pkg/health/server_ext.go | 28 ++++ pkg/memory/jsonl.go | 14 +- pkg/memory/migration.go | 6 + pkg/migrate/sources/openclaw/common.go | 2 +- pkg/providers/anthropic/provider.go | 15 +- pkg/routing/session_key.go | 10 +- pkg/skills/clawhub_registry.go | 3 +- pkg/skills/registry.go | 2 +- pkg/skills/search_cache.go | 4 +- pkg/utils/media.go | 8 +- workspace/IDENTITY.md | 3 - 47 files changed, 760 insertions(+), 346 deletions(-) create mode 100644 pkg/health/server_ext.go diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a839715c9..1e9a7919a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,7 +1,7 @@ name: PR on: - pull_request: {} + pull_request: { } jobs: lint: @@ -16,9 +16,6 @@ jobs: with: go-version-file: go.mod - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - - name: Run go generate run: go generate ./... @@ -58,26 +55,8 @@ jobs: with: go-version-file: go.mod - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - - - name: Setup Node - uses: actions/setup-node@v6 - with: - node-version: '24' - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - name: Run go generate run: go generate ./... - - name: Run frontend tests - run: | - pnpm --dir pkg/miniapp/frontend install --frozen-lockfile - pnpm --dir pkg/miniapp/frontend test - - name: Run go test run: go test ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0edd29f22..4a584773d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,6 +65,14 @@ jobs: with: go-version-file: go.mod + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Setup pnpm + run: corepack enable && corepack prepare pnpm@latest --activate + - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -96,6 +104,11 @@ jobs: GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} DOCKERHUB_IMAGE_NAME: ${{ vars.DOCKERHUB_REPOSITORY }} GOVERSION: ${{ steps.setup-go.outputs.go-version }} + MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} + MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} + MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} + MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} + MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} - name: Apply release flags shell: bash diff --git a/.gitignore b/.gitignore index d9fe0d5a9..61fe494ca 100644 --- a/.gitignore +++ b/.gitignore @@ -47,14 +47,12 @@ docs/plans/ # Added by goreleaser init: dist/ -!pkg/miniapp/static/dist/ -!pkg/miniapp/static/dist/** +*.vite/ # Windows Application Icon/Resource *.syso - -# Frontend dependencies -node_modules/ - - +# Keep embedded backend dist directory placeholder in VCS +!web/backend/dist/ +web/backend/dist/* +!web/backend/dist/.gitkeep diff --git a/.goreleaser.yaml b/.goreleaser.yaml index d531d106b..622cf054b 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,8 +6,9 @@ before: hooks: - go mod tidy - go generate ./... + - sh -c 'cd web/frontend && pnpm install && pnpm build:backend' - go install github.com/tc-hib/go-winres@latest - - go-winres make --in cmd/picoclaw-launcher/winres/winres.json --out cmd/picoclaw-launcher/rsrc --product-version={{ .Version }} --file-version={{ .Version }} + - go-winres make --in web/backend/winres/winres.json --out web/backend/rsrc --product-version={{ .Version }} --file-version={{ .Version }} builds: - id: picoclaw @@ -17,10 +18,10 @@ builds: - stdjson ldflags: - -s -w - - -X github.com/sipeed/picoclaw/cmd/picoclaw/internal.version={{ .Version }} - - -X github.com/sipeed/picoclaw/cmd/picoclaw/internal.gitCommit={{ .ShortCommit }} - - -X github.com/sipeed/picoclaw/cmd/picoclaw/internal.buildTime={{ .Date }} - - -X github.com/sipeed/picoclaw/cmd/picoclaw/internal.goVersion={{ .Env.GOVERSION }} + - -X github.com/sipeed/picoclaw/pkg/config.Version={{ .Version }} + - -X github.com/sipeed/picoclaw/pkg/config.GitCommit={{ .ShortCommit }} + - -X github.com/sipeed/picoclaw/pkg/config.BuildTime={{ .Date }} + - -X github.com/sipeed/picoclaw/pkg/config.GoVersion={{ .Env.GOVERSION }} goos: - linux - windows @@ -32,9 +33,13 @@ builds: - riscv64 - loong64 - arm + - s390x + - mipsle goarm: - "6" - "7" + gomips: + - softfloat main: ./cmd/picoclaw ignore: - goos: windows @@ -59,10 +64,14 @@ builds: - riscv64 - loong64 - arm + - s390x + - mipsle goarm: - "6" - "7" - main: ./cmd/picoclaw-launcher + gomips: + - softfloat + main: ./web/backend ignore: - goos: windows goarch: arm @@ -86,9 +95,13 @@ builds: - riscv64 - loong64 - arm + - s390x + - mipsle goarm: - "6" - "7" + gomips: + - softfloat main: ./cmd/picoclaw-launcher-tui ignore: - goos: windows @@ -103,15 +116,49 @@ dockers_v2: - picoclaw images: - "ghcr.io/{{ .Env.GITHUB_REPOSITORY_OWNER }}/picoclaw" - - "docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}" + - 'docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}' tags: - - "{{ .Tag }}" - - "latest" + - '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly{{ else }}{{ .Tag }}{{ end }}' + - '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly{{ else }}latest{{ end }}' platforms: - linux/amd64 - linux/arm64 - linux/riscv64 + - id: picoclaw-launcher + dockerfile: docker/Dockerfile.goreleaser.launcher + ids: + - picoclaw + - picoclaw-launcher + - picoclaw-launcher-tui + images: + - "ghcr.io/{{ .Env.GITHUB_REPOSITORY_OWNER }}/picoclaw" + - 'docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}' + tags: + - '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly-launcher{{ else }}{{ .Tag }}-launcher{{ end }}' + - '{{ if isEnvSet "NIGHTLY_BUILD" }}nightly-launcher{{ else }}launcher{{ end }}' + platforms: + - linux/amd64 + - linux/arm64 + - linux/riscv64 + +notarize: + macos: + - enabled: '{{ isEnvSet "MACOS_SIGN_P12" }}' + ids: + - picoclaw + - picoclaw-launcher + - picoclaw-launcher-tui + sign: + certificate: "{{.Env.MACOS_SIGN_P12}}" + password: "{{.Env.MACOS_SIGN_PASSWORD}}" + notarize: + issuer_id: "{{.Env.MACOS_NOTARY_ISSUER_ID}}" + key_id: "{{.Env.MACOS_NOTARY_KEY_ID}}" + key: "{{.Env.MACOS_NOTARY_KEY}}" + wait: true + timeout: 20m + archives: - formats: [tar.gz] # this name template makes the OS and Arch compatible with the results of `uname`. @@ -129,7 +176,7 @@ archives: nfpms: - id: picoclaw - builds: + ids: - picoclaw - picoclaw-launcher - picoclaw-launcher-tui @@ -149,6 +196,11 @@ nfpms: - rpm - deb bindir: /usr/bin + contents: + - src: web/picoclaw-launcher.desktop + dst: /usr/share/applications/picoclaw-launcher.desktop + - src: web/picoclaw-launcher.png + dst: /usr/share/icons/hicolor/512x512/apps/picoclaw-launcher.png changelog: sort: asc @@ -163,6 +215,7 @@ changelog: # lzma: true release: + disable: '{{ isEnvSet "NIGHTLY_BUILD" }}' footer: >- --- diff --git a/Makefile b/Makefile index 428e386c5..98642703f 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,8 @@ VERSION?=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") GIT_COMMIT=$(shell git rev-parse --short=8 HEAD 2>/dev/null || echo "dev") BUILD_TIME=$(shell date +%FT%T%z) GO_VERSION=$(shell $(GO) version | awk '{print $$3}') -INTERNAL=github.com/sipeed/picoclaw/cmd/picoclaw/internal -LDFLAGS=-ldflags "-X $(INTERNAL).version=$(VERSION) -X $(INTERNAL).gitCommit=$(GIT_COMMIT) -X $(INTERNAL).buildTime=$(BUILD_TIME) -X $(INTERNAL).goVersion=$(GO_VERSION) -s -w" +CONFIG_PKG=github.com/sipeed/picoclaw/pkg/config +LDFLAGS=-ldflags "-X $(CONFIG_PKG).Version=$(VERSION) -X $(CONFIG_PKG).GitCommit=$(GIT_COMMIT) -X $(CONFIG_PKG).BuildTime=$(BUILD_TIME) -X $(CONFIG_PKG).GoVersion=$(GO_VERSION) -s -w" # Go variables GO?=CGO_ENABLED=0 go @@ -110,12 +110,18 @@ build: generate @$(GO) build $(GOFLAGS) $(LDFLAGS) -o $(BINARY_PATH) ./$(CMD_DIR) @echo "Build complete: $(BINARY_PATH)" @ln -sf $(BINARY_NAME)-$(PLATFORM)-$(ARCH) $(BUILD_DIR)/$(BINARY_NAME) - @if [ "$(PLATFORM)" = "linux" ] || [ "$(PLATFORM)" = "darwin" ]; then \ - echo "Install to /usr/local/bin:"; \ - echo " sudo install -m 755 $(BINARY_PATH) /usr/local/bin/$(BINARY_NAME)"; \ - else \ - echo "Install hint skipped: /usr/local/bin command is for Linux/macOS (current: $(PLATFORM))."; \ + +## build-launcher: Build the picoclaw-launcher (web console) binary +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 + @$(GO) build $(GOFLAGS) -o $(BUILD_DIR)/picoclaw-launcher-$(PLATFORM)-$(ARCH) ./web/backend + @ln -sf picoclaw-launcher-$(PLATFORM)-$(ARCH) $(BUILD_DIR)/picoclaw-launcher + @echo "Build complete: $(BUILD_DIR)/picoclaw-launcher" ## build-whatsapp-native: Build with WhatsApp native (whatsmeow) support; larger binary build-whatsapp-native: generate diff --git a/README.fr.md b/README.fr.md index 08a1926b6..1d5a3a256 100644 --- a/README.fr.md +++ b/README.fr.md @@ -1,5 +1,5 @@
- PicoClaw + PicoClaw

PicoClaw : Assistant IA Ultra-Efficace en Go

@@ -206,9 +206,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d ### 🚀 Démarrage Rapide > [!TIP] -> Configurez votre clé API dans `~/.picoclaw/config.json`. -> Obtenir des clés API : [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM) -> La recherche web est **optionnelle** — obtenez gratuitement l'[API Brave Search](https://brave.com/search/api) (2000 requêtes gratuites/mois) ou utilisez le repli automatique intégré. +> Configurez votre clé API dans `~/.picoclaw/config.json`. Obtenez des clés API : [Volcengine (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM). La recherche web est optionnelle — obtenez gratuitement l'[API Tavily](https://tavily.com) (1000 requêtes gratuites/mois) ou l'[API Brave Search](https://brave.com/search/api) (2000 requêtes gratuites/mois). **1. Initialiser** @@ -222,8 +220,13 @@ picoclaw onboard { "model_list": [ { - "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key", "request_timeout": 300, "api_base": "https://api.openai.com/v1" @@ -231,7 +234,7 @@ picoclaw onboard ], "agents": { "defaults": { - "model_name": "gpt4" + "model_name": "gpt-5.4" } }, "channels": { @@ -649,7 +652,6 @@ PicoClaw stocke les données dans votre workspace configuré (par défaut : `~/. ├── HEARTBEAT.md # Invites de tâches périodiques (vérifiées toutes les 30 min) ├── IDENTITY.md # Identité de l'Agent ├── SOUL.md # Âme de l'Agent -├── TOOLS.md # Description des outils └── USER.md # Préférences utilisateur ``` @@ -978,8 +980,10 @@ Cette conception permet également le **support multi-agent** avec une sélectio | **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [Obtenir Clé](https://openrouter.ai/keys) | | **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | Local | | **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [Obtenir Clé](https://cerebras.ai) | -| **Volcengine** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Obtenir Clé](https://console.volcengine.com) | +| **VolcEngine (Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Obtenir Clé](https://console.volcengine.com) | | **ShengsuanYun** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - | +| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [Obtenir Clé](https://console.volcengine.com) | +| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [Obtenir une clé](https://longcat.chat/platform) | | **Antigravity** | `antigravity/` | Google Cloud | Custom | OAuth uniquement | | **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - | @@ -989,8 +993,13 @@ Cette conception permet également le **support multi-agent** avec une sélectio { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key" }, { @@ -1006,7 +1015,7 @@ Cette conception permet également le **support multi-agent** avec une sélectio ], "agents": { "defaults": { - "model": "gpt-5.2" + "model": "gpt-5.4" } } } @@ -1017,8 +1026,17 @@ Cette conception permet également le **support multi-agent** avec une sélectio **OpenAI** ```json { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", + "api_key": "sk-..." +} +``` + +**VolcEngine (Doubao)** +```json +{ + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", "api_key": "sk-..." } ``` @@ -1061,14 +1079,14 @@ Configurez plusieurs points de terminaison pour le même nom de modèle—PicoCl { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "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.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } @@ -1200,6 +1218,13 @@ Cela se produit lorsqu'une autre instance du bot est en cours d'exécution. Assu | Service | Offre Gratuite | Cas d'Utilisation | | ---------------- | -------------------- | ------------------------------------- | | **OpenRouter** | 200K tokens/mois | Multiples modèles (Claude, GPT-4, etc.) | -| **Zhipu** | 200K tokens/mois | Idéal pour les utilisateurs chinois | +| **Volcengine CodingPlan** | 9,9¥/premier mois | Idéal pour les utilisateurs chinois, multiples modèles SOTA (Doubao, DeepSeek, etc.) | +| **Zhipu** | 200K tokens/mois | Convient aux utilisateurs chinois | | **Brave Search** | 2000 requêtes/mois | Fonctionnalité de recherche web | | **Groq** | Offre gratuite dispo | Inférence ultra-rapide (Llama, Mixtral) | + +--- + +
+ PicoClaw Meme +
diff --git a/README.ja.md b/README.ja.md index c4c5b27a0..d41975294 100644 --- a/README.ja.md +++ b/README.ja.md @@ -1,5 +1,5 @@
-PicoClaw +PicoClaw

PicoClaw: Go で書かれた超効率 AI アシスタント

@@ -168,9 +168,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d ### 🚀 クイックスタート(ネイティブ) > [!TIP] -> `~/.picoclaw/config.json` に API キーを設定してください。 -> API キーの取得先: [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM) -> Web 検索は **任意** です - 無料の [Tavily API](https://tavily.com) (月 1000 クエリ無料) または [Brave Search API](https://brave.com/search/api) (月 2000 クエリ無料) +> `~/.picoclaw/config.json` に API キーを設定してください。API キーの取得先: [Volcengine (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM)。Web 検索は **任意** です — 無料の [Tavily API](https://tavily.com) (月 1000 クエリ無料) または [Brave Search API](https://brave.com/search/api) (月 2000 クエリ無料)。 **1. 初期化** @@ -184,8 +182,13 @@ picoclaw onboard { "model_list": [ { - "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key", "request_timeout": 300, "api_base": "https://api.openai.com/v1" @@ -193,7 +196,7 @@ picoclaw onboard ], "agents": { "defaults": { - "model_name": "gpt4" + "model_name": "gpt-5.4" } }, "channels": { @@ -610,7 +613,6 @@ PicoClaw は設定されたワークスペース(デフォルト: `~/.picoclaw ├── HEARTBEAT.md # 定期タスクプロンプト(30分ごとに確認) ├── IDENTITY.md # エージェントのアイデンティティ ├── SOUL.md # エージェントのソウル -├── TOOLS.md # ツールの説明 └── USER.md # ユーザー設定 ``` @@ -919,8 +921,10 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る | **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [キーを取得](https://openrouter.ai/keys) | | **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | ローカル | | **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [キーを取得](https://cerebras.ai) | -| **Volcengine** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [キーを取得](https://console.volcengine.com) | +| **VolcEngine (Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [キーを取得](https://console.volcengine.com) | | **ShengsuanYun** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - | +| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [キーを取得](https://console.volcengine.com) | +| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [キーを取得](https://longcat.chat/platform) | | **Antigravity** | `antigravity/` | Google Cloud | カスタム | OAuthのみ | | **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - | @@ -930,8 +934,13 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key" }, { @@ -947,7 +956,7 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る ], "agents": { "defaults": { - "model": "gpt-5.2" + "model": "gpt-5.4" } } } @@ -958,8 +967,17 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る **OpenAI** ```json { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", + "api_key": "sk-..." +} +``` + +**VolcEngine (Doubao)** +```json +{ + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", "api_key": "sk-..." } ``` @@ -1002,14 +1020,14 @@ HEARTBEAT_OK 応答 ユーザーが直接結果を受け取る { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "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.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } @@ -1120,9 +1138,16 @@ Web 検索を有効にするには: | サービス | 無料枠 | ユースケース | |---------|--------|------------| | **OpenRouter** | 月 200K トークン | 複数モデル(Claude, GPT-4 など) | -| **Zhipu** | 月 200K トークン | 中国ユーザー向け最適 | +| **Volcengine CodingPlan** | 9.9元/初月 | 中国ユーザーに最適、複数のSOTAモデル(Doubao、DeepSeek等) | +| **Zhipu** | 月 200K トークン | 中国ユーザーに適している | | **Qwen** | 無料枠あり | 通義千問 (Qwen) | | **Brave Search** | 月 2000 クエリ | Web 検索機能 | | **Tavily** | 月 1000 クエリ | AI エージェント検索最適化 | | **Groq** | 無料枠あり | 高速推論(Llama, Mixtral) | | **Cerebras** | 無料枠あり | 高速推論(Llama, Qwen など) | + +--- + +
+ PicoClaw Meme +
diff --git a/README.md b/README.md index 5cf9f6143..ebcd6dddf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
- PicoClaw + PicoClaw

PicoClaw: Ultra-Efficient AI Assistant in Go

@@ -194,6 +194,19 @@ docker compose -f docker/docker-compose.yml logs -f picoclaw-gateway docker compose -f docker/docker-compose.yml --profile gateway down ``` +### Launcher Mode (Web Console) + +The `launcher` image includes all three binaries (`picoclaw`, `picoclaw-launcher`, `picoclaw-launcher-tui`) and starts the web console by default, which provides a browser-based UI for configuration and chat. + +```bash +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. + ### Agent Mode (One-shot) ```bash @@ -214,9 +227,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d ### 🚀 Quick Start > [!TIP] -> Set your API key in `~/.picoclaw/config.json`. -> Get API keys: [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM) -> Web Search is **optional** - get free [Tavily API](https://tavily.com) (1000 free queries/month), [SearXNG](https://github.com/searxng/searxng) (free, self-hosted) or [Brave Search API](https://brave.com/search/api) (2000 free queries/month) or use built-in auto fallback. +> Set your API Key in `~/.picoclaw/config.json`. Get API Keys: [Volcengine (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM). Web search is optional — get a free [Tavily API](https://tavily.com) (1000 free queries/month) or [Brave Search API](https://brave.com/search/api) (2000 free queries/month). **1. Initialize** @@ -231,7 +242,7 @@ picoclaw onboard "agents": { "defaults": { "workspace": "~/.picoclaw/workspace", - "model_name": "gpt4", + "model_name": "gpt-5.4", "max_tokens": 8192, "temperature": 0.7, "max_tool_iterations": 20 @@ -239,8 +250,13 @@ picoclaw onboard }, "model_list": [ { - "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "your-api-key", "request_timeout": 300 }, @@ -774,7 +790,6 @@ PicoClaw stores data in your configured workspace (default: `~/.picoclaw/workspa ├── HEARTBEAT.md # Periodic task prompts (checked every 30 min) ├── IDENTITY.md # Agent identity ├── SOUL.md # Agent soul -├── TOOLS.md # Tool descriptions └── USER.md # User preferences ``` @@ -1018,9 +1033,11 @@ This design also enables **multi-agent support** with flexible provider selectio | **LiteLLM Proxy** | `litellm/` | `http://localhost:4000/v1` | OpenAI | Your LiteLLM proxy key | | **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | Local | | **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [Get Key](https://cerebras.ai) | -| **火山引擎** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Get Key](https://console.volcengine.com) | +| **VolcEngine (Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Get Key](https://console.volcengine.com) | | **神算云** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - | +| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [Get Key](https://console.volcengine.com) | | **Vivgrid** | `vivgrid/` | `https://api.vivgrid.com/v1` | OpenAI | [Get Key](https://vivgrid.com) | +| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [Get Key](https://longcat.chat/platform) | | **Antigravity** | `antigravity/` | Google Cloud | Custom | OAuth only | | **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - | @@ -1030,8 +1047,13 @@ This design also enables **multi-agent support** with flexible provider selectio { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key" }, { @@ -1047,7 +1069,7 @@ This design also enables **multi-agent support** with flexible provider selectio ], "agents": { "defaults": { - "model": "gpt-5.2" + "model": "gpt-5.4" } } } @@ -1059,8 +1081,18 @@ This design also enables **multi-agent support** with flexible provider selectio ```json { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", + "api_key": "sk-..." +} +``` + +**VolcEngine (Doubao)** + +```json +{ + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", "api_key": "sk-..." } ``` @@ -1139,14 +1171,14 @@ Configure multiple endpoints for the same model name—PicoClaw will automatical { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "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.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } @@ -1486,8 +1518,16 @@ This happens when another instance of the bot is running. Make sure only one `pi | Service | Free Tier | Use Case | | ---------------- | ------------------------ | ------------------------------------- | | **OpenRouter** | 200K tokens/month | Multiple models (Claude, GPT-4, etc.) | -| **Zhipu** | 200K tokens/month | Best for Chinese users | +| **Volcengine CodingPlan** | ¥9.9/first month | Best for Chinese users, multiple SOTA models (Doubao, DeepSeek, etc.) | +| **Zhipu** | 200K tokens/month | Suitable for Chinese users | | **Brave Search** | Paid ($5/1000 queries) | Web search functionality | | **SearXNG** | Unlimited (self-hosted) | Privacy-focused metasearch (70+ engines) | | **Groq** | Free tier available | Fast inference (Llama, Mixtral) | | **Cerebras** | Free tier available | Fast inference (Llama, Qwen, etc.) | +| **LongCat** | Up to 5M tokens/day | Fast inference (free tier) | + +--- + +
+ PicoClaw Meme +
diff --git a/README.pt-br.md b/README.pt-br.md index 5f37ba457..474cb199c 100644 --- a/README.pt-br.md +++ b/README.pt-br.md @@ -1,5 +1,5 @@
-PicoClaw +PicoClaw

PicoClaw: Assistente de IA Ultra-Eficiente em Go

@@ -207,9 +207,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d ### 🚀 Início Rápido > [!TIP] -> Configure sua API key em `~/.picoclaw/config.json`. -> Obtenha API keys: [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM) -> Busca web e **opcional** — obtenha a [Brave Search API](https://brave.com/search/api) gratuita (2000 consultas grátis/mês) ou use o fallback automático integrado. +> Configure sua API key em `~/.picoclaw/config.json`. Obtenha API keys: [Volcengine (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM). Busca web é **opcional** — obtenha a [API Tavily](https://tavily.com) gratuita (1000 consultas grátis/mês) ou a [Brave Search API](https://brave.com/search/api) (2000 consultas grátis/mês). **1. Inicializar** @@ -223,8 +221,13 @@ picoclaw onboard { "model_list": [ { - "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key", "request_timeout": 300, "api_base": "https://api.openai.com/v1" @@ -232,7 +235,7 @@ picoclaw onboard ], "agents": { "defaults": { - "model_name": "gpt4" + "model_name": "gpt-5.4" } }, "tools": { @@ -645,7 +648,6 @@ O PicoClaw armazena dados no workspace configurado (padrão: `~/.picoclaw/worksp ├── HEARTBEAT.md # Prompts de tarefas periodicas (verificado a cada 30 min) ├── IDENTITY.md # Identidade do Agente ├── SOUL.md # Alma do Agente -├── TOOLS.md # Descrição das ferramentas └── USER.md # Preferencias do usuario ``` @@ -974,8 +976,10 @@ Este design também possibilita o **suporte multi-agent** com seleção flexíve | **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [Obter Chave](https://openrouter.ai/keys) | | **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | Local | | **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [Obter Chave](https://cerebras.ai) | -| **Volcengine** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Obter Chave](https://console.volcengine.com) | +| **VolcEngine (Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Obter Chave](https://console.volcengine.com) | | **ShengsuanYun** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - | +| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [Obter Chave](https://console.volcengine.com) | +| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [Obter Chave](https://longcat.chat/platform) | | **Antigravity** | `antigravity/` | Google Cloud | Custom | Apenas OAuth | | **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - | @@ -985,8 +989,13 @@ Este design também possibilita o **suporte multi-agent** com seleção flexíve { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key" }, { @@ -1002,7 +1011,7 @@ Este design também possibilita o **suporte multi-agent** com seleção flexíve ], "agents": { "defaults": { - "model": "gpt-5.2" + "model": "gpt-5.4" } } } @@ -1013,8 +1022,17 @@ Este design também possibilita o **suporte multi-agent** com seleção flexíve **OpenAI** ```json { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", + "api_key": "sk-..." +} +``` + +**VolcEngine (Doubao)** +```json +{ + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", "api_key": "sk-..." } ``` @@ -1057,14 +1075,14 @@ Configure vários endpoints para o mesmo nome de modelo—PicoClaw fará round-r { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "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.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } @@ -1196,7 +1214,14 @@ Isso acontece quando outra instância do bot está em execução. Certifique-se | Serviço | Plano Gratuito | Caso de Uso | | --- | --- | --- | | **OpenRouter** | 200K tokens/mês | Múltiplos modelos (Claude, GPT-4, etc.) | -| **Zhipu** | 200K tokens/mês | Melhor para usuários chineses | +| **Volcengine CodingPlan** | ¥9,9/primeiro mês | Ideal para usuários chineses, múltiplos modelos SOTA (Doubao, DeepSeek, etc.) | +| **Zhipu** | 200K tokens/mês | Adequado para usuários chineses | | **Brave Search** | 2000 consultas/mês | Funcionalidade de busca web | | **Groq** | Plano gratuito disponível | Inferência ultra-rápida (Llama, Mixtral) | | **Cerebras** | Plano gratuito disponível | Inferência ultra-rápida (Llama 3.3 70B) | + +--- + +
+ PicoClaw Meme +
diff --git a/README.vi.md b/README.vi.md index 92c6ecbae..38eed4d4d 100644 --- a/README.vi.md +++ b/README.vi.md @@ -1,5 +1,5 @@
-PicoClaw +PicoClaw

PicoClaw: Trợ lý AI Siêu Nhẹ viết bằng Go

@@ -187,9 +187,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d ### 🚀 Bắt đầu nhanh > [!TIP] -> Thiết lập API key trong `~/.picoclaw/config.json`. -> Lấy API key: [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM) -> Tìm kiếm web là **tùy chọn** — lấy [Brave Search API](https://brave.com/search/api) miễn phí (2000 truy vấn/tháng) hoặc dùng tính năng auto fallback tích hợp sẵn. +> Thiết lập API key trong `~/.picoclaw/config.json`. Lấy API key: [Volcengine (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM). Tìm kiếm web là **tùy chọn** — lấy [Tavily API](https://tavily.com) miễn phí (1000 truy vấn/tháng) hoặc [Brave Search API](https://brave.com/search/api) (2000 truy vấn/tháng). **1. Khởi tạo** @@ -203,8 +201,13 @@ picoclaw onboard { "model_list": [ { - "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key", "request_timeout": 300, "api_base": "https://api.openai.com/v1" @@ -617,7 +620,6 @@ PicoClaw lưu trữ dữ liệu trong workspace đã cấu hình (mặc định: ├── HEARTBEAT.md # Prompt tác vụ định kỳ (kiểm tra mỗi 30 phút) ├── IDENTITY.md # Danh tính Agent ├── SOUL.md # Tâm hồn/Tính cách Agent -├── TOOLS.md # Mô tả công cụ └── USER.md # Tùy chọn người dùng ``` @@ -943,8 +945,10 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch | **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [Lấy Khóa](https://openrouter.ai/keys) | | **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | Local | | **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [Lấy Khóa](https://cerebras.ai) | -| **Volcengine** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Lấy Khóa](https://console.volcengine.com) | +| **VolcEngine (Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [Lấy Khóa](https://console.volcengine.com) | | **ShengsuanYun** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - | +| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [Lấy Khóa](https://console.volcengine.com) | +| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [Lấy Key](https://longcat.chat/platform) | | **Antigravity** | `antigravity/` | Google Cloud | Tùy chỉnh | Chỉ OAuth | | **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - | @@ -954,8 +958,13 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key" }, { @@ -971,7 +980,7 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch ], "agents": { "defaults": { - "model": "gpt-5.2" + "model": "gpt-5.4" } } } @@ -982,8 +991,17 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch **OpenAI** ```json { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", + "api_key": "sk-..." +} +``` + +**VolcEngine (Doubao)** +```json +{ + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", "api_key": "sk-..." } ``` @@ -1026,14 +1044,14 @@ Thiết kế này cũng cho phép **hỗ trợ đa tác nhân** với lựa ch { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "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.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } @@ -1165,6 +1183,13 @@ Một số nhà cung cấp (như Zhipu) có bộ lọc nội dung nghiêm ngặt | Dịch vụ | Gói miễn phí | Trường hợp sử dụng | | --- | --- | --- | | **OpenRouter** | 200K tokens/tháng | Đa model (Claude, GPT-4, v.v.) | -| **Zhipu** | 200K tokens/tháng | Tốt nhất cho người dùng Trung Quốc | +| **Volcengine CodingPlan** | ¥9.9/tháng đầu | Tốt nhất cho người dùng Trung Quốc, nhiều mô hình SOTA (Doubao, DeepSeek, v.v.) | +| **Zhipu** | 200K tokens/tháng | Phù hợp cho người dùng Trung Quốc | | **Brave Search** | 2000 truy vấn/tháng | Chức năng tìm kiếm web | | **Groq** | Có gói miễn phí | Suy luận siêu nhanh (Llama, Mixtral) | + +--- + +
+ PicoClaw Meme +
diff --git a/README.zh.md b/README.zh.md index c744e0d20..6395b1e7b 100644 --- a/README.zh.md +++ b/README.zh.md @@ -1,5 +1,5 @@
-PicoClaw +PicoClaw

PicoClaw: 基于Go语言的超高效 AI 助手

@@ -208,9 +208,7 @@ docker compose -f docker/docker-compose.yml --profile gateway up -d ### 🚀 快速开始 > [!TIP] -> 在 `~/.picoclaw/config.json` 中设置您的 API Key。 -> 获取 API Key: [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu (智谱)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM) -> 网络搜索是 **可选的** - 获取免费的 [Tavily API](https://tavily.com) (每月 1000 次免费查询) 或 [Brave Search API](https://brave.com/search/api) (每月 2000 次免费查询) +> 在 `~/.picoclaw/config.json` 中设置您的 API Key。获取 API Key: [火山引擎 (CodingPlan)](https://console.volcengine.com) (LLM) · [OpenRouter](https://openrouter.ai/keys) (LLM) · [Zhipu (智谱)](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) (LLM)。网络搜索是 **可选的** — 获取免费的 [Tavily API](https://tavily.com) (每月 1000 次免费查询) 或 [Brave Search API](https://brave.com/search/api) (每月 2000 次免费查询)。 **1. 初始化 (Initialize)** @@ -226,7 +224,7 @@ picoclaw onboard "agents": { "defaults": { "workspace": "~/.picoclaw/workspace", - "model_name": "gpt4", + "model_name": "gpt-5.4", "max_tokens": 8192, "temperature": 0.7, "max_tool_iterations": 20 @@ -234,8 +232,13 @@ picoclaw onboard }, "model_list": [ { - "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "your-api-key", "request_timeout": 300 }, @@ -365,7 +368,6 @@ PicoClaw 将数据存储在您配置的工作区中(默认:`~/.picoclaw/work ├── HEARTBEAT.md # 周期性任务提示词 (每 30 分钟检查一次) ├── IDENTITY.md # Agent 身份设定 ├── SOUL.md # Agent 灵魂/性格 -├── TOOLS.md # 工具描述 └── USER.md # 用户偏好 ``` @@ -515,8 +517,10 @@ Agent 读取 HEARTBEAT.md | **OpenRouter** | `openrouter/` | `https://openrouter.ai/api/v1` | OpenAI | [获取密钥](https://openrouter.ai/keys) | | **VLLM** | `vllm/` | `http://localhost:8000/v1` | OpenAI | 本地 | | **Cerebras** | `cerebras/` | `https://api.cerebras.ai/v1` | OpenAI | [获取密钥](https://cerebras.ai) | -| **火山引擎** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [获取密钥](https://console.volcengine.com) | +| **火山引擎(Doubao)** | `volcengine/` | `https://ark.cn-beijing.volces.com/api/v3` | OpenAI | [获取密钥](https://console.volcengine.com) | | **神算云** | `shengsuanyun/` | `https://router.shengsuanyun.com/api/v1` | OpenAI | - | +| **BytePlus** | `byteplus/` | `https://ark.ap-southeast.bytepluses.com/api/v3` | OpenAI | [获取密钥](https://console.volcengine.com) | +| **LongCat** | `longcat/` | `https://api.longcat.chat/openai` | OpenAI | [获取密钥](https://longcat.chat/platform) | | **Antigravity** | `antigravity/` | Google Cloud | 自定义 | 仅 OAuth | | **GitHub Copilot** | `github-copilot/` | `localhost:4321` | gRPC | - | @@ -526,8 +530,13 @@ Agent 读取 HEARTBEAT.md { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", + "api_key": "sk-your-api-key" + }, + { + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key" }, { @@ -543,7 +552,7 @@ Agent 读取 HEARTBEAT.md ], "agents": { "defaults": { - "model": "gpt-5.2" + "model": "gpt-5.4" } } } @@ -555,8 +564,18 @@ Agent 读取 HEARTBEAT.md ```json { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", + "api_key": "sk-..." +} +``` + +**火山引擎(Doubao)** + +```json +{ + "model_name": "ark-code-latest", + "model": "volcengine/ark-code-latest", "api_key": "sk-..." } ``` @@ -622,14 +641,14 @@ Agent 读取 HEARTBEAT.md { "model_list": [ { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "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.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_base": "https://api2.example.com/v1", "api_key": "sk-key2" } @@ -875,7 +894,15 @@ Discord: [https://discord.gg/V4sAZ9XWpN](https://discord.gg/V4sAZ9XWpN) | 服务 | 免费层级 | 适用场景 | | --- | --- | --- | | **OpenRouter** | 200K tokens/月 | 多模型聚合 (Claude, GPT-4 等) | -| **智谱 (Zhipu)** | 200K tokens/月 | 最适合中国用户 | +| **火山引擎 CodingPlan** | 9.9 元/首月 | 最适合国内用户,多种 SOTA 模型(豆包、DeepSeek 等) | +| **智谱 (Zhipu)** | 200K tokens/月 | 适合中国用户 | | **Brave Search** | 2000 次查询/月 | 网络搜索功能 | | **Tavily** | 1000 次查询/月 | AI Agent 搜索优化 | | **Groq** | 提供免费层级 | 极速推理 (Llama, Mixtral) | +| **LongCat** | 最多 5M tokens/天 | 推理速度快 (免费额度) | + +--- + +
+ PicoClaw Meme +
diff --git a/cmd/picoclaw-launcher-tui/internal/ui/channel.go b/cmd/picoclaw-launcher-tui/internal/ui/channel.go index 16b7d053b..2f28af123 100644 --- a/cmd/picoclaw-launcher-tui/internal/ui/channel.go +++ b/cmd/picoclaw-launcher-tui/internal/ui/channel.go @@ -12,7 +12,6 @@ import ( func (s *appState) buildChannelMenuItems() []MenuItem { return []MenuItem{ - {Label: "Back", Description: "Return to main menu", Action: func() { s.pop() }}, channelItem( "Telegram", "Telegram bot settings", @@ -101,10 +100,6 @@ func (s *appState) channelMenu() tview.Primitive { s.pop() return nil } - if event.Rune() == 'q' { - s.pop() - return nil - } return event }) return menu diff --git a/cmd/picoclaw-launcher-tui/internal/ui/style.go b/cmd/picoclaw-launcher-tui/internal/ui/style.go index 68cdd60b9..da3c3526d 100644 --- a/cmd/picoclaw-launcher-tui/internal/ui/style.go +++ b/cmd/picoclaw-launcher-tui/internal/ui/style.go @@ -41,3 +41,15 @@ func bannerView() *tview.TextView { text.SetBorder(false) return text } + +const footerText = "Esc: Back/Exit | Enter: Enter | ←↓↑→ : Move | Space: Select | Tab/Shift+Tab: Switch" + +func footerView() *tview.TextView { + text := tview.NewTextView() + text.SetTextAlign(tview.AlignCenter) + text.SetText(footerText) + text.SetBackgroundColor(tview.Styles.MoreContrastBackgroundColor) + text.SetTextColor(tview.Styles.PrimaryTextColor) + text.SetBorder(false) + return text +} diff --git a/cmd/picoclaw/internal/agent/command.go b/cmd/picoclaw/internal/agent/command.go index 8939ddb2c..47262fc85 100644 --- a/cmd/picoclaw/internal/agent/command.go +++ b/cmd/picoclaw/internal/agent/command.go @@ -6,11 +6,10 @@ import ( func NewAgentCommand() *cobra.Command { var ( - message string - sessionKey string - model string - debug bool - orchestrationEnabled bool + message string + sessionKey string + model string + debug bool ) cmd := &cobra.Command{ @@ -18,7 +17,7 @@ func NewAgentCommand() *cobra.Command { Short: "Interact with the agent directly", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { - return agentCmd(message, sessionKey, model, debug, orchestrationEnabled) + return agentCmd(message, sessionKey, model, debug) }, } @@ -26,7 +25,6 @@ func NewAgentCommand() *cobra.Command { cmd.Flags().StringVarP(&message, "message", "m", "", "Send a single message (non-interactive mode)") cmd.Flags().StringVarP(&sessionKey, "session", "s", "cli:default", "Session key") cmd.Flags().StringVarP(&model, "model", "", "", "Model to use") - cmd.Flags().BoolVar(&orchestrationEnabled, "orchestration", false, "Enable orchestration mode") return cmd } diff --git a/cmd/picoclaw/internal/agent/helpers.go b/cmd/picoclaw/internal/agent/helpers.go index fab1bc54c..a995945d2 100644 --- a/cmd/picoclaw/internal/agent/helpers.go +++ b/cmd/picoclaw/internal/agent/helpers.go @@ -18,7 +18,7 @@ import ( "github.com/sipeed/picoclaw/pkg/providers" ) -func agentCmd(message, sessionKey, model string, debug, orchestrationEnabled bool) error { +func agentCmd(message, sessionKey, model string, debug bool) error { if sessionKey == "" { sessionKey = "cli:default" } @@ -37,10 +37,6 @@ func agentCmd(message, sessionKey, model string, debug, orchestrationEnabled boo cfg.Agents.Defaults.ModelName = model } - if orchestrationEnabled { - cfg.Agents.Defaults.Orchestration = true - } - provider, modelID, err := providers.CreateProvider(cfg) if err != nil { return fmt.Errorf("error creating provider: %w", err) @@ -54,6 +50,7 @@ func agentCmd(message, sessionKey, model string, debug, orchestrationEnabled boo msgBus := bus.NewMessageBus() defer msgBus.Close() agentLoop := agent.NewAgentLoop(cfg, msgBus, provider) + defer agentLoop.Close() // Print agent startup info (only for interactive mode) startupInfo := agentLoop.GetStartupInfo() diff --git a/cmd/picoclaw/internal/auth/helpers.go b/cmd/picoclaw/internal/auth/helpers.go index a0a229167..4bf132685 100644 --- a/cmd/picoclaw/internal/auth/helpers.go +++ b/cmd/picoclaw/internal/auth/helpers.go @@ -72,14 +72,14 @@ func authLoginOpenAI(useDeviceCode bool) error { // If no openai in ModelList, add it if !foundOpenAI { appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{ - ModelName: "gpt-5.2", - Model: "openai/gpt-5.2", + ModelName: "gpt-5.4", + Model: "openai/gpt-5.4", AuthMethod: "oauth", }) } // Update default model to use OpenAI - appCfg.Agents.Defaults.ModelName = "gpt-5.2" + appCfg.Agents.Defaults.ModelName = "gpt-5.4" if err = config.SaveConfig(internal.GetConfigPath(), appCfg); err != nil { return fmt.Errorf("could not update config: %w", err) @@ -90,7 +90,7 @@ func authLoginOpenAI(useDeviceCode bool) error { if cred.AccountID != "" { fmt.Printf("Account: %s\n", cred.AccountID) } - fmt.Println("Default model set to: gpt-5.2") + fmt.Println("Default model set to: gpt-5.4") return nil } @@ -318,13 +318,13 @@ func authLoginPasteToken(provider string) error { } if !found { appCfg.ModelList = append(appCfg.ModelList, config.ModelConfig{ - ModelName: "gpt-5.2", - Model: "openai/gpt-5.2", + ModelName: "gpt-5.4", + Model: "openai/gpt-5.4", AuthMethod: "token", }) } // Update default model - appCfg.Agents.Defaults.ModelName = "gpt-5.2" + appCfg.Agents.Defaults.ModelName = "gpt-5.4" } if err := config.SaveConfig(internal.GetConfigPath(), appCfg); err != nil { return fmt.Errorf("could not update config: %w", err) diff --git a/cmd/picoclaw/internal/gateway/command.go b/cmd/picoclaw/internal/gateway/command.go index 35976ff10..bfa69f072 100644 --- a/cmd/picoclaw/internal/gateway/command.go +++ b/cmd/picoclaw/internal/gateway/command.go @@ -1,29 +1,42 @@ package gateway import ( + "fmt" + "github.com/spf13/cobra" + + "github.com/sipeed/picoclaw/pkg/logger" + "github.com/sipeed/picoclaw/pkg/utils" ) func NewGatewayCommand() *cobra.Command { - var ( - debug bool - orchestration bool - enableStats bool - ) + var debug bool + var noTruncate bool cmd := &cobra.Command{ Use: "gateway", Aliases: []string{"g"}, Short: "Start picoclaw gateway", Args: cobra.NoArgs, + PreRunE: func(_ *cobra.Command, _ []string) error { + if noTruncate && !debug { + return fmt.Errorf("the --no-truncate option can only be used in conjunction with --debug (-d)") + } + + if noTruncate { + utils.SetDisableTruncation(true) + logger.Info("String truncation is globally disabled via 'no-truncate' flag") + } + + return nil + }, RunE: func(_ *cobra.Command, _ []string) error { - return gatewayCmd(debug, orchestration, enableStats) + return gatewayCmd(debug) }, } cmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging") - cmd.Flags().BoolVar(&orchestration, "orchestration", false, "Enable subagent orchestration") - cmd.Flags().BoolVar(&enableStats, "stats", false, "Enable stats tracking") + cmd.Flags().BoolVarP(&noTruncate, "no-truncate", "T", false, "Disable string truncation in debug logs") return cmd } diff --git a/cmd/picoclaw/internal/helpers.go b/cmd/picoclaw/internal/helpers.go index f81d7013d..e04bccffb 100644 --- a/cmd/picoclaw/internal/helpers.go +++ b/cmd/picoclaw/internal/helpers.go @@ -1,23 +1,14 @@ package internal import ( - "fmt" "os" "path/filepath" - "runtime" "github.com/sipeed/picoclaw/pkg/config" ) const Logo = "🦞" -var ( - version = "dev" - gitCommit string - buildTime string - goVersion string -) - // GetPicoclawHome returns the picoclaw home directory. // Priority: $PICOCLAW_HOME > ~/.picoclaw func GetPicoclawHome() string { @@ -40,25 +31,19 @@ func LoadConfig() (*config.Config, error) { } // FormatVersion returns the version string with optional git commit +// Deprecated: Use pkg/config.FormatVersion instead func FormatVersion() string { - v := version - if gitCommit != "" { - v += fmt.Sprintf(" (git: %s)", gitCommit) - } - return v + return config.FormatVersion() } // FormatBuildInfo returns build time and go version info +// Deprecated: Use pkg/config.FormatBuildInfo instead func FormatBuildInfo() (string, string) { - build := buildTime - goVer := goVersion - if goVer == "" { - goVer = runtime.Version() - } - return build, goVer + return config.FormatBuildInfo() } // GetVersion returns the version string +// Deprecated: Use pkg/config.GetVersion instead func GetVersion() string { - return version + return config.GetVersion() } diff --git a/cmd/picoclaw/internal/onboard/command.go b/cmd/picoclaw/internal/onboard/command.go index e89c15ab7..ec1012959 100644 --- a/cmd/picoclaw/internal/onboard/command.go +++ b/cmd/picoclaw/internal/onboard/command.go @@ -1,6 +1,14 @@ package onboard -import "github.com/spf13/cobra" +import ( + "embed" + + "github.com/spf13/cobra" +) + +//go:generate cp -r ../../../../workspace . +//go:embed workspace +var embeddedFiles embed.FS func NewOnboardCommand() *cobra.Command { cmd := &cobra.Command{ diff --git a/cmd/picoclaw/internal/status/helpers.go b/cmd/picoclaw/internal/status/helpers.go index ab28f4885..dd7063fe6 100644 --- a/cmd/picoclaw/internal/status/helpers.go +++ b/cmd/picoclaw/internal/status/helpers.go @@ -6,6 +6,7 @@ import ( "github.com/sipeed/picoclaw/cmd/picoclaw/internal" "github.com/sipeed/picoclaw/pkg/auth" + "github.com/sipeed/picoclaw/pkg/config" ) func statusCmd() { @@ -18,8 +19,8 @@ func statusCmd() { configPath := internal.GetConfigPath() fmt.Printf("%s picoclaw Status\n", internal.Logo) - fmt.Printf("Version: %s\n", internal.FormatVersion()) - build, _ := internal.FormatBuildInfo() + fmt.Printf("Version: %s\n", config.FormatVersion()) + build, _ := config.FormatBuildInfo() if build != "" { fmt.Printf("Build: %s\n", build) } diff --git a/cmd/picoclaw/internal/version/command.go b/cmd/picoclaw/internal/version/command.go index 1cf686671..71c7dd2f8 100644 --- a/cmd/picoclaw/internal/version/command.go +++ b/cmd/picoclaw/internal/version/command.go @@ -6,6 +6,7 @@ import ( "github.com/spf13/cobra" "github.com/sipeed/picoclaw/cmd/picoclaw/internal" + "github.com/sipeed/picoclaw/pkg/config" ) func NewVersionCommand() *cobra.Command { @@ -22,8 +23,8 @@ func NewVersionCommand() *cobra.Command { } func printVersion() { - fmt.Printf("%s picoclaw %s\n", internal.Logo, internal.FormatVersion()) - build, goVer := internal.FormatBuildInfo() + fmt.Printf("%s picoclaw %s\n", internal.Logo, config.FormatVersion()) + build, goVer := config.FormatBuildInfo() if build != "" { fmt.Printf(" Build: %s\n", build) } diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index d9263462e..b82475905 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -22,15 +22,16 @@ import ( "github.com/sipeed/picoclaw/cmd/picoclaw/internal/skills" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/status" "github.com/sipeed/picoclaw/cmd/picoclaw/internal/version" + "github.com/sipeed/picoclaw/pkg/config" ) func NewPicoclawCommand() *cobra.Command { - short := fmt.Sprintf("%s picoclaw - Personal AI Assistant v%s\n\n", internal.Logo, internal.GetVersion()) + short := fmt.Sprintf("%s picoclaw - Personal AI Assistant v%s\n\n", internal.Logo, config.GetVersion()) cmd := &cobra.Command{ Use: "picoclaw", Short: short, - Example: "picoclaw list", + Example: "picoclaw version", } cmd.AddCommand( diff --git a/config/config.example.json b/config/config.example.json index 0e2cae8e5..b259df6f6 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -3,7 +3,7 @@ "defaults": { "workspace": "~/.picoclaw/workspace", "restrict_to_workspace": true, - "model_name": "gpt4", + "model_name": "gpt-5.4", "max_tokens": 8192, "temperature": 0.7, "max_tool_iterations": 20, @@ -13,8 +13,8 @@ }, "model_list": [ { - "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key", "api_base": "https://api.openai.com/v1" }, @@ -36,14 +36,19 @@ "api_key": "sk-your-deepseek-key" }, { - "model_name": "loadbalanced-gpt4", - "model": "openai/gpt-5.2", + "model_name": "longcat", + "model": "longcat/LongCat-Flash-Thinking", + "api_key": "your-longcat-api-key" + }, + { + "model_name": "loadbalanced-gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-key1", "api_base": "https://api1.example.com/v1" }, { - "model_name": "loadbalanced-gpt4", - "model": "openai/gpt-5.2", + "model_name": "loadbalanced-gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-key2", "api_base": "https://api2.example.com/v1" } @@ -194,8 +199,13 @@ "nickserv_password": "", "sasl_user": "", "sasl_password": "", - "channels": ["#mychannel"], - "request_caps": ["server-time", "message-tags"], + "channels": [ + "#mychannel" + ], + "request_caps": [ + "server-time", + "message-tags" + ], "allow_from": [], "group_trigger": { "mention_only": true @@ -269,6 +279,10 @@ "avian": { "api_key": "", "api_base": "https://api.avian.io/v1" + }, + "longcat": { + "api_key": "", + "api_base": "https://api.longcat.chat/openai" } }, "tools": { @@ -279,6 +293,9 @@ "brave": { "enabled": false, "api_key": "YOUR_BRAVE_API_KEY", + "api_keys": [ + "YOUR_BRAVE_API_KEY" + ], "max_results": 5 }, "tavily": { @@ -293,7 +310,10 @@ }, "perplexity": { "enabled": false, - "api_key": "", + "api_key": "pplx-xxx", + "api_keys": [ + "pplx-xxx" + ], "max_results": 5 }, "searxng": { @@ -316,6 +336,13 @@ }, "mcp": { "enabled": false, + "discovery": { + "enabled": false, + "ttl": 5, + "max_search_results": 5, + "use_bm25": true, + "use_regex": false + }, "servers": { "context7": { "enabled": false, @@ -459,6 +486,9 @@ "enabled": false, "monitor_usb": true }, + "voice": { + "echo_transcription": false + }, "gateway": { "host": "127.0.0.1", "port": 18790 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 9ec71abab..b26cf4199 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -19,7 +19,7 @@ services: # ───────────────────────────────────────────── # PicoClaw Gateway (Long-running Bot) - # docker compose -f docker/docker-compose.yml up picoclaw-gateway + # docker compose -f docker/docker-compose.yml --profile gateway up # ───────────────────────────────────────────── picoclaw-gateway: image: docker.io/sipeed/picoclaw:latest @@ -32,3 +32,21 @@ services: # - "host.docker.internal:host-gateway" volumes: - ./data:/root/.picoclaw + + # ───────────────────────────────────────────── + # PicoClaw Launcher (Web Console + Gateway) + # docker compose -f docker/docker-compose.yml --profile launcher up + # ───────────────────────────────────────────── + picoclaw-launcher: + image: docker.io/sipeed/picoclaw:launcher + container_name: picoclaw-launcher + restart: on-failure + profiles: + - launcher + environment: + - PICOCLAW_GATEWAY_HOST=0.0.0.0 + ports: + - "127.0.0.1:18800:18800" + - "127.0.0.1:18790:18790" + volumes: + - ./data:/root/.picoclaw diff --git a/docs/channels/matrix/README.md b/docs/channels/matrix/README.md index c213aa80b..233f5c0a3 100644 --- a/docs/channels/matrix/README.md +++ b/docs/channels/matrix/README.md @@ -22,7 +22,8 @@ Add this to `config.json`: "enabled": true, "text": "Thinking..." }, - "reasoning_channel_id": "" + "reasoning_channel_id": "", + "message_format": "richtext" } } } @@ -42,10 +43,12 @@ Add this to `config.json`: | group_trigger | object | No | Group trigger strategy (`mention_only` / `prefixes`) | | placeholder | object | No | Placeholder message config | | reasoning_channel_id | string | No | Target channel for reasoning output | +| message_format | string | No | Output format: `"richtext"` (default) renders markdown as HTML; `"plain"` sends plain text only | ## 3. Currently Supported -- Text message send/receive +- Text message send/receive with markdown rendering (bold, italic, headers, code blocks, etc.) +- Configurable message format (`richtext` / `plain`) - Incoming image/audio/video/file download (MediaStore first, local path fallback) - Incoming audio normalization into existing transcription flow (`[audio: ...]`) - Outgoing image/audio/video/file upload and send diff --git a/docs/design/provider-refactoring.md b/docs/design/provider-refactoring.md index a214d9857..38f379c50 100644 --- a/docs/design/provider-refactoring.md +++ b/docs/design/provider-refactoring.md @@ -66,7 +66,7 @@ Problem: Agent needs to know both `provider` and `model`, adding complexity. Inspired by [LiteLLM](https://docs.litellm.ai/docs/proxy/configs) design: 1. **Model-centric**: Users care about models, not providers -2. **Protocol prefix**: Use `protocol/model_name` format, e.g., `openai/gpt-5.2`, `anthropic/claude-sonnet-4.6` +2. **Protocol prefix**: Use `protocol/model_name` format, e.g., `openai/gpt-5.4`, `anthropic/claude-sonnet-4.6` 3. **Configuration-driven**: Adding new Providers only requires config changes, no code changes ### 2.2 New Configuration Structure @@ -81,8 +81,8 @@ Inspired by [LiteLLM](https://docs.litellm.ai/docs/proxy/configs) design: "api_key": "sk-xxx" }, { - "model_name": "gpt-5.2", - "model": "openai/gpt-5.2", + "model_name": "gpt-5.4", + "model": "openai/gpt-5.4", "api_key": "sk-xxx" }, { @@ -128,7 +128,7 @@ type Config struct { type ModelConfig struct { // Required ModelName string `json:"model_name"` // user-facing name (alias) - Model string `json:"model"` // protocol/model, e.g., openai/gpt-5.2 + Model string `json:"model"` // protocol/model, e.g., openai/gpt-5.4 // Common config APIBase string `json:"api_base,omitempty"` @@ -180,7 +180,7 @@ Identify protocol via prefix in `model` field: "model": "deepseek-chat" }, "coder": { - "model": "gpt-5.2", + "model": "gpt-5.4", "system_prompt": "You are a coding assistant..." }, "translator": { @@ -200,7 +200,7 @@ Each Agent only needs to specify `model` (corresponds to `model_name` in `model_ model_list: - model_name: gpt-4o litellm_params: - model: openai/gpt-5.2 + model: openai/gpt-5.4 api_key: xxx - model_name: my-custom litellm_params: diff --git a/docs/migration/model-list-migration.md b/docs/migration/model-list-migration.md index 0d4af719c..eed228d4d 100644 --- a/docs/migration/model-list-migration.md +++ b/docs/migration/model-list-migration.md @@ -40,7 +40,7 @@ The new `model_list` configuration offers several advantages: "agents": { "defaults": { "provider": "openai", - "model": "gpt-5.2" + "model": "gpt-5.4" } } } @@ -53,7 +53,7 @@ The new `model_list` configuration offers several advantages: "model_list": [ { "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model": "openai/gpt-5.4", "api_key": "sk-your-openai-key", "api_base": "https://api.openai.com/v1" }, @@ -82,7 +82,7 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier` | Prefix | Description | Example | |--------|-------------|---------| -| `openai/` | OpenAI API (default) | `openai/gpt-5.2` | +| `openai/` | OpenAI API (default) | `openai/gpt-5.4` | | `anthropic/` | Anthropic API | `anthropic/claude-opus-4` | | `antigravity/` | Google via Antigravity OAuth | `antigravity/gemini-2.0-flash` | | `gemini/` | Google Gemini API | `gemini/gemini-2.0-flash-exp` | @@ -109,7 +109,7 @@ The `model` field uses a protocol prefix format: `[protocol/]model-identifier` | Field | Required | Description | |-------|----------|-------------| | `model_name` | Yes | User-facing alias for the model | -| `model` | Yes | Protocol and model identifier (e.g., `openai/gpt-5.2`) | +| `model` | Yes | Protocol and model identifier (e.g., `openai/gpt-5.4`) | | `api_base` | No | API endpoint URL | | `api_key` | No* | API authentication key | | `proxy` | No | HTTP proxy URL | @@ -130,19 +130,19 @@ Configure multiple endpoints for the same model to distribute load: "model_list": [ { "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model": "openai/gpt-5.4", "api_key": "sk-key1", "api_base": "https://api1.example.com/v1" }, { "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model": "openai/gpt-5.4", "api_key": "sk-key2", "api_base": "https://api2.example.com/v1" }, { "model_name": "gpt4", - "model": "openai/gpt-5.2", + "model": "openai/gpt-5.4", "api_key": "sk-key3", "api_base": "https://api3.example.com/v1" } diff --git a/docs/tools_configuration.md b/docs/tools_configuration.md index e64a3a107..8c8eb31f0 100644 --- a/docs/tools_configuration.md +++ b/docs/tools_configuration.md @@ -7,11 +7,21 @@ PicoClaw's tools configuration is located in the `tools` field of `config.json`. ```json { "tools": { - "web": { ... }, - "mcp": { ... }, - "exec": { ... }, - "cron": { ... }, - "skills": { ... } + "web": { + ... + }, + "mcp": { + ... + }, + "exec": { + ... + }, + "cron": { + ... + }, + "skills": { + ... + } } } ``` @@ -23,7 +33,7 @@ Web tools are used for web search and fetching. ### Brave | Config | Type | Default | Description | -| ------------- | ------ | ------- | ------------------------- | +|---------------|--------|---------|---------------------------| | `enabled` | bool | false | Enable Brave search | | `api_key` | string | - | Brave Search API key | | `max_results` | int | 5 | Maximum number of results | @@ -31,14 +41,14 @@ Web tools are used for web search and fetching. ### DuckDuckGo | Config | Type | Default | Description | -| ------------- | ---- | ------- | ------------------------- | +|---------------|------|---------|---------------------------| | `enabled` | bool | true | Enable DuckDuckGo search | | `max_results` | int | 5 | Maximum number of results | ### Perplexity | Config | Type | Default | Description | -| ------------- | ------ | ------- | ------------------------- | +|---------------|--------|---------|---------------------------| | `enabled` | bool | false | Enable Perplexity search | | `api_key` | string | - | Perplexity API key | | `max_results` | int | 5 | Maximum number of results | @@ -48,7 +58,7 @@ Web tools are used for web search and fetching. The exec tool is used to execute shell commands. | Config | Type | Default | Description | -| ---------------------- | ----- | ------- | ------------------------------------------ | +|------------------------|-------|---------|--------------------------------------------| | `enable_deny_patterns` | bool | true | Enable default dangerous command blocking | | `custom_deny_patterns` | array | [] | Custom deny patterns (regular expressions) | @@ -81,7 +91,10 @@ By default, PicoClaw blocks the following dangerous commands: "tools": { "exec": { "enable_deny_patterns": true, - "custom_deny_patterns": ["\\brm\\s+-r\\b", "\\bkillall\\s+python"] + "custom_deny_patterns": [ + "\\brm\\s+-r\\b", + "\\bkillall\\s+python" + ] } } } @@ -92,24 +105,47 @@ By default, PicoClaw blocks the following dangerous commands: The cron tool is used for scheduling periodic tasks. | Config | Type | Default | Description | -| ---------------------- | ---- | ------- | ---------------------------------------------- | +|------------------------|------|---------|------------------------------------------------| | `exec_timeout_minutes` | int | 5 | Execution timeout in minutes, 0 means no limit | ## MCP Tool The MCP tool enables integration with external Model Context Protocol servers. +### Tool Discovery (Lazy Loading) + +When connecting to multiple MCP servers, exposing hundreds of tools simultaneously can exhaust the LLM's context window +and increase API costs. The **Discovery** feature solves this by keeping MCP tools *hidden* by default. + +Instead of loading all tools, the LLM is provided with a lightweight search tool (using BM25 keyword matching or Regex). +When the LLM needs a specific capability, it searches the hidden library. Matching tools are then temporarily "unlocked" +and injected into the context for a configured number of turns (`ttl`). + ### Global Config -| Config | Type | Default | Description | -| --------- | ------ | ------- | ----------------------------------- | -| `enabled` | bool | false | Enable MCP integration globally | -| `servers` | object | `{}` | Map of server name to server config | +| Config | Type | Default | Description | +|-------------|--------|---------|----------------------------------------------| +| `enabled` | bool | false | Enable MCP integration globally | +| `discovery` | object | `{}` | Configuration for Tool Discovery (see below) | +| `servers` | object | `{}` | Map of server name to server config | + +### Discovery Config (`discovery`) + +| Config | Type | Default | Description | +|----------------------|------|---------|-----------------------------------------------------------------------------------------------------------------------------------| +| `enabled` | bool | false | If true, MCP tools are hidden and loaded on-demand via search. If false, all tools are loaded | +| `ttl` | int | 5 | Number of conversational turns a discovered tool remains unlocked | +| `max_search_results` | int | 5 | Maximum number of tools returned per search query | +| `use_bm25` | bool | true | Enable the natural language/keyword search tool (`tool_search_tool_bm25`). **Warning**: consumes more resources than regex search | +| `use_regex` | bool | false | Enable the regex pattern search tool (`tool_search_tool_regex`) | + +> **Note:** If `discovery.enabled` is `true`, you MUST enable at least one search engine (`use_bm25` or `use_regex`), +> otherwise the application will fail to start. ### Per-Server Config | Config | Type | Required | Description | -| ---------- | ------ | -------- | ------------------------------------------ | +|------------|--------|----------|--------------------------------------------| | `enabled` | bool | yes | Enable this MCP server | | `type` | string | no | Transport type: `stdio`, `sse`, `http` | | `command` | string | stdio | Executable command for stdio transport | @@ -122,8 +158,8 @@ The MCP tool enables integration with external Model Context Protocol servers. ### Transport Behavior - If `type` is omitted, transport is auto-detected: - - `url` is set → `sse` - - `command` is set → `stdio` + - `url` is set → `sse` + - `command` is set → `stdio` - `http` and `sse` both use `url` + optional `headers`. - `env` and `env_file` are only applied to `stdio` servers. @@ -140,7 +176,11 @@ The MCP tool enables integration with external Model Context Protocol servers. "filesystem": { "enabled": true, "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem", + "/tmp" + ] } } } @@ -170,20 +210,76 @@ The MCP tool enables integration with external Model Context Protocol servers. } ``` +#### 3) Massive MCP setup with Tool Discovery enabled + +*In this example, the LLM will only see the `tool_search_tool_bm25`. It will search and unlock Github or Postgres tools +dynamically only when requested by the user.* + +```json +{ + "tools": { + "mcp": { + "enabled": true, + "discovery": { + "enabled": true, + "ttl": 5, + "max_search_results": 5, + "use_bm25": true, + "use_regex": false + }, + "servers": { + "github": { + "enabled": true, + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-github" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_GITHUB_TOKEN" + } + }, + "postgres": { + "enabled": true, + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-postgres", + "postgresql://user:password@localhost/dbname" + ] + }, + "slack": { + "enabled": true, + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-slack" + ], + "env": { + "SLACK_BOT_TOKEN": "YOUR_SLACK_BOT_TOKEN", + "SLACK_TEAM_ID": "YOUR_SLACK_TEAM_ID" + } + } + } + } + } +} +``` + ## Skills Tool The skills tool configures skill discovery and installation via registries like ClawHub. ### Registries -| Config | Type | Default | Description | -| ---------------------------------- | ------ | -------------------- | ----------------------- | -| `registries.clawhub.enabled` | bool | true | Enable ClawHub registry | -| `registries.clawhub.base_url` | string | `https://clawhub.ai` | ClawHub base URL | +| Config | Type | Default | Description | +|------------------------------------|--------|----------------------|----------------------------------------------| +| `registries.clawhub.enabled` | bool | true | Enable ClawHub registry | +| `registries.clawhub.base_url` | string | `https://clawhub.ai` | ClawHub base URL | | `registries.clawhub.auth_token` | string | `""` | Optional Bearer token for higher rate limits | -| `registries.clawhub.search_path` | string | `/api/v1/search` | Search API path | -| `registries.clawhub.skills_path` | string | `/api/v1/skills` | Skills API path | -| `registries.clawhub.download_path` | string | `/api/v1/download` | Download API path | +| `registries.clawhub.search_path` | string | `/api/v1/search` | Search API path | +| `registries.clawhub.skills_path` | string | `/api/v1/skills` | Skills API path | +| `registries.clawhub.download_path` | string | `/api/v1/download` | Download API path | ### Configuration Example @@ -217,4 +313,5 @@ For example: - `PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES=10` - `PICOCLAW_TOOLS_MCP_ENABLED=true` -Note: Nested map-style config (for example `tools.mcp.servers..*`) is configured in `config.json` rather than environment variables. +Note: Nested map-style config (for example `tools.mcp.servers..*`) is configured in `config.json` rather than +environment variables. diff --git a/pkg/channels/base.go b/pkg/channels/base.go index 5927408ee..edb5b6f08 100644 --- a/pkg/channels/base.go +++ b/pkg/channels/base.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "encoding/binary" "encoding/hex" + "regexp" "strconv" "strings" "sync/atomic" @@ -32,6 +33,9 @@ func init() { uniqueIDPrefix = hex.EncodeToString(b[:]) } +// audioAnnotationRe matches audio/voice annotations injected by channels (e.g. [voice], [audio: file.ogg]). +var audioAnnotationRe = regexp.MustCompile(`\[(voice|audio)(?::[^\]]*)?\]`) + // uniqueID generates a process-unique ID using a random prefix and an atomic counter. // This ID is intended for internal correlation (e.g. media scope keys) and is NOT // cryptographically secure — it must not be used in contexts where unpredictability matters. @@ -285,10 +289,10 @@ func (c *BaseChannel) HandleMessage( } } // Placeholder — independent pipeline. - // Skip for DraftSender channels: the streaming draft bubble serves - // as the placeholder, and sendMessage on final response replaces it. - // Sending both a placeholder AND drafts causes duplicate chat bubbles. - if _, isDrafter := c.owner.(DraftSender); !isDrafter { + // Skip when the message contains audio: the agent will send the + // placeholder after transcription completes, so the user sees + // "Thinking…" only once the voice has been processed. + if !audioAnnotationRe.MatchString(content) { if pc, ok := c.owner.(PlaceholderCapable); ok { if phID, err := pc.SendPlaceholder(ctx, chatID); err == nil && phID != "" { c.placeholderRecorder.RecordPlaceholder(c.name, chatID, phID) diff --git a/pkg/channels/dingtalk/dingtalk.go b/pkg/channels/dingtalk/dingtalk.go index 8642ad362..c03122892 100644 --- a/pkg/channels/dingtalk/dingtalk.go +++ b/pkg/channels/dingtalk/dingtalk.go @@ -10,6 +10,7 @@ import ( "github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot" "github.com/open-dingtalk/dingtalk-stream-sdk-go/client" + dinglog "github.com/open-dingtalk/dingtalk-stream-sdk-go/logger" "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/channels" @@ -39,6 +40,9 @@ func NewDingTalkChannel(cfg config.DingTalkConfig, messageBus *bus.MessageBus) ( return nil, fmt.Errorf("dingtalk client_id and client_secret are required") } + // Set the logger for the Stream SDK + dinglog.SetLogger(logger.NewLogger("dingtalk")) + base := channels.NewBaseChannel("dingtalk", cfg, messageBus, cfg.AllowFrom, channels.WithMaxMessageLength(20000), channels.WithGroupTrigger(cfg.GroupTrigger), diff --git a/pkg/channels/line/line.go b/pkg/channels/line/line.go index b36350a06..56ba02183 100644 --- a/pkg/channels/line/line.go +++ b/pkg/channels/line/line.go @@ -32,6 +32,10 @@ const ( lineBotInfoEndpoint = lineAPIBase + "/info" lineLoadingEndpoint = lineAPIBase + "/chat/loading/start" lineReplyTokenMaxAge = 25 * time.Second + + // Limit request body to prevent memory exhaustion (DoS). + // LINE webhook payloads are typically a few KB; 1 MiB is generous. + maxWebhookBodySize = 1 << 20 // 1 MiB ) type replyTokenEntry struct { @@ -166,7 +170,7 @@ func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) { return } - body, err := io.ReadAll(r.Body) + body, err := io.ReadAll(io.LimitReader(r.Body, maxWebhookBodySize+1)) if err != nil { logger.ErrorCF("line", "Failed to read request body", map[string]any{ "error": err.Error(), @@ -174,6 +178,11 @@ func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "Bad request", http.StatusBadRequest) return } + if int64(len(body)) > maxWebhookBodySize { + logger.WarnC("line", "Webhook request body too large, rejected") + http.Error(w, "Request entity too large", http.StatusRequestEntityTooLarge) + return + } signature := r.Header.Get("X-Line-Signature") if !c.verifySignature(body, signature) { diff --git a/pkg/commands/builtin.go b/pkg/commands/builtin.go index a36dd3eba..aed6a1874 100644 --- a/pkg/commands/builtin.go +++ b/pkg/commands/builtin.go @@ -12,5 +12,6 @@ func BuiltinDefinitions() []Definition { listCommand(), switchCommand(), checkCommand(), + clearCommand(), } } diff --git a/pkg/commands/runtime.go b/pkg/commands/runtime.go index 227d495f4..037184686 100644 --- a/pkg/commands/runtime.go +++ b/pkg/commands/runtime.go @@ -13,4 +13,5 @@ type Runtime struct { GetEnabledChannels func() []string SwitchModel func(value string) (oldModel string, err error) SwitchChannel func(value string) error + ClearHistory func() error } diff --git a/pkg/config/config.go b/pkg/config/config.go index 45cf8976e..0d1bacdbc 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -227,18 +227,18 @@ type AgentDefaults struct { ModelName string `json:"model_name,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL_NAME"` Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"` // Deprecated: use model_name instead ModelFallbacks []string `json:"model_fallbacks,omitempty"` - ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"` - ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"` PlanModel string `json:"plan_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_PLAN_MODEL"` PlanModelFallbacks []string `json:"plan_model_fallbacks,omitempty"` + ImageModel string `json:"image_model,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_IMAGE_MODEL"` + ImageModelFallbacks []string `json:"image_model_fallbacks,omitempty"` MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"` Temperature *float64 `json:"temperature,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"` MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"` - TaskReminderInterval int `json:"task_reminder_interval" env:"PICOCLAW_AGENTS_DEFAULTS_TASK_REMINDER_INTERVAL"` - Orchestration bool `json:"orchestration,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_ORCHESTRATION"` SummarizeMessageThreshold int `json:"summarize_message_threshold" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARIZE_MESSAGE_THRESHOLD"` SummarizeTokenPercent int `json:"summarize_token_percent" env:"PICOCLAW_AGENTS_DEFAULTS_SUMMARIZE_TOKEN_PERCENT"` MaxMediaSize int `json:"max_media_size,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_MEDIA_SIZE"` + TaskReminderInterval int `json:"task_reminder_interval" env:"PICOCLAW_AGENTS_DEFAULTS_TASK_REMINDER_INTERVAL"` + Orchestration bool `json:"orchestration,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_ORCHESTRATION"` Routing *RoutingConfig `json:"routing,omitempty"` } @@ -304,19 +304,20 @@ type WhatsAppConfig struct { AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_WHATSAPP_ALLOW_FROM"` ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_WHATSAPP_REASONING_CHANNEL_ID"` } + type TelegramConfig struct { Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_TELEGRAM_ENABLED"` Token string `json:"token" env:"PICOCLAW_CHANNELS_TELEGRAM_TOKEN"` BaseURL string `json:"base_url" env:"PICOCLAW_CHANNELS_TELEGRAM_BASE_URL"` Proxy string `json:"proxy" env:"PICOCLAW_CHANNELS_TELEGRAM_PROXY"` - WebAppURL string `json:"web_app_url" env:"PICOCLAW_CHANNELS_TELEGRAM_WEB_APP_URL"` AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_TELEGRAM_ALLOW_FROM"` GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"` Typing TypingConfig `json:"typing,omitempty"` Placeholder PlaceholderConfig `json:"placeholder,omitempty"` + ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_TELEGRAM_REASONING_CHANNEL_ID"` + WebAppURL string `json:"web_app_url" env:"PICOCLAW_CHANNELS_TELEGRAM_WEB_APP_URL"` SubagentThreadID int `json:"subagent_thread_id,omitempty" env:"PICOCLAW_CHANNELS_TELEGRAM_SUBAGENT_THREAD_ID"` HeartbeatThreadID int `json:"heartbeat_thread_id,omitempty" env:"PICOCLAW_CHANNELS_TELEGRAM_HEARTBEAT_THREAD_ID"` - ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_TELEGRAM_REASONING_CHANNEL_ID"` } type FeishuConfig struct { @@ -394,7 +395,7 @@ type MatrixConfig struct { AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_MATRIX_ALLOW_FROM"` GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"` Placeholder PlaceholderConfig `json:"placeholder,omitempty"` - ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_MATRIX_REASONING_CHANNEL_ID"` + ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_MATRIX_REASONING_CHANNEL_ID"` } type LINEConfig struct { @@ -609,12 +610,13 @@ type ModelConfig struct { AuthMethod string `json:"auth_method,omitempty"` // Authentication method: oauth, token ConnectMode string `json:"connect_mode,omitempty"` // Connection mode: stdio, grpc Workspace string `json:"workspace,omitempty"` // Workspace path for CLI-based providers + // Optional optimizations RPM int `json:"rpm,omitempty"` // Requests per minute limit MaxTokensField string `json:"max_tokens_field,omitempty"` // Field name for max tokens (e.g., "max_completion_tokens") - Stream *bool `json:"stream,omitempty"` // Use SSE streaming (default: protocol-dependent) RequestTimeout int `json:"request_timeout,omitempty"` ThinkingLevel string `json:"thinking_level,omitempty"` // Extended thinking: off|low|medium|high|xhigh|adaptive + Stream *bool `json:"stream,omitempty"` // Use SSE streaming (default: protocol-dependent) } // Validate checks if the ModelConfig has all required fields. diff --git a/pkg/config/migration.go b/pkg/config/migration.go index bb04fd2a8..af6391651 100644 --- a/pkg/config/migration.go +++ b/pkg/config/migration.go @@ -407,6 +407,23 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig { }, true }, }, + { + providerNames: []string{"longcat"}, + protocol: "longcat", + buildConfig: func(p ProvidersConfig) (ModelConfig, bool) { + if p.LongCat.APIKey == "" && p.LongCat.APIBase == "" { + return ModelConfig{}, false + } + return ModelConfig{ + ModelName: "longcat", + Model: "longcat/LongCat-Flash-Thinking", + APIKey: p.LongCat.APIKey, + APIBase: p.LongCat.APIBase, + Proxy: p.LongCat.Proxy, + RequestTimeout: p.LongCat.RequestTimeout, + }, true + }, + }, } // Process each provider migration diff --git a/pkg/health/server.go b/pkg/health/server.go index 0a530a3e8..5609ebdf6 100644 --- a/pkg/health/server.go +++ b/pkg/health/server.go @@ -2,7 +2,6 @@ package health import ( "context" - "crypto/tls" "encoding/json" "fmt" "maps" @@ -13,7 +12,6 @@ import ( type Server struct { server *http.Server - mux *http.ServeMux mu sync.RWMutex ready bool checks map[string]Check @@ -36,7 +34,6 @@ type StatusResponse struct { func NewServer(host string, port int) *Server { mux := http.NewServeMux() s := &Server{ - mux: mux, ready: false, checks: make(map[string]Check), startTime: time.Now(), @@ -49,34 +46,13 @@ func NewServer(host string, port int) *Server { s.server = &http.Server{ Addr: addr, Handler: mux, - ReadTimeout: 30 * time.Second, - WriteTimeout: 30 * time.Second, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, } return s } -// Mux returns the underlying ServeMux so additional routes can be registered. -func (s *Server) Mux() *http.ServeMux { - return s.mux -} - -// StartTLS starts the server with TLS using the provided certificate and key files. -func (s *Server) StartTLS(certFile, keyFile string) error { - s.mu.Lock() - s.ready = true - s.mu.Unlock() - - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return fmt.Errorf("failed to load TLS cert: %w", err) - } - s.server.TLSConfig = &tls.Config{ - Certificates: []tls.Certificate{cert}, - } - return s.server.ListenAndServeTLS("", "") -} - func (s *Server) Start() error { s.mu.Lock() s.ready = true diff --git a/pkg/health/server_ext.go b/pkg/health/server_ext.go new file mode 100644 index 000000000..8fff4a74c --- /dev/null +++ b/pkg/health/server_ext.go @@ -0,0 +1,28 @@ +package health + +import ( + "crypto/tls" + "fmt" + "net/http" +) + +// Mux returns the underlying ServeMux so additional routes can be registered. +func (s *Server) Mux() *http.ServeMux { + return s.server.Handler.(*http.ServeMux) +} + +// StartTLS starts the server with TLS using the provided certificate and key files. +func (s *Server) StartTLS(certFile, keyFile string) error { + s.mu.Lock() + s.ready = true + s.mu.Unlock() + + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return fmt.Errorf("failed to load TLS cert: %w", err) + } + s.server.TLSConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + return s.server.ListenAndServeTLS("", "") +} diff --git a/pkg/memory/jsonl.go b/pkg/memory/jsonl.go index e12e2c5ab..afe374166 100644 --- a/pkg/memory/jsonl.go +++ b/pkg/memory/jsonl.go @@ -86,14 +86,14 @@ func (s *JSONLStore) metaPath(key string) string { // sanitizeKey converts a session key to a safe filename component. // Mirrors pkg/session.sanitizeFilename so that migration paths match. -// -// Note: this is a lossy mapping — "telegram:123" and "telegram_123" -// both produce the same filename. This is an intentional tradeoff: -// keys with colons (e.g. from channels) are by far the common case, -// and a bidirectional encoding (like URL-encoding) would complicate -// file listings and debugging. +// Replaces ':' with '_' (session key separator) and '/' and '\' with '_' +// so composite IDs (e.g. Telegram forum "chatID/threadID", Slack "channel/thread_ts") +// do not create subdirectories or break on Windows. func sanitizeKey(key string) string { - return strings.ReplaceAll(key, ":", "_") + s := strings.ReplaceAll(key, ":", "_") + s = strings.ReplaceAll(s, "/", "_") + s = strings.ReplaceAll(s, "\\", "_") + return s } // readMeta loads the metadata file for a session. diff --git a/pkg/memory/migration.go b/pkg/memory/migration.go index c9d5176ab..b64c62a9f 100644 --- a/pkg/memory/migration.go +++ b/pkg/memory/migration.go @@ -48,6 +48,12 @@ func MigrateFromJSON( if !strings.HasSuffix(name, ".json") { continue } + // Skip JSONL metadata files. They are part of the new storage format, + // not legacy session snapshots, and re-importing them would overwrite + // the paired .jsonl history with an empty message list. + if strings.HasSuffix(name, ".meta.json") { + continue + } // Skip already-migrated files. if strings.HasSuffix(name, ".migrated") { continue diff --git a/pkg/migrate/sources/openclaw/common.go b/pkg/migrate/sources/openclaw/common.go index d57dbe34f..7cbd2d1e6 100644 --- a/pkg/migrate/sources/openclaw/common.go +++ b/pkg/migrate/sources/openclaw/common.go @@ -4,8 +4,8 @@ var migrateableFiles = []string{ "AGENTS.md", "SOUL.md", "USER.md", - "TOOLS.md", "HEARTBEAT.md", + "TOOLS.md", } var migrateableDirs = []string{ diff --git a/pkg/providers/anthropic/provider.go b/pkg/providers/anthropic/provider.go index 7a17e92bf..242ded175 100644 --- a/pkg/providers/anthropic/provider.go +++ b/pkg/providers/anthropic/provider.go @@ -181,8 +181,10 @@ func buildParams( } for _, tc := range msg.ToolCalls { args := tc.Arguments - if args == nil && tc.Function != nil && len(tc.Function.Arguments) > 0 { - args = tc.Function.Arguments + if args == nil && tc.Function != nil && tc.Function.Arguments != "" { + if err := json.Unmarshal([]byte(tc.Function.Arguments), &args); err != nil { + args = map[string]any{} + } } if args == nil { args = map[string]any{} @@ -306,19 +308,16 @@ func levelToBudget(level string) int { func translateTools(tools []ToolDefinition) []anthropic.ToolUnionParam { result := make([]anthropic.ToolUnionParam, 0, len(tools)) for _, t := range tools { - params := t.Function.ParametersMap() tool := anthropic.ToolParam{ Name: t.Function.Name, InputSchema: anthropic.ToolInputSchemaParam{ - Properties: params["properties"], + Properties: t.Function.Parameters["properties"], }, } if desc := t.Function.Description; desc != "" { tool.Description = anthropic.String(desc) } - - switch req := params["required"].(type) { - case []any: + if req, ok := t.Function.Parameters["required"].([]any); ok { required := make([]string, 0, len(req)) for _, r := range req { if s, ok := r.(string); ok { @@ -326,8 +325,6 @@ func translateTools(tools []ToolDefinition) []anthropic.ToolUnionParam { } } tool.InputSchema.Required = required - case []string: - tool.InputSchema.Required = append([]string(nil), req...) } result = append(result, anthropic.ToolUnionParam{OfTool: &tool}) } diff --git a/pkg/routing/session_key.go b/pkg/routing/session_key.go index 19e6dd390..fa08fdde3 100644 --- a/pkg/routing/session_key.go +++ b/pkg/routing/session_key.go @@ -42,11 +42,6 @@ func BuildAgentMainSessionKey(agentID string) string { return fmt.Sprintf("agent:%s:%s", NormalizeAgentID(agentID), DefaultMainKey) } -// BuildSubagentSessionKey returns "subagent:" for subagent sessions. -func BuildSubagentSessionKey(taskID string) string { - return fmt.Sprintf("subagent:%s", taskID) -} - // BuildAgentPeerSessionKey constructs a session key based on agent, channel, peer, and DM scope. func BuildAgentPeerSessionKey(params SessionKeyParams) string { agentID := NormalizeAgentID(params.AgentID) @@ -195,3 +190,8 @@ func resolveLinkedPeerID(identityLinks map[string][]string, channel, peerID stri } return "" } + +// BuildSubagentSessionKey returns "subagent:" for subagent sessions. +func BuildSubagentSessionKey(taskID string) string { + return fmt.Sprintf("subagent:%s", taskID) +} diff --git a/pkg/skills/clawhub_registry.go b/pkg/skills/clawhub_registry.go index 76e717e4b..bd4bed8fb 100644 --- a/pkg/skills/clawhub_registry.go +++ b/pkg/skills/clawhub_registry.go @@ -8,7 +8,6 @@ import ( "net/http" "net/url" "os" - "strconv" "time" "github.com/sipeed/picoclaw/pkg/utils" @@ -112,7 +111,7 @@ func (c *ClawHubRegistry) Search(ctx context.Context, query string, limit int) ( q := u.Query() q.Set("q", query) if limit > 0 { - q.Set("limit", strconv.Itoa(limit)) + q.Set("limit", fmt.Sprintf("%d", limit)) } u.RawQuery = q.Encode() diff --git a/pkg/skills/registry.go b/pkg/skills/registry.go index 5cc9ac0a9..45ae72253 100644 --- a/pkg/skills/registry.go +++ b/pkg/skills/registry.go @@ -180,7 +180,7 @@ func (rm *RegistryManager) SearchAll(ctx context.Context, query string, limit in close(resultsCh) }() - merged := make([]SearchResult, 0, len(regs)*limit) + var merged []SearchResult var lastErr error var anyRegistrySucceeded bool diff --git a/pkg/skills/search_cache.go b/pkg/skills/search_cache.go index 76a9147d2..1686e3f98 100644 --- a/pkg/skills/search_cache.go +++ b/pkg/skills/search_cache.go @@ -39,8 +39,8 @@ func NewSearchCache(maxEntries int, ttl time.Duration) *SearchCache { ttl = 5 * time.Minute } return &SearchCache{ - entries: make(map[string]*cacheEntry, maxEntries), - order: make([]string, 0, maxEntries), + entries: make(map[string]*cacheEntry), + order: make([]string, 0), maxEntries: maxEntries, ttl: ttl, } diff --git a/pkg/utils/media.go b/pkg/utils/media.go index 31d888f79..3e1c5d88e 100644 --- a/pkg/utils/media.go +++ b/pkg/utils/media.go @@ -14,13 +14,11 @@ import ( "github.com/sipeed/picoclaw/pkg/logger" ) -var ( - audioExtensions = []string{".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac", ".wma"} - audioTypes = []string{"audio/", "application/ogg", "application/x-ogg"} -) - // IsAudioFile checks if a file is an audio file based on its filename extension and content type. func IsAudioFile(filename, contentType string) bool { + audioExtensions := []string{".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac", ".wma"} + audioTypes := []string{"audio/", "application/ogg", "application/x-ogg"} + for _, ext := range audioExtensions { if strings.HasSuffix(strings.ToLower(filename), ext) { return true diff --git a/workspace/IDENTITY.md b/workspace/IDENTITY.md index dabb0e14b..20e3e49fa 100644 --- a/workspace/IDENTITY.md +++ b/workspace/IDENTITY.md @@ -6,9 +6,6 @@ PicoClaw 🦞 ## Description Ultra-lightweight personal AI assistant written in Go, inspired by nanobot. -## Version -0.1.0 - ## Purpose - Provide intelligent AI assistance with minimal resource usage - Support multiple LLM providers (OpenAI, Anthropic, Zhipu, etc.)