normalizeChatType exists in openclaw package internals but is not part of
the public plugin-sdk surface (not re-exported from openclaw/plugin-sdk).
Keep the explicit chatType guard which is equivalent and type-safe.
- effectiveReplyToId: skip thread-under-message for DMs (kind !== 'direct')
so DM history stays linear regardless of replyToMode setting
- channel.ts threading.resolveReplyToMode: replace manual chatType cast
with normalizeChatType() from plugin-sdk (same pattern as Slack)
Addresses Greptile review comments on PR #29587.
- resolveMattermostReplyToMode() now takes ResolvedMattermostAccount (not a
loose structural type), improving type safety and removing structural
duplication
- Remove local duplicate in monitor.ts; pass kind directly (ChatType is
already 'direct' | 'channel' | 'group' from channelKind())
- Eliminate the intermediate chatType variable at call site in monitor.ts
- Replace unsafe (chatType ?? '') cast in channel.ts with explicit narrowing
guard before passing to resolveMattermostReplyToMode
- Consolidate duplicate import lines in accounts.ts into single import block
All Greptile review comments addressed.
- Move resolveMattermostReplyToMode() from monitor.ts to mattermost/accounts.ts
as a shared utility (mirrors Slack's resolveSlackReplyToMode pattern)
- Use proper types (MattermostReplyToMode, MattermostChatTypeKey) instead of string
- Replace unsafe cast in channel.ts with explicit chatType guard
- Import shared utility in both monitor.ts and channel.ts
- Eliminates duplicated resolution logic (Greptile feedback)
Outbound agent replies need the channel plugin to declare a threading
section so the core reply-threading logic (auto-reply/reply/reply-threading.ts)
can pick up replyToMode for outbound messages via getChannelDock().threading.
Without this, replyToMode only affected session-key routing, not the
actual thread placement of agent replies.
Suggested by code review on PR #29587.
Adds replyToMode configuration option to the Mattermost channel plugin,
bringing it to parity with the Slack and Discord plugins.
- types.ts: add ReplyToMode import + replyToMode field with JSDoc
- config-schema.ts: add Zod validation for replyToMode
- monitor.ts: compute effectiveReplyToId based on replyToMode;
split session key by post.id when replyToMode is 'all' or 'first'
Behavior:
off (default): reply in channel, one session per channel
all: reply in thread under each message, one session per thread
first: same as 'all' (future: historyScope support)
Tested against live Mattermost instance with isolated dev container.
* fix(mattermost): prevent duplicate messages when block streaming + threading are active
Remove replyToId from createBlockReplyPayloadKey so identical content is
deduplicated regardless of threading target. Add explicit threading dock
to the Mattermost plugin with resolveReplyToMode reading from config
(default "all"), and add replyToMode to the Mattermost config schema.
Fixes#41219
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(mattermost): address PR review — per-account replyToMode and test clarity
Read replyToMode from the merged per-account config via
resolveMattermostAccount so account-level overrides are honored in
multi-account setups. Add replyToMode to MattermostAccountConfig type.
Rename misleading test to clarify it exercises shouldDropFinalPayloads
short-circuit, not payload key dedup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Replies: keep block-pipeline reply targets distinct
* Tests: cover block reply target-aware dedupe
* Update CHANGELOG.md
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(cron): prevent duplicate proactive delivery on transient retry
* refactor: scope skipQueue to retryTransient path only
Non-retrying direct delivery (structured content / thread) keeps the
write-ahead queue so recoverPendingDeliveries can replay after a crash.
Addresses review feedback from codex-connector.
* fix: preserve write-ahead queue on initial delivery attempt
The first call through retryTransientDirectCronDelivery now keeps the
write-ahead queue entry so recoverPendingDeliveries can replay after a
crash. Only subsequent retry attempts set skipQueue to prevent
duplicate sends.
Addresses second codex-connector review on ea5ae5c.
* ci: retrigger checks
* Cron: bypass write-ahead queue for direct isolated delivery
* Tests: assert isolated cron skipQueue invariants
* Changelog: add cron duplicate-delivery fix entry
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix: prevent duplicate assistant messages in TUI (fixes#35278)
When startAssistant() is called multiple times with the same runId,
it was creating duplicate AssistantMessageComponent instances instead
of reusing the existing one. This caused messages to appear twice in
the terminal UI.
The fix checks if a component already exists for the runId before
creating a new one. If it exists, we update its text instead of
appending a duplicate component.
Test coverage includes verification that:
- Only one component is created when startAssistant is called twice
- The second text replaces the first
- Component count remains 1 (prevents regression)
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
* Changelog: add TUI duplicate-render fix entry
---------
Co-authored-by: 沐沐 <mumu@example.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Fixes#5090
Without this plist key, macOS silently denies Reminders access when
running through OpenClaw.app, preventing the apple-reminders skill
from requesting permission.
(cherry picked from commit e5774471c8)