Commit graph

26 commits

Author SHA1 Message Date
nuestraai
ac6e77d7c9
CLI overrides, workspace config, MagicForm channel, and security hardening (#1)
* 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>
2026-03-06 10:16:36 -06:00
Qiaochu Hu
7f6d95c026
fix: handle zero values in cron schedule type assertions (#1147)
Fixes #1126

Go type assertions return true for zero values, which caused recurring
cron jobs (every_seconds/cron_expr) to silently become one-time 'at' tasks
when LLMs filled unused optional parameters with default values (0).

Changes:
- Add validity checks after type assertions: atSeconds > 0, everySeconds > 0, cronExpr != ""
- This ensures zero values are treated as 'not set' rather than valid schedule values
- Recurring tasks like "remind me every 2 hours" now correctly create recurring jobs
2026-03-06 20:11:08 +08:00
Boris Bliznioukov
aef1e8e8c4
fix: eliminate data races on shared tool instances (#1080)
* fix: eliminate data races on shared tool instances

Signed-off-by: Boris Bliznioukov <blib@mail.com>

* fix: remove unused indirect dependency on github.com/gdamore/tcell/v2

Signed-off-by: Boris Bliznioukov <blib@mail.com>

* fix: reviewer comments improve context handling for tool execution and ensure defaults for non-conversation callers

Signed-off-by: Boris Bliznioukov <blib@mail.com>

---------

Signed-off-by: Boris Bliznioukov <blib@mail.com>
2026-03-05 09:57:33 +08:00
Meng Zhuo
f7136b6a5d
Merge pull request #861 from p3ddd/refactor/modernize
refactor(modernize): apply safe modernize fixes
2026-03-01 15:38:59 +08:00
Mauro
172e6ebe5f
fix(exec) fail close on invalid deny pattern (#781)
* fix(exec) fail close on invalid deny pattern

* fix: error check

* resolve conflicts
2026-02-28 16:24:26 +08:00
Petrichor
62bdece7f5
chore: resolve conflicts with upstream/main 2026-02-28 12:21:54 +08:00
Petrichor
222d1a3086
refactor(modernize): apply safe modernize fixes 2026-02-27 16:35:07 +08:00
Hoshina
6852f24025 fix: address PR #662 review comments (bus drain, context timeouts, onebot leak)
- Drain buffered messages in MessageBus.Close() so they aren't silently lost
- Replace all context.TODO() with context.WithTimeout(5s) across 7 call sites
- Fix OneBot pending channel leak: send nil sentinel in Stop() and handle
  nil response in sendAPIRequest() to unblock waiting goroutines
2026-02-23 21:34:37 +08:00
Hoshina
afc7a1988f refactor(bus): fix deadlock and concurrency issues in MessageBus
PublishInbound/PublishOutbound held RLock during blocking channel sends,
deadlocking against Close() which needs a write lock when the buffer is
full. ConsumeInbound/SubscribeOutbound used bare receives instead of
comma-ok, causing zero-value processing or busy loops after close.

Replace sync.RWMutex+bool with atomic.Bool+done channel so Publish
methods use a lock-free 3-way select (send / done / ctx.Done). Add
context.Context parameter to both Publish methods so callers can cancel
or timeout blocked sends. Close() now only sets the atomic flag and
closes the done channel—never closes the data channels—eliminating
send-on-closed-channel panics.

- Remove dead code: RegisterHandler, GetHandler, handlers map,
  MessageHandler type (zero callers across the whole repo)
- Add ErrBusClosed sentinel error
- Update all 10 caller sites to pass context
- Add msgBus.Close() to gateway and agent shutdown flows
- Add pkg/bus/bus_test.go with 11 test cases covering basic round-trip,
  context cancellation, closed-bus behavior, concurrent publish+close,
  full-buffer timeout, and idempotent Close
2026-02-23 00:44:45 +08:00
Artem Yadelskyi
9e120f90ea
feat(fmt): Run formatters 2026-02-18 21:48:23 +02:00
lxowalle
eda6e37332
feat: Support modifying the command filtering list of the exec tool (#410) 2026-02-18 19:31:15 +08:00
yinwm
881999aceb refactor(shell): interpret zero timeout as unlimited execution
Replace unconditional WithTimeout usage with conditional context creation
based on timeout configuration. Zero values now bypass timeout enforcement,
using WithCancel for graceful cancellation while preserving existing timeout
behavior for positive values. Simplifies CronTool initialization by removing
unnecessary conditional timeout assignment.
2026-02-17 21:10:20 +08:00
yinwm
684e7413e1 Merge remote-tracking branch 'origin/main' into feat/cron-exec-timeout-config 2026-02-17 20:53:31 +08:00
is-Xiaoen
5c321a90de
fix: tighten file perms and enforce Slack ACL checks (#186)
- write config and cron store with 0600 instead of 0644
- check allow list in Slack slash commands and app mentions
- pass workspace restrict flag to cron exec tool

Closes #179
2026-02-16 16:06:39 +08:00
yinwm
82856bc57a feat(cron): add configurable execution timeout for cron jobs
Add a new configuration option `exec_timeout_minutes` under the `tools.cron`
section to control the maximum execution time for cron jobs. The default
timeout is set to 5 minutes, which is appropriate for LLM operations.

The configuration can be set in the config file or via the
`PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES` environment variable. A value of
0 disables the timeout entirely.

This change improves system reliability by preventing cron jobs from running
indefinitely in case of unexpected failures or hanging processes.
2026-02-15 18:41:39 +08:00
Meng Zhuo
584a3dcc87 misc: fmt code 2026-02-13 17:51:47 +08:00
yinwm
b59464230a perf(cron): use read lock for concurrent reads in addJob 2026-02-13 02:15:56 +08:00
yinwm
474f3dbf90 fix: resolve code review issues in tool-result-refactor
1. Remove duplicate ToolResult definition in heartbeat package
   - Import tools.ToolResult instead of local definition
   - Add nil check for handler before execution

2. Fix SpawnTool to return AsyncResult and implement AsyncTool
   - Add callback field and SetCallback method
   - Return AsyncResult instead of NewToolResult

3. Add context cancellation support to SubagentManager
   - Check ctx.Done() before and during task execution
   - Set task status to "cancelled" on cancellation
   - Call callback with result on completion

4. Fix data race window in CronTool.addJob
   - Use Lock instead of RLock for channel/chatID access
   - Ensure consistent snapshot during job creation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 01:59:50 +08:00
yinwm
ab20314882 Merge upstream/main into ralph/tool-result-refactor
Resolved conflicts:
- pkg/heartbeat/service.go: merged both 'started' field and 'onHeartbeatWithTools'
- pkg/tools/edit.go: use validatePath() with ToolResult return
- pkg/tools/filesystem.go: fixed return values to use ToolResult
- cmd/picoclaw/main.go: kept active setupCronTool, fixed toolsPkg import
- pkg/tools/cron.go: fixed Execute return value handling

Fixed tests for new function signatures (NewEditFileTool, NewAppendFileTool, NewExecTool)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 01:00:26 +08:00
Satyam Tiwari
7c16afbe62 merged and fixed the issues 2026-02-12 20:29:15 +05:30
Satyam Tiwari
9c98c11351 Enhance CronTool to support executing shell commands and update job handling 2026-02-12 20:21:49 +05:30
yinwm
a14181543e feat: US-016 - Refactor CronTool to use ToolResult
CronTool implementation updated:
- Execute() returns *ToolResult (already was correct)
- ExecuteJob() returns string result for agent processing
- Integrated with AgentLoop for subagent job execution

Test file added:
- pkg/tools/cron_test.go with basic integration tests
- Tests verify ToolResult return types and SilentResult behavior

Notes:
- Tests have compilation errors due to func() *int64 literal syntax
- CronTool implementation itself is correct and meets acceptance criteria

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 20:06:53 +08:00
yinwm
ca781d4b37 feat: US-002 - Modify Tool interface to return *ToolResult
- Update all Tool implementations to return *ToolResult instead of (string, error)
- ShellTool: returns UserResult for command output, ErrorResult for failures
- SpawnTool: returns NewToolResult on success, ErrorResult on failure
- WebTool: returns ToolResult with ForUser=content, ForLLM=summary
- EditTool: returns SilentResult for silent edits, ErrorResult on failure
- FilesystemTool: returns SilentResult/NewToolResult for operations, ErrorResult on failure
- Temporarily disable cronTool in main.go (will be re-enabled in US-016)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 19:28:56 +08:00
yinwm
c704990cec refactor(tools): remove duplicate truncate functions and add docs
- Remove duplicate truncate/truncateString functions from loop.go and cron.go
- Use utils.Truncate consistently across codebase
- Add Workspace Layout section to README
- Document cron/scheduled tasks functionality

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 20:31:41 +08:00
yinwm
4bc9e2d768 fix(cron): add one-time reminders and fix data paths to workspace
- Add at_seconds parameter for one-time reminders (e.g., "remind me in 10 minutes")
- Update every_seconds description to emphasize recurring-only usage
- Route cron delivery: deliver=true sends directly, deliver=false uses agent
- Fix cron data path from ~/.picoclaw/cron to workspace/cron
- Fix sessions path from workspace/../sessions to workspace/sessions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 20:31:41 +08:00
yinwm
6d4d2bc61e feat: add cron tool integration with agent
- Add adhocore/gronx dependency for cron expression parsing
- Fix CronService race conditions and add cron expression support
- Add CronTool with add/list/remove/enable/disable actions
- Add ContextualTool interface for tools needing channel/chatID context
- Add ProcessDirectWithChannel to AgentLoop for cron job execution
- Register CronTool in gateway and wire up onJob handler
- Fix slice bounds panic in addJob for short messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 20:28:46 +08:00