- MediaStore: use full UUID to prevent ref collisions, preserve and
expose metadata via ResolveWithMeta, include underlying OS errors
- Agent loop: populate MediaPart Type/Filename/ContentType from
MediaStore metadata so channels can dispatch media correctly
- SplitMessage: fix byte-vs-rune index mixup in code block header
parsing, remove dead candidateStr variable
- Pico auth: restrict query-param token behind AllowTokenQuery config
flag (default false) to prevent token leakage via logs/referer
- HandleMessage: replace context.TODO with caller-propagated ctx,
log PublishInbound failures instead of silently discarding
- Gateway shutdown: use fresh 15s timeout context for StopAll so
graceful shutdown is not short-circuited by the cancelled parent ctx
Add outbound media sending capability so the agent can publish media
attachments (images, files, audio, video) through channels via the bus.
- Add MediaPart and OutboundMediaMessage types to bus
- Add PublishOutboundMedia/SubscribeOutboundMedia bus methods
- Add MediaSender interface discovered via type assertion by Manager
- Add media dispatch/worker in Manager with shared retry logic
- Extend ToolResult with Media field and MediaResult constructor
- Publish outbound media from agent loop on tool results
- Implement SendMedia for Telegram, Discord, Slack, LINE, OneBot, WeCom
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
Channels previously deleted downloaded media files via defer os.Remove,
racing with the async Agent consumer. Introduce MediaStore to decouple
file ownership: channels register files on download, Agent releases them
after processing via ReleaseAll(scope).
- New pkg/media with MediaStore interface + FileMediaStore implementation
- InboundMessage gains MediaScope field for lifecycle tracking
- BaseChannel gains SetMediaStore/GetMediaStore + BuildMediaScope helper
- Manager injects MediaStore into channels; AgentLoop releases on completion
- Telegram, Discord, Slack, OneBot, LINE channels migrated from defer
os.Remove to store.Store() with media:// refs
Add bus.Peer struct and explicit Peer/MessageID fields to InboundMessage,
replacing the implicit peer_kind/peer_id/message_id metadata convention.
- Add Peer{Kind, ID} type to pkg/bus/types.go
- Extend InboundMessage with Peer and MessageID fields
- Change BaseChannel.HandleMessage signature to accept peer and messageID
- Adapt all 12 channel implementations to pass structured peer/messageID
- Simplify agent extractPeer() to read msg.Peer directly
- extractParentPeer unchanged (parent_peer still via metadata)
Resolved conflicts:
- pkg/config/config.go: Removed duplicate DefaultConfig() (already in defaults.go)
- pkg/config/defaults.go: Updated Temperature to *float64 (nil default)
Upstream changes included:
- Temperature changed from float64 to *float64 (nil means use provider default)
- New HeartbeatConfig and DevicesConfig
- Various agent and tool improvements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add MaxTokens and Temperature fields to AgentInstance and update related logic
* feat: add MaxTokens and Temperature options to SubagentManager and update tool loop logic
* feat: add default temperature handling and update related tests
* feat: allow temperature 0 and distinguish unset
* fix: format MockLLMProvider struct in subagent_tool_test.go
Add support for persisting thought_signature metadata from Google/Gemini 3
models. This introduces ExtraContent and GoogleExtra types to handle
provider-specific metadata, and ensures thought signatures are properly
preserved through the tool call lifecycle.
Append emergency compression note to the original system prompt
instead of creating a separate system message. Some APIs like
Zhipu reject two consecutive system messages.
- Preserve user's configured model during config migration (issue #5)
- Simplify ExtractProtocol using strings.Cut
- Extract NormalizeToolCall to shared utility, removing ~70 lines of duplicate code
- Clean up unused fields in providerMigrationConfig struct
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve conflicts in pkg/providers/types.go and pkg/agent/loop.go:
- types.go: use protocoltypes aliases from PR #213, keep fallback types
- loop.go: drop old single-agent createToolRegistry (replaced by multi-agent pattern)
Refactor to align with PR #213 patterns:
- instance.go: use NewExecToolWithConfig (accept full config for deny patterns)
- registry.go: pass full config to NewAgentInstance
- loop.go: add Perplexity web search options to registerSharedTools
Resolve conflicts:
- pkg/agent/loop.go: integrate context compression, command handling,
utf8 token estimation, and summarization notification into
multi-agent routing architecture
- pkg/config/config_test.go: merge imports from both branches
- pkg/agent/loop_test.go: update test to use registry-based sessions
Fixes four issues identified in the community code review:
- Session persistence broken on Windows: session keys like
"telegram:123456" contain ':', which is illegal in Windows
filenames. filepath.Base() strips drive-letter prefixes on Windows,
causing Save() to silently fail. Added sanitizeFilename() to
replace invalid chars in the filename while keeping the original
key in the JSON payload.
- HTTP client with no timeout: HTTPProvider used Timeout: 0 (infinite
wait), which can hang the entire agent if an API endpoint becomes
unresponsive. Set a 120s safety timeout.
- Slack AllowFrom type mismatch: SlackConfig used plain []string
while every other channel uses FlexibleStringSlice, so numeric
user IDs in Slack config would fail to parse.
- Token estimation wrong for CJK: estimateTokens() divided byte
length by 4, but CJK characters are 3 bytes each, causing ~3x
overestimation and premature summarization. Switched to
utf8.RuneCountInString() / 3 for better cross-language accuracy.
Also added unit tests for the session filename sanitization.
Ref #116
Update registerSharedTools to use new WebSearchToolOptions API and
add hardware tools (I2C, SPI) from upstream. Accept upstream's
new web tools config test.
* add I2C and SPI tools for hardware interaction
- Implemented I2CTool for I2C bus interaction, including device scanning, reading, and writing.
- Implemented SPITool for SPI bus communication, supporting device listing, data transfer, and reading.
- Added platform-specific implementations for Linux and stubs for non-Linux platforms.
- Updated agent loop to register new I2C and SPI tools.
- Created documentation for hardware skills, including usage examples and pinmux setup instructions.
* Remove build constraints for Linux from I2C and SPI tool files.
Resolve conflicts in loop.go, config.go, config_test.go,
spawn.go, and subagent.go. Integrate upstream ToolResult/AsyncTool
pattern with multi-agent routing features. Rename mockProvider
to mockRegistryProvider in registry_test.go to avoid redeclaration
with upstream's loop_test.go.
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 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
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>