* Introduce project_personas table with foreign keys to projects and agent_personas * Add project_skills table with foreign key to projects and agent_skills * Include override boolean field in agent_persona_skills and project_skills * Update schema and migration files to reflect new tables and fields * Enhance CORS handling to reflect request origin
This commit is contained in:
+22
-20
@@ -194,26 +194,28 @@ func routes(logger *slog.Logger, cfg *config.Config, info buildinfo.Info, db *st
|
||||
adminActions := newAdminActions(backfillTool, enrichmentRetryer, logger)
|
||||
|
||||
toolSet := mcpserver.ToolSet{
|
||||
Capture: tools.NewCaptureTool(db, embeddings, cfg.Capture, activeProjects, enrichmentRetryer, backfillTool),
|
||||
Search: tools.NewSearchTool(db, embeddings, cfg.Search, activeProjects),
|
||||
List: tools.NewListTool(db, cfg.Search, activeProjects),
|
||||
Stats: tools.NewStatsTool(db),
|
||||
Get: tools.NewGetTool(db),
|
||||
Update: tools.NewUpdateTool(db, embeddings, metadata, cfg.Capture, logger),
|
||||
Delete: tools.NewDeleteTool(db),
|
||||
Archive: tools.NewArchiveTool(db),
|
||||
Projects: tools.NewProjectsTool(db, activeProjects),
|
||||
Version: tools.NewVersionTool(cfg.MCP.ServerName, info),
|
||||
Learnings: tools.NewLearningsTool(db, activeProjects, cfg.Search),
|
||||
Plans: tools.NewPlansTool(db, activeProjects, cfg.Search),
|
||||
Context: tools.NewContextTool(db, embeddings, cfg.Search, activeProjects),
|
||||
Recall: tools.NewRecallTool(db, embeddings, cfg.Search, activeProjects),
|
||||
Summarize: tools.NewSummarizeTool(db, embeddings, metadata, cfg.Search, activeProjects),
|
||||
Links: tools.NewLinksTool(db, embeddings, cfg.Search),
|
||||
Files: filesTool,
|
||||
Backfill: backfillTool,
|
||||
Reparse: tools.NewReparseMetadataTool(db, bgMetadata, cfg.Capture, activeProjects, logger),
|
||||
RetryMetadata: tools.NewRetryEnrichmentTool(enrichmentRetryer),
|
||||
Capture: tools.NewCaptureTool(db, embeddings, cfg.Capture, activeProjects, enrichmentRetryer, backfillTool),
|
||||
Search: tools.NewSearchTool(db, embeddings, cfg.Search, activeProjects),
|
||||
List: tools.NewListTool(db, cfg.Search, activeProjects),
|
||||
Stats: tools.NewStatsTool(db),
|
||||
Get: tools.NewGetTool(db),
|
||||
Update: tools.NewUpdateTool(db, embeddings, metadata, cfg.Capture, logger),
|
||||
Delete: tools.NewDeleteTool(db),
|
||||
Archive: tools.NewArchiveTool(db),
|
||||
Projects: tools.NewProjectsTool(db, activeProjects),
|
||||
Version: tools.NewVersionTool(cfg.MCP.ServerName, info),
|
||||
Learnings: tools.NewLearningsTool(db, activeProjects, cfg.Search),
|
||||
Plans: tools.NewPlansTool(db, activeProjects, cfg.Search),
|
||||
ProjectPersonas: tools.NewProjectPersonasTool(db, activeProjects),
|
||||
WorldModel: tools.NewWorldModelTool(db, activeProjects),
|
||||
Context: tools.NewContextTool(db, embeddings, cfg.Search, activeProjects),
|
||||
Recall: tools.NewRecallTool(db, embeddings, cfg.Search, activeProjects),
|
||||
Summarize: tools.NewSummarizeTool(db, embeddings, metadata, cfg.Search, activeProjects),
|
||||
Links: tools.NewLinksTool(db, embeddings, cfg.Search),
|
||||
Files: filesTool,
|
||||
Backfill: backfillTool,
|
||||
Reparse: tools.NewReparseMetadataTool(db, bgMetadata, cfg.Capture, activeProjects, logger),
|
||||
RetryMetadata: tools.NewRetryEnrichmentTool(enrichmentRetryer),
|
||||
//Maintenance: tools.NewMaintenanceTool(db),
|
||||
Skills: tools.NewSkillsTool(db, activeProjects),
|
||||
Personas: tools.NewAgentPersonasTool(db),
|
||||
|
||||
@@ -28,6 +28,7 @@ func resolveSpecModels() []resolveSpecModel {
|
||||
{schema: "public", entity: "plan_skills", model: generatedmodels.ModelPublicPlanSkills{}},
|
||||
{schema: "public", entity: "plans", model: generatedmodels.ModelPublicPlans{}},
|
||||
{schema: "public", entity: "project_guardrails", model: generatedmodels.ModelPublicProjectGuardrails{}},
|
||||
{schema: "public", entity: "project_personas", model: generatedmodels.ModelPublicProjectPersonas{}},
|
||||
{schema: "public", entity: "project_skills", model: generatedmodels.ModelPublicProjectSkills{}},
|
||||
{schema: "public", entity: "projects", model: generatedmodels.ModelPublicProjects{}},
|
||||
{schema: "public", entity: "stored_files", model: generatedmodels.ModelPublicStoredFiles{}},
|
||||
|
||||
@@ -16,7 +16,7 @@ type ModelPublicAgentGuardrails struct {
|
||||
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
|
||||
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
|
||||
Severity resolvespec_common.SqlString `bun:"severity,type:text,default:'medium',notnull," json:"severity"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text,default:'{}',notnull," json:"tags"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
|
||||
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
|
||||
RelGuardrailIDPublicAgentPersonaGuardrails []*ModelPublicAgentPersonaGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicagentpersonaguardrails,omitempty"` // Has many ModelPublicAgentPersonaGuardrails
|
||||
RelGuardrailIDPublicPlanGuardrails []*ModelPublicPlanGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicplanguardrails,omitempty"` // Has many ModelPublicPlanGuardrails
|
||||
@@ -45,7 +45,7 @@ func (m ModelPublicAgentGuardrails) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicAgentGuardrails) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -17,7 +17,7 @@ type ModelPublicAgentParts struct {
|
||||
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
|
||||
PartType resolvespec_common.SqlString `bun:"part_type,type:text,notnull," json:"part_type"`
|
||||
Summary resolvespec_common.SqlString `bun:"summary,type:text,notnull," json:"summary"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text,default:'{}',notnull," json:"tags"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
|
||||
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
|
||||
RelPartIDPublicAgentPersonaParts []*ModelPublicAgentPersonaParts `bun:"rel:has-many,join:id=part_id" json:"relpartidpublicagentpersonaparts,omitempty"` // Has many ModelPublicAgentPersonaParts
|
||||
RelPartIDPublicArcStageParts []*ModelPublicArcStageParts `bun:"rel:has-many,join:id=part_id" json:"relpartidpublicarcstageparts,omitempty"` // Has many ModelPublicArcStageParts
|
||||
@@ -45,7 +45,7 @@ func (m ModelPublicAgentParts) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicAgentParts) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -38,7 +38,7 @@ func (m ModelPublicAgentPersonaGuardrails) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicAgentPersonaGuardrails) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -40,7 +40,7 @@ func (m ModelPublicAgentPersonaParts) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicAgentPersonaParts) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
type ModelPublicAgentPersonaSkills struct {
|
||||
bun.BaseModel `bun:"table:public.agent_persona_skills,alias:agent_persona_skills"`
|
||||
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
|
||||
Override bool `bun:"override,type:boolean,default:false,notnull," json:"override"`
|
||||
PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"`
|
||||
SkillID int64 `bun:"skill_id,type:bigint,notnull," json:"skill_id"`
|
||||
RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas
|
||||
@@ -38,7 +39,7 @@ func (m ModelPublicAgentPersonaSkills) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicAgentPersonaSkills) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -38,7 +38,7 @@ func (m ModelPublicAgentPersonaTraits) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicAgentPersonaTraits) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -19,10 +19,11 @@ type ModelPublicAgentPersonas struct {
|
||||
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
|
||||
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
|
||||
Summary resolvespec_common.SqlString `bun:"summary,type:text,notnull," json:"summary"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text,default:'{}',notnull," json:"tags"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
|
||||
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
|
||||
RelPersonaIDPublicAgentPersonaParts []*ModelPublicAgentPersonaParts `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonaparts,omitempty"` // Has many ModelPublicAgentPersonaParts
|
||||
RelPersonaIDPublicAgentPersonaSkills []*ModelPublicAgentPersonaSkills `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonaskills,omitempty"` // Has many ModelPublicAgentPersonaSkills
|
||||
RelPersonaIDPublicProjectPersonas []*ModelPublicProjectPersonas `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicprojectpersonas,omitempty"` // Has many ModelPublicProjectPersonas
|
||||
RelPersonaIDPublicAgentPersonaGuardrails []*ModelPublicAgentPersonaGuardrails `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonaguardrails,omitempty"` // Has many ModelPublicAgentPersonaGuardrails
|
||||
RelPersonaIDPublicAgentPersonaTraits []*ModelPublicAgentPersonaTraits `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonatraits,omitempty"` // Has many ModelPublicAgentPersonaTraits
|
||||
RelPersonaIDPublicPersonaArcs []*ModelPublicPersonaArc `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicpersonaarcs,omitempty"` // Has many ModelPublicPersonaArc
|
||||
@@ -50,7 +51,7 @@ func (m ModelPublicAgentPersonas) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicAgentPersonas) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -13,13 +13,13 @@ type ModelPublicAgentSkills struct {
|
||||
Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"`
|
||||
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
|
||||
Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"`
|
||||
DomainTags resolvespec_common.SqlStringArray `bun:"domain_tags,type:text,default:'{}',notnull," json:"domain_tags"`
|
||||
FrameworkTags resolvespec_common.SqlStringArray `bun:"framework_tags,type:text,default:'{}',notnull," json:"framework_tags"`
|
||||
DomainTags resolvespec_common.SqlStringArray `bun:"domain_tags,type:text[],default:'{}',notnull," json:"domain_tags"`
|
||||
FrameworkTags resolvespec_common.SqlStringArray `bun:"framework_tags,type:text[],default:'{}',notnull," json:"framework_tags"`
|
||||
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
|
||||
LanguageTags resolvespec_common.SqlStringArray `bun:"language_tags,type:text,default:'{}',notnull," json:"language_tags"`
|
||||
LibraryTags resolvespec_common.SqlStringArray `bun:"library_tags,type:text,default:'{}',notnull," json:"library_tags"`
|
||||
LanguageTags resolvespec_common.SqlStringArray `bun:"language_tags,type:text[],default:'{}',notnull," json:"language_tags"`
|
||||
LibraryTags resolvespec_common.SqlStringArray `bun:"library_tags,type:text[],default:'{}',notnull," json:"library_tags"`
|
||||
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text,default:'{}',notnull," json:"tags"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
|
||||
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
|
||||
RelSkillIDPublicAgentPersonaSkills []*ModelPublicAgentPersonaSkills `bun:"rel:has-many,join:id=skill_id" json:"relskillidpublicagentpersonaskills,omitempty"` // Has many ModelPublicAgentPersonaSkills
|
||||
RelRelatedSkillIDPublicLearnings []*ModelPublicLearnings `bun:"rel:has-many,join:id=related_skill_id" json:"relrelatedskillidpubliclearnings,omitempty"` // Has many ModelPublicLearnings
|
||||
@@ -49,7 +49,7 @@ func (m ModelPublicAgentSkills) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicAgentSkills) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -15,7 +15,7 @@ type ModelPublicAgentTraits struct {
|
||||
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
|
||||
Instruction resolvespec_common.SqlString `bun:"instruction,type:text,default:'',notnull," json:"instruction"`
|
||||
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text,default:'{}',notnull," json:"tags"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
|
||||
TraitType resolvespec_common.SqlString `bun:"trait_type,type:text,notnull," json:"trait_type"`
|
||||
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
|
||||
RelTraitIDPublicAgentPersonaTraits []*ModelPublicAgentPersonaTraits `bun:"rel:has-many,join:id=trait_id" json:"reltraitidpublicagentpersonatraits,omitempty"` // Has many ModelPublicAgentPersonaTraits
|
||||
@@ -43,7 +43,7 @@ func (m ModelPublicAgentTraits) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicAgentTraits) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -38,7 +38,7 @@ func (m ModelPublicArcStageParts) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicArcStageParts) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -43,7 +43,7 @@ func (m ModelPublicArcStages) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicArcStages) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -41,7 +41,7 @@ func (m ModelPublicCharacterArcs) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicCharacterArcs) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -14,7 +14,7 @@ type ModelPublicChatHistories struct {
|
||||
Channel resolvespec_common.SqlString `bun:"channel,type:text,nullzero," json:"channel"`
|
||||
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
|
||||
GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"`
|
||||
Messages resolvespec_common.SqlJSONB `bun:"messages,type:jsonb,default:'',notnull," json:"messages"`
|
||||
Messages resolvespec_common.SqlJSONB `bun:"messages,type:jsonb,default:'[]',notnull," json:"messages"`
|
||||
Metadata resolvespec_common.SqlJSONB `bun:"metadata,type:jsonb,default:'{}',notnull," json:"metadata"`
|
||||
ProjectID resolvespec_common.SqlInt64 `bun:"project_id,type:bigint,nullzero," json:"project_id"`
|
||||
SessionID resolvespec_common.SqlString `bun:"session_id,type:text,notnull," json:"session_id"`
|
||||
@@ -46,7 +46,7 @@ func (m ModelPublicChatHistories) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicChatHistories) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -42,7 +42,7 @@ func (m ModelPublicEmbeddings) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicEmbeddings) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -29,7 +29,7 @@ type ModelPublicLearnings struct {
|
||||
Status resolvespec_common.SqlString `bun:"status,type:text,default:'pending',notnull," json:"status"`
|
||||
Summary resolvespec_common.SqlString `bun:"summary,type:text,notnull," json:"summary"`
|
||||
SupersedesLearningID resolvespec_common.SqlInt64 `bun:"supersedes_learning_id,type:bigint,nullzero," json:"supersedes_learning_id"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text,default:'{}',notnull," json:"tags"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
|
||||
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
|
||||
RelDuplicateOfLearningID *ModelPublicLearnings `bun:"rel:has-one,join:duplicate_of_learning_id=id" json:"relduplicateoflearningid,omitempty"` // Has one ModelPublicLearnings
|
||||
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
|
||||
@@ -60,7 +60,7 @@ func (m ModelPublicLearnings) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicLearnings) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -13,7 +13,7 @@ type ModelPublicOauthClients struct {
|
||||
ClientID resolvespec_common.SqlString `bun:"client_id,type:text,notnull," json:"client_id"`
|
||||
ClientName resolvespec_common.SqlString `bun:"client_name,type:text,default:'',notnull," json:"client_name"`
|
||||
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
|
||||
RedirectUris resolvespec_common.SqlStringArray `bun:"redirect_uris,type:text,default:'{}',notnull," json:"redirect_uris"`
|
||||
RedirectUris resolvespec_common.SqlStringArray `bun:"redirect_uris,type:text[],default:'{}',notnull," json:"redirect_uris"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for ModelPublicOauthClients
|
||||
@@ -38,7 +38,7 @@ func (m ModelPublicOauthClients) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicOauthClients) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -41,7 +41,7 @@ func (m ModelPublicPersonaArc) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicPersonaArc) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -39,7 +39,7 @@ func (m ModelPublicPlanDependencies) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicPlanDependencies) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -39,7 +39,7 @@ func (m ModelPublicPlanGuardrails) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicPlanGuardrails) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -39,7 +39,7 @@ func (m ModelPublicPlanRelatedPlans) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicPlanRelatedPlans) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -39,7 +39,7 @@ func (m ModelPublicPlanSkills) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicPlanSkills) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -22,7 +22,7 @@ type ModelPublicPlans struct {
|
||||
ReviewedBy resolvespec_common.SqlString `bun:"reviewed_by,type:text,nullzero," json:"reviewed_by"`
|
||||
Status resolvespec_common.SqlString `bun:"status,type:text,default:'draft',notnull," json:"status"` // draft, active, blocked, completed, cancelled, superseded
|
||||
SupersedesPlanID resolvespec_common.SqlInt64 `bun:"supersedes_plan_id,type:bigint,nullzero," json:"supersedes_plan_id"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text,default:'{}',notnull," json:"tags"`
|
||||
Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"`
|
||||
Title resolvespec_common.SqlString `bun:"title,type:text,notnull," json:"title"`
|
||||
UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"`
|
||||
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
|
||||
@@ -57,7 +57,7 @@ func (m ModelPublicPlans) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicPlans) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -39,7 +39,7 @@ func (m ModelPublicProjectGuardrails) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicProjectGuardrails) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Code generated by relspecgo. DO NOT EDIT.
|
||||
package generatedmodels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type ModelPublicProjectPersonas struct {
|
||||
bun.BaseModel `bun:"table:public.project_personas,alias:project_personas"`
|
||||
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
|
||||
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
|
||||
IsDefault bool `bun:"is_default,type:boolean,default:false,notnull," json:"is_default"`
|
||||
PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"`
|
||||
ProjectID int64 `bun:"project_id,type:bigint,notnull," json:"project_id"`
|
||||
RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas
|
||||
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
|
||||
}
|
||||
|
||||
// TableName returns the table name for ModelPublicProjectPersonas
|
||||
func (m ModelPublicProjectPersonas) TableName() string {
|
||||
return "public.project_personas"
|
||||
}
|
||||
|
||||
// TableNameOnly returns the table name without schema for ModelPublicProjectPersonas
|
||||
func (m ModelPublicProjectPersonas) TableNameOnly() string {
|
||||
return "project_personas"
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name for ModelPublicProjectPersonas
|
||||
func (m ModelPublicProjectPersonas) SchemaName() string {
|
||||
return "public"
|
||||
}
|
||||
|
||||
// GetID returns the primary key value
|
||||
func (m ModelPublicProjectPersonas) GetID() int64 {
|
||||
return m.ID.Int64()
|
||||
}
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicProjectPersonas) GetIDStr() string {
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
func (m ModelPublicProjectPersonas) SetID(newid int64) {
|
||||
m.UpdateID(newid)
|
||||
}
|
||||
|
||||
// UpdateID updates the primary key value
|
||||
func (m *ModelPublicProjectPersonas) UpdateID(newid int64) {
|
||||
m.ID.FromString(fmt.Sprintf("%d", newid))
|
||||
}
|
||||
|
||||
// GetIDName returns the name of the primary key column
|
||||
func (m ModelPublicProjectPersonas) GetIDName() string {
|
||||
return "id"
|
||||
}
|
||||
|
||||
// GetPrefix returns the table prefix
|
||||
func (m ModelPublicProjectPersonas) GetPrefix() string {
|
||||
return "PPR"
|
||||
}
|
||||
@@ -11,6 +11,7 @@ type ModelPublicProjectSkills struct {
|
||||
bun.BaseModel `bun:"table:public.project_skills,alias:project_skills"`
|
||||
ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"`
|
||||
CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"`
|
||||
Override bool `bun:"override,type:boolean,default:false,notnull," json:"override"`
|
||||
ProjectID int64 `bun:"project_id,type:bigint,notnull," json:"project_id"`
|
||||
SkillID int64 `bun:"skill_id,type:bigint,notnull," json:"skill_id"`
|
||||
RelProjectID *ModelPublicProjects `bun:"rel:has-one,join:project_id=id" json:"relprojectid,omitempty"` // Has one ModelPublicProjects
|
||||
@@ -39,7 +40,7 @@ func (m ModelPublicProjectSkills) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicProjectSkills) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -16,6 +16,7 @@ type ModelPublicProjects struct {
|
||||
LastActiveAt resolvespec_common.SqlTimeStamp `bun:"last_active_at,type:timestamptz,default:now(),nullzero," json:"last_active_at"`
|
||||
Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"`
|
||||
ThoughtCount resolvespec_common.SqlInt64 `bun:"thought_count,scanonly" json:"thought_count"`
|
||||
RelProjectIDPublicProjectPersonas []*ModelPublicProjectPersonas `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicprojectpersonas,omitempty"` // Has many ModelPublicProjectPersonas
|
||||
RelProjectIDPublicThoughts []*ModelPublicThoughts `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicthoughts,omitempty"` // Has many ModelPublicThoughts
|
||||
RelProjectIDPublicStoredFiles []*ModelPublicStoredFiles `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicstoredfiles,omitempty"` // Has many ModelPublicStoredFiles
|
||||
RelProjectIDPublicChatHistories []*ModelPublicChatHistories `bun:"rel:has-many,join:id=project_id" json:"relprojectidpublicchathistories,omitempty"` // Has many ModelPublicChatHistories
|
||||
@@ -47,7 +48,7 @@ func (m ModelPublicProjects) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicProjects) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -48,7 +48,7 @@ func (m ModelPublicStoredFiles) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicStoredFiles) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -40,7 +40,7 @@ func (m ModelPublicThoughtLinks) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicThoughtLinks) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -47,7 +47,7 @@ func (m ModelPublicThoughts) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicThoughts) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -38,7 +38,7 @@ func (m ModelPublicToolAnnotations) GetID() int64 {
|
||||
|
||||
// GetIDStr returns the primary key as a string
|
||||
func (m ModelPublicToolAnnotations) GetIDStr() string {
|
||||
return fmt.Sprintf("%v", m.ID)
|
||||
return m.ID.String()
|
||||
}
|
||||
|
||||
// SetID sets the primary key value
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mcpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -37,12 +38,14 @@ type ToolSet struct {
|
||||
Reparse *tools.ReparseMetadataTool
|
||||
RetryMetadata *tools.RetryEnrichmentTool
|
||||
//Maintenance *tools.MaintenanceTool
|
||||
Skills *tools.SkillsTool
|
||||
Personas *tools.AgentPersonasTool
|
||||
ChatHistory *tools.ChatHistoryTool
|
||||
Describe *tools.DescribeTool
|
||||
Learnings *tools.LearningsTool
|
||||
Plans *tools.PlansTool
|
||||
Skills *tools.SkillsTool
|
||||
Personas *tools.AgentPersonasTool
|
||||
ChatHistory *tools.ChatHistoryTool
|
||||
Describe *tools.DescribeTool
|
||||
Learnings *tools.LearningsTool
|
||||
Plans *tools.PlansTool
|
||||
ProjectPersonas *tools.ProjectPersonasTool
|
||||
WorldModel *tools.WorldModelTool
|
||||
}
|
||||
|
||||
// Handlers groups the HTTP handlers produced for an MCP server instance.
|
||||
@@ -86,6 +89,7 @@ func NewHandlers(cfg config.MCPConfig, logger *slog.Logger, toolSet ToolSet, onS
|
||||
registerSystemTools,
|
||||
registerThoughtTools,
|
||||
registerProjectTools,
|
||||
registerWorldModelTools,
|
||||
registerLearningTools,
|
||||
registerPlanTools,
|
||||
registerFileTools,
|
||||
@@ -123,6 +127,33 @@ func NewHandlers(cfg config.MCPConfig, logger *slog.Logger, toolSet ToolSet, onS
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func registerWorldModelTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error {
|
||||
server.AddResource(&mcp.Resource{
|
||||
Name: "world_model_intro",
|
||||
URI: "amcs://world-model/intro",
|
||||
MIMEType: "text/markdown",
|
||||
Description: "Mandatory startup instructions for discovering and loading the AMCS world model.",
|
||||
}, func(_ context.Context, req *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
|
||||
return &mcp.ReadResourceResult{Contents: []*mcp.ResourceContents{{URI: req.Params.URI, MIMEType: "text/markdown", Text: string(amcsllm.WorldModelIntro)}}}, nil
|
||||
})
|
||||
if err := addTool(server, logger, &mcp.Tool{Name: "bootstrap_world_model", Description: "Validate and activate a project, then return its effective skills, guardrails, personas, and bounded recent context."}, toolSet.WorldModel.Bootstrap); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addTool(server, logger, &mcp.Tool{Name: "add_project_persona", Description: "Link a persona to a project and optionally make it the default."}, toolSet.ProjectPersonas.Add); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addTool(server, logger, &mcp.Tool{Name: "remove_project_persona", Description: "Unlink a persona from a project."}, toolSet.ProjectPersonas.Remove); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addTool(server, logger, &mcp.Tool{Name: "set_default_project_persona", Description: "Set a linked persona as the project's default persona."}, toolSet.ProjectPersonas.SetDefault); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addTool(server, logger, &mcp.Tool{Name: "list_project_personas", Description: "List personas linked to a project, with the default marked."}, toolSet.ProjectPersonas.List); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildServerIcons returns icon definitions referencing the server's own /images/icon.png endpoint.
|
||||
// Returns nil when publicURL is empty so the icons field is omitted from the MCP identity.
|
||||
func buildServerIcons(publicURL string) []mcp.Icon {
|
||||
@@ -713,6 +744,11 @@ func BuildToolCatalog() []tools.ToolEntry {
|
||||
{Name: "list_projects", Description: "List projects and their current thought counts.", Category: "projects"},
|
||||
{Name: "set_active_project", Description: "Set the active project for the current MCP session. Requires a stateful MCP client that reuses the same session across calls. If your client does not preserve sessions, pass project explicitly to each tool instead.", Category: "projects"},
|
||||
{Name: "get_active_project", Description: "Return the active project for the current MCP session. If your client does not preserve MCP sessions, pass project explicitly to project-scoped tools instead of relying on this.", Category: "projects"},
|
||||
{Name: "bootstrap_world_model", Description: "Validate and activate a project, then return effective skills, guardrails, personas, and bounded recent context.", Category: "projects"},
|
||||
{Name: "add_project_persona", Description: "Link a persona to a project and optionally make it the default.", Category: "personas"},
|
||||
{Name: "remove_project_persona", Description: "Unlink a persona from a project.", Category: "personas"},
|
||||
{Name: "set_default_project_persona", Description: "Set a linked persona as the project's default persona.", Category: "personas"},
|
||||
{Name: "list_project_personas", Description: "List personas linked to a project, with the default marked.", Category: "personas"},
|
||||
{Name: "get_project_context", Description: "Get recent and semantic context for a project. Uses the explicit project when provided, otherwise the active MCP session project. Falls back to full-text search when no embeddings exist.", Category: "projects"},
|
||||
|
||||
// learnings
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -60,6 +61,26 @@ func TestNewListsStoredFileResourceTemplate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewExposesWorldModelIntroResource(t *testing.T) {
|
||||
cs := newStreamableTestClient(t)
|
||||
|
||||
listed, err := cs.ListResources(context.Background(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ListResources() error = %v", err)
|
||||
}
|
||||
if len(listed.Resources) != 1 || listed.Resources[0].URI != "amcs://world-model/intro" {
|
||||
t.Fatalf("ListResources() = %#v, want world model intro", listed.Resources)
|
||||
}
|
||||
|
||||
read, err := cs.ReadResource(context.Background(), &mcp.ReadResourceParams{URI: "amcs://world-model/intro"})
|
||||
if err != nil {
|
||||
t.Fatalf("ReadResource() error = %v", err)
|
||||
}
|
||||
if len(read.Contents) != 1 || !strings.Contains(read.Contents[0].Text, "bootstrap_world_model") {
|
||||
t.Fatalf("ReadResource() did not return bootstrap instructions: %#v", read.Contents)
|
||||
}
|
||||
}
|
||||
|
||||
func newStreamableTestClient(t *testing.T) *mcp.ClientSession {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -189,28 +189,30 @@ func TestStreamableHTTPReturnsStructuredToolErrors(t *testing.T) {
|
||||
|
||||
func streamableTestToolSet() ToolSet {
|
||||
return ToolSet{
|
||||
Version: tools.NewVersionTool("test", buildinfo.Info{Version: "0.0.1", TagName: "v0.0.1", Commit: "test", BuildDate: "2026-03-31T00:00:00Z"}),
|
||||
Capture: new(tools.CaptureTool),
|
||||
Search: new(tools.SearchTool),
|
||||
List: new(tools.ListTool),
|
||||
Stats: new(tools.StatsTool),
|
||||
Get: new(tools.GetTool),
|
||||
Update: new(tools.UpdateTool),
|
||||
Delete: new(tools.DeleteTool),
|
||||
Archive: new(tools.ArchiveTool),
|
||||
Projects: new(tools.ProjectsTool),
|
||||
Context: new(tools.ContextTool),
|
||||
Recall: new(tools.RecallTool),
|
||||
Summarize: new(tools.SummarizeTool),
|
||||
Links: new(tools.LinksTool),
|
||||
Files: new(tools.FilesTool),
|
||||
Backfill: new(tools.BackfillTool),
|
||||
Reparse: new(tools.ReparseMetadataTool),
|
||||
RetryMetadata: new(tools.RetryEnrichmentTool),
|
||||
Skills: new(tools.SkillsTool),
|
||||
ChatHistory: new(tools.ChatHistoryTool),
|
||||
Describe: new(tools.DescribeTool),
|
||||
Learnings: new(tools.LearningsTool),
|
||||
Plans: new(tools.PlansTool),
|
||||
Version: tools.NewVersionTool("test", buildinfo.Info{Version: "0.0.1", TagName: "v0.0.1", Commit: "test", BuildDate: "2026-03-31T00:00:00Z"}),
|
||||
Capture: new(tools.CaptureTool),
|
||||
Search: new(tools.SearchTool),
|
||||
List: new(tools.ListTool),
|
||||
Stats: new(tools.StatsTool),
|
||||
Get: new(tools.GetTool),
|
||||
Update: new(tools.UpdateTool),
|
||||
Delete: new(tools.DeleteTool),
|
||||
Archive: new(tools.ArchiveTool),
|
||||
Projects: new(tools.ProjectsTool),
|
||||
Context: new(tools.ContextTool),
|
||||
Recall: new(tools.RecallTool),
|
||||
Summarize: new(tools.SummarizeTool),
|
||||
Links: new(tools.LinksTool),
|
||||
Files: new(tools.FilesTool),
|
||||
Backfill: new(tools.BackfillTool),
|
||||
Reparse: new(tools.ReparseMetadataTool),
|
||||
RetryMetadata: new(tools.RetryEnrichmentTool),
|
||||
Skills: new(tools.SkillsTool),
|
||||
ChatHistory: new(tools.ChatHistoryTool),
|
||||
Describe: new(tools.DescribeTool),
|
||||
Learnings: new(tools.LearningsTool),
|
||||
Plans: new(tools.PlansTool),
|
||||
ProjectPersonas: new(tools.ProjectPersonasTool),
|
||||
WorldModel: new(tools.WorldModelTool),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,7 @@ func (db *DB) GetPersona(ctx context.Context, name string, detail bool, override
|
||||
Name: s.Name,
|
||||
Description: s.Description,
|
||||
Tags: s.Tags,
|
||||
Override: s.Override,
|
||||
}
|
||||
if detail {
|
||||
entry.Content = s.Content
|
||||
@@ -341,6 +342,7 @@ func (db *DB) GetPersonaManifest(ctx context.Context, name string) (ext.PersonaM
|
||||
ID: s.ID,
|
||||
Name: s.Name,
|
||||
Description: s.Description,
|
||||
Override: s.Override,
|
||||
})
|
||||
}
|
||||
for _, g := range guardrails {
|
||||
@@ -567,11 +569,12 @@ func (db *DB) RemovePersonaPart(ctx context.Context, personaName, partName strin
|
||||
// Persona-Skill links
|
||||
// ──────────────────────────────────────────────
|
||||
|
||||
func (db *DB) AddPersonaSkill(ctx context.Context, personaID, skillID int64) error {
|
||||
func (db *DB) AddPersonaSkill(ctx context.Context, personaID, skillID int64, override bool) error {
|
||||
_, err := db.pool.Exec(ctx, `
|
||||
insert into agent_persona_skills (persona_id, skill_id)
|
||||
values ($1, $2) on conflict do nothing
|
||||
`, personaID, skillID)
|
||||
insert into agent_persona_skills (persona_id, skill_id, override)
|
||||
values ($1, $2, $3)
|
||||
on conflict (persona_id, skill_id) do update set override = excluded.override
|
||||
`, personaID, skillID, override)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add persona skill: %w", err)
|
||||
}
|
||||
@@ -1058,7 +1061,7 @@ func (db *DB) fetchPartsByNames(ctx context.Context, names []string) ([]rawPart,
|
||||
|
||||
func (db *DB) listPersonaSkills(ctx context.Context, personaID int64) ([]AgentSkillRow, error) {
|
||||
rows, err := db.pool.Query(ctx, `
|
||||
select s.id, s.name, s.description, s.content, s.tags::text[]
|
||||
select s.id, s.name, s.description, s.content, s.tags::text[], aps.override
|
||||
from agent_skills s
|
||||
join agent_persona_skills aps on aps.skill_id = s.id
|
||||
where aps.persona_id = $1
|
||||
@@ -1073,7 +1076,7 @@ func (db *DB) listPersonaSkills(ctx context.Context, personaID int64) ([]AgentSk
|
||||
for rows.Next() {
|
||||
var s AgentSkillRow
|
||||
var tags []string
|
||||
if err := rows.Scan(&s.ID, &s.Name, &s.Description, &s.Content, &tags); err != nil {
|
||||
if err := rows.Scan(&s.ID, &s.Name, &s.Description, &s.Content, &tags, &s.Override); err != nil {
|
||||
return nil, fmt.Errorf("scan persona skill: %w", err)
|
||||
}
|
||||
s.Tags = nilToEmptyStrings(tags)
|
||||
@@ -1139,6 +1142,7 @@ type AgentSkillRow struct {
|
||||
Description string
|
||||
Content string
|
||||
Tags []string
|
||||
Override bool
|
||||
}
|
||||
|
||||
type AgentGuardrailRow struct {
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
ext "git.warky.dev/wdevs/amcs/internal/types"
|
||||
)
|
||||
|
||||
func (db *DB) AddProjectPersona(ctx context.Context, projectID, personaID int64, isDefault bool) error {
|
||||
tx, err := db.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("begin add project persona: %w", err)
|
||||
}
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
|
||||
if _, err := tx.Exec(ctx, `select pg_advisory_xact_lock($1)`, projectID); err != nil {
|
||||
return fmt.Errorf("lock project persona defaults: %w", err)
|
||||
}
|
||||
if isDefault {
|
||||
if _, err := tx.Exec(ctx, `update project_personas set is_default = false where project_id = $1`, projectID); err != nil {
|
||||
return fmt.Errorf("clear default project persona: %w", err)
|
||||
}
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `
|
||||
insert into project_personas (project_id, persona_id, is_default)
|
||||
values ($1, $2, $3)
|
||||
on conflict (project_id, persona_id) do update set is_default = excluded.is_default
|
||||
`, projectID, personaID, isDefault); err != nil {
|
||||
return fmt.Errorf("add project persona: %w", err)
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return fmt.Errorf("commit add project persona: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) RemoveProjectPersona(ctx context.Context, projectID, personaID int64) error {
|
||||
tag, err := db.pool.Exec(ctx, `delete from project_personas where project_id = $1 and persona_id = $2`, projectID, personaID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("remove project persona: %w", err)
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return fmt.Errorf("project persona link not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) ListProjectPersonas(ctx context.Context, projectID int64) ([]ext.ProjectPersona, error) {
|
||||
rows, err := db.pool.Query(ctx, `
|
||||
select p.id, p.guid, p.name, p.description, p.summary, p.detail,
|
||||
p.compiled_summary, p.compiled_detail, p.compiled_at, p.tags::text[],
|
||||
p.created_at, p.updated_at, pp.is_default
|
||||
from agent_personas p
|
||||
join project_personas pp on pp.persona_id = p.id
|
||||
where pp.project_id = $1
|
||||
order by pp.is_default desc, p.name
|
||||
`, projectID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list project personas: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []ext.ProjectPersona
|
||||
for rows.Next() {
|
||||
var item ext.ProjectPersona
|
||||
var tags []string
|
||||
if err := rows.Scan(&item.Persona.ID, &item.Persona.GUID, &item.Persona.Name,
|
||||
&item.Persona.Description, &item.Persona.Summary, &item.Persona.Detail,
|
||||
&item.Persona.CompiledSummary, &item.Persona.CompiledDetail, &item.Persona.CompiledAt,
|
||||
&tags, &item.Persona.CreatedAt, &item.Persona.UpdatedAt, &item.IsDefault); err != nil {
|
||||
return nil, fmt.Errorf("scan project persona: %w", err)
|
||||
}
|
||||
item.Persona.Tags = nilToEmptyStrings(tags)
|
||||
result = append(result, item)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func (db *DB) GetDefaultProjectPersona(ctx context.Context, projectID int64) (*ext.Persona, error) {
|
||||
row := db.pool.QueryRow(ctx, `
|
||||
select p.id, p.guid, p.name, p.description, p.summary, p.detail,
|
||||
p.compiled_summary, p.compiled_detail, p.compiled_at, p.tags::text[],
|
||||
p.created_at, p.updated_at
|
||||
from agent_personas p
|
||||
join project_personas pp on pp.persona_id = p.id
|
||||
where pp.project_id = $1 and pp.is_default
|
||||
`, projectID)
|
||||
persona, err := scanPersona(row)
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get default project persona: %w", err)
|
||||
}
|
||||
return &persona, nil
|
||||
}
|
||||
@@ -116,6 +116,24 @@ func scanSkill(row skillScanner) (ext.AgentSkill, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func normalizeSkillSlices(skill *ext.AgentSkill) {
|
||||
if skill.Tags == nil {
|
||||
skill.Tags = []string{}
|
||||
}
|
||||
if skill.LanguageTags == nil {
|
||||
skill.LanguageTags = []string{}
|
||||
}
|
||||
if skill.LibraryTags == nil {
|
||||
skill.LibraryTags = []string{}
|
||||
}
|
||||
if skill.FrameworkTags == nil {
|
||||
skill.FrameworkTags = []string{}
|
||||
}
|
||||
if skill.DomainTags == nil {
|
||||
skill.DomainTags = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) GetSkill(ctx context.Context, id int64) (ext.AgentSkill, error) {
|
||||
row := db.pool.QueryRow(ctx, `select `+skillSelectCols+` from agent_skills where id = $1`, id)
|
||||
s, err := scanSkill(row)
|
||||
@@ -278,12 +296,12 @@ func (db *DB) GetGuardrail(ctx context.Context, id int64) (ext.AgentGuardrail, e
|
||||
|
||||
// Project Skills
|
||||
|
||||
func (db *DB) AddProjectSkill(ctx context.Context, projectID, skillID int64) error {
|
||||
func (db *DB) AddProjectSkill(ctx context.Context, projectID, skillID int64, override bool) error {
|
||||
_, err := db.pool.Exec(ctx, `
|
||||
insert into project_skills (project_id, skill_id)
|
||||
values ($1, $2)
|
||||
on conflict do nothing
|
||||
`, projectID, skillID)
|
||||
insert into project_skills (project_id, skill_id, override)
|
||||
values ($1, $2, $3)
|
||||
on conflict (project_id, skill_id) do update set override = excluded.override
|
||||
`, projectID, skillID, override)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add project skill: %w", err)
|
||||
}
|
||||
@@ -305,7 +323,9 @@ func (db *DB) RemoveProjectSkill(ctx context.Context, projectID, skillID int64)
|
||||
|
||||
func (db *DB) ListProjectSkills(ctx context.Context, projectID int64) ([]ext.AgentSkill, error) {
|
||||
rows, err := db.pool.Query(ctx, `
|
||||
select s.`+skillSelectCols+`
|
||||
select s.id, s.name, s.description, s.content, s.tags::text[],
|
||||
s.language_tags::text[], s.library_tags::text[], s.framework_tags::text[],
|
||||
s.domain_tags::text[], s.created_at, s.updated_at, ps.override
|
||||
from agent_skills s
|
||||
join project_skills ps on ps.skill_id = s.id
|
||||
where ps.project_id = $1
|
||||
@@ -318,10 +338,13 @@ func (db *DB) ListProjectSkills(ctx context.Context, projectID int64) ([]ext.Age
|
||||
|
||||
var skills []ext.AgentSkill
|
||||
for rows.Next() {
|
||||
s, err := scanSkill(rows)
|
||||
if err != nil {
|
||||
var s ext.AgentSkill
|
||||
if err := rows.Scan(&s.ID, &s.Name, &s.Description, &s.Content, &s.Tags,
|
||||
&s.LanguageTags, &s.LibraryTags, &s.FrameworkTags, &s.DomainTags,
|
||||
&s.CreatedAt, &s.UpdatedAt, &s.Override); err != nil {
|
||||
return nil, fmt.Errorf("scan project skill: %w", err)
|
||||
}
|
||||
normalizeSkillSlices(&s)
|
||||
skills = append(skills, s)
|
||||
}
|
||||
return skills, rows.Err()
|
||||
|
||||
@@ -422,6 +422,7 @@ func (t *AgentPersonasTool) RemovePersonaPart(ctx context.Context, _ *mcp.CallTo
|
||||
type AddPersonaSkillInput struct {
|
||||
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
|
||||
SkillID int64 `json:"skill_id" jsonschema:"agent skill id to link"`
|
||||
Override bool `json:"override,omitempty" jsonschema:"replace a project skill with the same name during world-model assembly"`
|
||||
}
|
||||
|
||||
type AddPersonaSkillOutput struct {
|
||||
@@ -430,7 +431,7 @@ type AddPersonaSkillOutput struct {
|
||||
}
|
||||
|
||||
func (t *AgentPersonasTool) AddPersonaSkill(ctx context.Context, _ *mcp.CallToolRequest, in AddPersonaSkillInput) (*mcp.CallToolResult, AddPersonaSkillOutput, error) {
|
||||
if err := t.store.AddPersonaSkill(ctx, in.PersonaID, in.SkillID); err != nil {
|
||||
if err := t.store.AddPersonaSkill(ctx, in.PersonaID, in.SkillID, in.Override); err != nil {
|
||||
return nil, AddPersonaSkillOutput{}, err
|
||||
}
|
||||
return nil, AddPersonaSkillOutput{PersonaID: in.PersonaID, SkillID: in.SkillID}, nil
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
||||
"git.warky.dev/wdevs/amcs/internal/session"
|
||||
"git.warky.dev/wdevs/amcs/internal/store"
|
||||
ext "git.warky.dev/wdevs/amcs/internal/types"
|
||||
)
|
||||
|
||||
type ProjectPersonasTool struct {
|
||||
store *store.DB
|
||||
sessions *session.ActiveProjects
|
||||
}
|
||||
|
||||
func NewProjectPersonasTool(db *store.DB, sessions *session.ActiveProjects) *ProjectPersonasTool {
|
||||
return &ProjectPersonasTool{store: db, sessions: sessions}
|
||||
}
|
||||
|
||||
type ProjectPersonaInput struct {
|
||||
Project string `json:"project,omitempty" jsonschema:"project name or id (uses active project if omitted)"`
|
||||
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
|
||||
IsDefault bool `json:"is_default,omitempty" jsonschema:"make this the default persona for the project"`
|
||||
}
|
||||
|
||||
type ProjectPersonaOutput struct {
|
||||
ProjectID int64 `json:"project_id"`
|
||||
PersonaID int64 `json:"persona_id"`
|
||||
}
|
||||
|
||||
type ListProjectPersonasInput struct {
|
||||
Project string `json:"project,omitempty" jsonschema:"project name or id (uses active project if omitted)"`
|
||||
}
|
||||
|
||||
type ListProjectPersonasOutput struct {
|
||||
ProjectID int64 `json:"project_id"`
|
||||
Personas []ext.ProjectPersona `json:"personas"`
|
||||
}
|
||||
|
||||
func (t *ProjectPersonasTool) Add(ctx context.Context, req *mcp.CallToolRequest, in ProjectPersonaInput) (*mcp.CallToolResult, ProjectPersonaOutput, error) {
|
||||
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, true)
|
||||
if err != nil {
|
||||
return nil, ProjectPersonaOutput{}, err
|
||||
}
|
||||
if err := t.store.AddProjectPersona(ctx, project.NumericID, in.PersonaID, in.IsDefault); err != nil {
|
||||
return nil, ProjectPersonaOutput{}, err
|
||||
}
|
||||
return nil, ProjectPersonaOutput{ProjectID: project.NumericID, PersonaID: in.PersonaID}, nil
|
||||
}
|
||||
|
||||
func (t *ProjectPersonasTool) Remove(ctx context.Context, req *mcp.CallToolRequest, in ProjectPersonaInput) (*mcp.CallToolResult, ProjectPersonaOutput, error) {
|
||||
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, true)
|
||||
if err != nil {
|
||||
return nil, ProjectPersonaOutput{}, err
|
||||
}
|
||||
if err := t.store.RemoveProjectPersona(ctx, project.NumericID, in.PersonaID); err != nil {
|
||||
return nil, ProjectPersonaOutput{}, err
|
||||
}
|
||||
return nil, ProjectPersonaOutput{ProjectID: project.NumericID, PersonaID: in.PersonaID}, nil
|
||||
}
|
||||
|
||||
func (t *ProjectPersonasTool) SetDefault(ctx context.Context, req *mcp.CallToolRequest, in ProjectPersonaInput) (*mcp.CallToolResult, ProjectPersonaOutput, error) {
|
||||
in.IsDefault = true
|
||||
return t.Add(ctx, req, in)
|
||||
}
|
||||
|
||||
func (t *ProjectPersonasTool) List(ctx context.Context, req *mcp.CallToolRequest, in ListProjectPersonasInput) (*mcp.CallToolResult, ListProjectPersonasOutput, error) {
|
||||
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, true)
|
||||
if err != nil {
|
||||
return nil, ListProjectPersonasOutput{}, err
|
||||
}
|
||||
personas, err := t.store.ListProjectPersonas(ctx, project.NumericID)
|
||||
if err != nil {
|
||||
return nil, ListProjectPersonasOutput{}, err
|
||||
}
|
||||
if personas == nil {
|
||||
personas = []ext.ProjectPersona{}
|
||||
}
|
||||
return nil, ListProjectPersonasOutput{ProjectID: project.NumericID, Personas: personas}, nil
|
||||
}
|
||||
@@ -259,8 +259,9 @@ func (t *SkillsTool) GetGuardrail(ctx context.Context, _ *mcp.CallToolRequest, i
|
||||
// add_project_skill
|
||||
|
||||
type AddProjectSkillInput struct {
|
||||
Project string `json:"project,omitempty" jsonschema:"project name or id (uses active project if omitted)"`
|
||||
SkillID int64 `json:"skill_id" jsonschema:"skill id to link"`
|
||||
Project string `json:"project,omitempty" jsonschema:"project name or id (uses active project if omitted)"`
|
||||
SkillID int64 `json:"skill_id" jsonschema:"skill id to link"`
|
||||
Override bool `json:"override,omitempty" jsonschema:"replace a lower-precedence skill with the same name during world-model assembly"`
|
||||
}
|
||||
|
||||
type AddProjectSkillOutput struct {
|
||||
@@ -273,7 +274,7 @@ func (t *SkillsTool) AddProjectSkill(ctx context.Context, req *mcp.CallToolReque
|
||||
if err != nil {
|
||||
return nil, AddProjectSkillOutput{}, err
|
||||
}
|
||||
if err := t.store.AddProjectSkill(ctx, project.NumericID, in.SkillID); err != nil {
|
||||
if err := t.store.AddProjectSkill(ctx, project.NumericID, in.SkillID, in.Override); err != nil {
|
||||
return nil, AddProjectSkillOutput{}, err
|
||||
}
|
||||
return nil, AddProjectSkillOutput{ProjectID: project.NumericID, SkillID: in.SkillID}, nil
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
||||
"git.warky.dev/wdevs/amcs/internal/session"
|
||||
"git.warky.dev/wdevs/amcs/internal/store"
|
||||
ext "git.warky.dev/wdevs/amcs/internal/types"
|
||||
)
|
||||
|
||||
type WorldModelTool struct {
|
||||
store *store.DB
|
||||
sessions *session.ActiveProjects
|
||||
}
|
||||
|
||||
func NewWorldModelTool(db *store.DB, sessions *session.ActiveProjects) *WorldModelTool {
|
||||
return &WorldModelTool{store: db, sessions: sessions}
|
||||
}
|
||||
|
||||
type BootstrapWorldModelInput struct {
|
||||
Project string `json:"project" jsonschema:"project name or id; must be explicit for deterministic startup"`
|
||||
ContextLimit int `json:"context_limit,omitempty" jsonschema:"recent project thoughts to include (default 10, maximum 50)"`
|
||||
}
|
||||
|
||||
type EffectiveSkill struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
Source string `json:"source"`
|
||||
Override bool `json:"override"`
|
||||
}
|
||||
|
||||
type WorldModelContextItem struct {
|
||||
ID int64 `json:"id"`
|
||||
Content string `json:"content"`
|
||||
Metadata ext.ThoughtMetadata `json:"metadata"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type BootstrapWorldModelOutput struct {
|
||||
Version int `json:"version"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
Project ext.Project `json:"project"`
|
||||
Skills []EffectiveSkill `json:"skills"`
|
||||
Guardrails []ext.AgentGuardrail `json:"guardrails"`
|
||||
Personas []ext.ProjectPersona `json:"personas"`
|
||||
PersonaManifests []ext.PersonaManifest `json:"persona_manifests"`
|
||||
DefaultPersona *ext.PersonaFull `json:"default_persona,omitempty"`
|
||||
Context []WorldModelContextItem `json:"context"`
|
||||
}
|
||||
|
||||
func (t *WorldModelTool) Bootstrap(ctx context.Context, req *mcp.CallToolRequest, in BootstrapWorldModelInput) (*mcp.CallToolResult, BootstrapWorldModelOutput, error) {
|
||||
project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, true)
|
||||
if err != nil {
|
||||
return nil, BootstrapWorldModelOutput{}, err
|
||||
}
|
||||
if t.sessions != nil && req != nil && req.Session != nil {
|
||||
t.sessions.Set(req.Session.ID(), project.ID)
|
||||
}
|
||||
|
||||
projectSkills, err := t.store.ListProjectSkills(ctx, project.NumericID)
|
||||
if err != nil {
|
||||
return nil, BootstrapWorldModelOutput{}, err
|
||||
}
|
||||
guardrails, err := t.store.ListProjectGuardrails(ctx, project.NumericID)
|
||||
if err != nil {
|
||||
return nil, BootstrapWorldModelOutput{}, err
|
||||
}
|
||||
personas, err := t.store.ListProjectPersonas(ctx, project.NumericID)
|
||||
if err != nil {
|
||||
return nil, BootstrapWorldModelOutput{}, err
|
||||
}
|
||||
|
||||
manifests := make([]ext.PersonaManifest, 0, len(personas))
|
||||
for _, linked := range personas {
|
||||
manifest, err := t.store.GetPersonaManifest(ctx, linked.Persona.Name)
|
||||
if err != nil {
|
||||
return nil, BootstrapWorldModelOutput{}, err
|
||||
}
|
||||
manifests = append(manifests, manifest)
|
||||
}
|
||||
|
||||
var defaultPersona *ext.PersonaFull
|
||||
for _, linked := range personas {
|
||||
if !linked.IsDefault {
|
||||
continue
|
||||
}
|
||||
loaded, err := t.store.GetPersona(ctx, linked.Persona.Name, true, nil)
|
||||
if err != nil {
|
||||
return nil, BootstrapWorldModelOutput{}, err
|
||||
}
|
||||
defaultPersona = &loaded
|
||||
break
|
||||
}
|
||||
|
||||
skills := mergeWorldModelSkills(projectSkills, defaultPersona)
|
||||
guardrails = mergeWorldModelGuardrails(guardrails, defaultPersona)
|
||||
limit := in.ContextLimit
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
if limit > 50 {
|
||||
limit = 50
|
||||
}
|
||||
recent, err := t.store.RecentThoughts(ctx, &project.NumericID, limit, 0)
|
||||
if err != nil {
|
||||
return nil, BootstrapWorldModelOutput{}, err
|
||||
}
|
||||
contextItems := make([]WorldModelContextItem, 0, len(recent))
|
||||
for _, thought := range recent {
|
||||
contextItems = append(contextItems, WorldModelContextItem{ID: thought.ID, Content: thought.Content, Metadata: thought.Metadata, CreatedAt: thought.CreatedAt})
|
||||
}
|
||||
if guardrails == nil {
|
||||
guardrails = []ext.AgentGuardrail{}
|
||||
}
|
||||
if personas == nil {
|
||||
personas = []ext.ProjectPersona{}
|
||||
}
|
||||
_ = t.store.TouchProject(ctx, project.NumericID)
|
||||
|
||||
return nil, BootstrapWorldModelOutput{
|
||||
Version: 1, GeneratedAt: time.Now().UTC(), Project: *project, Skills: skills,
|
||||
Guardrails: guardrails, Personas: personas, PersonaManifests: manifests,
|
||||
DefaultPersona: defaultPersona, Context: contextItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mergeWorldModelSkills(projectSkills []ext.AgentSkill, persona *ext.PersonaFull) []EffectiveSkill {
|
||||
result := make([]EffectiveSkill, 0, len(projectSkills))
|
||||
index := make(map[string]int, len(projectSkills))
|
||||
for _, skill := range projectSkills {
|
||||
key := strings.ToLower(skill.Name)
|
||||
index[key] = len(result)
|
||||
result = append(result, EffectiveSkill{ID: skill.ID, Name: skill.Name, Description: skill.Description, Content: skill.Content, Tags: skill.Tags, Source: "project", Override: skill.Override})
|
||||
}
|
||||
if persona == nil {
|
||||
return result
|
||||
}
|
||||
for _, skill := range persona.Skills {
|
||||
key := strings.ToLower(skill.Name)
|
||||
entry := EffectiveSkill{ID: skill.ID, Name: skill.Name, Description: skill.Description, Content: skill.Content, Tags: skill.Tags, Source: "persona", Override: skill.Override}
|
||||
if i, ok := index[key]; ok {
|
||||
if skill.Override {
|
||||
result[i] = entry
|
||||
}
|
||||
continue
|
||||
}
|
||||
index[key] = len(result)
|
||||
result = append(result, entry)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func mergeWorldModelGuardrails(project []ext.AgentGuardrail, persona *ext.PersonaFull) []ext.AgentGuardrail {
|
||||
result := append([]ext.AgentGuardrail(nil), project...)
|
||||
index := make(map[string]int, len(result))
|
||||
for i, guardrail := range result {
|
||||
index[strings.ToLower(guardrail.Name)] = i
|
||||
}
|
||||
if persona == nil {
|
||||
return result
|
||||
}
|
||||
severity := map[string]int{"low": 1, "medium": 2, "high": 3, "critical": 4}
|
||||
for _, guardrail := range persona.Guardrails {
|
||||
key := strings.ToLower(guardrail.Name)
|
||||
if i, ok := index[key]; ok {
|
||||
if severity[guardrail.Severity] > severity[result[i].Severity] {
|
||||
result[i].Severity = guardrail.Severity
|
||||
result[i].Content = guardrail.Content
|
||||
}
|
||||
continue
|
||||
}
|
||||
index[key] = len(result)
|
||||
result = append(result, ext.AgentGuardrail{ID: guardrail.ID, Name: guardrail.Name, Description: guardrail.Description, Content: guardrail.Content, Severity: guardrail.Severity, Tags: guardrail.Tags})
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
ext "git.warky.dev/wdevs/amcs/internal/types"
|
||||
)
|
||||
|
||||
func TestMergeWorldModelSkills(t *testing.T) {
|
||||
project := []ext.AgentSkill{
|
||||
{ID: 1, Name: "go", Content: "project", Tags: []string{}},
|
||||
{ID: 2, Name: "postgres", Content: "database", Tags: []string{}},
|
||||
}
|
||||
persona := &ext.PersonaFull{Skills: []ext.PersonaSkillEntry{
|
||||
{ID: 1, Name: "go", Content: "ignored additive duplicate", Tags: []string{}},
|
||||
{ID: 3, Name: "Postgres", Content: "persona override", Tags: []string{}, Override: true},
|
||||
{ID: 4, Name: "writing", Content: "additive", Tags: []string{}},
|
||||
}}
|
||||
|
||||
got := mergeWorldModelSkills(project, persona)
|
||||
if len(got) != 3 {
|
||||
t.Fatalf("len(skills) = %d, want 3", len(got))
|
||||
}
|
||||
if got[0].Content != "project" || got[0].Source != "project" {
|
||||
t.Fatalf("additive duplicate replaced project skill: %#v", got[0])
|
||||
}
|
||||
if got[1].Content != "persona override" || got[1].Source != "persona" || !got[1].Override {
|
||||
t.Fatalf("override did not replace project skill: %#v", got[1])
|
||||
}
|
||||
if got[2].Name != "writing" || got[2].Source != "persona" {
|
||||
t.Fatalf("new additive persona skill missing: %#v", got[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeWorldModelGuardrailsKeepsStrictest(t *testing.T) {
|
||||
project := []ext.AgentGuardrail{{ID: 1, Name: "safe", Severity: "high", Content: "project"}}
|
||||
persona := &ext.PersonaFull{Guardrails: []ext.PersonaGuardrailEntry{
|
||||
{ID: 2, Name: "Safe", Severity: "low", Content: "weaker"},
|
||||
{ID: 3, Name: "privacy", Severity: "critical", Content: "new"},
|
||||
}}
|
||||
|
||||
got := mergeWorldModelGuardrails(project, persona)
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("len(guardrails) = %d, want 2", len(got))
|
||||
}
|
||||
if got[0].Severity != "high" || got[0].Content != "project" {
|
||||
t.Fatalf("persona weakened project guardrail: %#v", got[0])
|
||||
}
|
||||
if got[1].Name != "privacy" || got[1].Severity != "critical" {
|
||||
t.Fatalf("new persona guardrail missing: %#v", got[1])
|
||||
}
|
||||
}
|
||||
@@ -81,17 +81,17 @@ type ArcStage struct {
|
||||
// 2. active arc-stage parts
|
||||
// 3. persona-linked parts (base)
|
||||
type PersonaFull struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Body string `json:"body"` // summary or detail, depending on mode
|
||||
CompiledSummary string `json:"compiled_summary,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
Parts []AssembledPart `json:"parts"`
|
||||
Skills []PersonaSkillEntry `json:"skills"`
|
||||
Guardrails []PersonaGuardrailEntry `json:"guardrails"`
|
||||
Traits []PersonaTraitEntry `json:"traits"`
|
||||
Arc *PersonaArcState `json:"arc,omitempty"`
|
||||
Detail bool `json:"detail"` // whether full detail was requested
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Body string `json:"body"` // summary or detail, depending on mode
|
||||
CompiledSummary string `json:"compiled_summary,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
Parts []AssembledPart `json:"parts"`
|
||||
Skills []PersonaSkillEntry `json:"skills"`
|
||||
Guardrails []PersonaGuardrailEntry `json:"guardrails"`
|
||||
Traits []PersonaTraitEntry `json:"traits"`
|
||||
Arc *PersonaArcState `json:"arc,omitempty"`
|
||||
Detail bool `json:"detail"` // whether full detail was requested
|
||||
}
|
||||
|
||||
type AssembledPart struct {
|
||||
@@ -109,13 +109,14 @@ type PersonaSkillEntry struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Content string `json:"content,omitempty"` // only when detail=true
|
||||
Tags []string `json:"tags"`
|
||||
Override bool `json:"override,omitempty"`
|
||||
}
|
||||
|
||||
type PersonaGuardrailEntry struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Content string `json:"content,omitempty"` // only when detail=true
|
||||
Content string `json:"content,omitempty"` // only when detail=true
|
||||
Severity string `json:"severity,omitempty"` // only when detail=true
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
@@ -168,6 +169,12 @@ type ManifestSkill struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Override bool `json:"override,omitempty"`
|
||||
}
|
||||
|
||||
type ProjectPersona struct {
|
||||
Persona Persona `json:"persona"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
}
|
||||
|
||||
type ManifestGuardrail struct {
|
||||
|
||||
@@ -229,6 +229,7 @@ type AgentSkill struct {
|
||||
DomainTags []string `json:"domain_tags"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Override bool `json:"override,omitempty"`
|
||||
}
|
||||
|
||||
type AgentGuardrail struct {
|
||||
|
||||
Reference in New Issue
Block a user