feat(event): log turn context fields

This commit is contained in:
Hoshina
2026-04-01 15:46:35 +08:00
parent e0ceea91f6
commit bb2167e3f3
2 changed files with 150 additions and 0 deletions
+83
View File
@@ -888,6 +888,8 @@ func (al *AgentLoop) logEvent(evt Event) {
fields["source"] = evt.Meta.Source
}
appendEventContextFields(fields, evt.Context)
switch payload := evt.Payload.(type) {
case TurnStartPayload:
fields["channel"] = payload.Channel
@@ -971,6 +973,87 @@ func (al *AgentLoop) logEvent(evt Event) {
logger.InfoCF("eventbus", fmt.Sprintf("Agent event: %s", evt.Kind.String()), fields)
}
func appendEventContextFields(fields map[string]any, turnCtx *TurnContext) {
if turnCtx == nil {
return
}
if inbound := turnCtx.Inbound; inbound != nil {
if inbound.Channel != "" {
fields["inbound_channel"] = inbound.Channel
}
if inbound.Account != "" {
fields["inbound_account"] = inbound.Account
}
if inbound.ChatID != "" {
fields["inbound_chat_id"] = inbound.ChatID
}
if inbound.ChatType != "" {
fields["inbound_chat_type"] = inbound.ChatType
}
if inbound.TopicID != "" {
fields["inbound_topic_id"] = inbound.TopicID
}
if inbound.SpaceType != "" {
fields["inbound_space_type"] = inbound.SpaceType
}
if inbound.SpaceID != "" {
fields["inbound_space_id"] = inbound.SpaceID
}
if inbound.SenderID != "" {
fields["inbound_sender_id"] = inbound.SenderID
}
if inbound.Mentioned {
fields["inbound_mentioned"] = true
}
}
if route := turnCtx.Route; route != nil {
if route.AgentID != "" {
fields["route_agent_id"] = route.AgentID
}
if route.Channel != "" {
fields["route_channel"] = route.Channel
}
if route.AccountID != "" {
fields["route_account_id"] = route.AccountID
}
if route.MatchedBy != "" {
fields["route_matched_by"] = route.MatchedBy
}
if route.SessionPolicy.DMScope != "" {
fields["route_dm_scope"] = string(route.SessionPolicy.DMScope)
}
if count := len(route.SessionPolicy.IdentityLinks); count > 0 {
fields["route_identity_link_count"] = count
}
}
if scope := turnCtx.Scope; scope != nil {
if scope.Version > 0 {
fields["scope_version"] = scope.Version
}
if scope.AgentID != "" {
fields["scope_agent_id"] = scope.AgentID
}
if scope.Channel != "" {
fields["scope_channel"] = scope.Channel
}
if scope.Account != "" {
fields["scope_account"] = scope.Account
}
if len(scope.Dimensions) > 0 {
fields["scope_dimensions"] = strings.Join(scope.Dimensions, ",")
}
for dim, value := range scope.Values {
if dim == "" || value == "" {
continue
}
fields["scope_"+dim] = value
}
}
}
func (al *AgentLoop) RegisterTool(tool tools.Tool) {
registry := al.GetRegistry()
for _, agentID := range registry.ListAgentIDs() {
+67
View File
@@ -20,6 +20,7 @@ import (
"github.com/sipeed/picoclaw/pkg/media"
"github.com/sipeed/picoclaw/pkg/providers"
"github.com/sipeed/picoclaw/pkg/routing"
"github.com/sipeed/picoclaw/pkg/session"
"github.com/sipeed/picoclaw/pkg/tools"
)
@@ -774,6 +775,72 @@ func TestExtractParentPeer_UsesInboundContextTopicID(t *testing.T) {
}
}
func TestAppendEventContextFields_IncludesInboundRouteAndScope(t *testing.T) {
fields := map[string]any{}
appendEventContextFields(fields, &TurnContext{
Inbound: &bus.InboundContext{
Channel: "slack",
Account: "workspace-a",
ChatID: "C123",
ChatType: "channel",
TopicID: "thread-42",
SpaceType: "workspace",
SpaceID: "T001",
SenderID: "U123",
Mentioned: true,
},
Route: &routing.ResolvedRoute{
AgentID: "support",
Channel: "slack",
AccountID: "workspace-a",
MatchedBy: "binding.team",
SessionPolicy: routing.SessionPolicy{
DMScope: routing.DMScopePerChannelPeer,
IdentityLinks: map[string][]string{
"canonical-user": {"slack:U123"},
},
},
},
Scope: &session.SessionScope{
Version: session.ScopeVersionV1,
AgentID: "support",
Channel: "slack",
Account: "workspace-a",
Dimensions: []string{"chat", "sender"},
Values: map[string]string{
"chat": "channel:c123",
"sender": "u123",
},
},
})
if fields["inbound_channel"] != "slack" {
t.Fatalf("inbound_channel = %v, want slack", fields["inbound_channel"])
}
if fields["inbound_topic_id"] != "thread-42" {
t.Fatalf("inbound_topic_id = %v, want thread-42", fields["inbound_topic_id"])
}
if fields["route_matched_by"] != "binding.team" {
t.Fatalf("route_matched_by = %v, want binding.team", fields["route_matched_by"])
}
if fields["route_dm_scope"] != string(routing.DMScopePerChannelPeer) {
t.Fatalf("route_dm_scope = %v, want %q", fields["route_dm_scope"], routing.DMScopePerChannelPeer)
}
if fields["route_identity_link_count"] != 1 {
t.Fatalf("route_identity_link_count = %v, want 1", fields["route_identity_link_count"])
}
if fields["scope_dimensions"] != "chat,sender" {
t.Fatalf("scope_dimensions = %v, want chat,sender", fields["scope_dimensions"])
}
if fields["scope_chat"] != "channel:c123" {
t.Fatalf("scope_chat = %v, want channel:c123", fields["scope_chat"])
}
if fields["scope_sender"] != "u123" {
t.Fatalf("scope_sender = %v, want u123", fields["scope_sender"])
}
}
func TestResolveMessageRoute_UsesInboundContextAccountAndSpace(t *testing.T) {
tmpDir := t.TempDir()
cfg := &config.Config{