mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
78fd080189
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.
198 lines
4.5 KiB
Go
198 lines
4.5 KiB
Go
package channels
|
|
|
|
import (
|
|
"github.com/sipeed/picoclaw/pkg/bus"
|
|
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
|
|
)
|
|
|
|
func channelTypeForEvent(m *Manager, channelName string) string {
|
|
if m == nil || m.config == nil {
|
|
return channelName
|
|
}
|
|
if bc := m.config.Channels.Get(channelName); bc != nil && bc.Type != "" {
|
|
return bc.Type
|
|
}
|
|
return channelName
|
|
}
|
|
|
|
func (m *Manager) publishChannelEvent(
|
|
kind runtimeevents.Kind,
|
|
channelName string,
|
|
scope runtimeevents.Scope,
|
|
severity runtimeevents.Severity,
|
|
payload any,
|
|
) {
|
|
if m == nil || m.runtimeEvents == nil {
|
|
return
|
|
}
|
|
if scope.Channel == "" {
|
|
scope.Channel = channelName
|
|
}
|
|
m.runtimeEvents.PublishNonBlocking(runtimeevents.Event{
|
|
Kind: kind,
|
|
Source: runtimeevents.Source{Component: "channel", Name: channelName},
|
|
Scope: scope,
|
|
Severity: severity,
|
|
Payload: payload,
|
|
Attrs: channelEventAttrs(payload),
|
|
})
|
|
}
|
|
|
|
func channelEventAttrs(payload any) map[string]any {
|
|
switch payload := payload.(type) {
|
|
case ChannelLifecyclePayload:
|
|
attrs := map[string]any{}
|
|
setAttrString(attrs, "type", payload.Type)
|
|
setAttrString(attrs, "error", payload.Error)
|
|
return attrs
|
|
case ChannelOutboundPayload:
|
|
attrs := map[string]any{}
|
|
if payload.Media {
|
|
attrs["media"] = payload.Media
|
|
}
|
|
if payload.ContentLen > 0 {
|
|
attrs["content_len"] = payload.ContentLen
|
|
}
|
|
if len(payload.MessageIDs) > 0 {
|
|
attrs["message_ids_count"] = len(payload.MessageIDs)
|
|
}
|
|
setAttrString(attrs, "reply_to_message_id", payload.ReplyToMessageID)
|
|
setAttrString(attrs, "error", payload.Error)
|
|
if payload.Retries > 0 {
|
|
attrs["retries"] = payload.Retries
|
|
}
|
|
return attrs
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func setAttrString(attrs map[string]any, key, value string) {
|
|
if value != "" {
|
|
attrs[key] = value
|
|
}
|
|
}
|
|
|
|
func (m *Manager) publishOutboundSent(
|
|
channelName string,
|
|
msg bus.OutboundMessage,
|
|
messageIDs []string,
|
|
) {
|
|
m.publishChannelEvent(
|
|
runtimeevents.KindChannelMessageOutboundSent,
|
|
channelName,
|
|
scopeFromOutboundContext(msg.Context),
|
|
runtimeevents.SeverityInfo,
|
|
ChannelOutboundPayload{
|
|
ContentLen: len([]rune(msg.Content)),
|
|
MessageIDs: append([]string(nil), messageIDs...),
|
|
ReplyToMessageID: msg.ReplyToMessageID,
|
|
},
|
|
)
|
|
}
|
|
|
|
func (m *Manager) publishOutboundQueued(
|
|
channelName string,
|
|
msg bus.OutboundMessage,
|
|
) {
|
|
m.publishChannelEvent(
|
|
runtimeevents.KindChannelMessageOutboundQueued,
|
|
channelName,
|
|
scopeFromOutboundContext(msg.Context),
|
|
runtimeevents.SeverityInfo,
|
|
ChannelOutboundPayload{
|
|
ContentLen: len([]rune(msg.Content)),
|
|
ReplyToMessageID: msg.ReplyToMessageID,
|
|
},
|
|
)
|
|
}
|
|
|
|
func (m *Manager) publishOutboundFailed(
|
|
channelName string,
|
|
msg bus.OutboundMessage,
|
|
err error,
|
|
media bool,
|
|
) {
|
|
payload := ChannelOutboundPayload{
|
|
Media: media,
|
|
ContentLen: len([]rune(msg.Content)),
|
|
ReplyToMessageID: msg.ReplyToMessageID,
|
|
Retries: maxRetries,
|
|
}
|
|
if err != nil {
|
|
payload.Error = err.Error()
|
|
}
|
|
m.publishChannelEvent(
|
|
runtimeevents.KindChannelMessageOutboundFailed,
|
|
channelName,
|
|
scopeFromOutboundContext(msg.Context),
|
|
runtimeevents.SeverityError,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
func (m *Manager) publishOutboundMediaSent(
|
|
channelName string,
|
|
msg bus.OutboundMediaMessage,
|
|
messageIDs []string,
|
|
) {
|
|
m.publishChannelEvent(
|
|
runtimeevents.KindChannelMessageOutboundSent,
|
|
channelName,
|
|
scopeFromOutboundContext(msg.Context),
|
|
runtimeevents.SeverityInfo,
|
|
ChannelOutboundPayload{
|
|
Media: true,
|
|
MessageIDs: append([]string(nil), messageIDs...),
|
|
},
|
|
)
|
|
}
|
|
|
|
func (m *Manager) publishOutboundMediaQueued(
|
|
channelName string,
|
|
msg bus.OutboundMediaMessage,
|
|
) {
|
|
m.publishChannelEvent(
|
|
runtimeevents.KindChannelMessageOutboundQueued,
|
|
channelName,
|
|
scopeFromOutboundContext(msg.Context),
|
|
runtimeevents.SeverityInfo,
|
|
ChannelOutboundPayload{Media: true},
|
|
)
|
|
}
|
|
|
|
func (m *Manager) publishOutboundMediaFailed(
|
|
channelName string,
|
|
msg bus.OutboundMediaMessage,
|
|
err error,
|
|
) {
|
|
payload := ChannelOutboundPayload{
|
|
Media: true,
|
|
Retries: maxRetries,
|
|
}
|
|
if err != nil {
|
|
payload.Error = err.Error()
|
|
}
|
|
m.publishChannelEvent(
|
|
runtimeevents.KindChannelMessageOutboundFailed,
|
|
channelName,
|
|
scopeFromOutboundContext(msg.Context),
|
|
runtimeevents.SeverityError,
|
|
payload,
|
|
)
|
|
}
|
|
|
|
func scopeFromOutboundContext(ctx bus.InboundContext) runtimeevents.Scope {
|
|
return runtimeevents.Scope{
|
|
Channel: ctx.Channel,
|
|
Account: ctx.Account,
|
|
ChatID: ctx.ChatID,
|
|
TopicID: ctx.TopicID,
|
|
SpaceID: ctx.SpaceID,
|
|
SpaceType: ctx.SpaceType,
|
|
ChatType: ctx.ChatType,
|
|
SenderID: ctx.SenderID,
|
|
MessageID: ctx.MessageID,
|
|
}
|
|
}
|