Files
xinmi/internal/im/agent_lifecycle.go
T
yawan f7fc992f57
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
WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
2026-09-22 16:19:06 +08:00

104 lines
4.2 KiB
Go

package im
import (
"context"
"errors"
"fmt"
"strings"
"github.com/Tencent/WeKnora/internal/types"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type verifiedAgentExecutionKey struct{}
type verifiedAgentExecution struct {
TenantID uint64
AgentID, OwnerID, ChannelID string
PrincipalID string
}
// IsVerifiedAgentExecution accepts only a proof minted inside the IM runtime
// after reading an enabled channel and its exact Agent from durable storage.
// A synthetic user, IM principal or request parameter alone never grants it.
func IsVerifiedAgentExecution(ctx context.Context, agent *types.CustomAgent) bool {
proof, ok := ctx.Value(verifiedAgentExecutionKey{}).(verifiedAgentExecution)
if !ok || agent == nil || proof.ChannelID == "" || proof.AgentID != agent.ID || proof.TenantID != agent.TenantID || proof.OwnerID != agent.CreatedBy {
return false
}
tenant, hasTenant := types.TenantIDFromContext(ctx)
user, hasUser := types.UserIDFromContext(ctx)
principal, hasPrincipal := types.PrincipalFromContext(ctx)
return hasTenant && hasUser && tenant == proof.TenantID && user == fmt.Sprintf("system-%d", tenant) &&
hasPrincipal && principal.Type == types.PrincipalIMUser && principal.ID == proof.PrincipalID
}
func (s *Service) resolveConfiguredChannelExecution(ctx context.Context, channel *IMChannel) (context.Context, *types.CustomAgent, error) {
agent, err := s.resolveConfiguredChannelAgent(ctx, channel)
if err != nil {
return ctx, nil, err
}
principal, hasPrincipal := types.PrincipalFromContext(ctx)
tenant, hasTenant := types.TenantIDFromContext(ctx)
user, hasUser := types.UserIDFromContext(ctx)
if !hasPrincipal || principal.Type != types.PrincipalIMUser || !hasTenant || tenant != channel.TenantID || !hasUser || user != fmt.Sprintf("system-%d", tenant) {
return ctx, nil, fmt.Errorf("verified IM identity is required for channel execution")
}
proof := verifiedAgentExecution{TenantID: channel.TenantID, AgentID: agent.ID, OwnerID: agent.CreatedBy, ChannelID: channel.ID, PrincipalID: principal.ID}
return context.WithValue(ctx, verifiedAgentExecutionKey{}, proof), agent, nil
}
// InvalidateDeletedChannels is called only after durable deletion commits.
func (s *Service) InvalidateDeletedChannels(ids []string) {
for _, id := range ids {
s.StopChannel(id)
s.publishChannelConfigChange(id)
}
}
func (s *Service) cancelChannelRequests(id string) {
s.inflight.Range(func(key, value any) bool {
userKey, ok := key.(string)
if ok && strings.HasPrefix(userKey, id+":") {
if entry, ok := value.(*inflightEntry); ok && entry.cancel != nil {
entry.cancel()
}
}
return true
})
}
// Serialize authoring with agent deletion so a concurrent channel create or
// reassignment cannot leave a new channel pointing at a deleted custom agent.
func lockChannelAgent(tx *gorm.DB, channel *IMChannel) error {
if types.IsBuiltinAgentID(channel.AgentID) {
return nil // Built-in agents cannot be deleted.
}
var agent types.CustomAgent
return tx.Clauses(clause.Locking{Strength: "UPDATE"}).Select("id").
Where("id = ? AND tenant_id = ?", channel.AgentID, channel.TenantID).First(&agent).Error
}
// resolveConfiguredChannelAgent is a trusted runtime path, not a user Agent
// lookup. A persisted, enabled channel is the explicit authorization to execute
// exactly its configured Agent, including an owner's private digital human.
func (s *Service) resolveConfiguredChannelAgent(ctx context.Context, channel *IMChannel) (*types.CustomAgent, error) {
if channel == nil || channel.ID == "" || channel.AgentID == "" {
return nil, fmt.Errorf("channel has no configured agent")
}
var fresh IMChannel
if err := s.db.WithContext(ctx).Where("id = ? AND tenant_id = ? AND agent_id = ? AND enabled = ?", channel.ID, channel.TenantID, channel.AgentID, true).First(&fresh).Error; err != nil {
return nil, err
}
var agent types.CustomAgent
err := s.db.WithContext(ctx).Where("id = ? AND tenant_id = ?", fresh.AgentID, fresh.TenantID).First(&agent).Error
if errors.Is(err, gorm.ErrRecordNotFound) && types.IsBuiltinAgentID(fresh.AgentID) {
return s.agentService.GetAgentByID(ctx, fresh.AgentID)
}
if err != nil {
return nil, err
}
agent.EnsureDefaults()
return &agent, nil
}