package room import ( "fmt" "regexp" "strconv" "strings" "sync" "github.com/sdaduanbilei/agent-team/internal/agent" "github.com/sdaduanbilei/agent-team/internal/llm" "github.com/sdaduanbilei/agent-team/internal/prompt" "github.com/sdaduanbilei/agent-team/internal/room/tools" "github.com/sdaduanbilei/agent-team/internal/skill" "github.com/sdaduanbilei/agent-team/internal/store" "github.com/sdaduanbilei/agent-team/internal/user" ) type RoomType string const ( TypeProject RoomType = "project" ) type Status string const ( StatusPending Status = "pending" StatusThinking Status = "thinking" StatusWorking Status = "working" ) type Config struct { Name string `yaml:"name"` Type RoomType `yaml:"type"` Master string `yaml:"master"` // agent name Members []string `yaml:"members"` // agent names Color string `yaml:"color"` // avatar color Team string `yaml:"team"` // installed team name } type Room struct { Config Config Dir string master *agent.Agent members map[string]*agent.Agent skillMeta []skill.Meta User *user.User Status Status ActiveAgent string // for working status display Broadcast func(Event) // set by api layer // master 的会话历史,保持多轮对话上下文 masterHistory []llm.Message historyMu sync.Mutex Mode string // "plan" | "build" pendingAssignments map[string]string // plan 模式下暂存的待执行任务 pendingPlanReply string // master 规划原文,用于生成计划文档 // Build 模式下成员对话跟踪 memberConvos map[string][]llm.Message // 成员名 -> 多轮对话历史 memberArtifacts map[string]string // 成员名 -> 已产出的文档文件名(用于后续覆盖更新) lastActiveMember string // 最后一个发出提问的成员 planFilename string // 当前任务计划文件名 systemRules string // SYSTEM.md 全局规则 projectTemplate *ProjectTemplate // 项目模板(可为 nil) teamWorkflow string // TEAM.md 中的流程描述(非 project-template 部分) Prompt *prompt.Engine // 提示词模板引擎 Store *store.Store currentGroupID int64 // 当前用户消息的 group_id ToolExecutor *tools.Executor cancelFunc func() cancelMu sync.Mutex } type EventType string const ( EvtAgentMessage EventType = "agent_message" EvtTaskAssign EventType = "task_assign" EvtReview EventType = "review" EvtRoomStatus EventType = "room_status" EvtTasksUpdate EventType = "tasks_update" EvtWorkspaceFile EventType = "workspace_file" EvtModeChange EventType = "mode_change" EvtArtifact EventType = "artifact" EvtTaskDone EventType = "task_done" EvtScheduleRun EventType = "schedule_run" EvtTokenUsage EventType = "token_usage" EvtFileRead EventType = "file_read" EvtFileWorking EventType = "file_working" // file-llm 开始生成文件 EvtFileDone EventType = "file_done" // file-llm 文件生成完成 ) type Event struct { Type EventType `json:"type"` RoomID string `json:"room_id"` Agent string `json:"agent,omitempty"` Role string `json:"role,omitempty"` // master | member | challenge Content string `json:"content,omitempty"` Streaming bool `json:"streaming,omitempty"` From string `json:"from,omitempty"` To string `json:"to,omitempty"` Task string `json:"task,omitempty"` Feedback string `json:"feedback,omitempty"` Status Status `json:"status,omitempty"` ActiveAgent string `json:"active_agent,omitempty"` Action string `json:"action,omitempty"` Filename string `json:"filename,omitempty"` Mode string `json:"mode,omitempty"` Title string `json:"title,omitempty"` PromptTokens int `json:"prompt_tokens,omitempty"` CompletionTokens int `json:"completion_tokens,omitempty"` TotalTokens int `json:"total_tokens,omitempty"` NoStore bool `json:"-"` // 跳过 emit 中的自动 DB 存储(调用方已显式存储) } // ProjectFile 项目模板中的单个文件 type ProjectFile struct { Path string // 如 "创作需求书.md" Owner string // 负责的 agent 名 Phase int // 阶段编号 IsDir bool // 是否为目录 Dynamic bool // ... 标记,可动态扩展 } // ProjectTemplate 从 TEAM.md 解析的项目模板 type ProjectTemplate struct { Files []ProjectFile } // extractTeamWorkflow 提取 TEAM.md 中除 project-template 代码块外的流程描述 func extractTeamWorkflow(body string) string { re := regexp.MustCompile("(?s)```project-template\\s*\\n.+?```") cleaned := re.ReplaceAllString(body, "") cleaned = strings.TrimSpace(cleaned) if cleaned == "" { return "" } return cleaned } // parseProjectTemplate 从 TEAM.md body 中提取 project-template 代码块并解析 func parseProjectTemplate(body string) *ProjectTemplate { re := regexp.MustCompile("(?s)```project-template\\s*\\n(.+?)```") match := re.FindStringSubmatch(body) if match == nil { return nil } block := match[1] lineRe := regexp.MustCompile(`[├└─│\s]+(.+?)\.md\s+@(\S+)\s*(?:phase:(\d+))?`) dirRe := regexp.MustCompile(`[├└─│\s]+(.+?)/\s*$`) dynamicRe := regexp.MustCompile(`[├└─│\s]+\.\.\.\s+@(\S+)\s*(?:phase:(\d+))?`) var files []ProjectFile for _, line := range strings.Split(block, "\n") { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "workspace/") { continue } if m := dynamicRe.FindStringSubmatch(line); m != nil { phase := 0 if m[2] != "" { phase, _ = strconv.Atoi(m[2]) } files = append(files, ProjectFile{ Path: "...", Owner: m[1], Phase: phase, Dynamic: true, }) continue } if m := dirRe.FindStringSubmatch(line); m != nil { files = append(files, ProjectFile{ Path: m[1] + "/", IsDir: true, }) continue } if m := lineRe.FindStringSubmatch(line); m != nil { phase := 0 if m[3] != "" { phase, _ = strconv.Atoi(m[3]) } files = append(files, ProjectFile{ Path: strings.TrimSpace(m[1]) + ".md", Owner: m[2], Phase: phase, }) } } if len(files) == 0 { return nil } return &ProjectTemplate{Files: files} } // LoadOption 用于 room.Load 的可选参数 type LoadOption func(*loadOptions) type loadOptions struct { store *store.Store } // WithStore 在加载时传入 store,用于从 DB 读取 agent 配置 func WithStore(s *store.Store) LoadOption { return func(o *loadOptions) { o.store = s } } type BoardEntry struct { Author string Content string Type string // "draft" | "challenge" } type SharedBoard struct { mu sync.RWMutex entries []BoardEntry } func (b *SharedBoard) Add(author, content, typ string) { b.mu.Lock() defer b.mu.Unlock() b.entries = append(b.entries, BoardEntry{Author: author, Content: content, Type: typ}) } func (b *SharedBoard) ToContext() string { b.mu.RLock() defer b.mu.RUnlock() if len(b.entries) == 0 { return "" } var sb strings.Builder sb.WriteString("\n") for _, e := range b.entries { fmt.Fprintf(&sb, " \n%s\n \n", e.Type, e.Author, e.Content) } sb.WriteString("") return sb.String() }