This commit is contained in:
OpenClaw-User 2026-03-14 17:01:11 +08:00
commit cf93a7fd03
6 changed files with 648 additions and 15 deletions

View file

@ -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": {

View file

@ -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
View 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)

View file

@ -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

View file

@ -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.

View 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
}