From ec63695956dfe18d8a1478cb18b89e7c93bfaccd Mon Sep 17 00:00:00 2001
From: stevef
Date: Sun, 19 Apr 2026 12:01:26 +0200
Subject: [PATCH] feat(freeride): integrate freeride changes from upstream
projects, excluding core config logic
---
.dockerignore | 2 +-
.github/workflows/build.yml | 2 +-
.github/workflows/create_dmg.yml | 27 +-
.github/workflows/nightly.yml | 18 +-
.github/workflows/pr.yml | 7 +-
.github/workflows/release.yml | 15 +-
.gitignore | 6 +-
.golangci.yaml | 205 +-
.goreleaser.yaml | 19 +-
CONTRIBUTING.md | 9 +-
Makefile | 97 +-
README.md | 88 +-
TEAMS_ID_MAPPING_ANALYSIS.md | 363 --
TEAMS_QUICK_REFERENCE.md | 315 --
assets/wechat.png | Bin 373987 -> 100337 bytes
cluster_config.json | 626 ---
cmd/membench/eval.go | 412 ++
cmd/membench/eval_llm.go | 346 ++
cmd/membench/eval_test.go | 182 +
cmd/membench/ingest.go | 85 +
cmd/membench/ingest_test.go | 79 +
cmd/membench/legacy_store.go | 34 +
cmd/membench/llm_client.go | 198 +
cmd/membench/locomo.go | 142 +
cmd/membench/locomo_test.go | 67 +
cmd/membench/main.go | 361 ++
cmd/membench/metrics.go | 227 ++
cmd/membench/metrics_test.go | 239 ++
cmd/picoclaw-launcher-tui/ui/channels.go | 6 +-
cmd/picoclaw/internal/agent/helpers.go | 4 +-
cmd/picoclaw/internal/auth/helpers.go | 16 +-
cmd/picoclaw/internal/auth/login.go | 8 +-
cmd/picoclaw/internal/auth/login_test.go | 1 +
cmd/picoclaw/internal/auth/wecom.go | 31 +-
cmd/picoclaw/internal/auth/wecom_test.go | 28 +-
cmd/picoclaw/internal/auth/weixin.go | 24 +-
cmd/picoclaw/internal/cliui/cliui.go | 147 +
cmd/picoclaw/internal/cliui/cliui_test.go | 180 +
cmd/picoclaw/internal/cliui/help_cmd.go | 298 ++
cmd/picoclaw/internal/cliui/help_error.go | 75 +
cmd/picoclaw/internal/cliui/onboard.go | 110 +
cmd/picoclaw/internal/cliui/status.go | 168 +
cmd/picoclaw/internal/cliui/version.go | 61 +
cmd/picoclaw/internal/gateway/command.go | 41 +-
cmd/picoclaw/internal/gateway/command_test.go | 34 +
cmd/picoclaw/internal/onboard/command.go | 7 +-
cmd/picoclaw/internal/onboard/command_test.go | 7 +-
cmd/picoclaw/internal/onboard/helpers.go | 62 +-
cmd/picoclaw/internal/onboard/purge.go | 58 -
cmd/picoclaw/internal/skills/command.go | 25 +-
cmd/picoclaw/internal/skills/helpers.go | 157 +-
cmd/picoclaw/internal/skills/helpers_test.go | 191 +
cmd/picoclaw/internal/skills/install.go | 15 +-
cmd/picoclaw/internal/skills/install_test.go | 6 +-
cmd/picoclaw/internal/skills/remove.go | 9 +-
cmd/picoclaw/internal/skills/remove_test.go | 2 +-
cmd/picoclaw/internal/status/helpers.go | 130 +-
cmd/picoclaw/internal/version/command.go | 11 +-
cmd/picoclaw/main.go | 87 +-
cmd/picoclaw/main_test.go | 9 +-
config/config.example.json | 71 +-
config/config.json.azure | 559 ---
docker/Dockerfile | 17 +-
docker/Dockerfile.full | 13 +-
docker/Dockerfile.goreleaser.launcher | 2 +-
docker/Dockerfile.heavy | 11 +-
docker/Dockerfile.rpi | 68 -
docker/docker-compose.yml | 7 +-
docs/README.md | 132 +
docs/api.md | 90 -
docs/architecture/README.md | 12 +
.../agent-refactor/README.md | 0
.../agent-refactor/context.md | 0
.../architecture/agent-refactor/loop-split.md | 86 +
docs/{ => architecture}/hooks/README.md | 63 +
docs/{ => architecture}/hooks/README.zh.md | 63 +
docs/architecture/hooks/hook-json-protocol.md | 568 +++
.../hooks/hook-json-protocol.zh.md | 568 +++
.../hooks/plugin-tool-injection.md | 587 +++
.../hooks/plugin-tool-injection.zh.md | 587 +++
docs/architecture/routing-system.md | 282 ++
docs/architecture/routing-system.zh.md | 281 ++
docs/architecture/session-system.md | 255 ++
docs/architecture/session-system.zh.md | 254 ++
docs/{ => architecture}/steering.md | 18 +-
docs/{ => architecture}/subturn.md | 18 +-
docs/channels/dingtalk/README.fr.md | 5 +-
docs/channels/dingtalk/README.ja.md | 5 +-
docs/channels/dingtalk/README.md | 3 +-
docs/channels/dingtalk/README.pt-br.md | 5 +-
docs/channels/dingtalk/README.vi.md | 5 +-
docs/channels/dingtalk/README.zh.md | 5 +-
docs/channels/discord/README.fr.md | 5 +-
docs/channels/discord/README.ja.md | 5 +-
docs/channels/discord/README.md | 3 +-
docs/channels/discord/README.pt-br.md | 5 +-
docs/channels/discord/README.vi.md | 5 +-
docs/channels/discord/README.zh.md | 5 +-
docs/channels/feishu/README.fr.md | 5 +-
docs/channels/feishu/README.ja.md | 5 +-
docs/channels/feishu/README.md | 3 +-
docs/channels/feishu/README.pt-br.md | 5 +-
docs/channels/feishu/README.vi.md | 5 +-
docs/channels/feishu/README.zh.md | 5 +-
docs/channels/line/README.fr.md | 5 +-
docs/channels/line/README.ja.md | 5 +-
docs/channels/line/README.md | 3 +-
docs/channels/line/README.pt-br.md | 5 +-
docs/channels/line/README.vi.md | 5 +-
docs/channels/line/README.zh.md | 5 +-
docs/channels/maixcam/README.fr.md | 5 +-
docs/channels/maixcam/README.ja.md | 5 +-
docs/channels/maixcam/README.md | 3 +-
docs/channels/maixcam/README.pt-br.md | 5 +-
docs/channels/maixcam/README.vi.md | 5 +-
docs/channels/maixcam/README.zh.md | 5 +-
docs/channels/matrix/README.fr.md | 5 +-
docs/channels/matrix/README.ja.md | 5 +-
docs/channels/matrix/README.md | 3 +-
docs/channels/matrix/README.pt-br.md | 5 +-
docs/channels/matrix/README.vi.md | 5 +-
docs/channels/matrix/README.zh.md | 5 +-
docs/channels/onebot/README.fr.md | 5 +-
docs/channels/onebot/README.ja.md | 5 +-
docs/channels/onebot/README.md | 3 +-
docs/channels/onebot/README.pt-br.md | 5 +-
docs/channels/onebot/README.vi.md | 5 +-
docs/channels/onebot/README.zh.md | 5 +-
docs/channels/qq/README.fr.md | 5 +-
docs/channels/qq/README.ja.md | 5 +-
docs/channels/qq/README.md | 3 +-
docs/channels/qq/README.pt-br.md | 5 +-
docs/channels/qq/README.vi.md | 5 +-
docs/channels/qq/README.zh.md | 5 +-
docs/channels/slack/README.fr.md | 5 +-
docs/channels/slack/README.ja.md | 5 +-
docs/channels/slack/README.md | 3 +-
docs/channels/slack/README.pt-br.md | 5 +-
docs/channels/slack/README.vi.md | 5 +-
docs/channels/slack/README.zh.md | 5 +-
docs/channels/telegram/README.fr.md | 8 +-
docs/channels/telegram/README.ja.md | 8 +-
docs/channels/telegram/README.md | 8 +-
docs/channels/telegram/README.pt-br.md | 8 +-
docs/channels/telegram/README.vi.md | 8 +-
docs/channels/telegram/README.zh.md | 10 +-
docs/channels/vk/README.md | 14 +-
docs/channels/wecom/README.fr.md | 5 +-
docs/channels/wecom/README.ja.md | 5 +-
docs/channels/wecom/README.md | 3 +-
docs/channels/wecom/README.pt-br.md | 5 +-
docs/channels/wecom/README.vi.md | 5 +-
docs/channels/wecom/README.zh.md | 5 +-
docs/channels/weixin/README.md | 3 +-
docs/channels/weixin/README.zh.md | 3 +-
docs/design/steering-spec.md | 63 +-
docs/examples/azure-config.json | 568 ---
docs/examples/config.json.azure | 569 ---
.../ANTIGRAVITY_USAGE.fr.md} | 2 +-
.../ANTIGRAVITY_USAGE.ja.md} | 2 +-
docs/{ => guides}/ANTIGRAVITY_USAGE.md | 0
.../ANTIGRAVITY_USAGE.pt-br.md} | 2 +-
.../ANTIGRAVITY_USAGE.vi.md} | 2 +-
.../ANTIGRAVITY_USAGE.zh.md} | 2 +-
docs/guides/README.md | 15 +
.../chat-apps.md => guides/chat-apps.fr.md} | 66 +-
.../chat-apps.md => guides/chat-apps.ja.md} | 60 +-
docs/{ => guides}/chat-apps.md | 74 +-
.../chat-apps.md => guides/chat-apps.ms.md} | 48 +-
.../chat-apps.pt-br.md} | 73 +-
.../chat-apps.md => guides/chat-apps.vi.md} | 73 +-
.../chat-apps.md => guides/chat-apps.zh.md} | 53 +-
.../configuration.fr.md} | 32 +-
.../configuration.ja.md} | 32 +-
docs/{ => guides}/configuration.md | 329 +-
.../configuration.ms.md} | 24 +-
.../configuration.pt-br.md} | 32 +-
.../configuration.vi.md} | 32 +-
.../configuration.zh.md} | 99 +-
docs/{fr/docker.md => guides/docker.fr.md} | 2 +-
docs/{ja/docker.md => guides/docker.ja.md} | 4 +-
docs/{ => guides}/docker.md | 15 -
docs/{my/docker.md => guides/docker.ms.md} | 2 +-
.../docker.md => guides/docker.pt-br.md} | 2 +-
docs/{vi/docker.md => guides/docker.vi.md} | 2 +-
docs/{zh/docker.md => guides/docker.zh.md} | 4 +-
docs/{ => guides}/freeride.md | 13 +-
.../hardware-compatibility.fr.md} | 4 +-
.../hardware-compatibility.ja.md} | 4 +-
docs/{ => guides}/hardware-compatibility.md | 2 +-
.../hardware-compatibility.pt-br.md} | 4 +-
.../hardware-compatibility.vi.md} | 4 +-
.../hardware-compatibility.zh.md} | 4 +-
.../providers.md => guides/providers.fr.md} | 13 +-
.../providers.md => guides/providers.ja.md} | 14 +-
docs/{ => guides}/providers.md | 16 +-
.../providers.pt-br.md} | 13 +-
.../providers.md => guides/providers.vi.md} | 13 +-
.../providers.md => guides/providers.zh.md} | 15 +-
docs/guides/routing-guide.md | 331 ++
docs/guides/routing-guide.zh.md | 331 ++
docs/guides/session-guide.md | 273 ++
docs/guides/session-guide.zh.md | 273 ++
.../spawn-tasks.fr.md} | 2 +-
.../spawn-tasks.ja.md} | 2 +-
docs/{ => guides}/spawn-tasks.md | 0
.../spawn-tasks.ms.md} | 2 +-
.../spawn-tasks.pt-br.md} | 2 +-
.../spawn-tasks.vi.md} | 2 +-
.../spawn-tasks.zh.md} | 2 +-
docs/migration/README.md | 5 +
docs/migration/model-list-migration.md | 2 +-
docs/operations/README.md | 6 +
docs/{fr/debug.md => operations/debug.fr.md} | 2 +-
docs/{ja/debug.md => operations/debug.ja.md} | 2 +-
docs/{ => operations}/debug.md | 0
docs/{my/debug.md => operations/debug.ms.md} | 0
.../debug.md => operations/debug.pt-br.md} | 2 +-
docs/{vi/debug.md => operations/debug.vi.md} | 2 +-
docs/{zh/debug.md => operations/debug.zh.md} | 2 +-
.../troubleshooting.fr.md} | 2 +-
.../troubleshooting.ja.md} | 2 +-
docs/{ => operations}/troubleshooting.md | 0
.../troubleshooting.ms.md} | 0
.../troubleshooting.pt-br.md} | 2 +-
.../troubleshooting.vi.md} | 2 +-
.../troubleshooting.zh.md} | 2 +-
.../project/CONTRIBUTING.zh.md | 2 +-
README.fr.md => docs/project/README.fr.md | 125 +-
README.id.md => docs/project/README.id.md | 121 +-
README.it.md => docs/project/README.it.md | 120 +-
README.ja.md => docs/project/README.ja.md | 121 +-
docs/project/README.ko.md | 634 +++
README.my.md => docs/project/README.ms.md | 114 +-
.../project/README.pt-br.md | 121 +-
README.vi.md => docs/project/README.vi.md | 125 +-
README.zh.md => docs/project/README.zh.md | 137 +-
docs/reference/README.md | 8 +
docs/{ => reference}/config-versioning.md | 60 +-
docs/{ => reference}/cron.md | 0
docs/{ => reference}/rate-limiting.md | 0
.../tools_configuration.fr.md} | 5 +-
.../tools_configuration.ja.md} | 5 +-
docs/{ => reference}/tools_configuration.md | 100 +-
.../tools_configuration.pt-br.md} | 5 +-
.../tools_configuration.vi.md} | 5 +-
.../tools_configuration.zh.md} | 33 +-
.../ANTIGRAVITY_AUTH.fr.md} | 2 +-
.../ANTIGRAVITY_AUTH.ja.md} | 2 +-
docs/{ => security}/ANTIGRAVITY_AUTH.md | 0
.../ANTIGRAVITY_AUTH.pt-br.md} | 2 +-
.../ANTIGRAVITY_AUTH.vi.md} | 2 +-
.../ANTIGRAVITY_AUTH.zh.md} | 2 +-
docs/security/README.md | 8 +
.../credential_encryption.fr.md} | 2 +-
.../credential_encryption.ja.md} | 2 +-
docs/{ => security}/credential_encryption.md | 0
.../credential_encryption.pt-br.md} | 2 +-
.../credential_encryption.vi.md} | 2 +-
.../credential_encryption.zh.md} | 2 +-
docs/{ => security}/security_configuration.md | 84 +-
.../sensitive_data_filtering.md | 2 +-
.../sensitive_data_filtering.zh.md} | 4 +-
go.mod | 61 +-
go.sum | 125 +-
k3s/README.md | 64 -
k3s/config.json | 703 ----
k3s/config.json.lockeddown | 684 ----
k3s/configmap.yaml | 263 --
k3s/cron.json | 25 -
k3s/deployment.yaml | 80 -
k3s/pvc.yaml | 11 -
k3s/secrets.yaml | 12 -
k3s/service.yaml | 13 -
logs/gateway.log | 2 -
logs/gateway_panic.log | 26 -
pkg/agent/context.go | 124 +-
pkg/agent/context_budget.go | 96 +-
pkg/agent/context_budget_test.go | 38 +-
pkg/agent/context_cache_test.go | 30 +-
pkg/agent/context_legacy.go | 16 +-
pkg/agent/context_manager.go | 5 +
pkg/agent/context_manager_test.go | 3 +
pkg/agent/context_seahorse.go | 282 ++
pkg/agent/context_seahorse_test.go | 1086 +++++
pkg/agent/context_seahorse_unsupported.go | 20 +
pkg/agent/context_test.go | 41 +
pkg/agent/definition.go | 20 +-
pkg/agent/definition_test.go | 16 +-
pkg/agent/dispatch_request.go | 147 +
pkg/agent/dispatch_request_test.go | 135 +
pkg/agent/eventbus_test.go | 48 +-
pkg/agent/events.go | 6 +-
pkg/agent/hook_process.go | 13 +-
pkg/agent/hook_process_test.go | 133 +-
pkg/agent/hooks.go | 47 +-
pkg/agent/hooks_test.go | 680 +++-
pkg/agent/instance.go | 113 +-
pkg/agent/instance_test.go | 239 +-
pkg/agent/isolation_tools_test.go | 234 --
pkg/agent/llm_media.go | 60 +
pkg/agent/loop.go | 3481 +----------------
pkg/agent/loop_command.go | 266 ++
pkg/agent/loop_event.go | 206 +
pkg/agent/loop_init.go | 359 ++
pkg/agent/loop_inject.go | 103 +
pkg/agent/loop_mcp.go | 213 +-
pkg/agent/loop_mcp_test.go | 60 +
pkg/agent/loop_message.go | 302 ++
pkg/agent/loop_outbound.go | 165 +
pkg/agent/loop_security_test.go | 253 --
pkg/agent/loop_steering.go | 96 +
pkg/agent/loop_test.go | 1825 ++++++++-
pkg/agent/loop_transcribe.go | 109 +
pkg/agent/loop_turn.go | 1879 +++++++++
pkg/agent/loop_utils.go | 482 +++
pkg/agent/multiuser_mcp_test.go | 55 -
pkg/agent/registry.go | 16 +-
pkg/agent/secret.txt | 1 -
pkg/agent/steering.go | 81 +-
pkg/agent/steering_test.go | 202 +-
pkg/agent/subturn.go | 26 +-
pkg/agent/turn.go | 55 +-
pkg/agent/turn_context.go | 92 +
pkg/audio/asr/{README_zh.md => README.zh.md} | 0
pkg/audio/asr/agent.go | 19 +-
pkg/audio/asr/agent_test.go | 4 +-
pkg/audio/tts/{README_zh.md => README.zh.md} | 0
pkg/auth/oauth.go | 135 +-
pkg/auth/oauth_test.go | 116 +
pkg/bus/bus.go | 20 +-
pkg/bus/bus_test.go | 453 ++-
pkg/bus/inbound_context.go | 81 +
pkg/bus/outbound_context.go | 84 +
pkg/bus/types.go | 89 +-
pkg/channels/README.md | 115 +-
pkg/channels/README.zh.md | 114 +-
pkg/channels/base.go | 74 +-
pkg/channels/base_test.go | 56 +
pkg/channels/dingtalk/dingtalk.go | 46 +-
pkg/channels/dingtalk/dingtalk_test.go | 33 +-
pkg/channels/dingtalk/init.go | 25 +-
pkg/channels/discord/discord.go | 44 +-
pkg/channels/discord/init.go | 26 +-
pkg/channels/feishu/feishu_32.go | 2 +-
pkg/channels/feishu/feishu_64.go | 80 +-
pkg/channels/feishu/feishu_reply.go | 298 ++
pkg/channels/feishu/feishu_reply_test.go | 229 ++
pkg/channels/feishu/init.go | 18 +-
pkg/channels/http/http.go | 45 -
pkg/channels/irc/handler.go | 23 +-
pkg/channels/irc/init.go | 31 +-
pkg/channels/irc/irc.go | 14 +-
pkg/channels/irc/irc_test.go | 15 +-
pkg/channels/line/init.go | 18 +-
pkg/channels/line/line.go | 44 +-
pkg/channels/line/line_test.go | 6 +-
pkg/channels/maixcam/init.go | 18 +-
pkg/channels/maixcam/maixcam.go | 32 +-
pkg/channels/manager.go | 321 +-
pkg/channels/manager_channel.go | 166 +-
pkg/channels/manager_channel_test.go | 120 +-
pkg/channels/manager_test.go | 195 +-
pkg/channels/matrix/init.go | 37 +-
pkg/channels/matrix/matrix.go | 50 +-
pkg/channels/matrix/matrix_test.go | 8 +-
pkg/channels/onebot/init.go | 18 +-
pkg/channels/onebot/onebot.go | 39 +-
pkg/channels/pico/client.go | 34 +-
pkg/channels/pico/client_test.go | 92 +-
pkg/channels/pico/init.go | 50 +-
pkg/channels/pico/pico.go | 96 +-
pkg/channels/pico/pico_test.go | 5 +-
pkg/channels/pico/protocol.go | 10 +
pkg/channels/qq/init.go | 18 +-
pkg/channels/qq/qq.go | 67 +-
pkg/channels/qq/qq_test.go | 17 +-
pkg/channels/registry.go | 48 +-
pkg/channels/slack/init.go | 18 +-
pkg/channels/slack/slack.go | 125 +-
pkg/channels/slack/slack_test.go | 48 +-
pkg/channels/teams_webhook/init.go | 32 +
pkg/channels/teams_webhook/teams_webhook.go | 425 ++
.../teams_webhook/teams_webhook_test.go | 582 +++
pkg/channels/telegram/init.go | 18 +-
pkg/channels/telegram/telegram.go | 92 +-
pkg/channels/telegram/telegram_test.go | 71 +-
pkg/channels/vk/init.go | 13 +-
pkg/channels/vk/vk.go | 69 +-
pkg/channels/vk/vk_test.go | 116 +-
pkg/channels/wecom/init.go | 18 +-
pkg/channels/wecom/media.go | 4 +-
pkg/channels/wecom/wecom.go | 24 +-
pkg/channels/wecom/wecom_test.go | 13 +-
pkg/channels/weixin/state.go | 6 +-
pkg/channels/weixin/weixin.go | 57 +-
pkg/channels/weixin/weixin_test.go | 10 +-
pkg/channels/whatsapp/init.go | 18 +-
pkg/channels/whatsapp/whatsapp.go | 34 +-
.../whatsapp/whatsapp_command_test.go | 2 +-
pkg/channels/whatsapp_native/init.go | 31 +-
.../whatsapp_native/whatsapp_command_test.go | 2 +-
.../whatsapp_native/whatsapp_native.go | 21 +-
.../whatsapp_native/whatsapp_native_stub.go | 9 +-
pkg/commands/builtin.go | 1 +
pkg/commands/builtin_test.go | 76 +
pkg/commands/cmd_btw.go | 51 +
pkg/commands/runtime.go | 7 +-
pkg/config/config_channel.go | 704 ++++
pkg/config/config_channel_test.go | 916 +++++
pkg/config/config_old.go | 1581 +++-----
pkg/config/config_struct.go | 426 +-
pkg/config/config_struct_test.go | 259 ++
pkg/config/config_test.go | 607 ++-
pkg/config/defaults.go | 243 +-
pkg/config/envkeys.go | 2 +-
pkg/config/gateway.go | 37 +-
pkg/config/gateway_host_env_test.go | 98 +
pkg/config/legacy_bindings.go | 267 ++
pkg/config/migration.go | 879 ++---
pkg/config/migration_integration_test.go | 535 ++-
pkg/config/migration_test.go | 923 ++---
pkg/config/model_config_test.go | 36 -
pkg/config/security.go | 160 +-
pkg/config/security_integration_test.go | 288 +-
pkg/config/security_test.go | 113 +-
pkg/credential/credential.go | 4 +-
pkg/devices/service.go | 3 +-
pkg/gateway/channel_matrix.go | 2 +-
pkg/gateway/gateway.go | 139 +-
pkg/gateway/gateway_test.go | 108 +
pkg/gateway/listen.go | 21 +
pkg/gateway/listen_test.go | 130 +
pkg/health/server.go | 456 +--
pkg/health/server_test.go | 86 +-
pkg/heartbeat/service.go | 3 +-
pkg/isolation/README.md | 238 ++
pkg/isolation/README.zh.md | 238 ++
pkg/isolation/platform_linux.go | 264 ++
pkg/isolation/platform_linux_test.go | 148 +
pkg/isolation/platform_other.go | 22 +
pkg/isolation/platform_windows.go | 217 +
pkg/isolation/runtime.go | 443 +++
pkg/isolation/runtime_test.go | 248 ++
pkg/logger/panic.go | 2 +-
pkg/logger/panic_unix.go | 7 +-
pkg/mcp/isolated_command_transport.go | 226 ++
pkg/mcp/manager.go | 3 +-
pkg/memory/jsonl.go | 374 +-
pkg/memory/jsonl_test.go | 138 +
pkg/memory/store.go | 3 +
.../sources/openclaw/openclaw_config.go | 268 +-
.../sources/openclaw/openclaw_config_test.go | 13 +-
pkg/netbind/netbind.go | 606 +++
pkg/netbind/netbind_test.go | 280 ++
pkg/netbind/socket_v6only_unix.go | 25 +
pkg/netbind/socket_v6only_windows.go | 25 +
pkg/pid/pidfile.go | 39 +-
pkg/pid/pidfile_test.go | 50 +
pkg/pid/pidfile_unix.go | 9 +-
pkg/pid/pidfile_windows.go | 8 +-
.../{ => cli}/claude_cli_provider.go | 8 +-
.../claude_cli_provider_integration_test.go | 2 +-
.../{ => cli}/claude_cli_provider_test.go | 81 +-
.../{ => cli}/codex_cli_credentials.go | 2 +-
.../{ => cli}/codex_cli_credentials_test.go | 2 +-
pkg/providers/{ => cli}/codex_cli_provider.go | 8 +-
.../codex_cli_provider_integration_test.go | 2 +-
.../{ => cli}/codex_cli_provider_test.go | 12 +-
.../{ => cli}/github_copilot_provider.go | 2 +-
pkg/providers/{ => cli}/tool_call_extract.go | 2 +-
pkg/providers/{ => cli}/toolcall_utils.go | 8 +-
pkg/providers/cli/types.go | 28 +
pkg/providers/cli_facade.go | 40 +
pkg/providers/cli_factory_test.go | 99 +
pkg/providers/common/common.go | 28 +-
pkg/providers/common/common_test.go | 32 +
pkg/providers/error_classifier.go | 77 +
pkg/providers/error_classifier_test.go | 137 +
pkg/providers/facade_compat_test.go | 44 +
pkg/providers/factory_provider.go | 35 +-
pkg/providers/factory_provider_test.go | 99 +
pkg/providers/fallback_test.go | 69 +
pkg/providers/httpapi/gemini_helpers.go | 139 +
pkg/providers/httpapi/gemini_provider.go | 796 ++++
pkg/providers/httpapi/gemini_provider_test.go | 763 ++++
pkg/providers/{ => httpapi}/http_provider.go | 27 +-
pkg/providers/httpapi/types.go | 43 +
pkg/providers/httpapi_facade.go | 46 +
.../{ => oauth}/antigravity_provider.go | 19 +-
.../{ => oauth}/antigravity_provider_test.go | 26 +-
pkg/providers/{ => oauth}/claude_provider.go | 5 +-
.../{ => oauth}/claude_provider_test.go | 2 +-
pkg/providers/{ => oauth}/codex_provider.go | 4 +-
.../{ => oauth}/codex_provider_test.go | 2 +-
pkg/providers/oauth/types.go | 32 +
pkg/providers/oauth_facade.go | 60 +
pkg/providers/openai_compat/provider.go | 117 +-
pkg/providers/openai_compat/provider_test.go | 111 +-
pkg/providers/types.go | 1 +
pkg/routing/route.go | 429 +-
pkg/routing/route_test.go | 316 +-
pkg/routing/session_key.go | 192 -
pkg/routing/session_key_test.go | 207 -
pkg/seahorse/compact_until_under_test.go | 58 +
pkg/seahorse/fts5_sanitize.go | 70 +
pkg/seahorse/fts5_sanitize_test.go | 237 ++
pkg/seahorse/parts_roundtrip_test.go | 144 +
pkg/seahorse/schema.go | 194 +
pkg/seahorse/schema_test.go | 301 ++
pkg/seahorse/short_assembler.go | 261 ++
pkg/seahorse/short_assembler_test.go | 536 +++
pkg/seahorse/short_bench_test.go | 336 ++
pkg/seahorse/short_compaction.go | 898 +++++
pkg/seahorse/short_compaction_test.go | 974 +++++
pkg/seahorse/short_constants.go | 30 +
pkg/seahorse/short_engine.go | 581 +++
pkg/seahorse/short_engine_test.go | 1448 +++++++
pkg/seahorse/short_retrieval.go | 212 +
pkg/seahorse/short_retrieval_test.go | 362 ++
pkg/seahorse/store.go | 1593 ++++++++
pkg/seahorse/store_test.go | 1338 +++++++
pkg/seahorse/tool_expand.go | 129 +
pkg/seahorse/tool_expand_test.go | 136 +
pkg/seahorse/tool_grep.go | 172 +
pkg/seahorse/tool_grep_test.go | 72 +
pkg/seahorse/types.go | 161 +
pkg/seahorse/types_test.go | 54 +
pkg/security/behavior/monitor.go | 97 -
pkg/security/behavior/monitor_test.go | 81 -
pkg/security/canary/hook.go | 80 -
pkg/security/canary/hook_test.go | 64 -
pkg/security/init.go | 58 -
pkg/security/ipia/detector.go | 70 -
pkg/security/ipia/detector_test.go | 60 -
pkg/security/pii/redactor.go | 205 -
pkg/security/pii/redactor_test.go | 66 -
pkg/security/policy/checker.go | 90 -
pkg/security/policy/checker_test.go | 51 -
pkg/security/proof_test.go | 205 -
pkg/session/allocator.go | 213 +
pkg/session/allocator_test.go | 160 +
pkg/session/jsonl_backend.go | 111 +
pkg/session/jsonl_backend_test.go | 125 +
pkg/session/key.go | 205 +
pkg/session/key_test.go | 100 +
pkg/session/manager.go | 10 +
pkg/session/scope.go | 32 +
pkg/session/session_store.go | 2 +
pkg/skills/clawhub_registry.go | 53 +
pkg/skills/config_bridge.go | 136 +
pkg/skills/github_registry.go | 305 ++
pkg/skills/github_registry_test.go | 218 ++
pkg/skills/installer.go | 417 +-
pkg/skills/installer_test.go | 296 ++
pkg/skills/loader.go | 92 +-
pkg/skills/loader_test.go | 61 +-
pkg/skills/provider_factory.go | 33 +
pkg/skills/registry.go | 73 +-
pkg/skills/registry_test.go | 77 +
pkg/tokenizer/estimator.go | 91 +
pkg/tools/cron.go | 14 +-
pkg/tools/cron_test.go | 6 +-
pkg/tools/facade_compat_test.go | 15 +
pkg/tools/{ => fs}/edit.go | 34 +-
pkg/tools/{ => fs}/edit_test.go | 32 +-
pkg/tools/{ => fs}/filesystem.go | 162 +-
pkg/tools/{ => fs}/filesystem_test.go | 285 +-
pkg/tools/{ => fs}/load_image.go | 2 +-
pkg/tools/{ => fs}/load_image_test.go | 25 +-
pkg/tools/{ => fs}/send_file.go | 20 +-
pkg/tools/{ => fs}/send_file_test.go | 2 +-
pkg/tools/fs/shared.go | 37 +
pkg/tools/fs_facade.go | 100 +
pkg/tools/fs_registry_compat_test.go | 46 +
pkg/tools/{ => hardware}/i2c.go | 14 +-
pkg/tools/{ => hardware}/i2c_linux.go | 2 +-
pkg/tools/{ => hardware}/i2c_other.go | 2 +-
pkg/tools/hardware/shared.go | 13 +
pkg/tools/{ => hardware}/spi.go | 6 +-
pkg/tools/{ => hardware}/spi_linux.go | 2 +-
pkg/tools/{ => hardware}/spi_other.go | 2 +-
pkg/tools/hardware_facade.go | 16 +
.../main.go => pkg/tools/identifier_compat.go | 22 +-
pkg/tools/integration/helpers.go | 134 +
pkg/tools/{ => integration}/mcp_tool.go | 2 +-
pkg/tools/{ => integration}/mcp_tool_test.go | 2 +-
pkg/tools/{ => integration}/message.go | 64 +-
pkg/tools/{ => integration}/message_test.go | 56 +-
pkg/tools/{ => integration}/reaction.go | 2 +-
pkg/tools/{ => integration}/reaction_test.go | 2 +-
pkg/tools/integration/shared.go | 77 +
pkg/tools/{ => integration}/skills_install.go | 224 +-
pkg/tools/integration/skills_install_test.go | 423 ++
pkg/tools/{ => integration}/skills_search.go | 28 +-
.../{ => integration}/skills_search_test.go | 14 +-
pkg/tools/{ => integration}/tts_send.go | 2 +-
pkg/tools/{ => integration}/web.go | 510 ++-
pkg/tools/{ => integration}/web_test.go | 273 +-
pkg/tools/integration_facade.go | 101 +
pkg/tools/load_image_compat_test.go | 29 +
pkg/tools/path_compat.go | 19 +
pkg/tools/registry.go | 59 +-
pkg/tools/registry_test.go | 39 -
pkg/tools/search_tool.go | 2 +-
pkg/tools/session.go | 8 -
pkg/tools/{ => shared}/base.go | 41 +-
pkg/tools/{ => shared}/result.go | 14 +-
pkg/tools/{ => shared}/types.go | 10 +-
pkg/tools/shared_facade.go | 110 +
pkg/tools/shell.go | 71 +-
pkg/tools/skills_install_test.go | 188 -
pkg/tools/validate.go | 3 -
pkg/updater/updater.go | 16 +-
pkg/updater/updater_test.go | 426 +-
pkg/utils/http_retry.go | 2 +-
pkg/utils/tool_feedback.go | 9 +
pkg/utils/tool_feedback_test.go | 11 +
scratch/json/main.go | 24 -
scratch/match/main.go | 15 -
scripts/lint-docs.sh | 219 ++
web/Makefile | 43 +-
web/README.md | 6 +-
web/backend/api/auth.go | 215 +-
web/backend/api/auth_test.go | 86 +-
web/backend/api/channels.go | 221 +-
web/backend/api/channels_test.go | 114 +-
web/backend/api/config.go | 293 +-
web/backend/api/config_test.go | 394 +-
web/backend/api/gateway.go | 248 +-
web/backend/api/gateway_host.go | 20 +-
web/backend/api/gateway_host_test.go | 83 +-
web/backend/api/gateway_test.go | 487 ++-
web/backend/api/model_status_test.go | 4 +-
web/backend/api/models.go | 46 +-
web/backend/api/models_test.go | 106 +
web/backend/api/pico.go | 88 +-
web/backend/api/pico_test.go | 341 +-
web/backend/api/router.go | 16 +
web/backend/api/session.go | 491 ++-
web/backend/api/session_test.go | 406 +-
web/backend/api/skills.go | 190 +-
web/backend/api/skills_test.go | 469 ++-
web/backend/api/tools.go | 358 ++
web/backend/api/tools_test.go | 217 +
web/backend/api/ui.go | 27 +
web/backend/api/ui_test.go | 48 +
web/backend/api/version.go | 2 +-
web/backend/api/wecom.go | 18 +-
web/backend/api/weixin.go | 23 +-
web/backend/api/weixin_test.go | 12 +-
web/backend/app_runtime.go | 38 +-
web/backend/dashboardauth/platform.go | 7 +
web/backend/dashboardauth/sql.go | 24 +
web/backend/dashboardauth/store.go | 96 +
.../dashboardauth/store_unsupported.go | 60 +
web/backend/i18n.go | 6 -
web/backend/launcherconfig/config.go | 8 +-
web/backend/main.go | 422 +-
web/backend/main_test.go | 363 ++
.../middleware/launcher_dashboard_auth.go | 4 +-
web/backend/systray.go | 15 +-
web/backend/systray_stub_nocgo.go | 2 +-
web/backend/tray_offers_copy.go | 5 -
web/backend/tray_offers_copy_stub.go | 5 -
web/backend/utils/runtime.go | 88 +-
web/frontend/package.json | 23 +-
web/frontend/pnpm-lock.yaml | 1842 +++++----
web/frontend/src/api/http.ts | 12 +-
web/frontend/src/api/launcher-auth.ts | 45 +-
web/frontend/src/api/models.ts | 1 +
web/frontend/src/api/tools.ts | 39 +
web/frontend/src/app-providers.tsx | 13 +
.../agent/hub/market-skill-card.tsx | 62 +-
.../components/agent/skills/detail-sheet.tsx | 5 +-
.../agent/tools/tool-library-tab.tsx | 245 ++
.../agent/tools/tool-status-badge.tsx | 28 +
.../src/components/agent/tools/tools-page.tsx | 332 +-
.../src/components/agent/tools/tools-tabs.tsx | 56 +
.../src/components/agent/tools/types.ts | 9 +
.../components/agent/tools/use-tools-page.ts | 194 +
.../tools/web-search-general-settings.tsx | 139 +
.../tools/web-search-provider-settings.tsx | 253 ++
.../components/agent/tools/web-search-tab.tsx | 109 +
web/frontend/src/components/app-header.tsx | 127 +-
.../channels/channel-config-page.tsx | 31 +-
.../channels/channel-forms/wecom-form.tsx | 3 +-
.../src/components/chat/assistant-message.tsx | 41 +-
.../src/components/chat/chat-composer.tsx | 53 +-
.../src/components/chat/chat-page.tsx | 79 +-
.../src/components/models/add-model-sheet.tsx | 17 +
.../components/models/edit-model-sheet.tsx | 20 +
.../src/components/models/model-card.tsx | 115 +-
web/frontend/src/components/shared-form.tsx | 5 +-
web/frontend/src/features/chat/controller.ts | 13 +-
web/frontend/src/features/chat/history.ts | 3 +-
web/frontend/src/features/chat/protocol.ts | 24 +-
web/frontend/src/hooks/use-gateway.ts | 21 +-
web/frontend/src/hooks/use-highlight-theme.ts | 70 +
web/frontend/src/i18n/index.ts | 10 +
web/frontend/src/i18n/locales/en.json | 106 +-
web/frontend/src/i18n/locales/zh.json | 100 +-
web/frontend/src/lib/launcher-login-path.ts | 9 +
web/frontend/src/main.tsx | 9 +-
web/frontend/src/routeTree.gen.ts | 21 +
web/frontend/src/routes/__root.tsx | 78 +-
web/frontend/src/routes/launcher-login.tsx | 57 +-
web/frontend/src/routes/launcher-setup.tsx | 146 +
web/frontend/src/store/chat.ts | 3 +
web/frontend/src/store/gateway.ts | 11 +-
workspace/HEARTBEAT.md | 22 -
workspace/cron/jobs.json | 4 -
workspace/skills/freeride/SKILL.md | 17 +
713 files changed, 63316 insertions(+), 21491 deletions(-)
delete mode 100644 TEAMS_ID_MAPPING_ANALYSIS.md
delete mode 100644 TEAMS_QUICK_REFERENCE.md
delete mode 100644 cluster_config.json
create mode 100644 cmd/membench/eval.go
create mode 100644 cmd/membench/eval_llm.go
create mode 100644 cmd/membench/eval_test.go
create mode 100644 cmd/membench/ingest.go
create mode 100644 cmd/membench/ingest_test.go
create mode 100644 cmd/membench/legacy_store.go
create mode 100644 cmd/membench/llm_client.go
create mode 100644 cmd/membench/locomo.go
create mode 100644 cmd/membench/locomo_test.go
create mode 100644 cmd/membench/main.go
create mode 100644 cmd/membench/metrics.go
create mode 100644 cmd/membench/metrics_test.go
create mode 100644 cmd/picoclaw/internal/cliui/cliui.go
create mode 100644 cmd/picoclaw/internal/cliui/cliui_test.go
create mode 100644 cmd/picoclaw/internal/cliui/help_cmd.go
create mode 100644 cmd/picoclaw/internal/cliui/help_error.go
create mode 100644 cmd/picoclaw/internal/cliui/onboard.go
create mode 100644 cmd/picoclaw/internal/cliui/status.go
create mode 100644 cmd/picoclaw/internal/cliui/version.go
delete mode 100644 cmd/picoclaw/internal/onboard/purge.go
create mode 100644 cmd/picoclaw/internal/skills/helpers_test.go
delete mode 100644 config/config.json.azure
delete mode 100644 docker/Dockerfile.rpi
create mode 100644 docs/README.md
delete mode 100644 docs/api.md
create mode 100644 docs/architecture/README.md
rename docs/{ => architecture}/agent-refactor/README.md (100%)
rename docs/{ => architecture}/agent-refactor/context.md (100%)
create mode 100644 docs/architecture/agent-refactor/loop-split.md
rename docs/{ => architecture}/hooks/README.md (89%)
rename docs/{ => architecture}/hooks/README.zh.md (90%)
create mode 100644 docs/architecture/hooks/hook-json-protocol.md
create mode 100644 docs/architecture/hooks/hook-json-protocol.zh.md
create mode 100644 docs/architecture/hooks/plugin-tool-injection.md
create mode 100644 docs/architecture/hooks/plugin-tool-injection.zh.md
create mode 100644 docs/architecture/routing-system.md
create mode 100644 docs/architecture/routing-system.zh.md
create mode 100644 docs/architecture/session-system.md
create mode 100644 docs/architecture/session-system.zh.md
rename docs/{ => architecture}/steering.md (86%)
rename docs/{ => architecture}/subturn.md (85%)
delete mode 100644 docs/examples/azure-config.json
delete mode 100644 docs/examples/config.json.azure
rename docs/{fr/ANTIGRAVITY_USAGE.md => guides/ANTIGRAVITY_USAGE.fr.md} (98%)
rename docs/{ja/ANTIGRAVITY_USAGE.md => guides/ANTIGRAVITY_USAGE.ja.md} (98%)
rename docs/{ => guides}/ANTIGRAVITY_USAGE.md (100%)
rename docs/{pt-br/ANTIGRAVITY_USAGE.md => guides/ANTIGRAVITY_USAGE.pt-br.md} (98%)
rename docs/{vi/ANTIGRAVITY_USAGE.md => guides/ANTIGRAVITY_USAGE.vi.md} (98%)
rename docs/{zh/ANTIGRAVITY_USAGE.md => guides/ANTIGRAVITY_USAGE.zh.md} (98%)
create mode 100644 docs/guides/README.md
rename docs/{fr/chat-apps.md => guides/chat-apps.fr.md} (92%)
rename docs/{ja/chat-apps.md => guides/chat-apps.ja.md} (94%)
rename docs/{ => guides}/chat-apps.md (84%)
rename docs/{my/chat-apps.md => guides/chat-apps.ms.md} (90%)
rename docs/{pt-br/chat-apps.md => guides/chat-apps.pt-br.md} (92%)
rename docs/{vi/chat-apps.md => guides/chat-apps.vi.md} (92%)
rename docs/{zh/chat-apps.md => guides/chat-apps.zh.md} (93%)
rename docs/{fr/configuration.md => guides/configuration.fr.md} (91%)
rename docs/{ja/configuration.md => guides/configuration.ja.md} (91%)
rename docs/{ => guides}/configuration.md (75%)
rename docs/{my/configuration.md => guides/configuration.ms.md} (90%)
rename docs/{pt-br/configuration.md => guides/configuration.pt-br.md} (92%)
rename docs/{vi/configuration.md => guides/configuration.vi.md} (92%)
rename docs/{zh/configuration.md => guides/configuration.zh.md} (88%)
rename docs/{fr/docker.md => guides/docker.fr.md} (99%)
rename docs/{ja/docker.md => guides/docker.ja.md} (97%)
rename docs/{ => guides}/docker.md (87%)
rename docs/{my/docker.md => guides/docker.ms.md} (99%)
rename docs/{pt-br/docker.md => guides/docker.pt-br.md} (99%)
rename docs/{vi/docker.md => guides/docker.vi.md} (99%)
rename docs/{zh/docker.md => guides/docker.zh.md} (97%)
rename docs/{ => guides}/freeride.md (89%)
rename docs/{fr/hardware-compatibility.md => guides/hardware-compatibility.fr.md} (98%)
rename docs/{ja/hardware-compatibility.md => guides/hardware-compatibility.ja.md} (98%)
rename docs/{ => guides}/hardware-compatibility.md (98%)
rename docs/{pt-br/hardware-compatibility.md => guides/hardware-compatibility.pt-br.md} (97%)
rename docs/{vi/hardware-compatibility.md => guides/hardware-compatibility.vi.md} (97%)
rename docs/{zh/hardware-compatibility.md => guides/hardware-compatibility.zh.md} (97%)
rename docs/{fr/providers.md => guides/providers.fr.md} (98%)
rename docs/{ja/providers.md => guides/providers.ja.md} (98%)
rename docs/{ => guides}/providers.md (97%)
rename docs/{pt-br/providers.md => guides/providers.pt-br.md} (98%)
rename docs/{vi/providers.md => guides/providers.vi.md} (98%)
rename docs/{zh/providers.md => guides/providers.zh.md} (97%)
create mode 100644 docs/guides/routing-guide.md
create mode 100644 docs/guides/routing-guide.zh.md
create mode 100644 docs/guides/session-guide.md
create mode 100644 docs/guides/session-guide.zh.md
rename docs/{fr/spawn-tasks.md => guides/spawn-tasks.fr.md} (97%)
rename docs/{ja/spawn-tasks.md => guides/spawn-tasks.ja.md} (98%)
rename docs/{ => guides}/spawn-tasks.md (100%)
rename docs/{my/spawn-tasks.md => guides/spawn-tasks.ms.md} (97%)
rename docs/{pt-br/spawn-tasks.md => guides/spawn-tasks.pt-br.md} (97%)
rename docs/{vi/spawn-tasks.md => guides/spawn-tasks.vi.md} (97%)
rename docs/{zh/spawn-tasks.md => guides/spawn-tasks.zh.md} (98%)
create mode 100644 docs/migration/README.md
create mode 100644 docs/operations/README.md
rename docs/{fr/debug.md => operations/debug.fr.md} (97%)
rename docs/{ja/debug.md => operations/debug.ja.md} (97%)
rename docs/{ => operations}/debug.md (100%)
rename docs/{my/debug.md => operations/debug.ms.md} (100%)
rename docs/{pt-br/debug.md => operations/debug.pt-br.md} (97%)
rename docs/{vi/debug.md => operations/debug.vi.md} (97%)
rename docs/{zh/debug.md => operations/debug.zh.md} (97%)
rename docs/{fr/troubleshooting.md => operations/troubleshooting.fr.md} (97%)
rename docs/{ja/troubleshooting.md => operations/troubleshooting.ja.md} (97%)
rename docs/{ => operations}/troubleshooting.md (100%)
rename docs/{my/troubleshooting.md => operations/troubleshooting.ms.md} (100%)
rename docs/{pt-br/troubleshooting.md => operations/troubleshooting.pt-br.md} (96%)
rename docs/{vi/troubleshooting.md => operations/troubleshooting.vi.md} (97%)
rename docs/{zh/troubleshooting.md => operations/troubleshooting.zh.md} (97%)
rename CONTRIBUTING.zh.md => docs/project/CONTRIBUTING.zh.md (99%)
rename README.fr.md => docs/project/README.fr.md (81%)
rename README.id.md => docs/project/README.id.md (81%)
rename README.it.md => docs/project/README.it.md (81%)
rename README.ja.md => docs/project/README.ja.md (82%)
create mode 100644 docs/project/README.ko.md
rename README.my.md => docs/project/README.ms.md (83%)
rename README.pt-br.md => docs/project/README.pt-br.md (80%)
rename README.vi.md => docs/project/README.vi.md (82%)
rename README.zh.md => docs/project/README.zh.md (80%)
create mode 100644 docs/reference/README.md
rename docs/{ => reference}/config-versioning.md (69%)
rename docs/{ => reference}/cron.md (100%)
rename docs/{ => reference}/rate-limiting.md (100%)
rename docs/{fr/tools_configuration.md => reference/tools_configuration.fr.md} (99%)
rename docs/{ja/tools_configuration.md => reference/tools_configuration.ja.md} (99%)
rename docs/{ => reference}/tools_configuration.md (88%)
rename docs/{pt-br/tools_configuration.md => reference/tools_configuration.pt-br.md} (99%)
rename docs/{vi/tools_configuration.md => reference/tools_configuration.vi.md} (99%)
rename docs/{zh/tools_configuration.md => reference/tools_configuration.zh.md} (93%)
rename docs/{fr/ANTIGRAVITY_AUTH.md => security/ANTIGRAVITY_AUTH.fr.md} (99%)
rename docs/{ja/ANTIGRAVITY_AUTH.md => security/ANTIGRAVITY_AUTH.ja.md} (99%)
rename docs/{ => security}/ANTIGRAVITY_AUTH.md (100%)
rename docs/{pt-br/ANTIGRAVITY_AUTH.md => security/ANTIGRAVITY_AUTH.pt-br.md} (99%)
rename docs/{vi/ANTIGRAVITY_AUTH.md => security/ANTIGRAVITY_AUTH.vi.md} (99%)
rename docs/{zh/ANTIGRAVITY_AUTH.md => security/ANTIGRAVITY_AUTH.zh.md} (99%)
create mode 100644 docs/security/README.md
rename docs/{fr/credential_encryption.md => security/credential_encryption.fr.md} (99%)
rename docs/{ja/credential_encryption.md => security/credential_encryption.ja.md} (99%)
rename docs/{ => security}/credential_encryption.md (100%)
rename docs/{pt-br/credential_encryption.md => security/credential_encryption.pt-br.md} (99%)
rename docs/{vi/credential_encryption.md => security/credential_encryption.vi.md} (99%)
rename docs/{zh/credential_encryption.md => security/credential_encryption.zh.md} (99%)
rename docs/{ => security}/security_configuration.md (83%)
rename docs/{ => security}/sensitive_data_filtering.md (98%)
rename docs/{zh/sensitive_data_filtering.md => security/sensitive_data_filtering.zh.md} (95%)
delete mode 100644 k3s/README.md
delete mode 100644 k3s/config.json
delete mode 100644 k3s/config.json.lockeddown
delete mode 100644 k3s/configmap.yaml
delete mode 100644 k3s/cron.json
delete mode 100644 k3s/deployment.yaml
delete mode 100644 k3s/pvc.yaml
delete mode 100644 k3s/secrets.yaml
delete mode 100644 k3s/service.yaml
delete mode 100644 logs/gateway.log
delete mode 100644 logs/gateway_panic.log
create mode 100644 pkg/agent/context_seahorse.go
create mode 100644 pkg/agent/context_seahorse_test.go
create mode 100644 pkg/agent/context_seahorse_unsupported.go
create mode 100644 pkg/agent/dispatch_request.go
create mode 100644 pkg/agent/dispatch_request_test.go
delete mode 100644 pkg/agent/isolation_tools_test.go
create mode 100644 pkg/agent/llm_media.go
create mode 100644 pkg/agent/loop_command.go
create mode 100644 pkg/agent/loop_event.go
create mode 100644 pkg/agent/loop_init.go
create mode 100644 pkg/agent/loop_inject.go
create mode 100644 pkg/agent/loop_message.go
create mode 100644 pkg/agent/loop_outbound.go
delete mode 100644 pkg/agent/loop_security_test.go
create mode 100644 pkg/agent/loop_steering.go
create mode 100644 pkg/agent/loop_transcribe.go
create mode 100644 pkg/agent/loop_turn.go
create mode 100644 pkg/agent/loop_utils.go
delete mode 100644 pkg/agent/multiuser_mcp_test.go
delete mode 100644 pkg/agent/secret.txt
create mode 100644 pkg/agent/turn_context.go
rename pkg/audio/asr/{README_zh.md => README.zh.md} (100%)
rename pkg/audio/tts/{README_zh.md => README.zh.md} (100%)
create mode 100644 pkg/bus/inbound_context.go
create mode 100644 pkg/bus/outbound_context.go
create mode 100644 pkg/channels/feishu/feishu_reply.go
create mode 100644 pkg/channels/feishu/feishu_reply_test.go
delete mode 100644 pkg/channels/http/http.go
create mode 100644 pkg/channels/teams_webhook/init.go
create mode 100644 pkg/channels/teams_webhook/teams_webhook.go
create mode 100644 pkg/channels/teams_webhook/teams_webhook_test.go
create mode 100644 pkg/commands/cmd_btw.go
create mode 100644 pkg/config/config_channel.go
create mode 100644 pkg/config/config_channel_test.go
create mode 100644 pkg/config/gateway_host_env_test.go
create mode 100644 pkg/config/legacy_bindings.go
create mode 100644 pkg/gateway/gateway_test.go
create mode 100644 pkg/gateway/listen.go
create mode 100644 pkg/gateway/listen_test.go
create mode 100644 pkg/isolation/README.md
create mode 100644 pkg/isolation/README.zh.md
create mode 100644 pkg/isolation/platform_linux.go
create mode 100644 pkg/isolation/platform_linux_test.go
create mode 100644 pkg/isolation/platform_other.go
create mode 100644 pkg/isolation/platform_windows.go
create mode 100644 pkg/isolation/runtime.go
create mode 100644 pkg/isolation/runtime_test.go
create mode 100644 pkg/mcp/isolated_command_transport.go
create mode 100644 pkg/netbind/netbind.go
create mode 100644 pkg/netbind/netbind_test.go
create mode 100644 pkg/netbind/socket_v6only_unix.go
create mode 100644 pkg/netbind/socket_v6only_windows.go
rename pkg/providers/{ => cli}/claude_cli_provider.go (96%)
rename pkg/providers/{ => cli}/claude_cli_provider_integration_test.go (99%)
rename pkg/providers/{ => cli}/claude_cli_provider_test.go (92%)
rename pkg/providers/{ => cli}/codex_cli_credentials.go (99%)
rename pkg/providers/{ => cli}/codex_cli_credentials_test.go (99%)
rename pkg/providers/{ => cli}/codex_cli_provider.go (96%)
rename pkg/providers/{ => cli}/codex_cli_provider_integration_test.go (99%)
rename pkg/providers/{ => cli}/codex_cli_provider_test.go (98%)
rename pkg/providers/{ => cli}/github_copilot_provider.go (99%)
rename pkg/providers/{ => cli}/tool_call_extract.go (98%)
rename pkg/providers/{ => cli}/toolcall_utils.go (87%)
create mode 100644 pkg/providers/cli/types.go
create mode 100644 pkg/providers/cli_facade.go
create mode 100644 pkg/providers/cli_factory_test.go
create mode 100644 pkg/providers/facade_compat_test.go
create mode 100644 pkg/providers/httpapi/gemini_helpers.go
create mode 100644 pkg/providers/httpapi/gemini_provider.go
create mode 100644 pkg/providers/httpapi/gemini_provider_test.go
rename pkg/providers/{ => httpapi}/http_provider.go (72%)
create mode 100644 pkg/providers/httpapi/types.go
create mode 100644 pkg/providers/httpapi_facade.go
rename pkg/providers/{ => oauth}/antigravity_provider.go (97%)
rename pkg/providers/{ => oauth}/antigravity_provider_test.go (59%)
rename pkg/providers/{ => oauth}/claude_provider.go (91%)
rename pkg/providers/{ => oauth}/claude_provider_test.go (99%)
rename pkg/providers/{ => oauth}/codex_provider.go (98%)
rename pkg/providers/{ => oauth}/codex_provider_test.go (99%)
create mode 100644 pkg/providers/oauth/types.go
create mode 100644 pkg/providers/oauth_facade.go
delete mode 100644 pkg/routing/session_key.go
delete mode 100644 pkg/routing/session_key_test.go
create mode 100644 pkg/seahorse/compact_until_under_test.go
create mode 100644 pkg/seahorse/fts5_sanitize.go
create mode 100644 pkg/seahorse/fts5_sanitize_test.go
create mode 100644 pkg/seahorse/parts_roundtrip_test.go
create mode 100644 pkg/seahorse/schema.go
create mode 100644 pkg/seahorse/schema_test.go
create mode 100644 pkg/seahorse/short_assembler.go
create mode 100644 pkg/seahorse/short_assembler_test.go
create mode 100644 pkg/seahorse/short_bench_test.go
create mode 100644 pkg/seahorse/short_compaction.go
create mode 100644 pkg/seahorse/short_compaction_test.go
create mode 100644 pkg/seahorse/short_constants.go
create mode 100644 pkg/seahorse/short_engine.go
create mode 100644 pkg/seahorse/short_engine_test.go
create mode 100644 pkg/seahorse/short_retrieval.go
create mode 100644 pkg/seahorse/short_retrieval_test.go
create mode 100644 pkg/seahorse/store.go
create mode 100644 pkg/seahorse/store_test.go
create mode 100644 pkg/seahorse/tool_expand.go
create mode 100644 pkg/seahorse/tool_expand_test.go
create mode 100644 pkg/seahorse/tool_grep.go
create mode 100644 pkg/seahorse/tool_grep_test.go
create mode 100644 pkg/seahorse/types.go
create mode 100644 pkg/seahorse/types_test.go
delete mode 100644 pkg/security/behavior/monitor.go
delete mode 100644 pkg/security/behavior/monitor_test.go
delete mode 100644 pkg/security/canary/hook.go
delete mode 100644 pkg/security/canary/hook_test.go
delete mode 100644 pkg/security/init.go
delete mode 100644 pkg/security/ipia/detector.go
delete mode 100644 pkg/security/ipia/detector_test.go
delete mode 100644 pkg/security/pii/redactor.go
delete mode 100644 pkg/security/pii/redactor_test.go
delete mode 100644 pkg/security/policy/checker.go
delete mode 100644 pkg/security/policy/checker_test.go
delete mode 100644 pkg/security/proof_test.go
create mode 100644 pkg/session/allocator.go
create mode 100644 pkg/session/allocator_test.go
create mode 100644 pkg/session/key.go
create mode 100644 pkg/session/key_test.go
create mode 100644 pkg/session/scope.go
create mode 100644 pkg/skills/config_bridge.go
create mode 100644 pkg/skills/github_registry.go
create mode 100644 pkg/skills/github_registry_test.go
create mode 100644 pkg/skills/provider_factory.go
create mode 100644 pkg/tokenizer/estimator.go
create mode 100644 pkg/tools/facade_compat_test.go
rename pkg/tools/{ => fs}/edit.go (79%)
rename pkg/tools/{ => fs}/edit_test.go (94%)
rename pkg/tools/{ => fs}/filesystem.go (88%)
rename pkg/tools/{ => fs}/filesystem_test.go (82%)
rename pkg/tools/{ => fs}/load_image.go (99%)
rename pkg/tools/{ => fs}/load_image_test.go (90%)
rename pkg/tools/{ => fs}/send_file.go (90%)
rename pkg/tools/{ => fs}/send_file_test.go (99%)
create mode 100644 pkg/tools/fs/shared.go
create mode 100644 pkg/tools/fs_facade.go
create mode 100644 pkg/tools/fs_registry_compat_test.go
rename pkg/tools/{ => hardware}/i2c.go (97%)
rename pkg/tools/{ => hardware}/i2c_linux.go (99%)
rename pkg/tools/{ => hardware}/i2c_other.go (95%)
create mode 100644 pkg/tools/hardware/shared.go
rename pkg/tools/{ => hardware}/spi.go (98%)
rename pkg/tools/{ => hardware}/spi_linux.go (99%)
rename pkg/tools/{ => hardware}/spi_other.go (94%)
create mode 100644 pkg/tools/hardware_facade.go
rename scratch/sanitize/main.go => pkg/tools/identifier_compat.go (84%)
create mode 100644 pkg/tools/integration/helpers.go
rename pkg/tools/{ => integration}/mcp_tool.go (99%)
rename pkg/tools/{ => integration}/mcp_tool_test.go (99%)
rename pkg/tools/{ => integration}/message.go (53%)
rename pkg/tools/{ => integration}/message_test.go (77%)
rename pkg/tools/{ => integration}/reaction.go (98%)
rename pkg/tools/{ => integration}/reaction_test.go (99%)
create mode 100644 pkg/tools/integration/shared.go
rename pkg/tools/{ => integration}/skills_install.go (51%)
create mode 100644 pkg/tools/integration/skills_install_test.go
rename pkg/tools/{ => integration}/skills_search.go (83%)
rename pkg/tools/{ => integration}/skills_search_test.go (82%)
rename pkg/tools/{ => integration}/tts_send.go (98%)
rename pkg/tools/{ => integration}/web.go (77%)
rename pkg/tools/{ => integration}/web_test.go (85%)
create mode 100644 pkg/tools/integration_facade.go
create mode 100644 pkg/tools/load_image_compat_test.go
create mode 100644 pkg/tools/path_compat.go
rename pkg/tools/{ => shared}/base.go (77%)
rename pkg/tools/{ => shared}/result.go (95%)
rename pkg/tools/{ => shared}/types.go (91%)
create mode 100644 pkg/tools/shared_facade.go
delete mode 100644 pkg/tools/skills_install_test.go
create mode 100644 pkg/utils/tool_feedback.go
create mode 100644 pkg/utils/tool_feedback_test.go
delete mode 100644 scratch/json/main.go
delete mode 100644 scratch/match/main.go
create mode 100755 scripts/lint-docs.sh
create mode 100644 web/backend/api/ui.go
create mode 100644 web/backend/api/ui_test.go
create mode 100644 web/backend/dashboardauth/platform.go
create mode 100644 web/backend/dashboardauth/sql.go
create mode 100644 web/backend/dashboardauth/store.go
create mode 100644 web/backend/dashboardauth/store_unsupported.go
delete mode 100644 web/backend/tray_offers_copy.go
delete mode 100644 web/backend/tray_offers_copy_stub.go
create mode 100644 web/frontend/src/app-providers.tsx
create mode 100644 web/frontend/src/components/agent/tools/tool-library-tab.tsx
create mode 100644 web/frontend/src/components/agent/tools/tool-status-badge.tsx
create mode 100644 web/frontend/src/components/agent/tools/tools-tabs.tsx
create mode 100644 web/frontend/src/components/agent/tools/types.ts
create mode 100644 web/frontend/src/components/agent/tools/use-tools-page.ts
create mode 100644 web/frontend/src/components/agent/tools/web-search-general-settings.tsx
create mode 100644 web/frontend/src/components/agent/tools/web-search-provider-settings.tsx
create mode 100644 web/frontend/src/components/agent/tools/web-search-tab.tsx
create mode 100644 web/frontend/src/hooks/use-highlight-theme.ts
create mode 100644 web/frontend/src/routes/launcher-setup.tsx
delete mode 100644 workspace/HEARTBEAT.md
delete mode 100644 workspace/cron/jobs.json
create mode 100644 workspace/skills/freeride/SKILL.md
diff --git a/.dockerignore b/.dockerignore
index f169f9361..d632da5ea 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -2,7 +2,7 @@
.gitignore
build/
.picoclaw/
-# config/
+config/
.env
.env.example
*.md
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9b89b69ae..def19c3e5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -16,5 +16,5 @@ jobs:
with:
go-version-file: go.mod
- - name: Build
+ - name: Build core binaries
run: make build-all
diff --git a/.github/workflows/create_dmg.yml b/.github/workflows/create_dmg.yml
index e03357566..626318619 100644
--- a/.github/workflows/create_dmg.yml
+++ b/.github/workflows/create_dmg.yml
@@ -17,29 +17,38 @@ jobs:
with:
ref: main
- # 1. 安装指定版本的 Go (可选,但推荐)
+ # 1. Install Go from go.mod
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
- # 2. 安装 pnpm
- - name: Install pnpm
- run: brew install pnpm
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v6
+ with:
+ version: 10.33.0
+ run_install: false
- # 3. 运行你的 Makefile 编译二进制文件
+ - name: Setup Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: 22
+ cache: pnpm
+ cache-dependency-path: web/frontend/pnpm-lock.yaml
+
+ # 3. Build the application bundle
- name: Build with Make
run: make build ARCH=${{ matrix.arch }} && make build-macos-app ARCH=${{ matrix.arch }}
- # 4. 签名
+ # 4. Apply ad-hoc signing
- name: Ad-hoc Sign
run: codesign --force --deep --sign - "build/PicoClaw Launcher.app"
- # 5. 安装打包工具
+ # 5. Install the DMG packaging tool
- name: Install create-dmg
run: brew install create-dmg
- # 6. 执行打包命令
+ # 6. Create the DMG
- name: Create DMG
run: |
mkdir -p dist
@@ -54,7 +63,7 @@ jobs:
"dist/picoclaw-${{ matrix.arch }}.dmg" \
"build/PicoClaw Launcher.app"
- # 7. 上传文件到 GitHub Artifacts (供你下载)
+ # 7. Upload the DMG as a GitHub artifact
- name: Upload DMG
uses: actions/upload-artifact@v7
with:
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index a5002fec5..39ad8810e 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -47,13 +47,18 @@ jobs:
with:
go-version-file: go.mod
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v6
+ with:
+ version: 10.33.0
+ run_install: false
+
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
-
- - name: Setup pnpm
- run: corepack enable && corepack prepare pnpm@latest --activate
+ cache: pnpm
+ cache-dependency-path: web/frontend/pnpm-lock.yaml
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -75,6 +80,9 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Install zip
+ run: sudo apt-get install -y zip
+
- name: Create local tag for GoReleaser
run: git tag "${{ steps.version.outputs.version }}"
@@ -90,6 +98,7 @@ jobs:
DOCKERHUB_IMAGE_NAME: ${{ vars.DOCKERHUB_REPOSITORY }}
GOVERSION: ${{ steps.setup-go.outputs.go-version }}
GORELEASER_CURRENT_TAG: ${{ steps.version.outputs.version }}
+ INCLUDE_ANDROID_BUNDLE: "true"
NIGHTLY_BUILD: "true"
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
@@ -123,7 +132,7 @@ jobs:
# Collect release artifacts from goreleaser dist/
ASSETS=()
- for f in dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/checksums.txt; do
+ for f in dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/checksums.txt build/picoclaw-android-universal.zip; do
[ -f "$f" ] && ASSETS+=("$f")
done
@@ -135,4 +144,3 @@ jobs:
--prerelease \
--latest=false \
"${ASSETS[@]}"
-
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 2d544d4f0..795fa5eba 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -41,10 +41,11 @@ jobs:
with:
go-version-file: go.mod
+ - name: Install govulncheck
+ run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4
+
- name: Run Govulncheck
- uses: golang/govulncheck-action@v1
- with:
- go-package: ./...
+ run: govulncheck -C . -format text ./...
test:
name: Tests
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2ce341770..1480d410d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -65,13 +65,18 @@ jobs:
with:
go-version-file: go.mod
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v6
+ with:
+ version: 10.33.0
+ run_install: false
+
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
-
- - name: Setup pnpm
- run: corepack enable && corepack prepare pnpm@latest --activate
+ cache: pnpm
+ cache-dependency-path: web/frontend/pnpm-lock.yaml
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -93,6 +98,9 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Install zip
+ run: sudo apt-get install -y zip
+
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v7
with:
@@ -104,6 +112,7 @@ jobs:
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
DOCKERHUB_IMAGE_NAME: ${{ vars.DOCKERHUB_REPOSITORY }}
GOVERSION: ${{ steps.setup-go.outputs.go-version }}
+ INCLUDE_ANDROID_BUNDLE: "true"
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
diff --git a/.gitignore b/.gitignore
index 169445797..135867842 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,17 +10,14 @@ build/
*.out
/picoclaw
/picoclaw-test
-/golangci-lint
cmd/**/workspace
# Picoclaw specific
# PicoClaw
.picoclaw/
-pkg/agent/secret.txt
config.json
sessions/
-logs/
build/
# Coverage
@@ -70,4 +67,5 @@ web/backend/dist/*
.claude/
docker/data
-workspace/
+
+.omc/
diff --git a/.golangci.yaml b/.golangci.yaml
index 7c8c82b2c..052e4c0dd 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -1,37 +1,178 @@
+version: "2"
+
linters:
- default: none
- enable:
+ default: all
+ disable:
+ # TODO: Tweak for current project needs
+ - containedctx
+ - cyclop
+ - depguard
+ - dupword
+ - err113
+ - exhaustruct
+ - funcorder
+ - gochecknoglobals
+ - gosmopolitan # Project legitimately uses CJK text in tests (FTS5, token counting)
+ - godot
+ - intrange
+ - ireturn
+ - nlreturn
+ - noctx
+ - noinlineerr
+ - nonamedreturns
+ - tagliatelle
+ - testpackage
+ - varnamelen
+ - wrapcheck
+ - wsl
+ - wsl_v5
+
+ # TODO: Disabled, because they are failing at the moment, we should fix them and enable (step by step)
+ - contextcheck
+ - embeddedstructfieldcheck
+ - errcheck
+ - errchkjson
+ - errorlint
+ - exhaustive
+ - forbidigo
+ - forcetypeassert
+ - funlen
+ - gochecknoinits
- gocognit
+ - goconst
+ - gocritic
- gocyclo
+ - godox
+ - gosec
+ - ineffassign
+ - lll
+ - maintidx
+ - mnd
+ - modernize
+ - nestif
+ - nilnil
+ - paralleltest
+ - perfsprint
+ - revive
+ - staticcheck
+ - tagalign
+ - testifylint
+ - thelper
+ - unparam
+ - usestdlibvars
+ - usetesting
+ settings:
+ gomoddirectives:
+ replace-allow-list:
+ - github.com/bwmarrin/discordgo
+ errcheck:
+ check-type-assertions: true
+ check-blank: true
+ exhaustive:
+ default-signifies-exhaustive: true
+ funlen:
+ lines: 120
+ statements: 40
+ gocognit:
+ min-complexity: 25
+ gocyclo:
+ min-complexity: 20
+ govet:
+ enable-all: true
+ disable:
+ - fieldalignment
+ lll:
+ line-length: 120
+ tab-width: 4
+ misspell:
+ locale: US
+ mnd:
+ checks:
+ - argument
+ - assign
+ - case
+ - condition
+ - operation
+ - return
+ nakedret:
+ max-func-lines: 3
+ revive:
+ enable-all-rules: true
+ rules:
+ - name: add-constant
+ disabled: true
+ - name: argument-limit
+ arguments:
+ - 7
+ severity: warning
+ - name: banned-characters
+ disabled: true
+ - name: cognitive-complexity
+ disabled: true
+ - name: comment-spacings
+ arguments:
+ - nolint
+ severity: warning
+ - name: cyclomatic
+ disabled: true
+ - name: file-header
+ disabled: true
+ - name: function-result-limit
+ arguments:
+ - 3
+ severity: warning
+ - name: function-length
+ disabled: true
+ - name: line-length-limit
+ disabled: true
+ - name: max-public-structs
+ disabled: true
+ - name: modifies-value-receiver
+ disabled: true
+ - name: package-comments
+ disabled: true
+ - name: unused-receiver
+ disabled: true
+ exclusions:
+ generated: lax
+ rules:
+ - linters:
+ - lll
+ source: '^//go:generate '
+ - linters:
+ - funlen
+ - maintidx
+ - gocognit
+ - gocyclo
+ path: _test\.go$
+ - linters:
+ - nolintlint
+ path: 'pkg/tools/(i2c\.go|spi\.go)$'
+
+issues:
+ max-issues-per-linter: 0
+ max-same-issues: 0
+
+formatters:
+ enable:
+ - gci
- gofmt
+ - gofumpt
- goimports
- - misspell
- - nakedret
-
-linters-settings:
- gocyclo:
- min-complexity: 30
- gocognit:
- min-complexity: 30
- gofmt:
- simplify: true
- goimports:
- local-prefixes: github.com/sipeed/picoclaw
- misspell:
- locale: US
- nakedret:
- max-func-lines: 30
-
-run:
- timeout: 30m
- skip-dirs:
- - vendor
- - web/frontend
- - scratch
- - pkg/channels
- - pkg/audio
- - cmd/picoclaw-launcher-tui
- - web/backend/api
- tests: false
- skip-files:
- - .*_test.go
\ No newline at end of file
+ - golines
+ settings:
+ gci:
+ sections:
+ - standard
+ - default
+ - localmodule
+ custom-order: true
+ gofmt:
+ simplify: true
+ rewrite-rules:
+ - pattern: "interface{}"
+ replacement: "any"
+ - pattern: "a[b:len(a)]"
+ replacement: "a[b:]"
+ golines:
+ max-len: 120
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 9c26de34f..d8c51b069 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -9,11 +9,10 @@ git:
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 web/backend/winres/winres.json --out web/backend/rsrc --product-version={{ .Version }} --file-version={{ .Version }}
+ - sh -c 'cd web/frontend && CI=true pnpm install --frozen-lockfile && pnpm build:backend'
+ - sh -c 'GOBIN="$(go env GOPATH)/bin"; mkdir -p "$GOBIN"; go install github.com/tc-hib/go-winres@v0.3.3 && "$GOBIN/go-winres" make --in web/backend/winres/winres.json --out web/backend/rsrc --product-version={{ .Version }} --file-version={{ .Version }}'
+ - sh -c 'if [ "${INCLUDE_ANDROID_BUNDLE:-}" = "true" ]; then make build-android-bundle; fi'
builds:
- id: picoclaw
@@ -27,7 +26,7 @@ builds:
- -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 }}
+ - -X github.com/sipeed/picoclaw/pkg/config.GoVersion={{ with index .Env "GOVERSION" }}{{ . }}{{ else }}unknown{{ end }}
goos:
- linux
- windows
@@ -67,6 +66,10 @@ builds:
- stdjson
ldflags:
- -s -w
+ - -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={{ with index .Env "GOVERSION" }}{{ . }}{{ else }}unknown{{ end }}
goos:
- linux
- windows
@@ -106,6 +109,10 @@ builds:
- stdjson
ldflags:
- -s -w
+ - -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={{ with index .Env "GOVERSION" }}{{ . }}{{ else }}unknown{{ end }}
goos:
- linux
- windows
@@ -245,6 +252,8 @@ changelog:
release:
disable: '{{ isEnvSet "NIGHTLY_BUILD" }}'
+ extra_files:
+ - glob: ./build/picoclaw-android-universal.zip
footer: >-
---
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ceff723d2..a78c41c36 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -35,6 +35,8 @@ We are committed to maintaining a welcoming and respectful community. Be kind, c
For substantial new features, please open an issue first to discuss the design before writing code. This prevents wasted effort and ensures alignment with the project's direction.
+For documentation contributions, prefer the layout and naming conventions in [`docs/README.md`](docs/README.md). Run `make lint-docs` after adding or moving Markdown files to catch common consistency issues early.
+
---
## Getting Started
@@ -64,7 +66,7 @@ For substantial new features, please open an issue first to discuss the design b
```bash
make build # Build binary (runs go generate first)
make generate # Run go generate only
-make check # Full pre-commit check: deps + fmt + vet + test
+make check # Full pre-commit check: deps + fmt + vet + test + docs consistency checks
```
### Running Tests
@@ -81,9 +83,10 @@ go test -bench=. -benchmem -run='^$' ./... # Run benchmarks
make fmt # Format code
make vet # Static analysis
make lint # Full linter run
+make lint-docs # Check common documentation layout and naming conventions
```
-All CI checks must pass before a PR can be merged. Run `make check` locally before pushing to catch issues early.
+All CI checks must pass before a PR can be merged. Run `make check` locally before pushing to catch issues early, including the common docs consistency checks from `make lint-docs`.
---
@@ -108,7 +111,7 @@ Use descriptive branch names, e.g. `fix/telegram-timeout`, `feat/ollama-provider
- Reference the related issue when relevant: `Fix session leak (#123)`.
- Keep commits focused. One logical change per commit is preferred.
- For minor cleanups or typo fixes, squash them into a single commit before opening a PR.
-- Refer to https://www.conventionalcommits.org/zh-hans/v1.0.0/
+- Refer to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
### Keeping Up to Date
diff --git a/Makefile b/Makefile
index beb718361..c462914e8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,9 @@
-.PHONY: all build install uninstall clean help test
+.PHONY: all build install uninstall clean help test build-all lint-docs
# Build variables
BINARY_NAME=picoclaw
BUILD_DIR=build
CMD_DIR=cmd/$(BINARY_NAME)
-DOCKER_USER=stevef1uk
MAIN_GO=$(CMD_DIR)/main.go
EXT=
@@ -57,8 +56,7 @@ PTY_PATCH_LOONG64=pty_dir=$$(go env GOMODCACHE)/github.com/creack/pty@v1.1.9; \
fi
# Golangci-lint
-GOLANGCI_LINT_BIN := $(shell if [ -f $(CURDIR)/golangci-lint ]; then echo $(CURDIR)/golangci-lint; else echo golangci-lint; fi)
-GOLANGCI_LINT?=$(GOLANGCI_LINT_BIN)
+GOLANGCI_LINT?=golangci-lint
# Installation
INSTALL_PREFIX?=$(HOME)/.local
@@ -207,18 +205,44 @@ build-linux-mipsle: generate
$(call PATCH_MIPS_FLAGS,$(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle)
@echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)-linux-mipsle"
+## build-android-arm64: Build core for Android ARM64
+build-android-arm64: generate
+ @echo "Building for android/arm64..."
+ @mkdir -p $(BUILD_DIR)
+ GOOS=android GOARCH=arm64 $(GO) build -tags stdjson -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-android-arm64 ./$(CMD_DIR)
+ @echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)-android-arm64"
+
+## build-launcher-android-arm64: Build launcher for Android ARM64
+build-launcher-android-arm64:
+ @echo "Building picoclaw-launcher for android/arm64..."
+ @mkdir -p $(BUILD_DIR)
+ @$(MAKE) -C web build-android-arm64 \
+ OUTPUT_ANDROID_ARM64="$(CURDIR)/$(BUILD_DIR)/picoclaw-launcher-android-arm64" \
+ GO='$(GO)' \
+ LDFLAGS='$(LDFLAGS)'
+ @echo "Build complete: $(BUILD_DIR)/picoclaw-launcher-android-arm64"
+
+## build-android-bundle: Build core and launcher for all Android architectures and package as universal zip
+build-android-bundle: generate
+ @echo "Building core for all Android architectures..."
+ @mkdir -p $(BUILD_DIR)
+ GOOS=android GOARCH=arm64 $(GO) build -tags stdjson -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-android-arm64 ./$(CMD_DIR)
+ @echo "Building launcher for Android arm64..."
+ @$(MAKE) build-launcher-android-arm64
+ @echo "Staging JNI libs..."
+ @rm -rf $(BUILD_DIR)/android-staging
+ @mkdir -p $(BUILD_DIR)/android-staging/arm64-v8a
+ @cp $(BUILD_DIR)/$(BINARY_NAME)-android-arm64 $(BUILD_DIR)/android-staging/arm64-v8a/libpicoclaw.so
+ @cp $(BUILD_DIR)/picoclaw-launcher-android-arm64 $(BUILD_DIR)/android-staging/arm64-v8a/libpicoclaw-web.so
+ @cd $(BUILD_DIR)/android-staging && zip -r ../picoclaw-android-universal.zip .
+ @rm -rf $(BUILD_DIR)/android-staging
+ @echo "All Android builds complete: $(BUILD_DIR)/picoclaw-android-universal.zip"
+
## build-pi-zero: Build for Raspberry Pi Zero 2 W (32-bit and 64-bit)
build-pi-zero: build-linux-arm build-linux-arm64
@echo "Pi Zero 2 W builds: $(BUILD_DIR)/$(BINARY_NAME)-linux-arm (32-bit), $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 (64-bit)"
-## build-raspberry-pi: Build binaries and Docker image for Raspberry Pi
-build-raspberry-pi: build-pi-zero docker-build-rpi
- @echo "Raspberry Pi full build complete (binaries and Docker image)"
-
-## build-rpi: Build binaries and Docker image for Raspberry Pi
-build-rpi: build-raspberry-pi
-
-## build-all: Build picoclaw for all platforms
+## build-all: Build the picoclaw core binary for all Makefile-managed platforms
build-all: generate
@echo "Building for multiple platforms..."
@mkdir -p $(BUILD_DIR)
@@ -235,7 +259,7 @@ build-all: generate
GOOS=windows GOARCH=amd64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe ./$(CMD_DIR)
GOOS=netbsd GOARCH=amd64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-netbsd-amd64 ./$(CMD_DIR)
GOOS=netbsd GOARCH=arm64 $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-netbsd-arm64 ./$(CMD_DIR)
- @echo "All builds complete"
+ @echo "Core builds complete"
## install: Install picoclaw to system and copy builtin skills
install: build
@@ -277,16 +301,21 @@ vet: generate
## test: Test Go code
test: generate
- @$(GO) test $(GOFLAGS) -p 1 $$($(GO) list $(GOFLAGS) ./... | grep -v github.com/sipeed/picoclaw/web/) -timeout 120s
+ @$(GO) test $(GOFLAGS) $$($(GO) list $(GOFLAGS) ./... | grep -v github.com/sipeed/picoclaw/web/)
@cd web && make test
## fmt: Format Go code
fmt:
- @gofmt -s -w $$(find . -name "*.go" -not -path "./web/*" -not -path "./vendor/*")
+ @go fmt ./...
+
+## lint-docs: Check common documentation layout and naming conventions
+lint-docs:
+ @./scripts/lint-docs.sh
## lint: Run linters
lint:
@$(GOLANGCI_LINT) run --build-tags $(GO_BUILD_TAGS)
+ @./scripts/lint-docs.sh
## fix: Fix linting issues
fix:
@@ -302,8 +331,8 @@ update-deps:
@$(GO) get -u ./...
@$(GO) mod tidy
-## check: Run vet, fmt, lint, and verify dependencies
-check: deps fmt vet test
+## check: Run deps, fmt, vet, tests, and docs consistency checks
+check: deps fmt vet test lint-docs
## run: Build and run picoclaw
run: build
@@ -327,20 +356,7 @@ docker-test:
## docker-run: Run picoclaw gateway in Docker (Alpine-based)
docker-run:
- docker compose -f docker/docker-compose.yml up -d
-
-## docker-build-rpi: Build Raspberry Pi specific Docker image (ARM64)
-docker-build-rpi:
- @echo "Building Raspberry Pi Docker image (ARM64)..."
- docker build --no-cache --platform linux/arm64 -t $(DOCKER_USER)/picoclaw-rpi:latest -f docker/Dockerfile.rpi .
-
-## docker-push-rpi: Push Raspberry Pi specific Docker image (ARM64)
-docker-push-rpi:
- @echo "Pushing Raspberry Pi Docker image (ARM64)..."
- docker push $(DOCKER_USER)/picoclaw-rpi:latest
-
-docker-build-raspberry-pi: docker-build-rpi
-docker-push-raspberry-pi: docker-push-rpi
+ docker compose -f docker/docker-compose.yml --profile gateway up
## docker-run-full: Run picoclaw gateway in Docker (full-featured)
docker-run-full:
@@ -371,6 +387,25 @@ build-macos-app:build-launcher
@./scripts/build-macos-app.sh $(PLATFORM)-$(ARCH)
@echo "macOS .app bundle created: $(BUILD_DIR)/PicoClaw.app"
+## mem: Build membench, download LOCOMO data (if needed), run benchmark, and show results
+mem:
+ @echo "Building membench..."
+ @mkdir -p $(BUILD_DIR)
+ @$(GO) build -o $(BUILD_DIR)/membench ./cmd/membench
+ @echo "Build complete: $(BUILD_DIR)/membench"
+ @if [ ! -f $(BUILD_DIR)/memdata/locomo10.json ]; then \
+ echo "Downloading LOCOMO dataset..."; \
+ mkdir -p $(BUILD_DIR)/memdata; \
+ curl -sfL "https://raw.githubusercontent.com/snap-research/locomo/main/data/locomo10.json" \
+ -o $(BUILD_DIR)/memdata/locomo10.json && [ -s $(BUILD_DIR)/memdata/locomo10.json ] || { echo "Error: LOCOMO download failed"; exit 1; }; \
+ echo "Download complete"; \
+ else \
+ echo "LOCOMO dataset already exists, skipping download"; \
+ fi
+ @echo "Running benchmark..."
+ @rm -rf $(BUILD_DIR)/memout
+ @$(BUILD_DIR)/membench run --data $(BUILD_DIR)/memdata --out $(BUILD_DIR)/memout --budget 4000
+
## help: Show this help message
help:
@echo "picoclaw Makefile"
diff --git a/README.md b/README.md
index f97f9aae8..15f489567 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
-[中文](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**
+[中文](docs/project/README.zh.md) | [日本語](docs/project/README.ja.md) | [한국어](docs/project/README.ko.md) | [Português](docs/project/README.pt-br.md) | [Tiếng Việt](docs/project/README.vi.md) | [Français](docs/project/README.fr.md) | [Italiano](docs/project/README.it.md) | [Bahasa Indonesia](docs/project/README.id.md) | [Malay](docs/project/README.ms.md) | **English**
@@ -99,9 +99,6 @@
🧬 **FreeRide**: Intelligent model rotation using OpenRouter's free pool — never pay for basic LLM traffic again. [Learn more](docs/freeride.md).
-🛡️ **Hardened Multi-User Isolation**: Built-in [Tenant Isolation](docs/configuration.md#🔒-multi-tenant-agent-isolation) for shared infrastructure (Azure/ACA) — automatically partitions workspaces, memory, and tools (including MCP) per-user session.
-
-🛡️ **Security Shield**: Active protection layers including Canary tokens (leak detection), PII Redaction, Indirect Prompt Injection (IPIA) Analysis, and Tool Policy-as-Code. [Learn more](docs/security_configuration.md#security-shield-active-protection).
_*Recent builds may use 10-20MB due to rapid PR merges. Resource optimization is planned. Boot speed comparison based on 0.8GHz single-core benchmarks (see table below)._
@@ -117,7 +114,7 @@ _*Recent builds may use 10-20MB due to rapid PR merges. Resource optimization is
-> **[Hardware Compatibility List](docs/hardware-compatibility.md)** — See all tested boards, from $5 RISC-V to Raspberry Pi to Android phones. Your board not listed? Submit a PR!
+> **[Hardware Compatibility List](docs/guides/hardware-compatibility.md)** — See all tested boards, from $5 RISC-V to Raspberry Pi to Android phones. Your board not listed? Submit a PR!
@@ -169,22 +166,32 @@ Alternatively, download the binary for your platform from the [GitHub Releases](
### Build from source (for development)
+Prerequisites:
+
+- Go 1.25+
+- Node.js 22+ and pnpm 10.33.0+ for Web UI / launcher builds
+
```bash
git clone https://github.com/sipeed/picoclaw.git
cd picoclaw
make deps
-# Build core binary
+# Install frontend dependencies
+(cd web/frontend && pnpm install --frozen-lockfile)
+
+# Build the core binary for the current platform
make build
-# Build Web UI Launcher (required for WebUI mode)
+# Build the Web UI Launcher (required for WebUI mode)
make build-launcher
-# Build for multiple platforms
+# Build core binaries for all Makefile-managed platforms
make build-all
-# Build for Raspberry Pi Zero 2 W (32-bit: make build-linux-arm; 64-bit: make build-linux-arm64)
+# Build for Raspberry Pi Zero 2 W
+# 32-bit: make build-linux-arm
+# 64-bit: make build-linux-arm64
make build-pi-zero
# Build and install
@@ -220,7 +227,7 @@ picoclaw-launcher
-**Getting started:**
+**Getting started:**
Open the WebUI, then: **1)** Configure a Provider (add your LLM API key) -> **2)** Configure a Channel (e.g., Telegram) -> **3)** Start the Gateway -> **4)** Chat!
@@ -298,12 +305,13 @@ picoclaw-launcher-tui
-**Getting started:**
+**Getting started:**
Use the TUI menus to: **1)** Configure a Provider -> **2)** Configure a Channel -> **3)** Start the Gateway -> **4)** Chat!
For detailed TUI documentation, see [docs.picoclaw.io](https://docs.picoclaw.io).
+
### 📱 Android
Give your decade-old phone a second life! Turn it into a smart AI Assistant with PicoClaw.
@@ -373,8 +381,8 @@ This creates `~/.picoclaw/config.json` and the workspace directory.
```
> See `config/config.example.json` in the repo for a complete configuration template with all available options.
->
-> Please note: config.example.json format is version 0, with sensitive codes in it, and will be auto migrated to version 1+, then, the config.json will only store insensitive data, the sensitive codes will be stored in .security.yml, if you need manually modify the codes, please see `docs/security_configuration.md` for more details.
+>
+> Please note: config.example.json format is version 0, with sensitive codes in it, and will be auto migrated to version 1+, then, the config.json will only store insensitive data, the sensitive codes will be stored in .security.yml, if you need manually modify the codes, please see `docs/security/security_configuration.md` for more details.
**3. Chat**
@@ -453,7 +461,7 @@ PicoClaw supports 30+ LLM providers through the `model_list` configuration. Use
}
```
-For full provider configuration details, see [Providers & Models](docs/providers.md).
+For full provider configuration details, see [Providers & Models](docs/guides/providers.md).
@@ -465,8 +473,8 @@ Talk to your PicoClaw through 18+ messaging platforms:
|---------|-------|----------|------|
| **Telegram** | Easy (bot token) | Long polling | [Guide](docs/channels/telegram/README.md) |
| **Discord** | Easy (bot token + intents) | WebSocket | [Guide](docs/channels/discord/README.md) |
-| **WhatsApp** | Easy (QR scan or bridge URL) | Native / Bridge | [Guide](docs/chat-apps.md#whatsapp) |
-| **Weixin** | Easy (Native QR scan) | iLink API | [Guide](docs/chat-apps.md#weixin) |
+| **WhatsApp** | Easy (QR scan or bridge URL) | Native / Bridge | [Guide](docs/guides/chat-apps.md#whatsapp) |
+| **Weixin** | Easy (Native QR scan) | iLink API | [Guide](docs/guides/chat-apps.md#weixin) |
| **QQ** | Easy (AppID + AppSecret) | WebSocket | [Guide](docs/channels/qq/README.md) |
| **Slack** | Easy (bot + app token) | Socket Mode | [Guide](docs/channels/slack/README.md) |
| **Matrix** | Medium (homeserver + token) | Sync API | [Guide](docs/channels/matrix/README.md) |
@@ -475,7 +483,7 @@ Talk to your PicoClaw through 18+ messaging platforms:
| **LINE** | Medium (credentials + webhook) | Webhook | [Guide](docs/channels/line/README.md) |
| **WeCom** | Easy (QR login or manual) | WebSocket | [Guide](docs/channels/wecom/README.md) |
| **VK** | Easy (group token) | Long Poll | [Guide](docs/channels/vk/README.md) |
-| **IRC** | Medium (server + nick) | IRC protocol | [Guide](docs/chat-apps.md#irc) |
+| **IRC** | Medium (server + nick) | IRC protocol | [Guide](docs/guides/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) |
| **Pico** | Easy (enable) | Native protocol | Built-in |
@@ -483,9 +491,9 @@ Talk to your PicoClaw through 18+ 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.
+> 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/guides/configuration.md#gateway-log-level) for details.
-For detailed channel setup instructions, see [Chat Apps Configuration](docs/chat-apps.md).
+For detailed channel setup instructions, see [Chat Apps Configuration](docs/guides/chat-apps.md).
## 🔧 Tools
@@ -505,7 +513,7 @@ PicoClaw can search the web to provide up-to-date information. Configure in `too
### ⚙️ Other Tools
-PicoClaw includes built-in tools for file operations, code execution, scheduling, and more. See [Tools Configuration](docs/tools_configuration.md) for details.
+PicoClaw includes built-in tools for file operations, code execution, scheduling, and more. See [Tools Configuration](docs/reference/tools_configuration.md) for details.
## 🎯 Skills
@@ -518,7 +526,7 @@ picoclaw skills search "web scraping"
picoclaw skills install
```
-**Configure ClawHub token** (optional, for higher rate limits):
+**Configure skill registries**:
Add to your `config.json`:
```json
@@ -528,6 +536,11 @@ Add to your `config.json`:
"registries": {
"clawhub": {
"auth_token": "your-clawhub-token"
+ },
+ "github": {
+ "base_url": "https://github.com",
+ "auth_token": "your-github-token",
+ "proxy": ""
}
}
}
@@ -535,7 +548,9 @@ Add to your `config.json`:
}
```
-For more details, see [Tools Configuration - Skills](docs/tools_configuration.md#skills-tool).
+`tools.skills.github.*` is deprecated. Use `tools.skills.registries.github.*` instead.
+
+For more details, see [Tools Configuration - Skills](docs/reference/tools_configuration.md#skills-tool).
## 🔗 MCP (Model Context Protocol)
@@ -558,7 +573,7 @@ PicoClaw natively supports [MCP](https://modelcontextprotocol.io/) — connect a
}
```
-For full MCP configuration (stdio, SSE, HTTP transports, Tool Discovery), see [Tools Configuration - MCP](docs/tools_configuration.md#mcp-tool).
+For full MCP configuration (stdio, SSE, HTTP transports, Tool Discovery), see [Tools Configuration - MCP](docs/reference/tools_configuration.md#mcp-tool).
##
Join the Agent Social Network
@@ -595,7 +610,7 @@ 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.
+See [docs/reference/cron.md](docs/reference/cron.md) for current schedule types, execution modes, command-job gates, and persistence details.
## 📚 Documentation
@@ -603,20 +618,19 @@ For detailed guides beyond this README:
| Topic | Description |
|-------|-------------|
-| [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 |
+| [Docker & Quick Start](docs/guides/docker.md) | Docker Compose setup, Launcher/Agent modes |
+| [Chat Apps](docs/guides/chat-apps.md) | All 17+ channel setup guides |
+| [Configuration](docs/guides/configuration.md) | Environment variables, workspace layout, security sandbox |
+| [Scheduled Tasks and Cron Jobs](docs/reference/cron.md) | Cron schedule types, deliver modes, command gates, job storage |
+| [Providers & Models](docs/guides/providers.md) | 30+ LLM providers, model routing, model_list configuration |
+| [Spawn & Async Tasks](docs/guides/spawn-tasks.md) | Quick tasks, long tasks with spawn, async sub-agent orchestration |
| [FreeRide](docs/freeride.md) | Dynamic free model rotation and K3s secret management |
-| [Hooks](docs/hooks/README.md) | Event-driven hook system: observers, interceptors, approval hooks |
-| [Steering](docs/steering.md) | Inject messages into a running agent loop between tool calls |
-| [SubTurn](docs/subturn.md) | Subagent coordination, concurrency control, lifecycle |
-| [Troubleshooting](docs/troubleshooting.md) | Common issues and solutions |
-| [Tools Configuration](docs/tools_configuration.md) | Per-tool enable/disable, exec policies, MCP, Skills |
-| [Gateway API Reference](docs/api.md) | HTTP endpoints: `/chat`, `/health`, `/ready`, `/reload` |
-| [Hardware Compatibility](docs/hardware-compatibility.md) | Tested boards, minimum requirements |
+| [Hooks](docs/architecture/hooks/README.md) | Event-driven hook system: observers, interceptors, approval hooks |
+| [Steering](docs/architecture/steering.md) | Inject messages into a running agent loop between tool calls |
+| [SubTurn](docs/architecture/subturn.md) | Subagent coordination, concurrency control, lifecycle |
+| [Troubleshooting](docs/operations/troubleshooting.md) | Common issues and solutions |
+| [Tools Configuration](docs/reference/tools_configuration.md) | Per-tool enable/disable, exec policies, MCP, Skills |
+| [Hardware Compatibility](docs/guides/hardware-compatibility.md) | Tested boards, minimum requirements |
## 🤝 Contribute & Roadmap
diff --git a/TEAMS_ID_MAPPING_ANALYSIS.md b/TEAMS_ID_MAPPING_ANALYSIS.md
deleted file mode 100644
index f26b14fed..000000000
--- a/TEAMS_ID_MAPPING_ANALYSIS.md
+++ /dev/null
@@ -1,363 +0,0 @@
-# Teams Channel Integration & ID Mapping Analysis
-
-## Executive Summary
-
-**Teams Channel Implementation Status**: ❌ **NOT YET IMPLEMENTED**
-- Search results show no Teams/MSTeams channel in `pkg/channels/`
-- Only reference found: migration config reference in `pkg/migrate/sources/openclaw/openclaw_config.go:123`
-- **Foundry Integration**: Only implemented as an LLM **provider** (Azure AI Foundry), not as a channel
-
----
-
-## InboundMessage Structure (Bus Layer)
-
-**Location**: [pkg/bus/types.go](pkg/bus/types.go)
-
-### Core Fields Available
-
-```go
-type InboundMessage struct {
- Channel string // Channel name (e.g., "teams", "slack", "telegram")
- SenderID string // Platform-specific sender identifier
- Sender SenderInfo // Structured sender information
- ChatID string // Conversation/chat identifier (CRITICAL FOR ISOLATION)
- Content string // Message text content
- Media []string // Media references (attachments)
- Peer Peer // Routing peer information
- MessageID string // Platform-specific message ID
- MediaScope string // Media lifecycle tracking scope
- SessionKey string // Session key (optional, can be auto-resolved)
- Metadata map[string]string // Platform-specific metadata
-}
-```
-
-### SenderInfo Sub-structure
-
-```go
-type SenderInfo struct {
- Platform string // "telegram", "discord", "slack", "teams", etc.
- PlatformID string // Raw platform ID (e.g., Teams UserID "29:...")
- CanonicalID string // Normalized "platform:id" format (e.g., "teams:29:...")
- Username string // Display username (e.g., "@alice")
- DisplayName string // Full display name
-}
-```
-
-### Peer Sub-structure
-
-```go
-type Peer struct {
- Kind string // "direct" | "group" | "channel" | ""
- ID string // Peer identifier (user_id, group_id, channel_id, etc.)
-}
-```
-
----
-
-## ID Mapping for Hypothetical Teams Implementation
-
-### What Teams Would Need to Provide
-
-If Teams were to be integrated, the following IDs should map as follows:
-
-| Teams ID | InboundMessage Field | Notes |
-|----------|----------------------|-------|
-| User ID (e.g., `29:1ABC123`) | `SenderID`, `Sender.PlatformID` | Teams uses format `29:uuid` |
-| Conversation ID | `ChatID` | CRITICAL: Identifies conversation scope |
-| Team ID | `Metadata["team_id"]`, potentially routing input | Can be used for team-level routing |
-| Channel ID | `Peer.ID` (if channel) | When in Team channel |
-| Service URL | `Metadata["service_url"]` | Teams service endpoint |
-| Activity ID | `MessageID` | Platform message identifier |
-
-### Canonical ID Format
-
-**Pattern**: `platform:platform_id`
-
-**Example for Teams**:
-```
-"teams:29:1ABC123" = Canonical ID for Teams user 29:1ABC123
-```
-
-Built via: [pkg/identity/identity.go](pkg/identity/identity.go)
-```go
-func BuildCanonicalID(platform, platformID string) string {
- p := strings.ToLower(strings.TrimSpace(platform))
- id := strings.TrimSpace(platformID)
- if p == "" || id == "" {
- return ""
- }
- return p + ":" + id // "teams:29:abc123"
-}
-```
-
----
-
-## ChatID Usage & Session Isolation
-
-**Location**: [pkg/agent/loop.go](pkg/agent/loop.go#L1250-L1270)
-
-### Current ChatID Role
-
-The `ChatID` field is **THE PRIMARY KEY** for conversation isolation:
-
-1. **Session Binding**: Each unique `ChatID` can map to a separate session depending on DMScope
-2. **Workspace Isolation**: When non-empty and not "direct", creates isolated agent workspace:
- ```go
- if isolationID != "" && isolationID != "direct" {
- // Create transient isolated instance for this chat session
- agent = NewAgentInstance(ac, cfg, baseAgent.Provider, isolationID)
- }
- ```
-3. **State Persistence**: Last ChatID tracked for workspace continuity
-
-**Example mapping**:
-- Single direct message with user → `ChatID = "teams:29:1ABC123"`
-- Team channel conversation → `ChatID = "teams-channel:xyz789"`
-- Group chat → `ChatID = "teams-groupchat:123abc"`
-
----
-
-## Session Key Construction & Resolution
-
-**Location**: [pkg/routing/session_key.go](pkg/routing/session_key.go) + [pkg/routing/route.go](pkg/routing/route.go)
-
-### RouteInput (What Channel Provides to Router)
-
-```go
-type RouteInput struct {
- Channel string // "teams" (if implemented)
- AccountID string // Bot account/app ID
- Peer *RoutePeer // Who message is from (user)
- ParentPeer *RoutePeer // Parent context (e.g., Team)
- GuildID string // Guild/workspace ID (if applicable)
- TeamID string // Teams Team ID (would go here)
-}
-```
-
-### ResolvedRoute Output
-
-```go
-type ResolvedRoute struct {
- AgentID string // Which agent handles this message
- SessionKey string // Session identifier pattern
- MainSessionKey string // Main session fallback
- MatchedBy string // How routing was matched
-}
-```
-
-### Session Key Patterns
-
-**DMScope** configuration determines how sessions are keyed:
-
-| DMScope Mode | Format | Example | Use Case |
-|--------------|--------|---------|----------|
-| `DMScopeMain` | `agent:agentid:main` | `agent:teams-bot:main` | Single shared session |
-| `DMScopePerPeer` | `agent:agentid:direct:peerid` | `agent:teams-bot:direct:user123` | Per-user sessions |
-| `DMScopePerChannelPeer` | `agent:agentid:channel:direct:peerid` | `agent:teams-bot:teams:direct:user123` | Per-channel-per-user |
-| `DMScopePerAccountChannelPeer` | `agent:agentid:channel:account:direct:peerid` | `agent:teams-bot:teams:acct1:direct:user123` | Per-account-channel-user |
-
-**Location**: [pkg/routing/session_key.go:40-100](pkg/routing/session_key.go#L40-L100)
-
-```go
-// For Teams direct message:
-BuildAgentPeerSessionKey(SessionKeyParams{
- AgentID: "teams-bot",
- Channel: "teams",
- AccountID: "bot-app-id",
- Peer: &RoutePeer{Kind: "direct", ID: "29:abc123"},
- DMScope: DMScopePerChannelPeer,
-})
-// Returns: "agent:teams-bot:teams:direct:29:abc123"
-```
-
----
-
-## ID Priority Cascade for Agent Routing
-
-**Location**: [pkg/routing/route.go:68-126](pkg/routing/route.go#L68-L126)
-
-The agent resolver uses this **7-level priority**:
-
-1. **Peer binding** → Match on specific user/peer ID
-2. **Parent peer binding** → Match on parent context (Team, Guild, etc.)
-3. **Guild binding** → Match on Guild/Workspace ID
-4. **Team binding** → Match on Team ID ← **TEAMS WOULD USE THIS**
-5. **Account binding** → Match on account/app ID
-6. **Channel wildcard** → Match on channel with wildcard
-7. **Default agent** → Fallback
-
-**For Teams, routing would likely use**:
-- Level 2: ParentPeer = Team
-- Level 3: GuildID = Team ID
-- Level 4: TeamID = Team ID
-
----
-
-## Foundry Integration Status
-
-**Locations**:
-- [pkg/providers/factory_provider.go:196](pkg/providers/factory_provider.go#L196)
-- [pkg/providers/openai_compat/provider.go:432](pkg/providers/openai_compat/provider.go#L432)
-
-### Current Foundry Support
-
-**Type**: LLM **Provider Only** (NOT Channel)
-
-```go
-case "azure-ai", "azure-foundry":
- // Azure AI Foundry / Studio compatible with OpenAI API format
- // Used for LLM backend, not message channeling
-```
-
-**What's Missing for Teams/Foundry Integration**:
-- ❌ No Teams Channel handler
-- ❌ No Foundry Agent channel integration
-- ❌ No Teams webhook receiver
-- ❌ No Teams message routing
-
-**What Exists**:
-- ✅ Azure AI Foundry as LLM provider backend
-- ✅ OpenAI-compatible API handling
-- ✅ Generic inbound message bus infrastructure
-
----
-
-## Metadata Field Usage
-
-All channels populate `InboundMessage.Metadata` with platform-specific data:
-
-### Example: WeCom (for comparison)
-**Location**: [pkg/channels/wecom/app.go:605-620](pkg/channels/wecom/app.go#L605-L620)
-
-```go
-metadata := map[string]string{
- "msg_type": msg.MsgType,
- "msg_id": fmt.Sprintf("%d", msg.MsgId),
- "agent_id": fmt.Sprintf("%d", msg.AgentID),
- "platform": "wecom",
- "media_id": msg.MediaId,
- "create_time": fmt.Sprintf("%d", msg.CreateTime),
-}
-```
-
-### For Teams Implementation, Would Include:
-
-```go
-metadata := map[string]string{
- "team_id": msg.TeamsTeamID,
- "channel_id": msg.TeamsChannelID,
- "service_url": msg.ServiceURL,
- "activity_id": msg.ActivityID,
- "conversation_id": msg.ConversationID,
- "from_user_id": msg.FromUserID,
- "platform": "teams",
- ...
-}
-```
-
----
-
-## Identity Matching System
-
-**Location**: [pkg/identity/identity.go](pkg/identity/identity.go)
-
-The framework provides legacy-compatible and modern identity matching:
-
-### Allowed Formats in Config
-
-```yaml
-allow_from:
- - "29:abc123" # Raw Teams user ID
- - "teams:29:abc123" # Canonical format
- - "@alice" # Username format
- - "29:abc123|alice" # Compound format
-```
-
-### Matching Logic
-
-```go
-func MatchAllowed(sender bus.SenderInfo, allowed string) bool {
- // 1. Try canonical "platform:id" first
- if platform, id, ok := ParseCanonicalID(allowed); ok {
- if sender.CanonicalID == BuildCanonicalID(platform, id) {
- return true
- }
- }
-
- // 2. Fall back to PlatformID or Username
- if sender.PlatformID == allowed { return true }
- if sender.Username == "@" + allowed { return true }
-
- return false
-}
-```
-
----
-
-## What a Teams Channel Implementation Would Need
-
-### Minimum Required Fields in InboundMessage
-
-```go
-InboundMessage{
- Channel: "teams",
- SenderID: userID, // Teams: "29:uuid"
- Sender: bus.SenderInfo{
- Platform: "teams",
- PlatformID: userID, // "29:uuid"
- CanonicalID: "teams:29:uuid",
- Username: userName,
- DisplayName: displayName,
- },
- ChatID: conversationID, // Teams ConversationReference.conversation_id
- Content: messageContent,
- Peer: bus.Peer{
- Kind: "direct" || "channel",
- ID: channelID || userID,
- },
- MessageID: activityID, // Teams Activity ID
- Metadata: map[string]string{
- "team_id": teamID,
- "channel_id": channelID,
- "service_url": serviceURL,
- // ... other Teams-specific fields
- },
-}
-```
-
-### Routing Setup in Config
-
-```yaml
-agents:
- routing:
- - agent_id: "teams-agent"
- match:
- channel: "teams"
- team_id: "team-xyz" # Route by Teams Team ID
-```
-
----
-
-## Key Takeaways for Teams + Foundry Integration
-
-1. **Framework is Ready**: GenericBus message structure can handle Teams IDs
-2. **ChatID is Primary**: Use Teams `ConversationReference.conversation_id` as ChatID for isolation
-3. **SessionKey Auto-Generated**: Routing + DMScope automatically creates session keys
-4. **Identity System Ready**: Canonical "teams:29:uuid" format supported
-5. **No Channel Implementation Yet**: Need to implement webhook receiver + message publisher
-6. **Foundry is Provider Only**: Currently only LLM backend, not messaging channel
-7. **User ID Format**: Teams uses `29:uuid` format - should populate both PlatformID and CanonicalID
-8. **Conversation Scope**: Teams conversation_id maps directly to InboundMessage.ChatID
-
----
-
-## Reference Architecture Files
-
-| Component | File | Key Types |
-|-----------|------|-----------|
-| Bus Types | [pkg/bus/types.go](pkg/bus/types.go) | InboundMessage, SenderInfo, Peer |
-| Routing | [pkg/routing/route.go](pkg/routing/route.go) | RouteInput, ResolvedRoute |
-| Session Keys | [pkg/routing/session_key.go](pkg/routing/session_key.go) | SessionKeyParams, DM scopes |
-| Identity | [pkg/identity/identity.go](pkg/identity/identity.go) | BuildCanonicalID, MatchAllowed |
-| Agent Loop | [pkg/agent/loop.go](pkg/agent/loop.go) | Message processing, session isolation |
-| Example Channel | [pkg/channels/wecom/app.go](pkg/channels/wecom/app.go) | Channel implementation pattern |
diff --git a/TEAMS_QUICK_REFERENCE.md b/TEAMS_QUICK_REFERENCE.md
deleted file mode 100644
index a789e1c34..000000000
--- a/TEAMS_QUICK_REFERENCE.md
+++ /dev/null
@@ -1,315 +0,0 @@
-# Quick Reference: Teams Integration Questions
-
-## Q1: Teams Channel Integration - Message Receiving & Processing
-
-**Status**: ❌ NOT IMPLEMENTED
-
-**Where it would go**: `pkg/channels/teams/` (currently doesn't exist)
-
-**Current Similar Implementation**: See [pkg/channels/wecom/app.go](pkg/channels/wecom/app.go) for webhook pattern
-
-**Expected Pattern**:
-1. HTTP webhook receiver on configured port
-2. Verify Teams Bot Framework signature
-3. Parse activity/message payload
-4. Build `InboundMessage` struct
-5. Publish to bus via `channel.HandleMessage()` or `messageBus.PublishInbound()`
-
-**Key Files to Reference**:
-- [pkg/channels/base.go](pkg/channels/base.go) - Base channel interface
-- [pkg/channels/manager.go](pkg/channels/manager.go) - Channel registration/lifecycle
-- [pkg/channels/wecom/app.go:605-650](pkg/channels/wecom/app.go#L605-L650) - HandleMessage pattern
-
----
-
-## Q2: InboundMessage Structure - All Available Fields
-
-**Location**: [pkg/bus/types.go:18-35](pkg/bus/types.go#L18-L35)
-
-### Complete Field List
-
-| Field | Type | Purpose | Example |
-|-------|------|---------|---------|
-| `Channel` | string | Platform identifier | `"teams"` |
-| `SenderID` | string | Raw user ID | `"29:1ABC123"` |
-| `Sender` | SenderInfo | Structured identity | (see below) |
-| `Sender.Platform` | string | Platform name | `"teams"` |
-| `Sender.PlatformID` | string | User platform ID | `"29:1ABC123"` |
-| `Sender.CanonicalID` | string | **Normalized format** | `"teams:29:1abc123"` |
-| `Sender.Username` | string | Handle/username | `"alice"` |
-| `Sender.DisplayName` | string | Full display name | `"Alice Smith"` |
-| `ChatID` | string | **Conversation ID (PRIMARY)** | `"teams-conv-abc123"` |
-| `Content` | string | Message text | `"Hello world"` |
-| `Media` | []string | Media references | `["media://ref123"]` |
-| `Peer.Kind` | string | Peer type | `"direct"` \| `"channel"` |
-| `Peer.ID` | string | Peer ID | User/channel ID |
-| `MessageID` | string | Platform message ID | `"activity-123"` |
-| `MediaScope` | string | Media cleanup scope | `"teams:conv-abc123:msg-123"` |
-| `SessionKey` | string | **Session identifier** | `"agent:bot:teams:direct:29:abc123"` |
-| `Metadata` | map | Platform-specific data | (see below) |
-
-### Metadata Map (Platform-Specific)
-
-```go
-metadata := map[string]string{
- "team_id": "T12345",
- "channel_id": "C12345",
- "conversation_id": "19:...",
- "service_url": "https://smba.trafficmanager.net/...",
- "activity_id": "...",
- "from_user_id": "29:...",
- "from_user_name": "alice",
- "recipient_id": "28:...",
- "conversation_type": "personal|groupChat|channel",
- "platform": "teams",
- // ... any other Teams-specific fields
-}
-```
-
----
-
-## Q3: Unique User/Conversation ID Capture from Teams
-
-### What Teams Provides vs. What PicoClaw Needs
-
-**Teams → PicoClaw Mapping**:
-
-```
-Teams Activity Object
-├── from.id → SenderID (raw), Sender.PlatformID
-├── from.aadObjectId → (optional, use if available)
-├── conversation.id → ChatID (THE KEY FIELD)
-├── conversation.tenantId → Metadata["tenant_id"]
-├── channelData.teamsChannelId → Peer.ID (if channel)
-├── channelData.teamsTeamId → Metadata["team_id"], routing input
-├── serviceUrl → Metadata["service_url"]
-└── id → MessageID
-```
-
-### ID Construction
-
-**User Identity Chain**:
-```
-Teams: from.id = "29:U123ABC"
- ↓
-Stored as: SenderID = "29:U123ABC"
-Stored as: Sender.PlatformID = "29:U123ABC"
-Normalized as: Sender.CanonicalID = "teams:29:u123abc" (lowercased)
-```
-
-**Conversation Identity Chain**:
-```
-Teams: conversation.id = "19:abc123@thread.v2"
- ↓
-Stored as: ChatID = "19:abc123@thread.v2" (conversation scope)
-Used for: Session isolation, message routing, state persistence
-```
-
-**Team Identity Chain**:
-```
-Teams: channelData.teamsTeamId = "T12345678"
- ↓
-Stored as: Metadata["team_id"] = "T12345678"
- ↓
-Used in: Routing cascade (Level 4), agent selection
-```
-
-### Canonical ID Format
-
-Built by [pkg/identity/identity.go:BuildCanonicalID()](pkg/identity/identity.go#L11-L20):
-
-```go
-BuildCanonicalID("teams", "29:U123ABC")
-// Returns: "teams:29:u123abc" (normalized to lowercase)
-```
-
-**Used for**:
-- Access control matching
-- Cross-platform user linking (via identity_links in config)
-- User identity validation
-
----
-
-## Q4: Foundry Agent Integration Points & ID Provision
-
-**Status**: ⚠️ PARTIAL - Foundry is an LLM Provider, NOT a Channel
-
-### Current Foundry Support
-
-**Location**: [pkg/providers/factory_provider.go:196](pkg/providers/factory_provider.go#L196)
-
-Foundry is integrated **only as LLM backend** (OpenAI-compatible API):
-
-```go
-case "azure-ai", "azure_foundry":
- // Use for model calls, not messaging
-```
-
-**What Foundry Would Provide (if implemented as channel)**:
-- Foundry Agent service/conversation IDs
-- Foundry user session tracking
-- Foundry-specific message format
-
-**What's MISSING**:
-1. ❌ Foundry Agent channel receiver
-2. ❌ Foundry conversation → ChatID mapping
-3. ❌ Foundry agent ID → Agent routing
-
-### If Foundry Channel Were to Exist
-
-Expected `InboundMessage` would be:
-
-```go
-InboundMessage{
- Channel: "foundry-agent",
- SenderID: foundryUserID,
- Sender: SenderInfo{
- Platform: "foundry",
- PlatformID: foundryUserID,
- CanonicalID: "foundry:" + foundryUserID,
- DisplayName: userName,
- },
- ChatID: foundryConversationID, // Critical for isolation
- Content: message,
- Metadata: map[string]string{
- "foundry_agent_id": agentID,
- "foundry_conversation_id": conversationID,
- "foundry_message_id": messageID,
- "platform": "foundry",
- // ... other Foundry fields
- },
-}
-```
-
-### Foundry ID Mapping Table (Hypothetical)
-
-| Foundry ID | InboundMessage Field | Purpose |
-|----------|----------------------|---------|
-| Agent ID | Routing/Config | Which agent handles |
-| User ID | SenderID | Who sent message |
-| Conversation ID | **ChatID** | Session isolation |
-| Message ID | MessageID | For threading |
-| Service Endpoint | Metadata | For API calls |
-
----
-
-## Q5: How ChatID is Currently Used for Session ID Association
-
-**Location**: [pkg/agent/loop.go:1248-1270](pkg/agent/loop.go#L1248-L1270)
-
-### ChatID → SessionKey Conversion
-
-**Process**:
-
-```
-1. InboundMessage arrives with ChatID
- ↓
-2. Router resolves agent (via RouteInput)
- ↓
-3. SessionKey built from:
- - Agent ID
- - Channel name
- - Peer information (ChatID wrapped as Peer.ID)
- - DMScope configuration
- ↓
-4. Result: SessionKey = "agent:botname:team:type:id"
- ↓
-5. SessionKey used to find/create workspace & history
-```
-
-### Session Key Patterns by DMScope
-
-**From config `session.dm_scope`**:
-
-| Setting | Session Behavior | Key Format |
-|---------|------------------|-----------|
-| Not set / `main` | Single shared session | `agent:bot:main` |
-| `per_peer` | One session per user | `agent:bot:direct:user123` |
-| `per_channel_peer` | One per channel+user | `agent:bot:teams:direct:user123` |
-| `per_account_channel_peer` | One per account+channel+user | `agent:bot:teams:act1:direct:user123` |
-
-**Code Reference**: [pkg/routing/session_key.go:40-100](pkg/routing/session_key.go#L40-L100)
-
-### Session Isolation via ChatID
-
-When ChatID is unique and non-"direct":
-
-```go
-// From pkg/agent/loop.go:1248-1270
-if isolationID != "" && isolationID != "direct" {
- // Creates isolated agent instance with separate:
- // - Workspace directory
- // - Session history
- // - Memory storage
- // - State
- agent = NewAgentInstance(ac, cfg, baseAgent.Provider, isolationID)
-}
-```
-
-**Isolation Example**:
-
-```
-ChatID = "teams-channel-abc123"
- ↓
-Creates: workspace/teams-channel-abc123/
- ├── sessions/
- ├── memory/
- ├── skills/
- └── state/
- ↓
-Each channel conversation has completely isolated history
-```
-
-### State Persistence
-
-Tracks last ChatID:
-
-```go
-// Record last chat for workspace continuity
-al.RecordLastChatID(chatID) // pkg/agent/loop.go
-```
-
-Stored in: `workspace/state/state.json`:
-```json
-{
- "last_channel": "teams",
- "last_chat_id": "19:abc123@thread.v2",
- "timestamp": "2025-03-26T10:00:00Z"
-}
-```
-
----
-
-## Summary Table: ID Field Mapping
-
-| Concept | Field | Example | Used For |
-|---------|-------|---------|----------|
-| **User** | `SenderID` + `Sender.PlatformID` | `"29:U123ABC"` | Message author |
-| **User (Normalized)** | `Sender.CanonicalID` | `"teams:29:u123abc"` | Access control |
-| **Conversation** | `ChatID` | `"19:abc123@thread.v2"` | **Session isolation** |
-| **Team** | `Metadata["team_id"]` | `"T12345678"` | Agent routing level |
-| **Channel** | `Peer.Kind` + `Peer.ID` | `"channel:C12345"` | Routing peer |
-| **Message** | `MessageID` | `"activity-123"` | Threading, dedup |
-| **Workspace** | Derived from ChatID | `workspace/19:abc123@thread.v2/` | Data isolation |
-| **Session** | `SessionKey` | `"agent:bot:teams:direct:29:u123abc"` | History tracking |
-
----
-
-## File Cross-References
-
-### For Teams Implementation
-- Start: [pkg/channels/manager.go](pkg/channels/manager.go) - Channel registration
-- Reference: [pkg/channels/wecom/app.go](pkg/channels/wecom/app.go) - Full implementation pattern
-- Base: [pkg/channels/base.go](pkg/channels/base.go) - Handler interface
-
-### For Routing/Session
-- Routing: [pkg/routing/route.go](pkg/routing/route.go) - 7-level cascade
-- Keys: [pkg/routing/session_key.go](pkg/routing/session_key.go) - Key building
-- Isolation: [pkg/agent/loop.go:1248+](pkg/agent/loop.go#L1248) - ChatID isolation
-
-### For Identity
-- Identity: [pkg/identity/identity.go](pkg/identity/identity.go) - CanonicalID logic
-- Matching: Lines 28-100 - Access control matching
-
-### For State
-- State: [pkg/state/state.go](pkg/state/state.go) - LastChatID persistence
diff --git a/assets/wechat.png b/assets/wechat.png
index 07a05dd91d0bbcd0b380df27591deb9e20d45149..d538f40e644ec6613adc74b2b5a6690d6dceb290 100644
GIT binary patch
literal 100337
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