mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-15 12:14:17 +00:00
feat(channels): add human-like chat behavior
Squash merge PR #99 after resolving conflicts with current dev. PR CI run 26703381807 passed release-versioning, go, and web.
This commit is contained in:
1 parent
3569f8d681
commit
f0f39ce31f
51 files changed
+1786
-205
No files matched your search
+1
-1
@@ -588,7 +588,7 @@ func runGateway() {
|
||||
registerConfigChannels(cfg, channelMgr, msgBus, pgStores, instanceLoader, audioMgr)
|
||||
|
||||
// Register channels/instances/links/teams RPC methods
|
||||
chInstancesM := wireChannelRPCMethods(server, pgStores, channelMgr, instanceLoader, agentRouter, msgBus, workspace)
|
||||
chInstancesM := wireChannelRPCMethods(server, pgStores, channelMgr, instanceLoader, agentRouter, msgBus, cfg, workspace)
|
||||
|
||||
// Bitrix24 orphan-bot cleaner. Fires from channel_instances delete handler
|
||||
// when the channel is no longer loaded in the Manager (typical scenario:
|
||||
|
||||
@@ -148,9 +148,10 @@ func registerConfigChannels(cfg *config.Config, channelMgr *channels.Manager, ms
|
||||
// Returns the channel-instances methods handler so the caller can register
|
||||
// per-channel-type orphan cleaners (e.g. Bitrix24 imbot.unregister) after
|
||||
// per-channel dependencies (portal store, encryption key) are in scope.
|
||||
func wireChannelRPCMethods(server *gateway.Server, pgStores *store.Stores, channelMgr *channels.Manager, instanceLoader *channels.InstanceLoader, agentRouter *agent.Router, msgBus *bus.MessageBus, dataDir string) *methods.ChannelInstancesMethods {
|
||||
func wireChannelRPCMethods(server *gateway.Server, pgStores *store.Stores, channelMgr *channels.Manager, instanceLoader *channels.InstanceLoader, agentRouter *agent.Router, msgBus *bus.MessageBus, cfg *config.Config, dataDir string) *methods.ChannelInstancesMethods {
|
||||
// Register channels RPC methods (after channelMgr is initialized with all channels)
|
||||
methods.NewChannelsMethods(channelMgr).Register(server.Router())
|
||||
methods.NewChatBehaviorMethods(cfg, channelMgr).Register(server.Router())
|
||||
|
||||
// Register channel instances WS RPC methods
|
||||
var chInstancesM *methods.ChannelInstancesMethods
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
@@ -239,9 +240,13 @@ func processNormalMessage(
|
||||
chatIDForRun = lk
|
||||
}
|
||||
blockReply := deps.ChannelMgr != nil && deps.ChannelMgr.ResolveBlockReply(msg.Channel, deps.Cfg.Gateway.BlockReply)
|
||||
chatBehavior := channels.ResolvedChatBehavior{}
|
||||
if deps.ChannelMgr != nil {
|
||||
chatBehavior = deps.ChannelMgr.ResolveChatBehavior(msg.Channel, deps.Cfg.Gateway.ChatBehavior)
|
||||
}
|
||||
toolStatus := deps.Cfg.Gateway.ToolStatus == nil || *deps.Cfg.Gateway.ToolStatus // default true
|
||||
if deps.ChannelMgr != nil {
|
||||
deps.ChannelMgr.RegisterRun(runID, msg.Channel, chatIDForRun, messageID, outMeta, msg.TenantID, enableStream, blockReply, toolStatus)
|
||||
deps.ChannelMgr.RegisterRunWithBehavior(runID, msg.Channel, chatIDForRun, messageID, outMeta, msg.TenantID, enableStream, blockReply, toolStatus, chatBehavior)
|
||||
}
|
||||
|
||||
// Group-aware system prompt: help the LLM adapt tone and behavior for group chats.
|
||||
@@ -429,37 +434,37 @@ func processNormalMessage(
|
||||
|
||||
// Schedule through main lane (per-session concurrency controlled by maxConcurrent)
|
||||
outCh := deps.Sched.ScheduleWithOpts(schedCtx, "main", agent.RunRequest{
|
||||
SessionKey: sessionKey,
|
||||
Message: msg.Content,
|
||||
Media: reqMedia,
|
||||
ForwardMedia: fwdMedia,
|
||||
Channel: msg.Channel,
|
||||
ChannelType: resolveChannelType(deps.ChannelMgr, msg.Channel),
|
||||
SessionKey: sessionKey,
|
||||
Message: msg.Content,
|
||||
Media: reqMedia,
|
||||
ForwardMedia: fwdMedia,
|
||||
Channel: msg.Channel,
|
||||
ChannelType: resolveChannelType(deps.ChannelMgr, msg.Channel),
|
||||
// Forward Bitrix24 portal domain from channel metadata so the
|
||||
// system prompt can teach the LLM the correct entity URL host.
|
||||
// Empty for non-bitrix24 channels — section is skipped downstream.
|
||||
BitrixPortalDomain: msg.Metadata["bitrix_portal"],
|
||||
ChatTitle: msg.Metadata[tools.MetaChatTitle],
|
||||
ChatID: msg.ChatID,
|
||||
WorkspaceChatID: msg.ChatID,
|
||||
PeerKind: peerKind,
|
||||
LocalKey: msg.Metadata["local_key"],
|
||||
UserID: userID,
|
||||
SenderID: effectiveSenderID,
|
||||
Role: effectiveRole,
|
||||
SenderName: resolveSenderName(msg),
|
||||
RunID: runID,
|
||||
Stream: enableStream,
|
||||
HistoryLimit: msg.HistoryLimit,
|
||||
ToolAllow: msg.ToolAllow,
|
||||
ExtraSystemPrompt: extraPrompt,
|
||||
SkillFilter: skillFilter,
|
||||
ChatID: msg.ChatID,
|
||||
WorkspaceChatID: msg.ChatID,
|
||||
PeerKind: peerKind,
|
||||
LocalKey: msg.Metadata["local_key"],
|
||||
UserID: userID,
|
||||
SenderID: effectiveSenderID,
|
||||
Role: effectiveRole,
|
||||
SenderName: resolveSenderName(msg),
|
||||
RunID: runID,
|
||||
Stream: enableStream,
|
||||
HistoryLimit: msg.HistoryLimit,
|
||||
ToolAllow: msg.ToolAllow,
|
||||
ExtraSystemPrompt: extraPrompt,
|
||||
SkillFilter: skillFilter,
|
||||
}, scheduler.ScheduleOpts{
|
||||
MaxConcurrent: maxConcurrent,
|
||||
})
|
||||
|
||||
// Handle result asynchronously to not block the flush callback.
|
||||
go func(agentKey, channel, chatID, session, rID, peerKind, inboundContent string, meta map[string]string, blockReplyEnabled bool, ptd *tools.PendingTeamDispatch, tenantID, agentUUID uuid.UUID, agentOtherConfig []byte) {
|
||||
go func(agentKey, channel, chatID, session, rID, peerKind, inboundContent string, meta map[string]string, blockReplyEnabled bool, chatBehavior channels.ResolvedChatBehavior, streaming bool, ptd *tools.PendingTeamDispatch, tenantID, agentUUID uuid.UUID, agentOtherConfig []byte) {
|
||||
outcome := <-outCh
|
||||
|
||||
// Release team create lock — tasks already visible in DB, other goroutines can list.
|
||||
@@ -573,13 +578,27 @@ func processNormalMessage(
|
||||
|
||||
appendMediaToOutbound(&outMsg, outcome.Result.Media)
|
||||
|
||||
deps.MsgBus.PublishOutbound(outMsg)
|
||||
parts := []string{replyContent}
|
||||
if !streaming && len(outMsg.Media) == 0 {
|
||||
parts = channels.SplitFinalMessages(replyContent, chatBehavior.FinalSplit)
|
||||
}
|
||||
for i, part := range parts {
|
||||
msgPart := outMsg
|
||||
msgPart.Content = part
|
||||
if i > 0 {
|
||||
msgPart.Metadata = channels.CopyFollowupRoutingMeta(meta)
|
||||
if chatBehavior.FinalSplit.DelayMs > 0 {
|
||||
time.Sleep(time.Duration(chatBehavior.FinalSplit.DelayMs) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
deps.MsgBus.PublishOutbound(msgPart)
|
||||
}
|
||||
|
||||
// Auto-set followup when lead agent replies on a real channel with in_progress tasks.
|
||||
if deps.TeamStore != nil && channel != tools.ChannelSystem && channel != tools.ChannelTeammate && channel != tools.ChannelDashboard {
|
||||
go autoSetFollowup(ctx, deps.TeamStore, deps.AgentStore, agentKey, channel, chatID, replyContent)
|
||||
}
|
||||
}(agentID, msg.Channel, msg.ChatID, sessionKey, runID, peerKind, msg.Content, outMeta, blockReply, ptd, msg.TenantID, agentLoop.UUID(), agentLoop.OtherConfig())
|
||||
}(agentID, msg.Channel, msg.ChatID, sessionKey, runID, peerKind, msg.Content, outMeta, blockReply, chatBehavior, enableStream, ptd, msg.TenantID, agentLoop.UUID(), agentLoop.OtherConfig())
|
||||
}
|
||||
|
||||
// isSafeBitrixEntityToken validates a webhook-sourced Bitrix entity token before
|
||||
|
||||
Reference in new issue
Block a user