Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
94 lines
3.4 KiB
Go
94 lines
3.4 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/Tencent/WeKnora/internal/types"
|
|
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// MemoryAllowedMessageIDs uses per-turn Agent identity; a session can switch
|
|
// Agents and its last-request settings cannot authorize older messages.
|
|
func (r *messageRepository) MemoryAllowedMessageIDs(ctx context.Context, scope interfaces.MemoryScope, sessionID string, messages []*types.Message) (map[string]bool, error) {
|
|
allowed := map[string]bool{}
|
|
owned, err := r.MemoryOwnedSessionIDs(ctx, scope, []string{sessionID})
|
|
if err != nil || !owned[sessionID] {
|
|
return allowed, err
|
|
}
|
|
for _, input := range messages {
|
|
if input == nil || input.Role != "user" || input.SessionID != sessionID {
|
|
continue
|
|
}
|
|
var answer types.Message
|
|
query := r.db.WithContext(ctx).Where("session_id = ? AND role = ?", sessionID, "assistant")
|
|
if input.RequestID != "" {
|
|
query = query.Where("request_id = ?", input.RequestID)
|
|
} else {
|
|
// Without a request identity, only an adjacent assistant can safely
|
|
// attribute a historical input. Never borrow another turn's Agent.
|
|
query = r.db.WithContext(ctx).Where("session_id = ? AND created_at > ?", sessionID, input.CreatedAt)
|
|
}
|
|
if err := query.Order("created_at ASC, id ASC").First(&answer).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
continue
|
|
}
|
|
return nil, err
|
|
}
|
|
if answer.Role != "assistant" {
|
|
continue
|
|
}
|
|
if !answer.IsCompleted {
|
|
// Keep the checkpoint behind a turn still streaming in this session.
|
|
return nil, errors.New("memory extraction is waiting for a completed turn")
|
|
}
|
|
if answer.AgentID == "" {
|
|
// Historical ordinary-assistant turns have no custom Agent binding.
|
|
allowed[input.ID] = true
|
|
continue
|
|
}
|
|
agentTenant := answer.AgentTenantID
|
|
if agentTenant == 0 {
|
|
agentTenant = scope.TenantID
|
|
}
|
|
var agent types.CustomAgent
|
|
err := r.db.WithContext(ctx).Where("tenant_id = ? AND id = ?", agentTenant, answer.AgentID).First(&agent).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// Built-in defaults may have no tenant override. A soft-deleted
|
|
// override must not be recreated through the built-in registry.
|
|
var deleted int64
|
|
if err := r.db.WithContext(ctx).Unscoped().Model(&types.CustomAgent{}).
|
|
Where("tenant_id = ? AND id = ?", agentTenant, answer.AgentID).Count(&deleted).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
builtin := types.GetBuiltinAgent(answer.AgentID, agentTenant)
|
|
if deleted != 0 || builtin == nil || agentTenant != scope.TenantID {
|
|
continue
|
|
}
|
|
agent = *builtin
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
if !agent.Enabled || (agent.Config.MemoryEnabled != nil && !*agent.Config.MemoryEnabled) {
|
|
continue
|
|
}
|
|
if agentTenant != scope.TenantID {
|
|
var shares int64
|
|
err := r.db.WithContext(ctx).Table("agent_shares").
|
|
Joins("JOIN organization_tenant_members otm ON otm.organization_id = agent_shares.organization_id").
|
|
Joins("JOIN organizations ON organizations.id = agent_shares.organization_id AND organizations.deleted_at IS NULL").
|
|
Where("agent_shares.agent_id = ? AND agent_shares.source_tenant_id = ? AND agent_shares.deleted_at IS NULL AND otm.tenant_id = ?", answer.AgentID, agentTenant, scope.TenantID).
|
|
Count(&shares).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if shares == 0 {
|
|
continue
|
|
}
|
|
}
|
|
allowed[input.ID] = true
|
|
}
|
|
return allowed, nil
|
|
}
|