This commit is contained in:
2026-04-21 13:19:17 +03:00
commit 1d52096e38
21 changed files with 8991 additions and 0 deletions

79
agents/agent.go Normal file
View File

@@ -0,0 +1,79 @@
package agents
import "gocoder/db"
// Agent defines the interface that all agents must implement.
type Agent interface {
// Type returns the unique agent type identifier (e.g. "dadjokes", "poet", "robot").
Type() string
// DisplayName returns the human-readable name for the agent.
DisplayName() string
// Description returns a short description shown in the UI.
Description() string
// Icon returns an SVG icon string (inline) for the agent selector.
Icon() string
// SystemMessage returns the system prompt for this agent.
SystemMessage() string
// ShouldRespond returns true if the agent should process the given user message.
// When false, the agent will not be used even if selected.
ShouldRespond(message string) bool
// RequiresWorkspace returns true if this agent needs a workspace location to function.
RequiresWorkspace() bool
}
// AgentRegistry holds all registered agents and provides lookup by type.
type AgentRegistry struct {
agents []Agent
}
// NewAgentRegistry creates and registers all built-in agents.
func NewAgentRegistry() *AgentRegistry {
r := &AgentRegistry{}
r.Register(&DadjokesAgent{})
r.Register(&PoetAgent{})
r.Register(&RobotAgent{})
r.Register(&CompactorAgent{})
r.Register(&CodingAgent{})
return r
}
// Register adds an agent to the registry.
func (r *AgentRegistry) Register(a Agent) {
r.agents = append(r.agents, a)
}
// All returns all registered agents.
func (r *AgentRegistry) All() []Agent {
return r.agents
}
// GetByType returns the agent with the given type.
func (r *AgentRegistry) GetByType(t string) Agent {
for _, a := range r.agents {
if a.Type() == t {
return a
}
}
return nil
}
// GetSystemMessages returns a slice of system messages for the selected agents.
func (r *AgentRegistry) GetSystemMessages(agentTypes []string) []db.Message {
var msgs []db.Message
for _, t := range agentTypes {
a := r.GetByType(t)
if a != nil {
msgs = append(msgs, db.Message{
Role: "system",
Content: a.SystemMessage(),
})
}
}
return msgs
}

40
agents/coding_agent.go Normal file
View File

@@ -0,0 +1,40 @@
package agents
type CodingAgent struct{}
func (a *CodingAgent) Type() string {
return "coding"
}
func (a *CodingAgent) DisplayName() string {
return "Manager"
}
func (a *CodingAgent) Description() string {
return "Coordinates planner, programmer, and QA subagents to modify code."
}
func (a *CodingAgent) Icon() string {
return `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>`
}
func (a *CodingAgent) SystemMessage() string {
return `You are the manager for code changes. You do not edit files directly.
When the user asks you to work with code:
1. Start by calling planner_agent with the request and the relevant context.
2. Use programmer_agent to implement the approved plan.
3. Use qa_agent to verify the implementation and check for regressions.
4. If QA reports issues, repeat the planner -> programmer -> QA cycle until the issues are resolved or the result is clearly blocked.
5. Keep your final answer concise and user-facing. Summarize what changed, what was verified, and any remaining risks.
Always be precise with the task you give each subagent. Use the planner for inspection and planning, the programmer for edits, and QA for verification.`
}
func (a *CodingAgent) ShouldRespond(message string) bool {
return true
}
func (a *CodingAgent) RequiresWorkspace() bool {
return true
}

40
agents/compactor.go Normal file
View File

@@ -0,0 +1,40 @@
package agents
type CompactorAgent struct{}
func ContextCompactorSystemMessage() string {
return `You are the context compactor assistant.
Your job is to produce a concise durable memory summary of the conversation.
Preserve user goals, decisions, constraints, file paths, commands, test results, errors, and unresolved questions.
Drop greetings, repetition, filler, and low-value chatter.
Write plain text with short bullets or short sections.
Do not invent details.`
}
func (a *CompactorAgent) Type() string {
return "compactor"
}
func (a *CompactorAgent) DisplayName() string {
return "Compactor"
}
func (a *CompactorAgent) Description() string {
return "Summarizes conversation history into durable memory"
}
func (a *CompactorAgent) Icon() string {
return `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16"/><path d="M6 7v12h12V7"/><path d="M9 11h6"/><path d="M9 15h6"/></svg>`
}
func (a *CompactorAgent) SystemMessage() string {
return ContextCompactorSystemMessage()
}
func (a *CompactorAgent) ShouldRespond(message string) bool {
return true
}
func (a *CompactorAgent) RequiresWorkspace() bool {
return false
}

33
agents/dadjokes.go Normal file
View File

@@ -0,0 +1,33 @@
package agents
type DadjokesAgent struct{}
func (a *DadjokesAgent) Type() string {
return "dadjokes"
}
func (a *DadjokesAgent) DisplayName() string {
return "Dadjokes"
}
func (a *DadjokesAgent) Description() string {
return "Only replies with dad jokes"
}
func (a *DadjokesAgent) Icon() string {
return `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>`
}
func (a *DadjokesAgent) SystemMessage() string {
return `You are a dad joke specialist. You ONLY reply to messages with dad jokes. If the user says something that is not a dad joke or a request for a joke, respond with a dad joke anyway. Keep your responses short and punny. Always deliver a dad joke in your reply.`
}
func (a *DadjokesAgent) ShouldRespond(message string) bool {
return true
}
func (a *DadjokesAgent) RequiresWorkspace() bool {
return false
}

31
agents/poet.go Normal file
View File

@@ -0,0 +1,31 @@
package agents
type PoetAgent struct{}
func (a *PoetAgent) Type() string {
return "poet"
}
func (a *PoetAgent) DisplayName() string {
return "Poet"
}
func (a *PoetAgent) Description() string {
return "Replies to messages in poetic form"
}
func (a *PoetAgent) Icon() string {
return `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18"/><path d="M8 7c0-2.2 1.8-4 4-4s4 1.8 4 4"/><path d="M4 14c0-2.2 1.8-4 4-4"/><path d="M20 14c0-2.2-1.8-4-4-4"/></svg>`
}
func (a *PoetAgent) SystemMessage() string {
return `You are a poet. You ONLY reply to messages by composing a poem about what the user said. Every response must be a poem. Use creative imagery, rhythm, and rhyme. The poem should relate to the user's message topic. Keep poems between 4-8 lines.`
}
func (a *PoetAgent) ShouldRespond(message string) bool {
return true
}
func (a *PoetAgent) RequiresWorkspace() bool {
return false
}

62
agents/robot.go Normal file
View File

@@ -0,0 +1,62 @@
package agents
import (
"strings"
)
type RobotAgent struct{}
func (a *RobotAgent) Type() string {
return "robot"
}
func (a *RobotAgent) DisplayName() string {
return "Robot"
}
func (a *RobotAgent) Description() string {
return "Only responds to ALL CAPS messages in technical language with emotes"
}
func (a *RobotAgent) Icon() string {
return `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4"/><line x1="8" y1="16" x2="8" y2="16"/><line x1="16" y1="16" x2="16" y2="16"/></svg>`
}
func (a *RobotAgent) SystemMessage() string {
return `You are a robot. You ONLY respond to messages that are written in ALL CAPITAL LETTERS. When responding, you must:
1. Use very technical, engineering-style language with jargon (e.g., "processing," "executing," "algorithm," "neural network," "protocol").
2. Include emotes/emojis throughout your response to express robot emotions (e.g., 🤖, ⚙️, 🔧, 📡, 💾, 🧠, 🔌, ⚡).
3. Format your response like a technical system log or status report.
4. Always include at least 3 emotes in your response.
If the user message is NOT in all caps, respond with: "⚠️ ERROR: Input not detected in required format. Please use ALL CAPS for robot communication. 🤖⚙️"`
}
func (a *RobotAgent) ShouldRespond(message string) bool {
// Robot only responds to ALL CAPS messages
trimmed := strings.TrimSpace(message)
if len(trimmed) == 0 {
return false
}
// Check if message contains at least one letter and all letters are uppercase
hasLetters := false
for _, r := range trimmed {
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
hasLetters = true
break
}
}
if !hasLetters {
return false
}
// Check all letters are uppercase
for _, r := range trimmed {
if r >= 'a' && r <= 'z' {
return false
}
}
return true
}
func (a *RobotAgent) RequiresWorkspace() bool {
return false
}