mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
refactor(runtime): drop non-session legacy context compatibility
This commit is contained in:
@@ -610,12 +610,6 @@ func TestAgentLoop_EmitsFollowUpQueuedEvent(t *testing.T) {
|
||||
if payload.SourceTool != "async_followup" {
|
||||
t.Fatalf("expected source tool async_followup, got %q", payload.SourceTool)
|
||||
}
|
||||
if payload.Channel != "cli" {
|
||||
t.Fatalf("expected channel cli, got %q", payload.Channel)
|
||||
}
|
||||
if payload.ChatID != "direct" {
|
||||
t.Fatalf("expected chat id direct, got %q", payload.ChatID)
|
||||
}
|
||||
if payload.ContentLen != len("background result") {
|
||||
t.Fatalf("expected content len %d, got %d", len("background result"), payload.ContentLen)
|
||||
}
|
||||
|
||||
@@ -116,8 +116,6 @@ const (
|
||||
|
||||
// TurnStartPayload describes the start of a turn.
|
||||
type TurnStartPayload struct {
|
||||
Channel string
|
||||
ChatID string
|
||||
UserMessage string
|
||||
MediaCount int
|
||||
}
|
||||
@@ -217,8 +215,6 @@ type SteeringInjectedPayload struct {
|
||||
// FollowUpQueuedPayload describes an async follow-up queued back into the inbound bus.
|
||||
type FollowUpQueuedPayload struct {
|
||||
SourceTool string
|
||||
Channel string
|
||||
ChatID string
|
||||
ContentLen int
|
||||
}
|
||||
|
||||
|
||||
@@ -94,8 +94,6 @@ type LLMHookRequest struct {
|
||||
Messages []providers.Message `json:"messages,omitempty"`
|
||||
Tools []providers.ToolDefinition `json:"tools,omitempty"`
|
||||
Options map[string]any `json:"options,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
ChatID string `json:"chat_id,omitempty"`
|
||||
GracefulTerminal bool `json:"graceful_terminal,omitempty"`
|
||||
}
|
||||
|
||||
@@ -117,8 +115,6 @@ type LLMHookResponse struct {
|
||||
Context *TurnContext `json:"context,omitempty"`
|
||||
Model string `json:"model"`
|
||||
Response *providers.LLMResponse `json:"response,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
ChatID string `json:"chat_id,omitempty"`
|
||||
}
|
||||
|
||||
func (r *LLMHookResponse) Clone() *LLMHookResponse {
|
||||
@@ -137,8 +133,6 @@ type ToolCallHookRequest struct {
|
||||
Context *TurnContext `json:"context,omitempty"`
|
||||
Tool string `json:"tool"`
|
||||
Arguments map[string]any `json:"arguments,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
ChatID string `json:"chat_id,omitempty"`
|
||||
}
|
||||
|
||||
func (r *ToolCallHookRequest) Clone() *ToolCallHookRequest {
|
||||
@@ -157,8 +151,6 @@ type ToolApprovalRequest struct {
|
||||
Context *TurnContext `json:"context,omitempty"`
|
||||
Tool string `json:"tool"`
|
||||
Arguments map[string]any `json:"arguments,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
ChatID string `json:"chat_id,omitempty"`
|
||||
}
|
||||
|
||||
func (r *ToolApprovalRequest) Clone() *ToolApprovalRequest {
|
||||
@@ -179,8 +171,6 @@ type ToolResultHookResponse struct {
|
||||
Arguments map[string]any `json:"arguments,omitempty"`
|
||||
Result *tools.ToolResult `json:"result,omitempty"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
ChatID string `json:"chat_id,omitempty"`
|
||||
}
|
||||
|
||||
func (r *ToolResultHookResponse) Clone() *ToolResultHookResponse {
|
||||
|
||||
+32
-151
@@ -107,14 +107,6 @@ const (
|
||||
defaultResponse = "The model returned an empty response. This may indicate a provider error or token limit."
|
||||
toolLimitResponse = "I've reached `max_tool_iterations` without a final response. Increase `max_tool_iterations` in config.json if this task needs more tool steps."
|
||||
handledToolResponseSummary = "Requested output delivered via tool attachment."
|
||||
sessionKeyAgentPrefix = "agent:"
|
||||
sessionKeyOpaquePrefix = "sk_"
|
||||
metadataKeyAccountID = "account_id"
|
||||
metadataKeyGuildID = "guild_id"
|
||||
metadataKeyTeamID = "team_id"
|
||||
metadataKeyReplyToMessage = "reply_to_message_id"
|
||||
metadataKeyParentPeerKind = "parent_peer_kind"
|
||||
metadataKeyParentPeerID = "parent_peer_id"
|
||||
)
|
||||
|
||||
func NewAgentLoop(
|
||||
@@ -234,9 +226,9 @@ func registerSharedTools(
|
||||
messageTool.SetSendCallback(func(channel, chatID, content, replyToMessageID string) error {
|
||||
pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer pubCancel()
|
||||
outboundCtx := bus.NewOutboundContext(channel, chatID, replyToMessageID)
|
||||
return msgBus.PublishOutbound(pubCtx, bus.OutboundMessage{
|
||||
Channel: channel,
|
||||
ChatID: chatID,
|
||||
Context: outboundCtx,
|
||||
Content: content,
|
||||
ReplyToMessageID: replyToMessageID,
|
||||
})
|
||||
@@ -657,8 +649,7 @@ func (al *AgentLoop) PublishResponseIfNeeded(ctx context.Context, channel, chatI
|
||||
}
|
||||
|
||||
al.bus.PublishOutbound(ctx, bus.OutboundMessage{
|
||||
Channel: channel,
|
||||
ChatID: chatID,
|
||||
Context: bus.NewOutboundContext(channel, chatID, ""),
|
||||
Content: response,
|
||||
})
|
||||
logger.InfoCF("agent", "Published outbound response",
|
||||
@@ -714,11 +705,7 @@ func outboundContextFromInbound(
|
||||
channel, chatID, replyToMessageID string,
|
||||
) bus.InboundContext {
|
||||
if inbound == nil {
|
||||
return bus.ContextFromLegacyOutbound(bus.OutboundMessage{
|
||||
Channel: channel,
|
||||
ChatID: chatID,
|
||||
ReplyToMessageID: replyToMessageID,
|
||||
})
|
||||
return bus.NewOutboundContext(channel, chatID, replyToMessageID)
|
||||
}
|
||||
|
||||
outboundCtx := *cloneInboundContext(inbound)
|
||||
@@ -736,8 +723,6 @@ func outboundContextFromInbound(
|
||||
|
||||
func outboundMessageForTurn(ts *turnState, content string) bus.OutboundMessage {
|
||||
return bus.OutboundMessage{
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
Context: outboundContextFromInbound(
|
||||
ts.opts.InboundContext,
|
||||
ts.channel,
|
||||
@@ -894,8 +879,6 @@ func (al *AgentLoop) logEvent(evt Event) {
|
||||
|
||||
switch payload := evt.Payload.(type) {
|
||||
case TurnStartPayload:
|
||||
fields["channel"] = payload.Channel
|
||||
fields["chat_id"] = payload.ChatID
|
||||
fields["user_len"] = len(payload.UserMessage)
|
||||
fields["media_count"] = payload.MediaCount
|
||||
case TurnEndPayload:
|
||||
@@ -948,8 +931,6 @@ func (al *AgentLoop) logEvent(evt Event) {
|
||||
fields["total_content_len"] = payload.TotalContentLen
|
||||
case FollowUpQueuedPayload:
|
||||
fields["source_tool"] = payload.SourceTool
|
||||
fields["channel"] = payload.Channel
|
||||
fields["chat_id"] = payload.ChatID
|
||||
fields["content_len"] = payload.ContentLen
|
||||
case InterruptReceivedPayload:
|
||||
fields["interrupt_kind"] = payload.Kind
|
||||
@@ -1292,8 +1273,7 @@ func (al *AgentLoop) sendTranscriptionFeedback(
|
||||
}
|
||||
|
||||
err := al.channelManager.SendMessage(ctx, bus.OutboundMessage{
|
||||
Channel: channel,
|
||||
ChatID: chatID,
|
||||
Context: bus.NewOutboundContext(channel, chatID, messageID),
|
||||
Content: feedbackMsg,
|
||||
ReplyToMessageID: messageID,
|
||||
})
|
||||
@@ -1369,13 +1349,15 @@ func (al *AgentLoop) ProcessDirectWithChannel(
|
||||
}
|
||||
|
||||
msg := bus.InboundMessage{
|
||||
Channel: channel,
|
||||
SenderID: "cron",
|
||||
ChatID: chatID,
|
||||
Context: bus.InboundContext{
|
||||
Channel: channel,
|
||||
ChatID: chatID,
|
||||
ChatType: "direct",
|
||||
SenderID: "cron",
|
||||
},
|
||||
Content: content,
|
||||
SessionKey: sessionKey,
|
||||
}
|
||||
msg.Context = bus.ContextFromLegacyInbound(msg)
|
||||
|
||||
return al.processMessage(ctx, msg)
|
||||
}
|
||||
@@ -1481,7 +1463,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
Channel: msg.Channel,
|
||||
ChatID: msg.ChatID,
|
||||
MessageID: msg.MessageID,
|
||||
ReplyToMessageID: inboundMetadata(msg, metadataKeyReplyToMessage),
|
||||
ReplyToMessageID: msg.Context.ReplyToMessageID,
|
||||
SenderID: msg.SenderID,
|
||||
SenderDisplayName: msg.Sender.DisplayName,
|
||||
UserMessage: msg.Content,
|
||||
@@ -1515,18 +1497,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
func (al *AgentLoop) resolveMessageRoute(msg bus.InboundMessage) (routing.ResolvedRoute, *AgentInstance, error) {
|
||||
registry := al.GetRegistry()
|
||||
inboundCtx := normalizedInboundContext(msg)
|
||||
channel := strings.TrimSpace(inboundCtx.Channel)
|
||||
if channel == "" {
|
||||
channel = msg.Channel
|
||||
}
|
||||
route := registry.ResolveRoute(routing.RouteInput{
|
||||
Channel: channel,
|
||||
AccountID: routeAccountID(msg),
|
||||
Peer: extractPeer(msg),
|
||||
ParentPeer: extractParentPeer(msg),
|
||||
GuildID: routeGuildID(msg),
|
||||
TeamID: routeTeamID(msg),
|
||||
})
|
||||
route := registry.ResolveRoute(inboundCtx)
|
||||
|
||||
agent, ok := registry.GetAgent(route.AgentID)
|
||||
if !ok {
|
||||
@@ -1551,8 +1522,7 @@ func resolveScopeKey(routeSessionKey, msgSessionKey string) string {
|
||||
}
|
||||
|
||||
func isExplicitSessionKey(sessionKey string) bool {
|
||||
sessionKey = strings.TrimSpace(strings.ToLower(sessionKey))
|
||||
return strings.HasPrefix(sessionKey, sessionKeyAgentPrefix) || strings.HasPrefix(sessionKey, sessionKeyOpaquePrefix)
|
||||
return session.IsExplicitSessionKey(sessionKey)
|
||||
}
|
||||
|
||||
func buildSessionAliases(canonicalKey string, keys ...string) []string {
|
||||
@@ -1621,8 +1591,7 @@ func (al *AgentLoop) requeueInboundMessage(msg bus.InboundMessage) error {
|
||||
pubCtx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
return al.bus.PublishOutbound(pubCtx, bus.OutboundMessage{
|
||||
Channel: msg.Channel,
|
||||
ChatID: msg.ChatID,
|
||||
Context: msg.Context,
|
||||
Content: msg.Content,
|
||||
})
|
||||
}
|
||||
@@ -1679,7 +1648,7 @@ func (al *AgentLoop) processSystemMessage(
|
||||
}
|
||||
|
||||
// Use the origin session for context
|
||||
sessionKey := routing.BuildAgentMainSessionKey(agent.ID)
|
||||
sessionKey := session.BuildMainSessionKey(agent.ID)
|
||||
|
||||
return al.runAgentLoop(ctx, agent, processOptions{
|
||||
SessionKey: sessionKey,
|
||||
@@ -1739,8 +1708,6 @@ func (al *AgentLoop) runAgentLoop(
|
||||
|
||||
if opts.SendResponse && result.finalContent != "" {
|
||||
al.bus.PublishOutbound(ctx, bus.OutboundMessage{
|
||||
Channel: opts.Channel,
|
||||
ChatID: opts.ChatID,
|
||||
Context: outboundContextFromInbound(
|
||||
opts.InboundContext,
|
||||
opts.Channel,
|
||||
@@ -1796,8 +1763,7 @@ func (al *AgentLoop) handleReasoning(
|
||||
defer pubCancel()
|
||||
|
||||
if err := al.bus.PublishOutbound(pubCtx, bus.OutboundMessage{
|
||||
Channel: channelName,
|
||||
ChatID: channelID,
|
||||
Context: bus.NewOutboundContext(channelName, channelID, ""),
|
||||
Content: reasoningContent,
|
||||
}); err != nil {
|
||||
// Treat context.DeadlineExceeded / context.Canceled as expected
|
||||
@@ -1851,8 +1817,6 @@ func (al *AgentLoop) runTurn(ctx context.Context, ts *turnState) (turnResult, er
|
||||
EventKindTurnStart,
|
||||
ts.eventMeta("runTurn", "turn.start"),
|
||||
TurnStartPayload{
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
UserMessage: ts.userMessage,
|
||||
MediaCount: len(ts.media),
|
||||
},
|
||||
@@ -2085,8 +2049,6 @@ turnLoop:
|
||||
Messages: callMessages,
|
||||
Tools: providerToolDefs,
|
||||
Options: llmOpts,
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
GracefulTerminal: gracefulTerminal,
|
||||
})
|
||||
switch decision.normalizedAction() {
|
||||
@@ -2314,8 +2276,6 @@ turnLoop:
|
||||
Context: cloneTurnContext(ts.turnCtx),
|
||||
Model: llmModel,
|
||||
Response: response,
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
})
|
||||
switch decision.normalizedAction() {
|
||||
case HookActionContinue, HookActionModify:
|
||||
@@ -2346,7 +2306,7 @@ turnLoop:
|
||||
reasoningContent = response.ReasoningContent
|
||||
}
|
||||
go al.handleReasoning(
|
||||
turnCtx,
|
||||
ctx,
|
||||
reasoningContent,
|
||||
ts.channel,
|
||||
al.targetReasoningChannelID(ts.channel),
|
||||
@@ -2467,8 +2427,6 @@ turnLoop:
|
||||
Context: cloneTurnContext(ts.turnCtx),
|
||||
Tool: toolName,
|
||||
Arguments: toolArgs,
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
})
|
||||
switch decision.normalizedAction() {
|
||||
case HookActionContinue, HookActionModify:
|
||||
@@ -2514,8 +2472,6 @@ turnLoop:
|
||||
Context: cloneTurnContext(ts.turnCtx),
|
||||
Tool: toolName,
|
||||
Arguments: toolArgs,
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
})
|
||||
if !approval.Approved {
|
||||
allResponsesHandled = false
|
||||
@@ -2605,8 +2561,6 @@ turnLoop:
|
||||
ts.scope.meta(toolIteration, "runTurn", "turn.follow_up.queued"),
|
||||
FollowUpQueuedPayload{
|
||||
SourceTool: asyncToolName,
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
ContentLen: len(content),
|
||||
},
|
||||
)
|
||||
@@ -2614,10 +2568,13 @@ turnLoop:
|
||||
pubCtx, pubCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer pubCancel()
|
||||
_ = al.bus.PublishInbound(pubCtx, bus.InboundMessage{
|
||||
Channel: "system",
|
||||
SenderID: fmt.Sprintf("async:%s", asyncToolName),
|
||||
ChatID: fmt.Sprintf("%s:%s", ts.channel, ts.chatID),
|
||||
Content: content,
|
||||
Context: bus.InboundContext{
|
||||
Channel: "system",
|
||||
ChatID: fmt.Sprintf("%s:%s", ts.channel, ts.chatID),
|
||||
ChatType: "direct",
|
||||
SenderID: fmt.Sprintf("async:%s", asyncToolName),
|
||||
},
|
||||
Content: content,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2652,8 +2609,6 @@ turnLoop:
|
||||
Arguments: toolArgs,
|
||||
Result: toolResult,
|
||||
Duration: toolDuration,
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
})
|
||||
switch decision.normalizedAction() {
|
||||
case HookActionContinue, HookActionModify:
|
||||
@@ -2692,9 +2647,13 @@ turnLoop:
|
||||
parts = append(parts, part)
|
||||
}
|
||||
outboundMedia := bus.OutboundMediaMessage{
|
||||
Channel: ts.channel,
|
||||
ChatID: ts.chatID,
|
||||
Parts: parts,
|
||||
Context: outboundContextFromInbound(
|
||||
ts.opts.InboundContext,
|
||||
ts.channel,
|
||||
ts.chatID,
|
||||
ts.opts.ReplyToMessageID,
|
||||
),
|
||||
Parts: parts,
|
||||
}
|
||||
if al.channelManager != nil && ts.channel != "" && !constants.IsInternalChannel(ts.channel) {
|
||||
if err := al.channelManager.SendMedia(ctx, outboundMedia); err != nil {
|
||||
@@ -3758,84 +3717,6 @@ func mapCommandError(result commands.ExecuteResult) string {
|
||||
return fmt.Sprintf("Failed to execute /%s: %v", result.Command, result.Err)
|
||||
}
|
||||
|
||||
// extractPeer extracts the routing peer from the inbound message's structured Peer field.
|
||||
func extractPeer(msg bus.InboundMessage) *routing.RoutePeer {
|
||||
if msg.Peer.Kind != "" {
|
||||
peerID := msg.Peer.ID
|
||||
if peerID == "" {
|
||||
if msg.Peer.Kind == "direct" {
|
||||
peerID = msg.SenderID
|
||||
} else {
|
||||
peerID = msg.ChatID
|
||||
}
|
||||
}
|
||||
return &routing.RoutePeer{Kind: msg.Peer.Kind, ID: peerID}
|
||||
}
|
||||
|
||||
inboundCtx := normalizedInboundContext(msg)
|
||||
peerKind := strings.TrimSpace(inboundCtx.ChatType)
|
||||
if peerKind == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
peerID := strings.TrimSpace(inboundCtx.ChatID)
|
||||
if peerKind == "direct" && peerID == "" {
|
||||
peerID = strings.TrimSpace(inboundCtx.SenderID)
|
||||
}
|
||||
if peerID == "" {
|
||||
return nil
|
||||
}
|
||||
return &routing.RoutePeer{Kind: peerKind, ID: peerID}
|
||||
}
|
||||
|
||||
func inboundMetadata(msg bus.InboundMessage, key string) string {
|
||||
if msg.Metadata == nil {
|
||||
return ""
|
||||
}
|
||||
return msg.Metadata[key]
|
||||
}
|
||||
|
||||
// extractParentPeer extracts the parent peer (reply-to) from inbound message metadata.
|
||||
func extractParentPeer(msg bus.InboundMessage) *routing.RoutePeer {
|
||||
inboundCtx := normalizedInboundContext(msg)
|
||||
if topicID := strings.TrimSpace(inboundCtx.TopicID); topicID != "" {
|
||||
return &routing.RoutePeer{Kind: "topic", ID: topicID}
|
||||
}
|
||||
|
||||
parentKind := inboundMetadata(msg, metadataKeyParentPeerKind)
|
||||
parentID := inboundMetadata(msg, metadataKeyParentPeerID)
|
||||
if parentKind == "" || parentID == "" {
|
||||
return nil
|
||||
}
|
||||
return &routing.RoutePeer{Kind: parentKind, ID: parentID}
|
||||
}
|
||||
|
||||
func routeAccountID(msg bus.InboundMessage) string {
|
||||
if accountID := strings.TrimSpace(normalizedInboundContext(msg).Account); accountID != "" {
|
||||
return accountID
|
||||
}
|
||||
return inboundMetadata(msg, metadataKeyAccountID)
|
||||
}
|
||||
|
||||
func routeGuildID(msg bus.InboundMessage) string {
|
||||
inboundCtx := normalizedInboundContext(msg)
|
||||
if strings.EqualFold(strings.TrimSpace(inboundCtx.SpaceType), "guild") {
|
||||
return strings.TrimSpace(inboundCtx.SpaceID)
|
||||
}
|
||||
return inboundMetadata(msg, metadataKeyGuildID)
|
||||
}
|
||||
|
||||
func routeTeamID(msg bus.InboundMessage) string {
|
||||
inboundCtx := normalizedInboundContext(msg)
|
||||
switch strings.ToLower(strings.TrimSpace(inboundCtx.SpaceType)) {
|
||||
case "team", "workspace":
|
||||
if spaceID := strings.TrimSpace(inboundCtx.SpaceID); spaceID != "" {
|
||||
return spaceID
|
||||
}
|
||||
}
|
||||
return inboundMetadata(msg, metadataKeyTeamID)
|
||||
}
|
||||
|
||||
// isNativeSearchProvider reports whether the given LLM provider implements
|
||||
// NativeSearchCapable and returns true for SupportsNativeSearch.
|
||||
func isNativeSearchProvider(p providers.LLMProvider) bool {
|
||||
|
||||
+101
-142
@@ -140,7 +140,7 @@ func TestProcessMessage_IncludesCurrentSenderInDynamicContext(t *testing.T) {
|
||||
provider := &recordingProvider{}
|
||||
al := NewAgentLoop(cfg, msgBus, provider)
|
||||
|
||||
response, err := al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "discord",
|
||||
SenderID: "discord:123",
|
||||
Sender: bus.SenderInfo{
|
||||
@@ -148,7 +148,7 @@ func TestProcessMessage_IncludesCurrentSenderInDynamicContext(t *testing.T) {
|
||||
},
|
||||
ChatID: "group-1",
|
||||
Content: "hello",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() error = %v", err)
|
||||
}
|
||||
@@ -199,12 +199,12 @@ func TestProcessMessage_UseCommandLoadsRequestedSkill(t *testing.T) {
|
||||
provider := &recordingProvider{}
|
||||
al := NewAgentLoop(cfg, msgBus, provider)
|
||||
|
||||
response, err := al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
SenderID: "telegram:123",
|
||||
ChatID: "chat-1",
|
||||
Content: "/use shell explain how to list files",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() error = %v", err)
|
||||
}
|
||||
@@ -289,12 +289,12 @@ func TestProcessMessage_UseCommandArmsSkillForNextMessage(t *testing.T) {
|
||||
provider := &recordingProvider{}
|
||||
al := NewAgentLoop(cfg, msgBus, provider)
|
||||
|
||||
response, err := al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
SenderID: "telegram:123",
|
||||
ChatID: "chat-1",
|
||||
Content: "/use shell",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() arm error = %v", err)
|
||||
}
|
||||
@@ -302,12 +302,12 @@ func TestProcessMessage_UseCommandArmsSkillForNextMessage(t *testing.T) {
|
||||
t.Fatalf("arm response = %q, want armed confirmation", response)
|
||||
}
|
||||
|
||||
response, err = al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err = al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
SenderID: "telegram:123",
|
||||
ChatID: "chat-1",
|
||||
Content: "explain how to list files",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() follow-up error = %v", err)
|
||||
}
|
||||
@@ -620,12 +620,12 @@ func TestProcessMessage_MediaToolHandledSkipsFollowUpLLMAndFinalText(t *testing.
|
||||
path: imagePath,
|
||||
})
|
||||
|
||||
response, err := al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
ChatID: "chat1",
|
||||
SenderID: "user1",
|
||||
Content: "take a screenshot of the screen and send it to me",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() error = %v", err)
|
||||
}
|
||||
@@ -662,21 +662,21 @@ func TestProcessMessage_MediaToolHandledSkipsFollowUpLLMAndFinalText(t *testing.
|
||||
if defaultAgent == nil {
|
||||
t.Fatal("expected default agent")
|
||||
}
|
||||
route, _, err := al.resolveMessageRoute(bus.InboundMessage{
|
||||
route, _, err := al.resolveMessageRoute(testInboundMessage(bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
ChatID: "chat1",
|
||||
SenderID: "user1",
|
||||
Content: "take a screenshot of the screen and send it to me",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("resolveMessageRoute() error = %v", err)
|
||||
}
|
||||
sessionKey := resolveScopeKey(al.allocateRouteSession(route, bus.InboundMessage{
|
||||
sessionKey := resolveScopeKey(al.allocateRouteSession(route, testInboundMessage(bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
ChatID: "chat1",
|
||||
SenderID: "user1",
|
||||
Content: "take a screenshot of the screen and send it to me",
|
||||
}).SessionKey, "")
|
||||
})).SessionKey, "")
|
||||
history := defaultAgent.Sessions.GetHistory(sessionKey)
|
||||
if len(history) == 0 {
|
||||
t.Fatal("expected session history to be saved")
|
||||
@@ -720,12 +720,12 @@ func TestProcessMessage_HandledToolProcessesQueuedSteeringBeforeReturning(t *tes
|
||||
loop: al,
|
||||
})
|
||||
|
||||
response, err := al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
ChatID: "chat1",
|
||||
SenderID: "user1",
|
||||
Content: "take a screenshot of the screen and send it to me",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() error = %v", err)
|
||||
}
|
||||
@@ -740,41 +740,6 @@ func TestProcessMessage_HandledToolProcessesQueuedSteeringBeforeReturning(t *tes
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractPeer_UsesInboundContextWhenLegacyPeerMissing(t *testing.T) {
|
||||
msg := bus.InboundMessage{
|
||||
Context: bus.InboundContext{
|
||||
Channel: "slack",
|
||||
ChatID: "C001",
|
||||
ChatType: "channel",
|
||||
SenderID: "U001",
|
||||
},
|
||||
}
|
||||
|
||||
peer := extractPeer(msg)
|
||||
if peer == nil {
|
||||
t.Fatal("expected peer from inbound context")
|
||||
}
|
||||
if peer.Kind != "channel" || peer.ID != "C001" {
|
||||
t.Fatalf("peer = %+v, want channel/C001", peer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractParentPeer_UsesInboundContextTopicID(t *testing.T) {
|
||||
msg := bus.InboundMessage{
|
||||
Context: bus.InboundContext{
|
||||
TopicID: "thread-42",
|
||||
},
|
||||
}
|
||||
|
||||
parentPeer := extractParentPeer(msg)
|
||||
if parentPeer == nil {
|
||||
t.Fatal("expected parent peer from topic context")
|
||||
}
|
||||
if parentPeer.Kind != "topic" || parentPeer.ID != "thread-42" {
|
||||
t.Fatalf("parent peer = %+v, want topic/thread-42", parentPeer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendEventContextFields_IncludesInboundRouteAndScope(t *testing.T) {
|
||||
fields := map[string]any{}
|
||||
|
||||
@@ -872,7 +837,7 @@ func TestResolveMessageRoute_UsesInboundContextAccountAndSpace(t *testing.T) {
|
||||
msgBus := bus.NewMessageBus()
|
||||
al := NewAgentLoop(cfg, msgBus, &simpleMockProvider{response: "ok"})
|
||||
|
||||
route, _, err := al.resolveMessageRoute(bus.InboundMessage{
|
||||
route, _, err := al.resolveMessageRoute(testInboundMessage(bus.InboundMessage{
|
||||
Context: bus.InboundContext{
|
||||
Channel: "slack",
|
||||
Account: "workspace-a",
|
||||
@@ -883,7 +848,7 @@ func TestResolveMessageRoute_UsesInboundContextAccountAndSpace(t *testing.T) {
|
||||
SpaceType: "workspace",
|
||||
},
|
||||
Content: "hello",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("resolveMessageRoute() error = %v", err)
|
||||
}
|
||||
@@ -926,12 +891,12 @@ func TestProcessMessage_MediaArtifactCanBeForwardedBySendFile(t *testing.T) {
|
||||
path: imagePath,
|
||||
})
|
||||
|
||||
response, err := al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
ChatID: "chat1",
|
||||
SenderID: "user1",
|
||||
Content: "take a screenshot of the screen and send it to me",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() error = %v", err)
|
||||
}
|
||||
@@ -1518,13 +1483,39 @@ func (h testHelper) executeAndGetResponse(tb testing.TB, ctx context.Context, ms
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, responseTimeout)
|
||||
defer cancel()
|
||||
|
||||
response, err := h.al.processMessage(timeoutCtx, msg)
|
||||
response, err := h.al.processMessage(timeoutCtx, testInboundMessage(msg))
|
||||
if err != nil {
|
||||
tb.Fatalf("processMessage failed: %v", err)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func testInboundMessage(msg bus.InboundMessage) bus.InboundMessage {
|
||||
if msg.Context.Channel == "" &&
|
||||
msg.Context.Account == "" &&
|
||||
msg.Context.ChatID == "" &&
|
||||
msg.Context.ChatType == "" &&
|
||||
msg.Context.TopicID == "" &&
|
||||
msg.Context.SpaceID == "" &&
|
||||
msg.Context.SpaceType == "" &&
|
||||
msg.Context.SenderID == "" &&
|
||||
msg.Context.MessageID == "" &&
|
||||
!msg.Context.Mentioned &&
|
||||
msg.Context.ReplyToMessageID == "" &&
|
||||
msg.Context.ReplyToSenderID == "" &&
|
||||
len(msg.Context.ReplyHandles) == 0 &&
|
||||
len(msg.Context.Raw) == 0 {
|
||||
msg.Context = bus.InboundContext{
|
||||
Channel: msg.Channel,
|
||||
ChatID: msg.ChatID,
|
||||
ChatType: "direct",
|
||||
SenderID: msg.SenderID,
|
||||
MessageID: msg.MessageID,
|
||||
}
|
||||
}
|
||||
return bus.NormalizeInboundMessage(msg)
|
||||
}
|
||||
|
||||
const responseTimeout = 3 * time.Second
|
||||
|
||||
func TestProcessMessage_UsesRouteSessionKey(t *testing.T) {
|
||||
@@ -1550,20 +1541,16 @@ func TestProcessMessage_UsesRouteSessionKey(t *testing.T) {
|
||||
al := NewAgentLoop(cfg, msgBus, provider)
|
||||
|
||||
msg := bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "hello",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
Context: bus.InboundContext{
|
||||
Channel: "telegram",
|
||||
ChatID: "chat1",
|
||||
ChatType: "direct",
|
||||
SenderID: "user1",
|
||||
},
|
||||
Content: "hello",
|
||||
}
|
||||
|
||||
route := al.registry.ResolveRoute(routing.RouteInput{
|
||||
Channel: msg.Channel,
|
||||
Peer: extractPeer(msg),
|
||||
})
|
||||
route := al.registry.ResolveRoute(bus.NormalizeInboundMessage(msg).Context)
|
||||
sessionKey := al.allocateRouteSession(route, msg).SessionKey
|
||||
|
||||
defaultAgent := al.registry.GetDefaultAgent()
|
||||
@@ -1610,21 +1597,22 @@ func TestProcessMessage_CommandOutcomes(t *testing.T) {
|
||||
helper := testHelper{al: al}
|
||||
|
||||
baseMsg := bus.InboundMessage{
|
||||
Channel: "whatsapp",
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
Context: bus.InboundContext{
|
||||
Channel: "whatsapp",
|
||||
ChatID: "chat1",
|
||||
ChatType: "direct",
|
||||
SenderID: "user1",
|
||||
},
|
||||
}
|
||||
|
||||
showResp := helper.executeAndGetResponse(t, context.Background(), bus.InboundMessage{
|
||||
Channel: baseMsg.Channel,
|
||||
SenderID: baseMsg.SenderID,
|
||||
ChatID: baseMsg.ChatID,
|
||||
Content: "/show channel",
|
||||
Peer: baseMsg.Peer,
|
||||
Context: bus.InboundContext{
|
||||
Channel: baseMsg.Context.Channel,
|
||||
ChatID: baseMsg.Context.ChatID,
|
||||
ChatType: baseMsg.Context.ChatType,
|
||||
SenderID: baseMsg.Context.SenderID,
|
||||
},
|
||||
Content: "/show channel",
|
||||
})
|
||||
if showResp != "Current Channel: whatsapp" {
|
||||
t.Fatalf("unexpected /show reply: %q", showResp)
|
||||
@@ -1634,11 +1622,13 @@ func TestProcessMessage_CommandOutcomes(t *testing.T) {
|
||||
}
|
||||
|
||||
fooResp := helper.executeAndGetResponse(t, context.Background(), bus.InboundMessage{
|
||||
Channel: baseMsg.Channel,
|
||||
SenderID: baseMsg.SenderID,
|
||||
ChatID: baseMsg.ChatID,
|
||||
Content: "/foo",
|
||||
Peer: baseMsg.Peer,
|
||||
Context: bus.InboundContext{
|
||||
Channel: baseMsg.Context.Channel,
|
||||
ChatID: baseMsg.Context.ChatID,
|
||||
ChatType: baseMsg.Context.ChatType,
|
||||
SenderID: baseMsg.Context.SenderID,
|
||||
},
|
||||
Content: "/foo",
|
||||
})
|
||||
if fooResp != "LLM reply" {
|
||||
t.Fatalf("unexpected /foo reply: %q", fooResp)
|
||||
@@ -1648,11 +1638,13 @@ func TestProcessMessage_CommandOutcomes(t *testing.T) {
|
||||
}
|
||||
|
||||
newResp := helper.executeAndGetResponse(t, context.Background(), bus.InboundMessage{
|
||||
Channel: baseMsg.Channel,
|
||||
SenderID: baseMsg.SenderID,
|
||||
ChatID: baseMsg.ChatID,
|
||||
Content: "/new",
|
||||
Peer: baseMsg.Peer,
|
||||
Context: bus.InboundContext{
|
||||
Channel: baseMsg.Context.Channel,
|
||||
ChatID: baseMsg.Context.ChatID,
|
||||
ChatType: baseMsg.Context.ChatType,
|
||||
SenderID: baseMsg.Context.SenderID,
|
||||
},
|
||||
Content: "/new",
|
||||
})
|
||||
if newResp != "LLM reply" {
|
||||
t.Fatalf("unexpected /new reply: %q", newResp)
|
||||
@@ -1705,10 +1697,6 @@ func TestProcessMessage_SwitchModelShowModelConsistency(t *testing.T) {
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "/switch model to deepseek",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if !strings.Contains(switchResp, "Switched model from local to deepseek") {
|
||||
t.Fatalf("unexpected /switch reply: %q", switchResp)
|
||||
@@ -1719,10 +1707,6 @@ func TestProcessMessage_SwitchModelShowModelConsistency(t *testing.T) {
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "/show model",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if !strings.Contains(showResp, "Current Model: deepseek (Provider: openrouter)") {
|
||||
t.Fatalf("unexpected /show model reply after switch: %q", showResp)
|
||||
@@ -1770,10 +1754,6 @@ func TestProcessMessage_SwitchModelRejectsUnknownAlias(t *testing.T) {
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "/switch model to missing",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if switchResp != `model "missing" not found in model_list or providers` {
|
||||
t.Fatalf("unexpected /switch error reply: %q", switchResp)
|
||||
@@ -1784,10 +1764,6 @@ func TestProcessMessage_SwitchModelRejectsUnknownAlias(t *testing.T) {
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "/show model",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if !strings.Contains(showResp, "Current Model: local (Provider: openai)") {
|
||||
t.Fatalf("unexpected /show model reply after rejected switch: %q", showResp)
|
||||
@@ -1854,10 +1830,6 @@ func TestProcessMessage_SwitchModelRoutesSubsequentRequestsToSelectedProvider(t
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "hello before switch",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if firstResp != "local reply" {
|
||||
t.Fatalf("unexpected response before switch: %q", firstResp)
|
||||
@@ -1877,10 +1849,6 @@ func TestProcessMessage_SwitchModelRoutesSubsequentRequestsToSelectedProvider(t
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "/switch model to deepseek",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if !strings.Contains(switchResp, "Switched model from local to deepseek") {
|
||||
t.Fatalf("unexpected /switch reply: %q", switchResp)
|
||||
@@ -1891,10 +1859,6 @@ func TestProcessMessage_SwitchModelRoutesSubsequentRequestsToSelectedProvider(t
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "hello after switch",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if secondResp != "remote reply" {
|
||||
t.Fatalf("unexpected response after switch: %q", secondResp)
|
||||
@@ -1984,10 +1948,6 @@ func TestProcessMessage_ModelRoutingUsesLightProvider(t *testing.T) {
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "hi",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
},
|
||||
})
|
||||
if resp != "light reply" {
|
||||
t.Fatalf("response = %q, want %q", resp, "light reply")
|
||||
@@ -2260,22 +2220,16 @@ func TestAgentLoop_ToolLimitUsesDedicatedFallback(t *testing.T) {
|
||||
if defaultAgent == nil {
|
||||
t.Fatal("No default agent found")
|
||||
}
|
||||
route := al.registry.ResolveRoute(routing.RouteInput{
|
||||
Channel: "test",
|
||||
Peer: &routing.RoutePeer{
|
||||
Kind: "direct",
|
||||
ID: "cron",
|
||||
},
|
||||
route := al.registry.ResolveRoute(bus.InboundContext{
|
||||
Channel: "test",
|
||||
ChatType: "direct",
|
||||
SenderID: "cron",
|
||||
})
|
||||
history := defaultAgent.Sessions.GetHistory(al.allocateRouteSession(route, bus.InboundMessage{
|
||||
history := defaultAgent.Sessions.GetHistory(al.allocateRouteSession(route, testInboundMessage(bus.InboundMessage{
|
||||
Channel: "test",
|
||||
SenderID: "cron",
|
||||
ChatID: "chat1",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "cron",
|
||||
},
|
||||
}).SessionKey)
|
||||
})).SessionKey)
|
||||
if len(history) != 4 {
|
||||
t.Fatalf("history len = %d, want 4", len(history))
|
||||
}
|
||||
@@ -2533,8 +2487,7 @@ func TestHandleReasoning(t *testing.T) {
|
||||
for i := 0; ; i++ {
|
||||
fillCtx, fillCancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
||||
err := msgBus.PublishOutbound(fillCtx, bus.OutboundMessage{
|
||||
Channel: "filler",
|
||||
ChatID: "filler",
|
||||
Context: bus.NewOutboundContext("filler", "filler", ""),
|
||||
Content: fmt.Sprintf("filler-%d", i),
|
||||
})
|
||||
fillCancel()
|
||||
@@ -2608,12 +2561,12 @@ func TestProcessMessage_PublishesReasoningContentToReasoningChannel(t *testing.T
|
||||
chManager.RegisterChannel("telegram", &fakeChannel{id: "reason-chat"})
|
||||
al.SetChannelManager(chManager)
|
||||
|
||||
response, err := al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "hello",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() error = %v", err)
|
||||
}
|
||||
@@ -2629,6 +2582,9 @@ func TestProcessMessage_PublishesReasoningContentToReasoningChannel(t *testing.T
|
||||
if outbound.ChatID != "reason-chat" {
|
||||
t.Fatalf("reasoning chatID = %q, want %q", outbound.ChatID, "reason-chat")
|
||||
}
|
||||
if outbound.Context.Channel != "telegram" || outbound.Context.ChatID != "reason-chat" {
|
||||
t.Fatalf("unexpected reasoning context: %+v", outbound.Context)
|
||||
}
|
||||
if outbound.Content != "thinking trace" {
|
||||
t.Fatalf("reasoning content = %q, want %q", outbound.Content, "thinking trace")
|
||||
}
|
||||
@@ -2714,12 +2670,12 @@ func TestProcessMessage_PublishesToolFeedbackWhenEnabled(t *testing.T) {
|
||||
provider := &toolFeedbackProvider{filePath: heartbeatFile}
|
||||
al := NewAgentLoop(cfg, msgBus, provider)
|
||||
|
||||
response, err := al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
SenderID: "user-1",
|
||||
ChatID: "chat-1",
|
||||
Content: "check tool feedback",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() error = %v", err)
|
||||
}
|
||||
@@ -2735,6 +2691,9 @@ func TestProcessMessage_PublishesToolFeedbackWhenEnabled(t *testing.T) {
|
||||
if outbound.ChatID != "chat-1" {
|
||||
t.Fatalf("tool feedback chatID = %q, want %q", outbound.ChatID, "chat-1")
|
||||
}
|
||||
if outbound.Context.Channel != "telegram" || outbound.Context.ChatID != "chat-1" {
|
||||
t.Fatalf("unexpected tool feedback context: %+v", outbound.Context)
|
||||
}
|
||||
if !strings.Contains(outbound.Content, "`read_file`") {
|
||||
t.Fatalf("tool feedback content = %q, want read_file preview", outbound.Content)
|
||||
}
|
||||
@@ -3157,13 +3116,13 @@ func TestProcessMessage_ContextOverflowRecovery(t *testing.T) {
|
||||
agent.Sessions.AddFullMessage(sessionKey, providers.Message{Role: "assistant", Content: "response"})
|
||||
}
|
||||
|
||||
response, err := al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "test",
|
||||
ChatID: "chat1",
|
||||
SenderID: "user1",
|
||||
SessionKey: "test-session",
|
||||
Content: "trigger recovery",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() error = %v", err)
|
||||
}
|
||||
@@ -3199,12 +3158,12 @@ func TestProcessMessage_ContextOverflow_AnthropicStyle(t *testing.T) {
|
||||
return &providers.LLMResponse{Content: "Anthropic recovery success"}, nil
|
||||
}
|
||||
|
||||
response, err := al.processMessage(context.Background(), bus.InboundMessage{
|
||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||
Channel: "test",
|
||||
ChatID: "chat1",
|
||||
SenderID: "user1",
|
||||
Content: "hello",
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("processMessage() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package agent
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
@@ -64,9 +65,9 @@ func (r *AgentRegistry) GetAgent(agentID string) (*AgentInstance, bool) {
|
||||
return agent, ok
|
||||
}
|
||||
|
||||
// ResolveRoute determines which agent handles the message.
|
||||
func (r *AgentRegistry) ResolveRoute(input routing.RouteInput) routing.ResolvedRoute {
|
||||
return r.resolver.ResolveRoute(input)
|
||||
// ResolveRoute determines which agent handles the normalized inbound context.
|
||||
func (r *AgentRegistry) ResolveRoute(inbound bus.InboundContext) routing.ResolvedRoute {
|
||||
return r.resolver.ResolveRoute(inbound)
|
||||
}
|
||||
|
||||
// ListAgentIDs returns all registered agent IDs.
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
"github.com/sipeed/picoclaw/pkg/routing"
|
||||
"github.com/sipeed/picoclaw/pkg/session"
|
||||
"github.com/sipeed/picoclaw/pkg/tools"
|
||||
)
|
||||
@@ -332,7 +331,7 @@ func (al *AgentLoop) agentForSession(sessionKey string) *AgentInstance {
|
||||
return agent
|
||||
}
|
||||
|
||||
if parsed := routing.ParseAgentSessionKey(sessionKey); parsed != nil {
|
||||
if parsed := session.ParseLegacyAgentSessionKey(sessionKey); parsed != nil {
|
||||
if agent, ok := registry.GetAgent(parsed.AgentID); ok {
|
||||
return agent
|
||||
}
|
||||
|
||||
+29
-33
@@ -366,14 +366,13 @@ func TestDrainBusToSteering_RequeuesDifferentScopeMessage(t *testing.T) {
|
||||
al := NewAgentLoop(cfg, msgBus, &mockProvider{})
|
||||
|
||||
activeMsg := bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "active turn",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
Context: bus.InboundContext{
|
||||
Channel: "telegram",
|
||||
ChatID: "chat1",
|
||||
ChatType: "direct",
|
||||
SenderID: "user1",
|
||||
},
|
||||
Content: "active turn",
|
||||
}
|
||||
activeScope, activeAgentID, ok := al.resolveSteeringTarget(activeMsg)
|
||||
if !ok {
|
||||
@@ -381,14 +380,13 @@ func TestDrainBusToSteering_RequeuesDifferentScopeMessage(t *testing.T) {
|
||||
}
|
||||
|
||||
otherMsg := bus.InboundMessage{
|
||||
Channel: "telegram",
|
||||
SenderID: "user2",
|
||||
ChatID: "chat2",
|
||||
Content: "other session",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user2",
|
||||
Context: bus.InboundContext{
|
||||
Channel: "telegram",
|
||||
ChatID: "chat2",
|
||||
ChatType: "direct",
|
||||
SenderID: "user2",
|
||||
},
|
||||
Content: "other session",
|
||||
}
|
||||
otherScope, _, ok := al.resolveSteeringTarget(otherMsg)
|
||||
if !ok {
|
||||
@@ -425,7 +423,7 @@ func TestDrainBusToSteering_RequeuesDifferentScopeMessage(t *testing.T) {
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("timeout waiting for requeued message on outbound bus")
|
||||
case requeued := <-msgBus.OutboundChan():
|
||||
if requeued.Channel != otherMsg.Channel || requeued.ChatID != otherMsg.ChatID ||
|
||||
if requeued.Context.Channel != otherMsg.Context.Channel || requeued.Context.ChatID != otherMsg.Context.ChatID ||
|
||||
requeued.Content != otherMsg.Content {
|
||||
t.Fatalf("requeued message mismatch: got %+v want %+v", requeued, otherMsg)
|
||||
}
|
||||
@@ -842,24 +840,22 @@ func TestAgentLoop_Run_AutoContinuesLateSteeringMessage(t *testing.T) {
|
||||
}()
|
||||
|
||||
first := bus.InboundMessage{
|
||||
Channel: "test",
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "first message",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
Context: bus.InboundContext{
|
||||
Channel: "test",
|
||||
ChatID: "chat1",
|
||||
ChatType: "direct",
|
||||
SenderID: "user1",
|
||||
},
|
||||
Content: "first message",
|
||||
}
|
||||
late := bus.InboundMessage{
|
||||
Channel: "test",
|
||||
SenderID: "user1",
|
||||
ChatID: "chat1",
|
||||
Content: "late append",
|
||||
Peer: bus.Peer{
|
||||
Kind: "direct",
|
||||
ID: "user1",
|
||||
Context: bus.InboundContext{
|
||||
Channel: "test",
|
||||
ChatID: "chat1",
|
||||
ChatType: "direct",
|
||||
SenderID: "user1",
|
||||
},
|
||||
Content: "late append",
|
||||
}
|
||||
|
||||
pubCtx, pubCancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
@@ -950,7 +946,7 @@ func TestAgentLoop_Steering_DirectResponseContinuesWithQueuedMessage(t *testing.
|
||||
},
|
||||
}
|
||||
|
||||
sessionKey := routing.BuildAgentMainSessionKey(routing.DefaultAgentID)
|
||||
sessionKey := session.BuildMainSessionKey(routing.DefaultAgentID)
|
||||
provider := &blockingDirectProvider{
|
||||
firstStarted: make(chan struct{}),
|
||||
releaseFirst: make(chan struct{}),
|
||||
@@ -1117,7 +1113,7 @@ func TestAgentLoop_Continue_PreservesSteeringMedia(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
sessionKey := routing.BuildAgentMainSessionKey(routing.DefaultAgentID)
|
||||
sessionKey := session.BuildMainSessionKey(routing.DefaultAgentID)
|
||||
msgBus := bus.NewMessageBus()
|
||||
al := NewAgentLoop(cfg, msgBus, provider)
|
||||
al.SetMediaStore(store)
|
||||
@@ -1225,7 +1221,7 @@ func TestAgentLoop_InterruptGraceful_UsesTerminalNoToolCall(t *testing.T) {
|
||||
al := NewAgentLoop(cfg, msgBus, provider)
|
||||
al.RegisterTool(tool1)
|
||||
al.RegisterTool(tool2)
|
||||
sessionKey := routing.BuildAgentMainSessionKey(routing.DefaultAgentID)
|
||||
sessionKey := session.BuildMainSessionKey(routing.DefaultAgentID)
|
||||
|
||||
sub := al.SubscribeEvents(32)
|
||||
defer al.UnsubscribeEvents(sub.ID)
|
||||
@@ -1379,7 +1375,7 @@ func TestAgentLoop_InterruptHard_RestoresSession(t *testing.T) {
|
||||
al := NewAgentLoop(cfg, msgBus, provider)
|
||||
started := make(chan struct{})
|
||||
al.RegisterTool(&interruptibleTool{name: "cancel_tool", started: started})
|
||||
sessionKey := routing.BuildAgentMainSessionKey(routing.DefaultAgentID)
|
||||
sessionKey := session.BuildMainSessionKey(routing.DefaultAgentID)
|
||||
|
||||
defaultAgent := al.registry.GetDefaultAgent()
|
||||
if defaultAgent == nil {
|
||||
|
||||
Reference in New Issue
Block a user