Promote workspace_root from channel-specific (magicform) to agents.defaults
so both CLI --workspace/--config-dir and gateway metadata overrides are
validated against the same immutable boundary. Paths must be relative
subdirectories — absolute paths, .., and empty/dot are rejected. The
boundary is snapshotted before overlay merges and cannot be widened by
workspace config files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add separate User and RealName config fields (fall back to Nick)
- Make RequestCaps configurable (defaults to server-time, message-tags)
- Refactor isBotMentioned into nickMentionedAt returning position;
stripBotMention now uses nickMentionedAt internally
- Replace custom isAlphanumeric with unicode.IsLetter/unicode.IsDigit
- Update tests for new nickMentionedAt function
* feat(cli): add workspace, config-dir, tools, and skills override flags
Add --workspace, --config-dir, --tools, and --skills flags to
`picoclaw agent` for single-shot invocations. Supports workspace
override with bootstrap file injection (AGENTS.md, IDENTITY.md,
SOUL.md, USER.md), tool allowlisting, and skills filtering.
Also extracts FormatSkillsSummary from SkillsLoader for reuse,
adds SetSkillsFilter to ContextBuilder, and wires SkillsFilter
from agent config into the context builder.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(channels): add MagicForm webhook channel plugin
Implement MagicForm channel for webhook-based agentic task delegation.
Supports Bearer token auth, async processing with HTTP callback,
workspace path validation against configurable workspace_root,
bootstrap file injection, tool/skill filtering via metadata, and
per-conversation session key isolation.
Includes Bus() accessor on BaseChannel for direct message publishing,
MagicFormConfig with workspace_root for path traversal prevention,
request body size limits, and TTL cleanup for stale request contexts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(agent): support per-request workspace, tool, and skill overrides
Add workspace override, tool allowlisting, and skills filtering to the
agent loop via processOptions metadata. When a workspace override is
active (e.g. from MagicForm channel), creates isolated SessionManager
and ContextBuilder instances per request.
Threads effSessions/effContextBuilder through all downstream paths
including runLLMIteration, forceCompression, maybeSummarize, and
summarizeSession to ensure full workspace isolation. Adds defense-in-
depth tool execution guard alongside LLM-facing tool definition filter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(auth): add response body size limits to OAuth HTTP calls
Wrap all io.ReadAll(resp.Body) calls in the OAuth flow with
io.LimitReader capped at 1 MB to prevent memory exhaustion from
malicious or unexpectedly large server responses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(tools): add response body size limits to search providers
Add 2 MB LimitReader to BraveSearch, Tavily, DuckDuckGo, and
Perplexity search providers to prevent memory exhaustion from
unexpectedly large responses. Matches existing pattern used by
GLMSearchProvider.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(tools): add 20 MB file write size limit
Reject writes exceeding 20 MB in WriteFileTool to prevent disk
exhaustion from unexpectedly large LLM-generated file content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(tools): use crypto/rand for temp file naming
Replace predictable PID+nanosecond temp file names with
cryptographically random hex strings to prevent symlink
pre-placement attacks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(skills): validate GitHub repo format and limit download size
Add regex validation for 'owner/repo' format to prevent URL injection
in InstallFromGitHub. Add 5 MB LimitReader on skill file downloads
to prevent memory exhaustion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(auth): allow OAuth credentials to be overridden via env vars
Add PICOCLAW_OPENAI_CLIENT_ID, PICOCLAW_GOOGLE_CLIENT_ID, and
PICOCLAW_GOOGLE_CLIENT_SECRET env var overrides. Hardcoded values
remain as fallback defaults for backward compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(auth): sanitize OAuth error messages
Replace raw HTTP response bodies in error messages with generic
status-code-only messages. Full response details are logged at
debug level for troubleshooting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(launcher): HTML-escape error messages in auth callback
Use html.EscapeString on all error text rendered in HTML responses
to prevent XSS via crafted OAuth error parameters or error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(launcher): remove internal details from HTTP error responses
Log detailed errors server-side and return generic messages to
clients to prevent information disclosure of file paths and
internal state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(launcher): add HTTP security headers middleware
Add SecurityHeaders middleware setting X-Content-Type-Options,
X-Frame-Options, and Content-Security-Policy on all responses
to mitigate MIME sniffing, clickjacking, and content injection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(skills): validate ClawHub registry base URL scheme
Reject non-HTTPS registry URLs (unless localhost) to prevent
config-based redirection to malicious skill servers. Falls back
to the default https://clawhub.ai with a warning.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(skills): prevent workspace skills from shadowing builtins
Builtin skill names are now reserved — workspace and global skills
with the same name as a builtin are skipped with a warning log.
LoadSkill also checks builtins first. This prevents supply chain
attacks where a malicious skill replaces a trusted builtin.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(skills): warn when safety metadata is unavailable
Add MetadataAvailable flag to InstallResult. When the registry
metadata fetch fails (silent fallback), the user now sees a
warning that safety checks could not be completed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* security(shell): add opt-in environment variable filtering
When filter_env is enabled in config, shell commands run with a minimal
allowlist of environment variables (PATH, HOME, LANG, TERM, etc. plus
PICOCLAW_* prefix), preventing accidental leakage of sensitive env vars
like API keys to spawned processes. Disabled by default for backward compat.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(config): add workspace-local config.json overlay for per-tenant isolation
Support per-workspace config overrides via --config-dir (CLI) or configDir
(webhook), enabling different API keys, models, and agent settings per tenant.
- Add LoadWorkspaceConfig/MergeWorkspaceConfig with raw JSON overlay to
preserve unmentioned bool fields (prevents clobbering tool enabled flags)
- Add Config.Clone() for safe per-request config copies in gateway mode
- Gateway: per-request provider creation from workspace config, with
effProvider/effModel threaded through all LLM call sites and summarization
- CLI: workspace config merged before provider creation, CLI flags win
- MagicForm: replace inline bootstrap fields with configDir path;
agent loop copies bootstrap files and loads config.json from configDir
- Fix --session flag: format as agent:main:cli:{key} so router honors it
- Fix cron session key: format as agent:main:cron:{id} for isolation
- Add shared CopyBootstrapFiles helper in pkg/agent/bootstrap.go
- Add config/workspace.config.example.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(agent): honor workspace config overrides in model routing
selectCandidates uses agent.Model/Candidates baked in at startup, which
ignores per-request workspace config overrides (effProvider/effModel).
When the workspace overrides the provider, skip routing and fallback
candidates entirely to avoid cross-provider credential issues. When only
the model is overridden, allow routing but use the effective model.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: fix linter errors (gci, gofumpt, golines, govet, unused)
- gci: alphabetize magicform import in gateway/helpers.go
- gofumpt: reformat multi-line function signature in agent/helpers.go
- golines: wrap lines exceeding 120 chars across 5 files
- govet: rename shadowed variables (err → wcErr, s → trimmed)
- unused: remove maybeSummarize, forceCompression, summarizeSession
wrappers superseded by their *With variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: nuestraai <nuestraai@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: admin-mf <admin@magicform.ai>
* feat(feishu): implement SendMedia and add send_file tool
Add outbound media support for the Feishu channel so the agent can send
images and files to users via the MediaStore pipeline.
Feishu channel:
- SendMedia dispatches media parts as image or file uploads
- sendImage uploads via Image.Create then sends image message
- sendFile uploads via File.Create then sends file message
- feishuFileType maps extensions to Feishu file_type values
send_file tool:
- New tool lets the LLM send a local file to the current chat
- Validates path, registers file in MediaStore, returns media ref
- Agent loop wires tool registration, MediaStore propagation, and
context updates
Tested on Radxa Cubie A7A (arm64) with Feishu websocket channel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(agent): publish outbound media regardless of SendResponse flag
The SendResponse flag controls whether the agent loop publishes the
final text response (callers that publish it themselves set this to
false). However, the media publish path was also gated behind this
flag, which meant tool-produced media was silently dropped for normal
channel messages.
Media should be published immediately when a tool returns media refs,
independent of how the text response is delivered.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tools): use magic-bytes MIME detection and add file size limit to send_file
- Replace hardcoded extension-to-MIME map with h2non/filetype (magic
bytes) + mime.TypeByExtension fallback, consistent with the vision
pipeline in resolveMediaRefs
- Add configurable max file size check (defaults to config.DefaultMaxMediaSize,
20 MB) to prevent oversized uploads
- Add tests for magic-bytes detection, extension fallback, size limit,
and default max size
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(agent): add ForEachTool to AgentRegistry for cross-agent tool lookup
Extract the pattern of iterating agents to find a named tool into
AgentRegistry.ForEachTool, simplifying SetMediaStore propagation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(agent,tools): adapt send_file to ctx-based channel injection after upstream refactor
Replace ContextualTool interface (removed upstream) with direct ctx
reading in SendFileTool.Execute, using ToolChannel/ToolChatID helpers.
Remove updateToolContexts which is no longer needed since ExecuteWithContext
already injects channel/chatID into ctx for all tools.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(tools): support toggling send_file tool via config
Add SendFileConfig with Enabled field to ToolsConfig, defaulting to
true. Wrap send_file tool registration in loop.go with the config
check, consistent with the pattern used by other tools.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Upstream added ThinkingLevel, SummarizeMessageThreshold,
SummarizeTokenPercent, MaxMediaSize, and maybeSummarize.
Our branch added Router, LightCandidates, and selectCandidates.
Both sets of changes are kept. Dead updateToolContexts removed
(upstream deleted it; no callers exist).
Add TimeoutSeconds field to ExecConfig so the shell command execution
timeout can be configured instead of being hardcoded to 60s.
- Add TimeoutSeconds int field to ExecConfig in pkg/config/config.go
with json/env tags (PICOCLAW_TOOLS_EXEC_TIMEOUT_SECONDS)
- Set default value of 60s in DefaultConfig() in pkg/config/defaults.go
- Read TimeoutSeconds from config in NewExecToolWithConfig() in
pkg/tools/shell.go; falls back to 60s when value is 0 or unset
Add IRC as a new channel for picoclaw, supporting server connections,
channel joins, DMs, mention-based group triggers, and IRCv3 typing
indicators. Uses ergochat/irc-go for connection management with SASL,
NickServ, and automatic reconnection support.
Closes#1137
* feat: add extended thinking support for Anthropic models
Support configurable thinking levels (off/low/medium/high/xhigh/adaptive)
via `agents.defaults.thinking_level` config field.
- "adaptive": uses Anthropic's adaptive thinking API (Claude 4.6+)
- "low/medium/high/xhigh": uses budget_tokens (all thinking-capable models)
- "off": disables thinking (default)
API constraints handled:
- Temperature cleared when thinking is enabled
- budget_tokens clamped to max_tokens-1
- Thinking response blocks parsed into Reasoning field
Relates to #645, #966
* fix: address PR review feedback for thinking support
- Add ThinkingCapable interface for provider capability detection
- Warn when thinking_level is set but provider doesn't support it
- Warn when temperature is cleared due to thinking enabled
- Adjust budget values per Anthropic best practices (medium=16K, xhigh=64K)
- Add budget clamp warning and 80% threshold warning
- Add parseResponse thinking block tests
- Add thinking_level field to config.example.json
* refactor: move ThinkingLevel from AgentDefaults to ModelConfig
Thinking is a model-level capability, not a global agent property.
Per-model config avoids silent ignoring on non-Anthropic providers
and eliminates spurious warning logs in multi-provider setups.
Addresses PR #1076 review feedback from @yinwm.
Resolve merge conflicts to keep both SearXNG and GLM Search
providers. Updated search priority order to:
Perplexity > Brave > SearXNG > Tavily > DuckDuckGo > GLM Search
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The TestConvertProvidersToModelList_AllProviders test expected 19
providers but adding Avian brings the total to 20.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(config): add GLMSearchConfig for GLM Search provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(tools): add failing tests for GLM Search provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(tools): add GLMSearchProvider for web search
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(agent): wire GLM Search config into web search tool registration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add Avian (https://avian.io) as an OpenAI-compatible provider with
API base https://api.avian.io/v1 and AVIAN_API_KEY env var support.
Models: deepseek/deepseek-v3.2, moonshotai/kimi-k2.5, z-ai/glm-5,
minimax/minimax-m2.5. Supports chat completions, streaming, and
function calling.
Changes:
- Add Avian to ProvidersConfig struct, IsEmpty(), HasProvidersConfig()
- Add avian protocol to factory provider and default API base
- Add avian case to legacy provider selection (factory.go)
- Add avian migration rule for old config format
- Add default model entries to ModelList (deepseek-v3.2, kimi-k2.5)
- Add avian to example config
- Update AllProviders test count from 18 to 19
* feat(telegram): add base_url support for custom Telegram Bot API server
Allow users to specify a custom Telegram Bot API server URL via
config field `base_url` or env var `PICOCLAW_CHANNELS_TELEGRAM_BASE_URL`.
Defaults to the official https://api.telegram.org when left empty.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(telegram): trim whitespace and trailing slash from base_url
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Upgrade the Feishu channel from basic text-only to full feature parity with
Telegram/Discord: interactive card messages with markdown rendering, message
editing (MessageEditor), placeholder messages (PlaceholderCapable), emoji
reactions (ReactionCapable), and inbound/outbound media support (MediaSender).
Also add @mention detection with lazy bot open_id discovery, group trigger
filtering with mention awareness, and multi-type inbound message parsing
(text, post, image, file, audio, video).
Introduce RoutingConfig with three fields:
- enabled: activates per-turn model routing
- light_model: references a model_name in model_list
- threshold: complexity score cutoff in [0,1]
When routing.enabled is true and the incoming message scores below
threshold, the agent switches to light_model for that turn. Absent or
disabled config leaves existing behaviour completely unchanged.
Example:
"agents": {
"defaults": {
"model": "claude-sonnet-4-6",
"routing": {
"enabled": true,
"light_model": "gemini-flash",
"threshold": 0.35
}
}
}
- Introduced WeCom AIBot channel configuration in config.go with relevant fields.
- Implemented WeCom AIBot channel factory registration in init.go.
- Created unit tests for WeCom AIBot channel functionalities including initialization, start/stop behavior, webhook path handling, message encryption/decryption, and signature generation.
- Set default values for WeCom AIBot configuration in defaults.go.