picoclaw/pkg/channels/interfaces.go
dj-oyu c5a3990dd3 fix: deduplicate status/task messages by editing instead of creating new bubbles
IsStatus and IsTaskStatus flags on OutboundMessage were set by the agent
loop but completely ignored by the channel Manager. Every status update
created a new message bubble, flooding chats with redundant messages.

Add MessageSenderWithID interface and implement it for Telegram, Discord,
and Pico channels. Manager now routes IsStatus/IsTaskStatus messages to
dedicated handlers that track message IDs and edit in-place. Final
responses also reuse the tracked status message via preSend. TTL janitor
extended to clean up stale tracking entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 05:38:09 +09:00

47 lines
2.2 KiB
Go

package channels
import "context"
// TypingCapable — channels that can show a typing/thinking indicator.
// StartTyping begins the indicator and returns a stop function.
// The stop function MUST be idempotent and safe to call multiple times.
type TypingCapable interface {
StartTyping(ctx context.Context, chatID string) (stop func(), err error)
}
// MessageEditor — channels that can edit an existing message.
// messageID is always string; channels convert platform-specific types internally.
type MessageEditor interface {
EditMessage(ctx context.Context, chatID string, messageID string, content string) error
}
// ReactionCapable — channels that can add a reaction (e.g. 👀) to an inbound message.
// ReactToMessage adds a reaction and returns an undo function to remove it.
// The undo function MUST be idempotent and safe to call multiple times.
type ReactionCapable interface {
ReactToMessage(ctx context.Context, chatID, messageID string) (undo func(), err error)
}
// MessageSenderWithID — channels that can send a message and return its platform-specific ID.
// Used by Manager to track status/task messages for later editing.
type MessageSenderWithID interface {
SendWithID(ctx context.Context, chatID string, content string) (messageID string, err error)
}
// PlaceholderCapable — channels that can send a placeholder message
// (e.g. "Thinking... 💭") that will later be edited to the actual response.
// The channel MUST also implement MessageEditor for the placeholder to be useful.
// SendPlaceholder returns the platform message ID of the placeholder so that
// Manager.preSend can later edit it via MessageEditor.EditMessage.
type PlaceholderCapable interface {
SendPlaceholder(ctx context.Context, chatID string) (messageID string, err error)
}
// PlaceholderRecorder is injected into channels by Manager.
// Channels call these methods on inbound to register typing/placeholder state.
// Manager uses the registered state on outbound to stop typing and edit placeholders.
type PlaceholderRecorder interface {
RecordPlaceholder(channel, chatID, placeholderID string)
RecordTypingStop(channel, chatID string, stop func())
RecordReactionUndo(channel, chatID string, undo func())
}