Merge PR #1519
This commit is contained in:
commit
cf93a7fd03
6 changed files with 648 additions and 15 deletions
63
README.md
63
README.md
|
|
@ -312,7 +312,7 @@ picoclaw onboard
|
|||
* [Tavily](https://tavily.com) - Optimized for AI Agents (1000 requests/month)
|
||||
* DuckDuckGo - Built-in fallback (no API key required)
|
||||
|
||||
> **Note**: See `config.example.json` for a complete configuration template.
|
||||
> **Note**: See [config/config.example.json](config/config.example.json) for a complete template, and [docs/configuration.md](docs/configuration.md) for multi-agent, routing, session, channel, and tool-limit reference notes.
|
||||
|
||||
**4. Chat**
|
||||
|
||||
|
|
@ -752,6 +752,8 @@ Connect Picoclaw to the Agent Social Network simply by sending a single message
|
|||
|
||||
Config file: `~/.picoclaw/config.json`
|
||||
|
||||
Start from [config/config.example.json](config/config.example.json). For advanced fields that are easy to miss, see [docs/configuration.md](docs/configuration.md) and [docs/tools_configuration.md](docs/tools_configuration.md).
|
||||
|
||||
### Environment Variables
|
||||
|
||||
You can override default paths using environment variables. This is useful for portable installations, containerized deployments, or running picoclaw as a system service. These variables are independent and control different paths.
|
||||
|
|
@ -1018,6 +1020,59 @@ This design also enables **multi-agent support** with flexible provider selectio
|
|||
- **Load balancing**: Distribute requests across multiple endpoints
|
||||
- **Centralized configuration**: Manage all providers in one place
|
||||
|
||||
#### Multi-Agent and Bindings
|
||||
|
||||
Use `agents.list` as an array of agent definitions, then route specific traffic with top-level `bindings`:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4",
|
||||
"routing": {
|
||||
"enabled": true,
|
||||
"light_model": "deepseek",
|
||||
"threshold": 0.35
|
||||
}
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"id": "main",
|
||||
"default": true,
|
||||
"model": "gpt-5.4",
|
||||
"subagents": {
|
||||
"allow_agents": ["coder"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coder",
|
||||
"workspace": "~/.picoclaw/workspace/code",
|
||||
"model": {
|
||||
"primary": "claude-sonnet-4.6",
|
||||
"fallbacks": ["gpt-5.4"]
|
||||
},
|
||||
"skills": ["Code", "git-essentials"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"bindings": [
|
||||
{
|
||||
"agent_id": "coder",
|
||||
"match": {
|
||||
"channel": "telegram",
|
||||
"account_id": "*",
|
||||
"peer": {
|
||||
"kind": "direct",
|
||||
"id": "123456789"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The `skills` field limits which skills are visible to that agent. Tool permissions still come from the global `tools` section. For `dm_scope`, `identity_links`, binding priority, Pico/IRC channel blocks, and tool hard limits such as `web.fetch_limit_bytes`, see [docs/configuration.md](docs/configuration.md).
|
||||
|
||||
#### 📋 All Supported Vendors
|
||||
|
||||
| Vendor | `model` Prefix | Default API Base | Protocol | API Key |
|
||||
|
|
@ -1308,12 +1363,14 @@ picoclaw agent -m "Hello"
|
|||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model": "anthropic/claude-opus-4-5"
|
||||
"model": "openrouter/anthropic/claude-sonnet-4.6"
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"dm_scope": "per-channel-peer",
|
||||
"backlog_limit": 20
|
||||
"identity_links": {
|
||||
"me": ["telegram:123456789", "discord:me#1234"]
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"openrouter": {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,85 @@
|
|||
"defaults": {
|
||||
"workspace": "~/.picoclaw/workspace",
|
||||
"restrict_to_workspace": true,
|
||||
"allow_read_outside_workspace": false,
|
||||
"model_name": "gpt-5.4",
|
||||
"model_fallbacks": [
|
||||
"claude-sonnet-4.6"
|
||||
],
|
||||
"image_model": "gpt-5.4",
|
||||
"image_model_fallbacks": [],
|
||||
"max_tokens": 8192,
|
||||
"temperature": 0.7,
|
||||
"max_tool_iterations": 20,
|
||||
"summarize_message_threshold": 20,
|
||||
"summarize_token_percent": 75
|
||||
"summarize_token_percent": 75,
|
||||
"max_media_size": 20971520,
|
||||
"routing": {
|
||||
"enabled": false,
|
||||
"light_model": "deepseek",
|
||||
"threshold": 0.35
|
||||
}
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"id": "main",
|
||||
"default": true,
|
||||
"name": "Main Assistant",
|
||||
"model": "gpt-5.4",
|
||||
"subagents": {
|
||||
"allow_agents": [
|
||||
"coder"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coder",
|
||||
"name": "Code Agent",
|
||||
"workspace": "~/.picoclaw/workspace/code",
|
||||
"model": {
|
||||
"primary": "claude-sonnet-4.6",
|
||||
"fallbacks": [
|
||||
"gpt-5.4"
|
||||
]
|
||||
},
|
||||
"skills": [
|
||||
"Code",
|
||||
"git-essentials"
|
||||
],
|
||||
"subagents": {
|
||||
"allow_agents": [
|
||||
"main"
|
||||
],
|
||||
"model": {
|
||||
"primary": "claude-sonnet-4.6",
|
||||
"fallbacks": [
|
||||
"gpt-5.4"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"bindings": [
|
||||
{
|
||||
"agent_id": "coder",
|
||||
"match": {
|
||||
"channel": "telegram",
|
||||
"account_id": "*",
|
||||
"peer": {
|
||||
"kind": "direct",
|
||||
"id": "YOUR_TELEGRAM_USER_ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"session": {
|
||||
"dm_scope": "per-channel-peer",
|
||||
"identity_links": {
|
||||
"duomi": [
|
||||
"telegram:YOUR_TELEGRAM_USER_ID",
|
||||
"discord:YOUR_DISCORD_USER_ID"
|
||||
]
|
||||
}
|
||||
},
|
||||
"model_list": [
|
||||
|
|
@ -75,6 +148,13 @@
|
|||
"allow_from": [
|
||||
"YOUR_USER_ID"
|
||||
],
|
||||
"typing": {
|
||||
"enabled": true
|
||||
},
|
||||
"placeholder": {
|
||||
"enabled": true,
|
||||
"text": "Thinking... 💭"
|
||||
},
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"discord": {
|
||||
|
|
@ -85,6 +165,13 @@
|
|||
"group_trigger": {
|
||||
"mention_only": false
|
||||
},
|
||||
"typing": {
|
||||
"enabled": true
|
||||
},
|
||||
"placeholder": {
|
||||
"enabled": true,
|
||||
"text": "Thinking... 💭"
|
||||
},
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"qq": {
|
||||
|
|
@ -92,6 +179,11 @@
|
|||
"app_id": "YOUR_QQ_APP_ID",
|
||||
"app_secret": "YOUR_QQ_APP_SECRET",
|
||||
"allow_from": [],
|
||||
"group_trigger": {
|
||||
"mention_only": true
|
||||
},
|
||||
"max_message_length": 2000,
|
||||
"send_markdown": false,
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"maixcam": {
|
||||
|
|
@ -124,6 +216,9 @@
|
|||
"client_id": "YOUR_CLIENT_ID",
|
||||
"client_secret": "YOUR_CLIENT_SECRET",
|
||||
"allow_from": [],
|
||||
"group_trigger": {
|
||||
"mention_only": true
|
||||
},
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"slack": {
|
||||
|
|
@ -131,6 +226,16 @@
|
|||
"bot_token": "xoxb-YOUR-BOT-TOKEN",
|
||||
"app_token": "xapp-YOUR-APP-TOKEN",
|
||||
"allow_from": [],
|
||||
"group_trigger": {
|
||||
"mention_only": true
|
||||
},
|
||||
"typing": {
|
||||
"enabled": true
|
||||
},
|
||||
"placeholder": {
|
||||
"enabled": true,
|
||||
"text": "Thinking... 💭"
|
||||
},
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"matrix": {
|
||||
|
|
@ -154,8 +259,20 @@
|
|||
"enabled": false,
|
||||
"channel_secret": "YOUR_LINE_CHANNEL_SECRET",
|
||||
"channel_access_token": "YOUR_LINE_CHANNEL_ACCESS_TOKEN",
|
||||
"webhook_host": "0.0.0.0",
|
||||
"webhook_port": 18791,
|
||||
"webhook_path": "/webhook/line",
|
||||
"allow_from": [],
|
||||
"group_trigger": {
|
||||
"mention_only": true
|
||||
},
|
||||
"typing": {
|
||||
"enabled": true
|
||||
},
|
||||
"placeholder": {
|
||||
"enabled": true,
|
||||
"text": "Thinking... 💭"
|
||||
},
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"onebot": {
|
||||
|
|
@ -165,6 +282,18 @@
|
|||
"reconnect_interval": 5,
|
||||
"group_trigger_prefix": [],
|
||||
"allow_from": [],
|
||||
"group_trigger": {
|
||||
"prefixes": [
|
||||
"!pico"
|
||||
]
|
||||
},
|
||||
"typing": {
|
||||
"enabled": false
|
||||
},
|
||||
"placeholder": {
|
||||
"enabled": true,
|
||||
"text": "Thinking... 💭"
|
||||
},
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"wecom": {
|
||||
|
|
@ -176,6 +305,9 @@
|
|||
"webhook_path": "/webhook/wecom",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5,
|
||||
"group_trigger": {
|
||||
"mention_only": true
|
||||
},
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"wecom_app": {
|
||||
|
|
@ -189,6 +321,9 @@
|
|||
"webhook_path": "/webhook/wecom-app",
|
||||
"allow_from": [],
|
||||
"reply_timeout": 5,
|
||||
"group_trigger": {
|
||||
"mention_only": true
|
||||
},
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"wecom_aibot": {
|
||||
|
|
@ -201,6 +336,21 @@
|
|||
"welcome_message": "Hello! I'm your AI assistant. How can I help you today?",
|
||||
"reasoning_channel_id": ""
|
||||
},
|
||||
"pico": {
|
||||
"enabled": false,
|
||||
"token": "YOUR_PICO_CHANNEL_TOKEN",
|
||||
"allow_token_query": false,
|
||||
"allow_origins": [],
|
||||
"ping_interval": 30,
|
||||
"read_timeout": 60,
|
||||
"write_timeout": 10,
|
||||
"max_connections": 100,
|
||||
"allow_from": [],
|
||||
"placeholder": {
|
||||
"enabled": true,
|
||||
"text": "Thinking... 💭"
|
||||
}
|
||||
},
|
||||
"irc": {
|
||||
"enabled": false,
|
||||
"server": "irc.libera.chat:6697",
|
||||
|
|
@ -307,6 +457,7 @@
|
|||
"allow_write_paths": null,
|
||||
"web": {
|
||||
"enabled": true,
|
||||
"proxy": "",
|
||||
"brave": {
|
||||
"enabled": false,
|
||||
"api_key": "YOUR_BRAVE_API_KEY",
|
||||
|
|
@ -426,8 +577,10 @@
|
|||
"exec": {
|
||||
"enabled": true,
|
||||
"enable_deny_patterns": true,
|
||||
"allow_remote": true,
|
||||
"custom_deny_patterns": null,
|
||||
"custom_allow_patterns": null
|
||||
"custom_allow_patterns": null,
|
||||
"timeout_seconds": 60
|
||||
},
|
||||
"skills": {
|
||||
"enabled": true,
|
||||
|
|
@ -481,7 +634,8 @@
|
|||
"enabled": true
|
||||
},
|
||||
"read_file": {
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"max_read_file_size": 65536
|
||||
},
|
||||
"spawn": {
|
||||
"enabled": true
|
||||
|
|
|
|||
246
docs/configuration.md
Normal file
246
docs/configuration.md
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
# Configuration Reference
|
||||
|
||||
`config/config.example.json` is the copy-paste template. This document is the field guide for the parts that are easy to miss when you only skim `README.md`.
|
||||
|
||||
## Top-Level Layout
|
||||
|
||||
| Key | Type | Default | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `agents` | object | required | Agent defaults plus optional `agents.list` overrides |
|
||||
| `bindings` | array | `[]` | Route specific channels/peers/accounts to specific agent IDs |
|
||||
| `session` | object | `{"dm_scope":"per-channel-peer"}` | Controls DM session isolation and cross-platform identity linking |
|
||||
| `model_list` | array | built-in starter list | Preferred provider/model configuration |
|
||||
| `channels` | object | channel-specific defaults | Each channel stays disabled until `enabled: true` |
|
||||
| `tools` | object | see [Tools Configuration](tools_configuration.md) | Global tool availability and limits |
|
||||
| `heartbeat` | object | `{"enabled":true,"interval":30}` | Periodic task loop |
|
||||
| `devices` | object | `{"enabled":false,"monitor_usb":true}` | USB/device integration |
|
||||
| `voice` | object | `{"echo_transcription":false}` | Voice UX tweaks |
|
||||
| `gateway` | object | `{"host":"127.0.0.1","port":18790}` | Shared HTTP server for webhook channels |
|
||||
|
||||
## Agent Defaults
|
||||
|
||||
`agents.defaults` sets the baseline for every agent instance. Entries in `agents.list` override these fields per agent.
|
||||
|
||||
| Field | Type | Default | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `workspace` | string | `~/.picoclaw/workspace` | Base workspace for the implicit main agent |
|
||||
| `restrict_to_workspace` | bool | `true` | Restricts file and exec tools to the workspace boundary |
|
||||
| `allow_read_outside_workspace` | bool | `false` | Lets read-only tools escape the workspace while writes stay restricted |
|
||||
| `model_name` | string | empty | Preferred alias from `model_list` |
|
||||
| `model` | string | empty | Deprecated alias for `model_name`; still accepted |
|
||||
| `model_fallbacks` | array | `[]` | Fallback aliases from `model_list` |
|
||||
| `image_model` | string | empty | Separate model alias for image tasks |
|
||||
| `image_model_fallbacks` | array | `[]` | Image-model fallback aliases |
|
||||
| `max_tokens` | int | `32768` | Agent response budget |
|
||||
| `temperature` | number or null | provider default | `null` means do not override provider defaults |
|
||||
| `max_tool_iterations` | int | `50` | Upper bound on tool loop turns |
|
||||
| `summarize_message_threshold` | int | `20` | Start compressing history after this many messages |
|
||||
| `summarize_token_percent` | int | `75` | Compression target relative to current context |
|
||||
| `max_media_size` | int | `20971520` | Per-attachment cap in bytes (20 MB) |
|
||||
| `routing` | object | disabled | Lightweight model-routing classifier for cheap/simple turns |
|
||||
|
||||
### Routing
|
||||
|
||||
When `agents.defaults.routing.enabled` is `true`, PicoClaw scores each incoming message. Messages below `threshold` are sent to `light_model`; everything else stays on the agent's primary model.
|
||||
|
||||
| Field | Type | Default | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `enabled` | bool | `false` | Turns routing on/off |
|
||||
| `light_model` | string | empty | Alias from `model_list` used for simpler turns |
|
||||
| `threshold` | number | `0` | Score in `[0,1]`; lower scores are considered simpler |
|
||||
|
||||
## Multi-Agent Setup
|
||||
|
||||
`agents.list` is an array, not a map. Each entry creates a concrete agent instance.
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | string | yes | Stable agent ID used by bindings and subagent permissions |
|
||||
| `default` | bool | no | Marks the default agent; otherwise the first list entry wins |
|
||||
| `name` | string | no | Human-readable label |
|
||||
| `workspace` | string | no | Per-agent workspace override |
|
||||
| `model` | string or object | no | Either `"gpt-5.4"` or `{"primary":"gpt-5.4","fallbacks":["claude-sonnet-4.6"]}` |
|
||||
| `skills` | array | no | Limits which `SKILL.md` entries are exposed to this agent |
|
||||
| `subagents` | object | no | Controls which agents this agent may spawn |
|
||||
|
||||
### Subagent Rules
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `allow_agents` | array | Agent IDs this agent may spawn, or `"*"` to allow every configured agent |
|
||||
| `model` | string or object | Override the model/fallback set used when this agent spawns a subagent |
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"model_name": "gpt-5.4",
|
||||
"routing": {
|
||||
"enabled": true,
|
||||
"light_model": "deepseek",
|
||||
"threshold": 0.35
|
||||
}
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"id": "main",
|
||||
"default": true,
|
||||
"model": "gpt-5.4",
|
||||
"subagents": {
|
||||
"allow_agents": ["coder"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "coder",
|
||||
"workspace": "~/.picoclaw/workspace/code",
|
||||
"model": {
|
||||
"primary": "claude-sonnet-4.6",
|
||||
"fallbacks": ["gpt-5.4"]
|
||||
},
|
||||
"skills": ["Code", "git-essentials"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"bindings": [
|
||||
{
|
||||
"agent_id": "coder",
|
||||
"match": {
|
||||
"channel": "telegram",
|
||||
"account_id": "*",
|
||||
"peer": {
|
||||
"kind": "direct",
|
||||
"id": "123456789"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`skills` only filters visible skills. Tool permissions still come from the global `tools.*` config and the workspace sandbox.
|
||||
|
||||
## Bindings
|
||||
|
||||
Bindings route inbound traffic to an agent before any tool execution starts.
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `agent_id` | string | Target agent ID |
|
||||
| `match.channel` | string | Required channel name such as `telegram`, `discord`, `feishu` |
|
||||
| `match.account_id` | string | Optional multi-account filter; use `*` for any account |
|
||||
| `match.peer.kind` | string | Optional peer type: `direct`, `group`, or `channel` |
|
||||
| `match.peer.id` | string | Optional peer ID |
|
||||
| `match.guild_id` | string | Optional Discord guild match |
|
||||
| `match.team_id` | string | Optional Slack team match |
|
||||
|
||||
Binding priority is fixed in code:
|
||||
|
||||
1. `peer`
|
||||
2. `parent_peer`
|
||||
3. `guild_id`
|
||||
4. `team_id`
|
||||
5. `account_id`
|
||||
6. channel-wide wildcard (`account_id: "*"`)
|
||||
7. default agent
|
||||
|
||||
## Session Management
|
||||
|
||||
### DM Scope
|
||||
|
||||
`session.dm_scope` only affects direct messages. Group and channel peers always keep their own per-peer session keys.
|
||||
|
||||
| Value | Behavior |
|
||||
| --- | --- |
|
||||
| `main` | Reuse a single shared main session |
|
||||
| `per-peer` | One DM session per person across all channels |
|
||||
| `per-channel-peer` | One DM session per channel/person pair |
|
||||
| `per-account-channel-peer` | One DM session per account + channel + person pair |
|
||||
|
||||
### Identity Links
|
||||
|
||||
`session.identity_links` collapses multiple platform IDs into one canonical identity when DM scope is not `main`.
|
||||
|
||||
```json
|
||||
{
|
||||
"session": {
|
||||
"dm_scope": "per-peer",
|
||||
"identity_links": {
|
||||
"duomi": [
|
||||
"telegram:123456789",
|
||||
"discord:duomi#1234",
|
||||
"feishu:ou_xxx"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If a DM arrives from any listed identity, PicoClaw reuses the canonical session key (`duomi` in this example).
|
||||
|
||||
## Channels
|
||||
|
||||
Channel docs under `docs/channels/` remain the setup guide for tokens and platform-specific steps. The table below focuses on shared runtime fields that were missing from the central docs.
|
||||
|
||||
### Shared Channel Fields
|
||||
|
||||
| Field | Applies To | Notes |
|
||||
| --- | --- | --- |
|
||||
| `allow_from` | most channels | Whitelist of users, groups, or room IDs allowed to talk to the bot |
|
||||
| `group_trigger.mention_only` | group-capable channels | Reply only when mentioned |
|
||||
| `group_trigger.prefixes` | prefix-based channels | Reply when a message starts with one of the configured prefixes |
|
||||
| `typing.enabled` | Telegram, Discord, Slack, LINE, OneBot, IRC | Enables typing indicators where supported |
|
||||
| `placeholder.enabled` | Telegram, Feishu, Discord, Matrix, LINE, OneBot, Pico, Slack | Send a quick placeholder response before the final answer |
|
||||
| `placeholder.text` | same as above | Placeholder message text |
|
||||
| `reasoning_channel_id` | supported channels | Redirect long reasoning traces to a separate channel/room |
|
||||
|
||||
### Less-Documented Channel Blocks
|
||||
|
||||
| Channel | Field | Default | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `qq` | `max_message_length` | `2000` | Hard cap before QQ responses are split |
|
||||
| `qq` | `send_markdown` | `false` | Prefer QQ markdown cards when supported |
|
||||
| `matrix` | `device_id` | empty | Optional device identifier for login/session reuse |
|
||||
| `matrix` | `join_on_invite` | `true` | Auto-join rooms when invited |
|
||||
| `matrix` | `message_format` | empty | Rendering mode override |
|
||||
| `maixcam` | `host` / `port` | `0.0.0.0` / `18790` | TCP endpoint PicoClaw listens on for MaixCam clients |
|
||||
| `wecom` / `wecom_app` / `wecom_aibot` | `reply_timeout` | `5` | Timeout in seconds before falling back to async behavior |
|
||||
| `wecom_aibot` | `max_steps` | `10` | Maximum streaming steps per response |
|
||||
| `wecom_aibot` | `welcome_message` | built-in greeting | Sent on `enter_chat`; empty disables it |
|
||||
| `pico` | `allow_token_query` | `false` | Accept auth token in query string instead of headers |
|
||||
| `pico` | `allow_origins` | `[]` | CORS allow-list for browser clients |
|
||||
| `pico` | `ping_interval` | `30` | WebSocket ping interval in seconds |
|
||||
| `pico` | `read_timeout` | `60` | Max read wait in seconds |
|
||||
| `pico` | `write_timeout` | `10` | Max write wait in seconds |
|
||||
| `pico` | `max_connections` | `100` | Concurrent connection ceiling |
|
||||
| `irc` | `request_caps` | `["server-time","message-tags"]` in example | Requested IRC capabilities |
|
||||
| `irc` | `typing.enabled` | `false` | Whether to emit typing indicators into IRC |
|
||||
|
||||
## Tool Limits and Escape Hatches
|
||||
|
||||
See [Tools Configuration](tools_configuration.md) for full per-tool examples. These are the fields most likely to affect safety or operational limits:
|
||||
|
||||
| Field | Default | Notes |
|
||||
| --- | --- | --- |
|
||||
| `tools.allow_read_paths` | `null` | Regex allow-list applied on top of the workspace sandbox for read-only tools |
|
||||
| `tools.allow_write_paths` | `null` | Regex allow-list for write operations |
|
||||
| `tools.web.proxy` | empty | Optional proxy for web search/fetch |
|
||||
| `tools.web.fetch_limit_bytes` | `10485760` | Max bytes fetched by web tools before truncation/refusal |
|
||||
| `tools.exec.allow_remote` | `true` | Allows exec from remote channels; set `false` to require internal channels only |
|
||||
| `tools.exec.custom_allow_patterns` | `null` | Regex allow-list evaluated before deny rules |
|
||||
| `tools.exec.custom_deny_patterns` | `null` | Extra regex deny rules |
|
||||
| `tools.exec.timeout_seconds` | `60` | Per-command timeout |
|
||||
| `tools.read_file.max_read_file_size` | `65536` | Max bytes returned by the read-file tool |
|
||||
| `tools.media_cleanup.max_age_minutes` | `30` | Delete old media files after this many minutes |
|
||||
| `tools.media_cleanup.interval_minutes` | `5` | Cleanup sweep interval |
|
||||
| `tools.skills.max_concurrent_searches` | `2` | Limits parallel skill searches/download lookups |
|
||||
| `tools.skills.search_cache.max_size` | `50` | Skill search cache size |
|
||||
| `tools.skills.search_cache.ttl_seconds` | `300` | Skill search cache TTL |
|
||||
| `tools.mcp.discovery.ttl` | `5` | How many turns a discovered MCP tool remains unlocked |
|
||||
|
||||
## See Also
|
||||
|
||||
- [config/config.example.json](../config/config.example.json)
|
||||
- [Tools Configuration](tools_configuration.md)
|
||||
- [Model-list Migration Guide](migration/model-list-migration.md)
|
||||
|
|
@ -179,18 +179,22 @@ Identify protocol via prefix in `model` field:
|
|||
"defaults": {
|
||||
"model": "deepseek-chat"
|
||||
},
|
||||
"coder": {
|
||||
"model": "gpt-5.4",
|
||||
"system_prompt": "You are a coding assistant..."
|
||||
},
|
||||
"translator": {
|
||||
"model": "claude-sonnet-4.6"
|
||||
}
|
||||
"list": [
|
||||
{
|
||||
"id": "coder",
|
||||
"default": true,
|
||||
"model": "gpt-5.4"
|
||||
},
|
||||
{
|
||||
"id": "translator",
|
||||
"model": "claude-sonnet-4.6"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each Agent only needs to specify `model` (corresponds to `model_name` in `model_list`).
|
||||
Each agent only needs to specify `model` (corresponds to `model_name` in `model_list`).
|
||||
|
||||
### 3.3 Industry Comparison
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
# Tools Configuration
|
||||
|
||||
PicoClaw's tools configuration is located in the `tools` field of `config.json`.
|
||||
PicoClaw's tools configuration lives under the `tools` field in `config.json`.
|
||||
For agent/session/channel fields outside the tool system, see [configuration.md](configuration.md).
|
||||
|
||||
## Top-Level Tool Gates
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `allow_read_paths` | array or null | `null` | Regex allow-list layered on top of the workspace sandbox for read-only tools |
|
||||
| `allow_write_paths` | array or null | `null` | Regex allow-list for write-capable tools |
|
||||
|
||||
## Directory Structure
|
||||
|
||||
|
|
@ -30,6 +38,14 @@ PicoClaw's tools configuration is located in the `tools` field of `config.json`.
|
|||
|
||||
Web tools are used for web search and fetching.
|
||||
|
||||
### Global Web Config
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `enabled` | bool | true | Enable/disable the entire web tool family |
|
||||
| `proxy` | string | `""` | Optional proxy URL for web search/fetch |
|
||||
| `fetch_limit_bytes` | int | `10485760` | Maximum response size fetched by web tools before truncation/refusal |
|
||||
|
||||
### Brave
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
|
|
@ -53,19 +69,53 @@ Web tools are used for web search and fetching.
|
|||
| `api_key` | string | - | Perplexity API key |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
||||
### Tavily
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
|---------------|--------|---------|---------------------------|
|
||||
| `enabled` | bool | false | Enable Tavily search |
|
||||
| `api_key` | string | - | Tavily API key |
|
||||
| `base_url` | string | `""` | Optional Tavily-compatible endpoint |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
||||
### SearXNG
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
|---------------|--------|---------|------------------------------|
|
||||
| `enabled` | bool | false | Enable SearXNG search |
|
||||
| `base_url` | string | `""` | Base URL of your SearXNG instance |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
||||
### GLM Search
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
|-----------------|--------|---------|-------------|
|
||||
| `enabled` | bool | false | Enable Zhipu GLM web search |
|
||||
| `api_key` | string | - | GLM API key |
|
||||
| `base_url` | string | `https://open.bigmodel.cn/api/paas/v4/web_search` | Search endpoint |
|
||||
| `search_engine` | string | `search_std` | Backend name such as `search_std`, `search_pro`, `search_pro_sogou`, `search_pro_quark` |
|
||||
| `max_results` | int | 5 | Maximum number of results |
|
||||
|
||||
## Exec Tool
|
||||
|
||||
The exec tool is used to execute shell commands.
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
|------------------------|-------|---------|--------------------------------------------|
|
||||
| `enabled` | bool | true | Enable/disable the exec tool |
|
||||
| `enable_deny_patterns` | bool | true | Enable default dangerous command blocking |
|
||||
| `allow_remote` | bool | true | Allow exec calls from remote channels |
|
||||
| `timeout_seconds` | int | 60 | Per-command timeout (`0` uses the built-in default) |
|
||||
| `custom_deny_patterns` | array | [] | Custom deny patterns (regular expressions) |
|
||||
| `custom_allow_patterns` | array | [] | Regex allow-list checked before deny rules |
|
||||
|
||||
### Functionality
|
||||
|
||||
- **`enable_deny_patterns`**: Set to `false` to completely disable the default dangerous command blocking patterns
|
||||
- **`allow_remote`**: Set to `false` to require an internal/local channel context before exec may run
|
||||
- **`timeout_seconds`**: Set to `0` to fall back to the compiled default timeout
|
||||
- **`custom_deny_patterns`**: Add custom deny regex patterns; commands matching these will be blocked
|
||||
- **`custom_allow_patterns`**: Explicit allow regexes that are evaluated before deny rules
|
||||
|
||||
### Default Blocked Command Patterns
|
||||
|
||||
|
|
@ -106,8 +156,48 @@ The cron tool is used for scheduling periodic tasks.
|
|||
|
||||
| Config | Type | Default | Description |
|
||||
|------------------------|------|---------|------------------------------------------------|
|
||||
| `enabled` | bool | true | Enable/disable the cron tool |
|
||||
| `exec_timeout_minutes` | int | 5 | Execution timeout in minutes, 0 means no limit |
|
||||
|
||||
## Skills Tool
|
||||
|
||||
The skills tool controls skill search, installation, and registry lookup.
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `enabled` | bool | true | Enable skill-related tools |
|
||||
| `max_concurrent_searches` | int | 2 | Max concurrent skill search/download lookups |
|
||||
| `github.proxy` | string | `""` | Optional proxy for GitHub-backed skill downloads |
|
||||
| `github.token` | string | `""` | GitHub token for higher rate limits/private repos |
|
||||
| `search_cache.max_size` | int | 50 | Number of cached skill-search results |
|
||||
| `search_cache.ttl_seconds` | int | 300 | Skill-search cache TTL |
|
||||
| `registries.clawhub.enabled` | bool | true | Enable the default ClawHub registry |
|
||||
| `registries.clawhub.base_url` | string | `https://clawhub.ai` | Registry base URL |
|
||||
| `registries.clawhub.auth_token` | string | `""` | Optional registry auth token |
|
||||
| `registries.clawhub.search_path` | string | `""` | Override search endpoint path |
|
||||
| `registries.clawhub.skills_path` | string | `""` | Override skill metadata endpoint path |
|
||||
| `registries.clawhub.download_path` | string | `""` | Override download endpoint path |
|
||||
| `registries.clawhub.timeout` | int | `0` | Custom timeout override (`0` means use default) |
|
||||
| `registries.clawhub.max_zip_size` | int | `0` | Custom zip size cap (`0` means use default) |
|
||||
| `registries.clawhub.max_response_size` | int | `0` | Custom response size cap (`0` means use default) |
|
||||
|
||||
## Media Cleanup Tool
|
||||
|
||||
Media cleanup deletes temporary media artifacts created during channel/tool use.
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `enabled` | bool | true | Enable background cleanup |
|
||||
| `max_age_minutes` | int | 30 | Delete media older than this |
|
||||
| `interval_minutes` | int | 5 | Cleanup sweep cadence |
|
||||
|
||||
## Read File Tool
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `enabled` | bool | true | Enable the read-file tool |
|
||||
| `max_read_file_size` | int | 65536 | Maximum bytes returned by a single read |
|
||||
|
||||
## MCP Tool
|
||||
|
||||
The MCP tool enables integration with external Model Context Protocol servers.
|
||||
|
|
|
|||
82
pkg/config/config_example_test.go
Normal file
82
pkg/config/config_example_test.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfigExample_DocumentsAdvancedFields(t *testing.T) {
|
||||
examplePath := filepath.Join("..", "..", "config", "config.example.json")
|
||||
data, err := os.ReadFile(examplePath)
|
||||
if err != nil {
|
||||
t.Fatalf("read config example: %v", err)
|
||||
}
|
||||
|
||||
var root map[string]any
|
||||
if err := json.Unmarshal(data, &root); err != nil {
|
||||
t.Fatalf("unmarshal config example: %v", err)
|
||||
}
|
||||
|
||||
requiredPaths := [][]any{
|
||||
{"agents", "defaults", "allow_read_outside_workspace"},
|
||||
{"agents", "defaults", "image_model"},
|
||||
{"agents", "defaults", "image_model_fallbacks"},
|
||||
{"agents", "defaults", "max_media_size"},
|
||||
{"agents", "defaults", "routing", "light_model"},
|
||||
{"agents", "list", 0, "subagents", "allow_agents"},
|
||||
{"bindings", 0, "match", "peer", "kind"},
|
||||
{"session", "identity_links"},
|
||||
{"channels", "telegram", "typing", "enabled"},
|
||||
{"channels", "telegram", "placeholder", "text"},
|
||||
{"channels", "qq", "max_message_length"},
|
||||
{"channels", "qq", "send_markdown"},
|
||||
{"channels", "dingtalk", "group_trigger", "mention_only"},
|
||||
{"channels", "slack", "typing", "enabled"},
|
||||
{"channels", "line", "typing", "enabled"},
|
||||
{"channels", "onebot", "group_trigger", "prefixes"},
|
||||
{"channels", "pico", "allow_token_query"},
|
||||
{"channels", "pico", "max_connections"},
|
||||
{"tools", "web", "proxy"},
|
||||
{"tools", "web", "fetch_limit_bytes"},
|
||||
{"tools", "exec", "allow_remote"},
|
||||
{"tools", "exec", "custom_allow_patterns"},
|
||||
{"tools", "exec", "timeout_seconds"},
|
||||
{"tools", "media_cleanup", "max_age_minutes"},
|
||||
{"tools", "read_file", "max_read_file_size"},
|
||||
}
|
||||
|
||||
for _, path := range requiredPaths {
|
||||
if _, ok := lookupPath(root, path...); !ok {
|
||||
t.Errorf("config example missing documented path %v", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lookupPath(root any, parts ...any) (any, bool) {
|
||||
current := root
|
||||
for _, part := range parts {
|
||||
switch key := part.(type) {
|
||||
case string:
|
||||
obj, ok := current.(map[string]any)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
next, ok := obj[key]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
current = next
|
||||
case int:
|
||||
list, ok := current.([]any)
|
||||
if !ok || key < 0 || key >= len(list) {
|
||||
return nil, false
|
||||
}
|
||||
current = list[key]
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return current, true
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue