* feat(chat,seahorse): persist and display model_name across history
* test(seahorse): fix lint regressions in repair coverage
* fix(pico): preserve model_name in live updates
* fix(pico): preserve model_name through live stream wrappers
* Support streaming
* fix: stream pico reasoning updates
Route Pico reasoning through the active streamer and hide empty thought placeholders.
* fix: harden configured streaming delivery
* fix ci
* fix split issue
* fix(channels): dismiss tool feedback animation when turn ends via ResponseHandled
When a tool sets ResponseHandled=true (e.g., send_file), the turn ends
without producing a final assistant response. This meant no outbound
message triggered FinalizeToolFeedbackMessage, leaving the animation
goroutine running indefinitely — editing the Feishu card every 3 seconds
with "." / ".." suffixes long after the tool had finished.
Fix: call DismissToolFeedback at "Tool output satisfied delivery" so the
tracker is cleared and the animation goroutine is stopped immediately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(adapters): add DismissToolFeedback to channelManagerAdapter
The adapter must implement the new interface method added in the
previous commit, otherwise the package fails to compile.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(channels): pass InboundContext to DismissToolFeedback for topic-aware keys
Telegram forum topics use scoped tracker keys like "chatID/topicID",
resolved via ToolFeedbackMessageChatID with the InboundContext. The
previous nil context caused the lookup to fall back to the raw chatID,
missing the topic-scoped entry and leaving the animation goroutine
orphaned in forum-topic conversations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style: wrap long function signatures for golines
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(feishu): fix image download with API fallback and post image support
- Add Image.Get API fallback when MessageResource.Get fails (different
permission scope: im:resource vs im:message:readonly)
- Extract and download images from post (rich text) messages
- Extract images from interactive card messages
- Deduplicate post image keys across locales
- Add comprehensive tests for new helpers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(media): add image path tags alongside base64 for LLM file access
Images are still base64-encoded into msg.Media for multimodal LLMs,
but now also get [image:path] tags injected into message content so
the LLM knows the local file path for save/forward operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(media): only auto-inject images for tool results, not user messages
Channel-received images (role=user) now get path tags only, letting
the LLM decide whether to view via load_image or just operate on
the file. Tool result images (role=tool, e.g. load_image) are
base64-encoded into a synthetic user message appended after the tool
message, since many LLM APIs don't support image_url in tool messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(media): preserve tool-message ordering for multi-tool-call scenarios
Move synthetic user message (carrying base64 tool images) to after the
entire contiguous tool-message block instead of immediately after each
tool message. This preserves the assistant→tool→tool ordering required
by OpenAI-compatible APIs.
Also fix load_image to use generic [image: photo] placeholder so
injectPathTags can properly replace it with the actual path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(test): update load_image test for [image: photo] placeholder
The test was checking ForLLM for the media:// ref, but load_image now
emits the generic [image: photo] placeholder instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(media): match all channel image placeholders in injectPathTags
Different channels emit different placeholder formats — Telegram/Feishu
use [image: photo], WeCom/WeChat/Line use bare [image], QQ/Discord use
[image: <filename>]. The previous string-match code only handled
[image: photo], so for the other channels the path tag was appended as
a duplicate, producing content like "[image] [image:/path]".
Switch to per-type regex that matches all generic placeholder shapes
while leaving path tags ([image:/path]) untouched. Also fixes the same
issue for [audio], [video], [file] tags. Added test coverage for the
various placeholder shapes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(media): skip path tag append for JSON content (Feishu cards/posts)
When content is structured JSON (interactive cards, post messages),
injectPathTags now skips the fallback append — only placeholder
replacement is attempted. This prevents corrupting JSON payloads
like {"schema":"2.0",...} with appended [image:/path] tags.
Adds looksLikeJSON() helper and three test cases covering JSON
objects, arrays, and an end-to-end resolveMediaRefs scenario with
Feishu card content.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(media): prepend path tags for JSON content, narrow looksLikeJSON
Two fixes from code review:
1. looksLikeJSON now only checks for '{' prefix (not '['), avoiding
false positives on regular text like "[update] see attached".
2. For JSON content (Feishu cards/posts), path tags are prepended
before the JSON instead of being silently dropped. This ensures
the LLM can discover attached images via the path tag while the
JSON payload stays valid for downstream parsing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add detection for 'unknown variant' + 'image_url' error pattern used by
DeepSeek and other strict providers when vision is not supported.
These providers reject the image_url field at the JSON schema level
rather than returning a semantic 'not supported' message.
- Use fmt.Sprintf fallback instead of {} on encoding errors
- Normalize nil args to {} in FormatArgsJSON for consistent output
- Update tests to expect {} instead of null for nil args
Based on PR #2670 review feedback from afjcjsbx
Add a non-blocking runtime publish path and switch hot-path publishers to it.
Enforce subscription timeout boundaries, keep ordered subscriber snapshots up to date on subscribe changes, expose all runtime kinds to process hooks, add safe log attrs for non-agent events, and close the gateway message bus on full shutdown.
Replace leftover SubTurnOrphanResultEvent and short subturn event references with runtime event kinds in comments, tests, and hook design notes.
Validation: GOCACHE=/tmp/picoclaw-go-cache go test ./pkg/agent -run TestSpawnSubTurn_OrphanResultRouting; make lint
Remove the legacy EventKind/Event envelope mapping and let agent event emission build pkg/events.Event values directly.
Keep HookMeta as the shared hook metadata shape and preserve legacy observe string aliases by mapping them to runtime event kinds.
Validation: GOCACHE=/tmp/picoclaw-go-cache go test ./pkg/agent; make lint
Drop the old agent EventBus, SubscribeEvents/EventDrops public surface, legacy hook observer dispatch, and hook.event process notification path. Agent observations now flow through pkg/events runtime events.
Validation: go test ./pkg/agent; make lint
Move agent domain event payload structs out of the legacy event envelope file so the remaining EventKind/Event/EventMeta compatibility layer can be removed independently later.
Validation: go test ./pkg/agent; make lint
Use RuntimeEventObserver for the normal in-process hook observer path and make the process-hook helper assert hook.runtime_event notifications.
Validation: go test ./pkg/agent; make lint
Move AgentLoop event assertions to the runtime event stream and keep the legacy SubscribeEvents test only for dual-publish compatibility.
Validation: go test ./pkg/agent; make lint
Deprecate the legacy agent event APIs and add a runtime event test helper, then migrate the follow-up queued test to the runtime event stream.
Validation: go test ./pkg/agent; make lint
Migrate hook observation to runtime events and update the process hook notification protocol. Add runtime event publication for message bus failures, channel lifecycle/outbound flow, gateway reloads, MCP server state, and MCP tool calls.
Validation: go test ./pkg/events/... ./pkg/bus ./pkg/agent ./pkg/channels ./pkg/mcp ./pkg/tools/integration ./pkg/gateway; make lint