- Fix image_generation nil-pointer crash on codex agents: the sentinel now
carries a name-only Function so the many sites reading td.Function.Name never
nil-deref; codex_build still branches on Type.
- Agent-declared trigger words in IDENTITY.md wake the bot in groups without an
@mention (whole-word, Cyrillic-aware; text + caption), cached per-agent 60s.
- channel_post support with a synthetic sender + a recover() guard so a
malformed update can't crash the gateway.
- message tool action=edit (editMessageText + editMessageCaption fallback),
targeting the replied-to message via reply_to_message_id.
- message tool topic=<name> posts into a named forum topic; topics learned from
forum_topic_created into channel_contacts and resolved by name.
Co-authored-by: skensel <skensel@MacBook-Pro-skensel.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: resolve Zalo group name to real chat ID, notify origin on failed forward
Two related root causes behind "forward message to group by name" silently
failing while the agent reports success:
1. No tool let the agent resolve a group's display name (e.g. "Ban Dieu
Hanh") to the chat ID the message tool actually requires. sessions_list
only exposes session keys (numeric group IDs), never human-readable
names, so the agent had no reliable way to turn a name into a real
target and ended up passing the display name itself as `target`.
Adds zalo_list_groups, wrapping the already-used (dashboard picker)
protocol.FetchGroups behind the same optional-interface pattern as
list_group_members/GroupMemberProvider (GroupListProvider on
channels.Manager, gated to zalo_personal via RequiredChannelTypes).
2. When the resulting send fails downstream (e.g. Zalo rejects a bad
chat_id), dispatchOutbound only ever retried/notified media failures on
the same (already-broken) destination, and dropped text-only failures
entirely — even though message.go's own postCrossTargetNotice comment
states forwards must never announce a fake delivery. Because the bus
publish is fire-and-forget, the tool had already returned "sent" and
announced success to the origin chat before the real send was even
attempted.
message.go now tags cross-target forwards with origin channel/chat in
OutboundMessage.Metadata; dispatchOutbound uses it to notify the ORIGIN
chat with the real failure instead of silently dropping it or retrying
against the same invalid target.
* test: cover forward-origin metadata tagging and dispatch failure notice
Extracts dispatchOutbound's error branch into handleSendFailure so it can
be unit tested without driving the consumer loop/goroutine, and adds
coverage for: forward failures notifying the origin chat (not the broken
destination), pre-existing non-forward media/text-only behavior staying
unchanged, message.go tagging cross-target group forwards with origin
metadata alongside group_id, and the new zalo_list_groups tool/Manager
delegator.
Guard unit coverage:
- MessageTargetEnforced helper table (10 session shapes)
- TestMessageToolCrossTargetGuard — 9 scenarios: DM/group same-target
pass, cross-target block, forward opt-in pass with notice; cron,
heartbeat, subagent, team FREE
- TestMessageToolCrossTargetGuard_TraceReplay — anchors the 019d9fcf
production trace against regression
- NoticeFallbackSender — msgBus-less deployment still gets the audit
breadcrumb via t.sender
- NoNoticeOnSendFailure — failed forward does not post a phantom notice
System prompt coverage:
- <current_reply_target> emitted for direct + group, omitted when
ChatID is empty
Pre-existing TestSelfSendGuard/text_to_different_chat_allowed updated
to pass forward=true — the test now correctly isolates the self-send
guard from the new cross-target guard.
Prevent SSRF bypass via provider_type change on update — ACP skips URL
validation, so changing type post-creation could circumvent the check.
Add table-driven unit tests for argString() covering all JSON type
coercion paths (float64, int, NaN, json.Number, nil).
* fix(providers): skip URL validation for ACP provider api_base
ACP uses api_base to store agent command/binary, so enforcing http/https URL validation rejects valid ACP configs. Bypass provider URL checks for ACP while keeping URL validation for HTTP-based providers.
* fix(message): enhance target handling in message tool to support numeric chat IDs
- Introduced argString function to correctly parse numeric chat IDs from tool arguments, ensuring they are treated as strings when necessary.
- Added a new test case to verify that numeric targets are correctly processed and sent via the Telegram channel.
Message tool sent raw MEDIA: paths to channels when LLMs embedded them
in multi-line text instead of using standalone prefix. Also added tenant
validation to prevent agents from sending to channels belonging to other
tenants.
- Extract embedded MEDIA: tokens from message text, resolve as media
attachments, strip from visible content (keeps surrounding text)
- Add ChannelTenantChecker callback to validate channel belongs to
calling agent's tenant before any send path
- Add ChannelTenantID() to channel Manager for tenant lookup
- Fix sendMedia() missing ContentType on MediaAttachment
- Fix .docx/.xlsx MIME types to proper OpenXML values
Two issues fixed:
1. effectiveRestrict() now always returns true (multi-tenant security),
so the "unrestricted" test case expected wrong results — paths outside
workspace are now blocked regardless of the restrict parameter.
2. Tests used bare Unix paths (/etc/passwd) which are not absolute on
Windows (no drive letter), causing them to resolve inside the workspace
and pass incorrectly. Added outsidePath() helper that prepends the
volume name for cross-platform absolute paths.
ShellDenyGroups was defined in SystemPromptConfig but lacked full propagation
through parser, Loop fields, context injection, and system prompt population.
Per-agent overrides from other_config JSONB had zero runtime effect.
Changes:
- agent_store.go: Add ParseShellDenyGroups() to extract overrides from JSONB
- loop_types.go: Add shellDenyGroups field to Loop and LoopConfig, wire in NewLoop
- resolver.go: Wire agent-parsed shell deny groups into LoopConfig
- loop.go: Inject shellDenyGroups into context via store.WithShellDenyGroups
- loop_history.go: Populate ShellDenyGroups in system prompt config
- message_test.go: Fix macOS symlink path normalization in test expectations
Fixes test failures on macOS where /var/folders symlinks to /private/var/folders.
MessageTool.parseMediaPath() was hardcoded to only allow files in /tmp/,
while all other filesystem tools (read_file, write_file, edit, exec) use
workspace-aware resolvePath() with restrict_to_workspace enforcement.
This meant agents could create files in their workspace via write_file
but couldn't send them as attachments — only /tmp/ files from
create_image/create_audio worked.
Replace parseMediaPath() with resolveMediaPath() that:
- Reuses resolvePath() for consistent security (symlink, hardlink, traversal)
- Honors per-agent workspace + restrict_to_workspace from context
- Still allows /tmp/ as fallback (for create_image, create_audio, etc.)
- Supports relative paths resolved against workspace
- Updates tool description so LLM knows about MEDIA: prefix
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
- Fix CI: use %s for json.Number in e2e test (was %d)
- Fix path traversal in parseMediaPath: restrict to os.TempDir()
- Add 25MB file size limit (checkFileSize) before upload reads
- Drain stale uploadCallbacks on Listener.reset() to prevent leaks
- Split send.go (688 LOC) into send.go, send_image.go, send_file.go, send_helpers.go
- Add unit tests for parseMediaPath (12 cases incl. traversal attacks)