Merge upstream/main into bugfix/fix-duplicate-telegram-messages.
Conflict resolutions:
- pkg/agent/loop.go: Adopt upstream's processSystemMessage which removes
runAgentLoop call entirely (subagents now communicate via message tool
directly). Keep PR's HasSentInRound() check in Run() for normal
message processing path.
- pkg/tools/message.go: Merge both changes - keep sentInRound tracking
from PR and adopt upstream's *ToolResult return type with Silent: true.
- Add Heartbeat section explaining periodic task execution
- Document spawn tool for async subagent creation
- Explain independent context and message tool communication
- Add workflow diagram for subagent communication
- Update workspace layout with HEARTBEAT.md and state/
- Add heartbeat config to config.example.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add constants package with IsInternalChannel helper to centralize internal channel checks across agent, channels, and heartbeat services
- Add ToProviderDefs method to ToolRegistry to consolidate tool definition conversion logic used in agent loop and tool loop
- Refactor SubagentTool.Execute to use RunToolLoop for consistent tool execution with iteration tracking
- Remove duplicate inline map definitions and type assertion code throughout codebase
Two issues caused duplicate messages to be sent to users:
1. System messages (from subagent): processSystemMessage set SendResponse:true,
causing runAgentLoop to publish outbound. Then Run() also published outbound
using the returned response string, resulting in two identical messages.
Fix: processSystemMessage now returns empty string since runAgentLoop already
handles the send.
2. Message tool double-send: When LLM called the "message" tool during
processing, it published outbound immediately. Then Run() published the
final response again. Fix: Track whether MessageTool sent a message in the
current round (sentInRound flag, reset on each SetContext call). Run()
checks HasSentInRound() before publishing to avoid duplicates.
Extract core LLM tool loop logic into shared RunToolLoop function that can be
used by both main agent and subagents. Subagents now run their own tool loop
with dedicated tool registry, enabling full independence.
Key changes:
- New pkg/tools/toolloop.go with reusable tool execution logic
- Subagents use message tool to communicate directly with users
- Heartbeat processing is now stateless via ProcessHeartbeat
- Simplified system message routing without result forwarding
- Shared tool registry creation for consistency between agents
This architecture follows openclaw's design where async tools notify via
bus and subagents handle their own user communication.
feat(config): add heartbeat interval configuration with default 30 minutes
feat(state): migrate state file from workspace root to state directory
feat(channels): skip internal channels in outbound dispatcher
feat(agent): record last active channel for heartbeat context
refactor(subagent): use configurable default model instead of provider default
- Remove redundant ChannelSender interface, use *bus.MessageBus directly
- Consolidate two handlers (onHeartbeat, onHeartbeatWithTools) into one
- Move HEARTBEAT.md and heartbeat.log to workspace root
- Simplify NewHeartbeatService signature (remove handler param)
- Add SetBus and SetHandler methods for dependency injection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Re-enable cronTool service integration after completing the ToolResult
refactor (US-016). Removed all temporary disable comments and restored
full cron service lifecycle including start/stop operations.
Additional improvements:
- Add thread-safe access to onHeartbeatWithTools handler
- Fix channel parsing to handle user IDs with special characters
- Add error handling for state file loading failures
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>
- Add ChannelSender interface for sending heartbeat results to users
- Add sendResponse() to automatically deliver results to last channel
- Add createDefaultHeartbeatTemplate() for first-run setup
- Support HEARTBEAT_OK silent response (legacy compatibility)
- Add structured logging with INFO/ERROR levels
- Move integration tests to separate file with build tag
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- Deleted docker-compose.discord.yml and merged into docker-compose.yml
- Moved config.example.json to config/
- Updated volume mounts to use ./config/config.json
- Updated verify script permissions (implied if valid)
- Add Dockerfile with multi-stage build for picoclaw
- Add docker-compose.discord.yml for Discord bot service
- Add docker-compose.yml for agent mode service
- Add .env.example with environment variable template
- Add .dockerignore for optimized builds
- Update README.md with Docker Compose section and language switch
- Add README.ja.md (Japanese documentation)
- Update .gitignore with Docker-related entries
- Refactor web tool to use Provider pattern (Brave/DuckDuckGo)
- Add robust HTML scraping for keyless DuckDuckGo search
- Update README with search provider guidelines
Remove .ralph/ directory files from git tracking.
These are no longer needed as the tool-result-refactor is complete.
Also removes root-level prd.json and progress.txt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>