mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-11 03:13:24 +00:00
171 lines
7.8 KiB
Go
171 lines
7.8 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const SkillRecoveryBundledSlugCollision = "bundled_slug_collision"
|
|
|
|
// ErrSystemSkillSlugConflict prevents a bundled skill from replacing or
|
|
// shadowing an existing custom skill with the same slug.
|
|
var ErrSystemSkillSlugConflict = errors.New("system skill slug conflicts with custom skill")
|
|
|
|
// ErrMisclassifiedCustomSkill marks a custom row repaired by the schema
|
|
// migration that still needs its metadata restored from managed files.
|
|
var ErrMisclassifiedCustomSkill = errors.New("custom skill requires bundled collision recovery")
|
|
|
|
// SkillInfo describes a discovered skill.
|
|
type SkillInfo struct {
|
|
ID string `json:"id,omitempty" db:"id"` // DB UUID
|
|
TenantID string `json:"-" db:"tenant_id"`
|
|
Name string `json:"name" db:"name"`
|
|
Slug string `json:"slug" db:"slug"`
|
|
Path string `json:"path" db:"path"`
|
|
BaseDir string `json:"baseDir" db:"-"`
|
|
Source string `json:"source" db:"-"`
|
|
Description string `json:"description" db:"description"`
|
|
Visibility string `json:"visibility,omitempty" db:"visibility"`
|
|
OwnerID string `json:"-" db:"owner_id"`
|
|
Tags []string `json:"tags,omitempty" db:"tags"`
|
|
Version int `json:"version,omitempty" db:"version"`
|
|
IsSystem bool `json:"is_system,omitempty" db:"is_system"`
|
|
Status string `json:"status,omitempty" db:"status"`
|
|
Enabled bool `json:"enabled" db:"enabled"`
|
|
Author string `json:"author,omitempty" db:"author"`
|
|
CreatorAgent *SkillAgentRef `json:"creator_agent,omitempty" db:"-"`
|
|
ManagerAgents []SkillAgentRef `json:"manager_agents,omitempty" db:"-"`
|
|
MissingDeps []string `json:"missing_deps,omitempty" db:"missing_deps"`
|
|
}
|
|
|
|
// SkillAgentRef is a small UI/API-safe agent reference for skill metadata.
|
|
type SkillAgentRef struct {
|
|
ID string `json:"id,omitempty" db:"id"`
|
|
AgentKey string `json:"agent_key,omitempty" db:"agent_key"`
|
|
DisplayName string `json:"display_name,omitempty" db:"display_name"`
|
|
}
|
|
|
|
// SkillSearchResult is a scored skill returned from embedding search.
|
|
type SkillSearchResult struct {
|
|
Name string `json:"name" db:"name"`
|
|
Slug string `json:"slug" db:"slug"`
|
|
Description string `json:"description" db:"description"`
|
|
Path string `json:"path" db:"path"`
|
|
Score float64 `json:"score" db:"score"`
|
|
}
|
|
|
|
// SkillStore manages skill discovery and loading.
|
|
// Backed by Postgres (PGSkillStore) or filesystem (FileSkillStore).
|
|
type SkillStore interface {
|
|
ListSkills(ctx context.Context) []SkillInfo
|
|
LoadSkill(ctx context.Context, name string) (string, bool)
|
|
LoadForContext(ctx context.Context, allowList []string) string
|
|
BuildSummary(ctx context.Context, allowList []string) string
|
|
GetSkill(ctx context.Context, name string) (*SkillInfo, bool)
|
|
FilterSkills(ctx context.Context, allowList []string) []SkillInfo
|
|
Version() int64
|
|
BumpVersion()
|
|
Dirs() []string
|
|
}
|
|
|
|
// SkillAccessStore is an optional interface for stores that support
|
|
// per-agent skill access filtering.
|
|
type SkillAccessStore interface {
|
|
ListAccessible(ctx context.Context, agentID uuid.UUID, userID string) ([]SkillInfo, error)
|
|
}
|
|
|
|
// EmbeddingSkillSearcher is an optional interface for stores that support
|
|
// vector-based skill search. PGSkillStore implements this; FileSkillStore does not.
|
|
type EmbeddingSkillSearcher interface {
|
|
SearchByEmbedding(ctx context.Context, embedding []float32, limit int) ([]SkillSearchResult, error)
|
|
SetEmbeddingProvider(provider EmbeddingProvider)
|
|
BackfillSkillEmbeddings(ctx context.Context) (int, error)
|
|
}
|
|
|
|
// SkillCreateParams holds parameters for creating a managed skill.
|
|
// Shared by PGSkillStore and SQLiteSkillStore.
|
|
type SkillCreateParams struct {
|
|
Name string
|
|
Slug string
|
|
Description *string
|
|
OwnerID string
|
|
Visibility string
|
|
Status string // "active", "archived" (missing deps), or "deleted" (user-deleted)
|
|
MissingDeps []string
|
|
Version int
|
|
FilePath string
|
|
FileSize int64
|
|
FileHash *string
|
|
Frontmatter map[string]string
|
|
}
|
|
|
|
// SkillWithGrantStatus is a skill with its grant status for a specific agent.
|
|
type SkillWithGrantStatus struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
Name string `json:"name" db:"name"`
|
|
Slug string `json:"slug" db:"slug"`
|
|
Description string `json:"description" db:"description"`
|
|
Visibility string `json:"visibility" db:"visibility"`
|
|
Version int `json:"version" db:"version"`
|
|
Granted bool `json:"granted" db:"granted"`
|
|
CanManage bool `json:"can_manage" db:"can_manage"`
|
|
PinnedVer *int `json:"pinned_version,omitempty" db:"pinned_version"`
|
|
IsSystem bool `json:"is_system" db:"is_system"`
|
|
}
|
|
|
|
// SkillAgentGrantInfo is a grant row for one skill across agents.
|
|
type SkillAgentGrantInfo struct {
|
|
AgentID uuid.UUID `json:"agent_id" db:"agent_id"`
|
|
AgentKey string `json:"agent_key,omitempty" db:"agent_key"`
|
|
DisplayName string `json:"display_name,omitempty" db:"display_name"`
|
|
PinnedVersion int `json:"pinned_version" db:"pinned_version"`
|
|
GrantedBy string `json:"granted_by" db:"granted_by"`
|
|
CanManage bool `json:"can_manage" db:"can_manage"`
|
|
}
|
|
|
|
// SkillUserGrantInfo is a user grant row for one skill.
|
|
type SkillUserGrantInfo struct {
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
GrantedBy string `json:"granted_by" db:"granted_by"`
|
|
}
|
|
|
|
// SkillManageStore extends SkillStore with CRUD, ownership, and grant operations
|
|
// needed by HTTP upload handlers and agent tools (skill_manage, publish_skill).
|
|
// Implemented by both PGSkillStore and SQLiteSkillStore.
|
|
type SkillManageStore interface {
|
|
SkillStore
|
|
// CRUD
|
|
CreateSkillManaged(ctx context.Context, p SkillCreateParams) (uuid.UUID, error)
|
|
UpdateSkill(ctx context.Context, id uuid.UUID, updates map[string]any) error
|
|
DeleteSkill(ctx context.Context, id uuid.UUID) error
|
|
ToggleSkill(ctx context.Context, id uuid.UUID, enabled bool) error
|
|
// Queries
|
|
GetSkillByID(ctx context.Context, id uuid.UUID) (SkillInfo, bool)
|
|
GetSkillOwnerID(ctx context.Context, id uuid.UUID) (string, bool)
|
|
GetSkillOwnerIDBySlug(ctx context.Context, slug string) (string, bool)
|
|
GetNextVersion(ctx context.Context, slug string) int
|
|
GetNextVersionLocked(ctx context.Context, slug string) (int, func() error, error)
|
|
// GetSkillHashBySlug returns the content hash and version of the latest non-deleted skill
|
|
// version for the given slug (tenant-scoped). Returns ok=false if no skill exists.
|
|
GetSkillHashBySlug(ctx context.Context, slug string) (hash string, version int, ok bool)
|
|
IsSystemSkill(slug string) bool
|
|
// System skill management
|
|
ListAllSkills(ctx context.Context) []SkillInfo
|
|
ListAllSystemSkills(ctx context.Context) []SkillInfo
|
|
ListSystemSkillDirs(ctx context.Context) map[string]string
|
|
StoreMissingDeps(ctx context.Context, id uuid.UUID, missing []string) error
|
|
// Grants
|
|
GrantToAgent(ctx context.Context, skillID, agentID uuid.UUID, version int, grantedBy string, canManage ...bool) error
|
|
RevokeFromAgent(ctx context.Context, skillID, agentID uuid.UUID) error
|
|
GrantToUser(ctx context.Context, skillID uuid.UUID, userID, grantedBy string) error
|
|
RevokeFromUser(ctx context.Context, skillID uuid.UUID, userID string) error
|
|
ListWithGrantStatus(ctx context.Context, agentID uuid.UUID) ([]SkillWithGrantStatus, error)
|
|
ListAgentGrantsForSkill(ctx context.Context, skillID uuid.UUID) ([]SkillAgentGrantInfo, error)
|
|
ListUserGrantsForSkill(ctx context.Context, skillID uuid.UUID) ([]SkillUserGrantInfo, error)
|
|
AgentCanManageSkill(ctx context.Context, skillID, agentID uuid.UUID) (bool, error)
|
|
// Files
|
|
GetSkillFilePath(ctx context.Context, id uuid.UUID) (filePath string, slug string, version int, isSystem bool, ok bool)
|
|
}
|