mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix(session): restore thread and legacy compatibility
This commit is contained in:
+56
-10
@@ -44,6 +44,7 @@ func AllocateRouteSession(input AllocationInput) Allocation {
|
||||
|
||||
func buildSessionScope(input AllocationInput) SessionScope {
|
||||
inbound := input.Context
|
||||
includeTopicInChatDimension := shouldPreserveTelegramForumIsolation(input)
|
||||
scope := SessionScope{
|
||||
Version: ScopeVersionV1,
|
||||
AgentID: routing.NormalizeAgentID(input.AgentID),
|
||||
@@ -73,6 +74,11 @@ func buildSessionScope(input AllocationInput) SessionScope {
|
||||
if chatID == "" {
|
||||
continue
|
||||
}
|
||||
if includeTopicInChatDimension {
|
||||
if topicID := strings.TrimSpace(inbound.TopicID); topicID != "" {
|
||||
chatID = chatID + "/" + topicID
|
||||
}
|
||||
}
|
||||
chatType := strings.ToLower(strings.TrimSpace(inbound.ChatType))
|
||||
if chatType == "" {
|
||||
chatType = "direct"
|
||||
@@ -111,18 +117,16 @@ func buildLegacySessionAliases(input AllocationInput) []string {
|
||||
inbound := input.Context
|
||||
|
||||
if strings.EqualFold(strings.TrimSpace(inbound.ChatType), "direct") {
|
||||
senderID := CanonicalSessionIdentityID(
|
||||
inbound.Channel,
|
||||
inbound.SenderID,
|
||||
input.SessionPolicy.IdentityLinks,
|
||||
)
|
||||
if senderID == "" {
|
||||
peerIDs := buildLegacyDirectPeerIDs(input)
|
||||
if len(peerIDs) == 0 {
|
||||
return uniqueAliases(aliases)
|
||||
}
|
||||
aliases = append(
|
||||
aliases,
|
||||
BuildLegacyDirectAliases(input.AgentID, inbound.Channel, inbound.Account, senderID)...,
|
||||
)
|
||||
for _, peerID := range peerIDs {
|
||||
aliases = append(
|
||||
aliases,
|
||||
BuildLegacyDirectAliases(input.AgentID, inbound.Channel, inbound.Account, peerID)...,
|
||||
)
|
||||
}
|
||||
return uniqueAliases(aliases)
|
||||
}
|
||||
|
||||
@@ -143,6 +147,48 @@ func buildLegacySessionAliases(input AllocationInput) []string {
|
||||
return uniqueAliases(aliases)
|
||||
}
|
||||
|
||||
func shouldPreserveTelegramForumIsolation(input AllocationInput) bool {
|
||||
inbound := input.Context
|
||||
if !strings.EqualFold(strings.TrimSpace(inbound.Channel), "telegram") {
|
||||
return false
|
||||
}
|
||||
if strings.TrimSpace(inbound.TopicID) == "" {
|
||||
return false
|
||||
}
|
||||
for _, dimension := range input.SessionPolicy.Dimensions {
|
||||
if strings.EqualFold(strings.TrimSpace(dimension), "topic") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func buildLegacyDirectPeerIDs(input AllocationInput) []string {
|
||||
inbound := input.Context
|
||||
peerIDs := make([]string, 0, 3)
|
||||
|
||||
rawSenderID := strings.TrimSpace(inbound.SenderID)
|
||||
if rawSenderID != "" {
|
||||
peerIDs = append(peerIDs, strings.ToLower(rawSenderID))
|
||||
}
|
||||
|
||||
canonicalSenderID := CanonicalSessionIdentityID(
|
||||
inbound.Channel,
|
||||
inbound.SenderID,
|
||||
input.SessionPolicy.IdentityLinks,
|
||||
)
|
||||
if canonicalSenderID != "" {
|
||||
peerIDs = append(peerIDs, canonicalSenderID)
|
||||
}
|
||||
|
||||
chatID := strings.TrimSpace(inbound.ChatID)
|
||||
if chatID != "" {
|
||||
peerIDs = append(peerIDs, strings.ToLower(chatID))
|
||||
}
|
||||
|
||||
return uniqueAliases(peerIDs)
|
||||
}
|
||||
|
||||
func uniqueAliases(aliases []string) []string {
|
||||
if len(aliases) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -80,6 +80,65 @@ func TestAllocateRouteSession_GroupPeer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateRouteSession_TelegramForumTopicsRemainIsolatedByDefault(t *testing.T) {
|
||||
first := AllocateRouteSession(AllocationInput{
|
||||
AgentID: "main",
|
||||
Context: bus.InboundContext{
|
||||
Channel: "telegram",
|
||||
ChatID: "-1001234567890",
|
||||
ChatType: "group",
|
||||
TopicID: "42",
|
||||
SenderID: "7",
|
||||
},
|
||||
SessionPolicy: routing.SessionPolicy{
|
||||
Dimensions: []string{"chat"},
|
||||
},
|
||||
})
|
||||
second := AllocateRouteSession(AllocationInput{
|
||||
AgentID: "main",
|
||||
Context: bus.InboundContext{
|
||||
Channel: "telegram",
|
||||
ChatID: "-1001234567890",
|
||||
ChatType: "group",
|
||||
TopicID: "99",
|
||||
SenderID: "7",
|
||||
},
|
||||
SessionPolicy: routing.SessionPolicy{
|
||||
Dimensions: []string{"chat"},
|
||||
},
|
||||
})
|
||||
|
||||
if first.SessionKey == second.SessionKey {
|
||||
t.Fatalf("forum topics should not share default session key: %q", first.SessionKey)
|
||||
}
|
||||
if got := first.Scope.Values["chat"]; got != "group:-1001234567890/42" {
|
||||
t.Fatalf("first.Scope.Values[chat] = %q, want %q", got, "group:-1001234567890/42")
|
||||
}
|
||||
if got := second.Scope.Values["chat"]; got != "group:-1001234567890/99" {
|
||||
t.Fatalf("second.Scope.Values[chat] = %q, want %q", got, "group:-1001234567890/99")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateRouteSession_PicoDirectAliasesIncludeLegacyChatKey(t *testing.T) {
|
||||
allocation := AllocateRouteSession(AllocationInput{
|
||||
AgentID: "main",
|
||||
Context: bus.InboundContext{
|
||||
Channel: "pico",
|
||||
Account: "default",
|
||||
ChatID: "pico:session-123",
|
||||
ChatType: "direct",
|
||||
SenderID: "pico-user",
|
||||
},
|
||||
SessionPolicy: routing.SessionPolicy{
|
||||
Dimensions: []string{"sender"},
|
||||
},
|
||||
})
|
||||
|
||||
if !containsAlias(allocation.SessionAliases, "agent:main:pico:direct:pico:session-123") {
|
||||
t.Fatalf("SessionAliases = %v, want pico legacy alias", allocation.SessionAliases)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOpaqueSessionKey_IsStable(t *testing.T) {
|
||||
first := BuildOpaqueSessionKey("agent:main:direct:user123")
|
||||
second := BuildOpaqueSessionKey("agent:main:direct:user123")
|
||||
|
||||
@@ -84,6 +84,13 @@ func (b *JSONLBackend) EnsureSessionMetadata(sessionKey string, scope *SessionSc
|
||||
return
|
||||
}
|
||||
|
||||
canonicalMeta, metaErr := metaStore.GetSessionMeta(ctx, sessionKey)
|
||||
if metaErr != nil {
|
||||
log.Printf("session: get canonical session metadata: %v", metaErr)
|
||||
} else if canonicalMeta.Count > 0 || strings.TrimSpace(canonicalMeta.Summary) != "" {
|
||||
return
|
||||
}
|
||||
|
||||
canonicalHistory, historyErr := b.store.GetHistory(ctx, sessionKey)
|
||||
if historyErr != nil {
|
||||
log.Printf("session: get canonical history: %v", historyErr)
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/memory"
|
||||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
"github.com/sipeed/picoclaw/pkg/routing"
|
||||
"github.com/sipeed/picoclaw/pkg/session"
|
||||
)
|
||||
|
||||
@@ -239,3 +241,44 @@ func TestJSONLBackend_EnsureSessionMetadata_PromotesLegacyAliasHistory(t *testin
|
||||
t.Fatalf("promoted summary = %q, want %q", summary, "legacy summary")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONLBackend_EnsureSessionMetadata_PromotesLegacyPicoDirectAliasHistory(t *testing.T) {
|
||||
b := newBackend(t)
|
||||
|
||||
legacyKey := "agent:main:pico:direct:pico:session-123"
|
||||
b.AddMessage(legacyKey, "user", "legacy pico history")
|
||||
|
||||
scope := &session.SessionScope{
|
||||
Version: session.ScopeVersionV1,
|
||||
AgentID: "main",
|
||||
Channel: "pico",
|
||||
Account: "default",
|
||||
Dimensions: []string{"sender"},
|
||||
Values: map[string]string{
|
||||
"sender": "pico-user",
|
||||
},
|
||||
}
|
||||
allocation := session.AllocateRouteSession(session.AllocationInput{
|
||||
AgentID: "main",
|
||||
Context: bus.InboundContext{
|
||||
Channel: "pico",
|
||||
Account: "default",
|
||||
ChatID: "pico:session-123",
|
||||
ChatType: "direct",
|
||||
SenderID: "pico-user",
|
||||
},
|
||||
SessionPolicy: routing.SessionPolicy{
|
||||
Dimensions: []string{"sender"},
|
||||
},
|
||||
})
|
||||
|
||||
b.EnsureSessionMetadata(allocation.SessionKey, scope, allocation.SessionAliases)
|
||||
|
||||
if got := b.ResolveSessionKey(legacyKey); got != allocation.SessionKey {
|
||||
t.Fatalf("ResolveSessionKey() = %q, want %q", got, allocation.SessionKey)
|
||||
}
|
||||
history := b.GetHistory(allocation.SessionKey)
|
||||
if len(history) != 1 || history[0].Content != "legacy pico history" {
|
||||
t.Fatalf("promoted history = %+v", history)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user