Sort irc import alphabetically in helpers.go and fix struct field
alignment in irc.go to satisfy golangci-lint gci formatter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
io.ReadAll errors were silently discarded with `body, _ := io.ReadAll(...)`,
which could cause empty or partial data to be used for JSON unmarshaling
or error messages. This adds proper error checks for all instances.
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
* 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>
* 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>
Accept upstream versions for all non-Telegram files to keep PR
scope focused on Telegram message chunking only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After re-splitting an oversized chunk, sub-chunks were sent without
verifying their HTML also fits under 4096 chars. Non-uniform HTML
expansion (e.g. a sub-chunk dense with bold/links) could still exceed
the limit. Use a queue that pushes sub-chunks back for re-validation
instead of sending them blindly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When HTML parsing fails, the fallback was re-sending the same HTML
string with ParseMode cleared, showing raw HTML tags to users.
Now pass the original markdown chunk so the fallback displays
readable plain text instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Manager splits at MaxMessageLength before calling Send(), and
Telegram's Send() was re-splitting at 4000 internally. Aligning the
channel-level limit to 4000 avoids that redundant second split while
preserving the safety margin for markdown-to-HTML expansion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
addMsg now calls f.Sync() before f.Close(), matching the durability
guarantee of writeMeta and rewriteJSONL (both use WriteFileAtomic
with fsync). Without this, a power loss could leave the appended
line in the kernel page cache only — lost on reboot.
When the LLM returns multiple tool calls, they are now executed
concurrently using goroutines + sync.WaitGroup instead of sequentially.
Results are collected in an indexed slice and processed in original order
to preserve message ordering. MessageTool.sentInRound is changed to
atomic.Bool for thread safety.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Address Copilot review feedback:
- Move resolveDiscordRefs(content) before the referenced message
concatenation to prevent message links in quoted replies from being
expanded twice.
- Add unit tests for channelRefRe and msgLinkRe regex patterns,
covering valid/invalid inputs and the 3-link cap.
* 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>
Security fix: resolveDiscordRefs now takes a guildID parameter and
skips message links pointing to a different guild, preventing the bot
from leaking content across guilds.
Also uses s.State.Channel() cache before falling back to API calls
to reduce Discord API usage and rate limit risk.
- Guard against nil ReferencedMessage.Author to prevent panic
- Hoist regexp.MustCompile to package-level vars to avoid
re-compilation on every handleMessage call
- Both are defensive programming improvements
Add resolveDiscordRefs method that:
1. Resolves <#id> channel mentions to #channel-name by calling
the Discord API to fetch channel info
2. Expands Discord message links (up to 3) by fetching the linked
message content and appending it as '[linked message from User]: content'
Applied to both quoted/referenced messages and the main message
content for full context resolution.
When a user replies to a message in Discord, the bot now reads
m.ReferencedMessage and prepends its content to the incoming
message as '[quoted message from Username]: content'.
This gives the LLM full context of what message the user is
replying to, enabling meaningful follow-up conversations.
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
Keep both KimiCodeUserAgent test (from this branch) and
serializeMessages tests (from upstream) — no overlap.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add missing LITELLM entry to loadProviderEnvOverrides so
PICOCLAW_PROVIDERS_LITELLM_API_KEY/API_BASE env vars work
- Replace valid .env content in TestLoadConfig_MalformedDotenv_NonFatal
with genuinely malformed content (bare key without '=') to actually
exercise the non-fatal error path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address Copilot review feedback:
- Use guardCommand() instead of Execute() to avoid running dangerous
commands (format, mkfs, diskpart) on the host if regex regresses
- Assert error message contains "blocked" for blocked commands
- Replace "go fmt ./..." with "echo go fmt ./..." to avoid accidental
file modification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve go.mod conflict: keep h2non/filetype from upstream,
tcell/v2 stays in direct deps only (not duplicated in indirect).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Avoid re-parsing apiBase URL on every Chat() invocation by computing
isKimiAPI once in NewProvider(). Also document why the KimiCLI/0.77
User-Agent string is required.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* 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>
- Separate third-party imports from local module imports (gci)
- Fix byte slice literal formatting (gofumpt)
- Rename shadowed err variable to ftErr (govet)
- Remove trailing blank lines in test files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Consolidate extractImageKey/extractFileKey/extractFileName into shared
extractJSONStringField helper to reduce code duplication
- Move mentionPlaceholderRegex to package-level position after imports
- Rename feishuCfg field to config for clarity within FeishuChannel
- Replace @_user_1 heuristic with GET /open-apis/bot/v3/info API call
at startup for reliable bot @mention detection
- Fix double close on file handle in downloadResource by removing defer
and using explicit close in both success and error paths
- Add unit tests for common.go and feishu_64.go helpers (53 test cases)
- serializeMessages: preserve ToolCallID/ToolCalls when Media is present
- resolveMediaRefs: add 20MB file size limit to prevent OOM
- mimeFromExtension: return empty string for unknown extensions
- Add 11 unit tests for serializeMessages, resolveMediaRefs, mimeFromExtension
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce markdown input from 700 to 600 repeats (3600 runes) so it stays
under the 4000-rune chunk threshold. This ensures the test actually
exercises the HTML-expansion re-splitting logic rather than being split
at the markdown level first.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "kimi-code" to the moonshot provider's providerNames in
ConvertProvidersToModelList so configs using
agents.defaults.provider: "kimi-code" migrate correctly.
- Add TestProviderChat_KimiCodeUserAgent verifying that
User-Agent: KimiCLI/0.77 is set when apiBase hostname is
api.kimi.com and not set for other hosts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Break function signatures and assert calls that exceed the 120-char
golines limit onto multiple lines.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove stale "falls back to plain text" comment on Send
- Add empty ChatID validation in SendMedia to match Send
- Use messageID+fileKey as local filename to avoid write collisions
- Check allowlist before downloading inbound media to avoid wasted I/O
- Return errUnsupported consistently from all 32-bit stub methods
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).
- Use atomic.Bool for closed flag to prevent TOCTOU race between
CallTool and Close operations
- Add double-check pattern in CallTool for thread-safe closed state
- Use atomic Swap in Close to ensure no new calls can start after
closed flag is set
- Move MCP manager cleanup defer before initialization to handle
partial initialization failures
- Update tests to use atomic.Bool operations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add migrateChannelConfigs() and ValidateModelList() to the fresh-
install path (no config.json) so legacy env vars are migrated and
model list is validated consistently with the normal loading path
- Use os.LookupEnv instead of os.Getenv in loadProviderEnvOverrides
so explicitly empty env vars (e.g. PICOCLAW_PROVIDERS_X_API_BASE=)
can clear values from config.json
- Guard .env loading with sync.Once to avoid repeated disk I/O and
noisy log messages when LoadConfig is called from polling handlers
- Add tests: .env file loading, missing config.json with env vars,
malformed .env non-fatal behavior, and LookupEnv empty-override
Note: go.mod tcell/v2 and tview are correctly listed as direct deps
(they are imported by the launcher TUI); upstream go.mod was stale.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous dedupe map rotation logic completely cleared the map when it reached max size, causing an 'amnesia cliff' where immediately arriving duplicates of just-forgotten messages would be processed.
This change replaces that with a MessageDeduplicator struct that uses a circular queue (ring buffer) to track insertions. When the limit is reached, it only evicts the absolute oldest message from the map, completely resolving the cliff issue.
This also cleans up the WeCom Bot and App webhook handlers by encapsulating the mutex and map state.
When the dedupe map rotates, the previous logic entirely cleared the map, meaning the message that triggered the rotation was immediately forgotten and could be duplicated immediately.
This change seeds the new map with the current message to prevent that. Also adds a defensive nil check.
Match rotation semantics to prior behavior by fully resetting the dedupe map
once the size limit is exceeded, and add focused tests for duplicate detection
and boundary rotation behavior.
Centralize dedupe map access behind a mutex-safe helper and use it in both
WeCom bot and WeCom app channels to eliminate concurrent map access races while
preserving current dedupe behavior.