Files

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)
}