Files
Agent-System/internal/squad/workspace.go
T
weichen 721af4ccb8 fix(squad): 区分未注入对话的原因并附上采集统计
占位说明不再把开关关闭、项目未绑定和本轮没有对话包混成一句,避免把未采集误写成对话已被滤掉。
2026-09-28 15:36:38 +00:00

274 lines
7.7 KiB
Go

package squad
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"chat-agent-system/internal/template"
)
// WorkspacePrepareRequest 工作区准备请求
type WorkspacePrepareRequest struct {
WorkDir string
SessionID FlexSessionID
ProjectID int64
TaskKey string
StageTitle string
ProjectName string
CustomerName string
DeviceType string
IntendedUse string
TargetMarket string
Backend string
TemplateContent string
Materials []MaterialItem
KnowledgeHits []KnowledgeHit
PriorDocs []PriorDocItem
UserContext string
ProjectContext string
AgentContext string
MemoryBankFiles map[string]string
// DialoguePackage is optional layered project dialogue (digest/turns/facts).
DialoguePackage *DialoguePackage
CustomAgents []AgentSpec
WorkflowSteps []StepSpec
InitialFields map[string]string
Metadata map[string]string
}
// DialoguePackage is the squad-facing dialogue injection payload.
type DialoguePackage struct {
DigestMD string
TurnsMD string
FactsJSON []byte
ConfirmationsMD string
TurnCount int
ExcludedPrivate int
ExcludedNoAccess int
ExcludedNoise int
}
// Workspace 表示已初始化的工作区句柄
type Workspace struct {
WorkDir string
Meta WorkspaceMetadata
Squad SquadConfig
Workflow WorkflowPlan
}
// WorkspaceEngine 工作区生命周期与沙箱构建引擎
type WorkspaceEngine struct{}
func NewWorkspaceEngine() *WorkspaceEngine {
return &WorkspaceEngine{}
}
// PrepareWorkspace 初始化自包含工作区目录
func (e *WorkspaceEngine) PrepareWorkspace(req WorkspacePrepareRequest) (*Workspace, error) {
workDir := req.WorkDir
if workDir == "" {
ts := time.Now().UnixNano()
workDir = filepath.Join("data", "dev", "workspaces", "bizdev", fmt.Sprintf("task_%s_%d", req.TaskKey, ts))
}
if err := os.MkdirAll(workDir, 0o755); err != nil {
return nil, fmt.Errorf("create workspace root dir: %w", err)
}
now := time.Now()
wsID := fmt.Sprintf("ws_%s_%d", req.TaskKey, now.UnixNano())
if req.Metadata != nil && req.Metadata["id"] != "" {
wsID = req.Metadata["id"]
}
// 1. 初始化 workspace.json
meta := WorkspaceMetadata{
ID: wsID,
SessionID: req.SessionID,
ProjectID: req.ProjectID,
TaskKey: req.TaskKey,
StageTitle: req.StageTitle,
ProjectName: req.ProjectName,
CustomerName: req.CustomerName,
DeviceType: req.DeviceType,
IntendedUse: req.IntendedUse,
TargetMarket: req.TargetMarket,
Status: "created",
Backend: req.Backend,
WorkDir: workDir,
CreatedAt: now,
UpdatedAt: now,
Metadata: req.Metadata,
}
if err := writeJSON(filepath.Join(workDir, "workspace.json"), meta); err != nil {
return nil, fmt.Errorf("write workspace.json: %w", err)
}
// 2. 初始化自适应小队与工作流规划
var squadCfg SquadConfig
var wf WorkflowPlan
tmplContent := req.TemplateContent
if len(req.WorkflowSteps) > 0 {
agents := req.CustomAgents
if len(agents) == 0 {
agents = DefaultSquadAgents(req.TaskKey)
}
squadCfg = SquadConfig{
SquadName: fmt.Sprintf("%s_squad", req.TaskKey),
Version: "1.0.0",
Agents: agents,
}
steps := req.WorkflowSteps
for idx := range steps {
if steps[idx].Status == "" {
steps[idx].Status = "pending"
}
}
wf = WorkflowPlan{
WorkflowID: fmt.Sprintf("wf_%s_%d", req.TaskKey, now.UnixNano()),
TaskKey: req.TaskKey,
Title: req.StageTitle,
Status: "pending",
CurrentStepIndex: 0,
Steps: steps,
}
} else {
planner := NewAdaptivePlanner(nil)
pRes, pErr := planner.Plan(PlannerRequest{
TaskKey: req.TaskKey,
Title: req.StageTitle,
ProjectName: req.ProjectName,
CustomerName: req.CustomerName,
DeviceType: req.DeviceType,
IntendedUse: req.IntendedUse,
TargetMarket: req.TargetMarket,
Materials: req.Materials,
KnowledgeHits: req.KnowledgeHits,
Fields: req.InitialFields,
CustomSteps: req.WorkflowSteps,
})
if pErr == nil && pRes != nil {
squadCfg = pRes.Squad
wf = pRes.Workflow
if strings.TrimSpace(tmplContent) == "" {
tmplContent = pRes.TemplateContent
}
} else {
agents := req.CustomAgents
if len(agents) == 0 {
agents = DefaultSquadAgents(req.TaskKey)
}
squadCfg = SquadConfig{
SquadName: fmt.Sprintf("%s_squad", req.TaskKey),
Version: "1.0.0",
Agents: agents,
}
steps := req.WorkflowSteps
if len(steps) == 0 {
steps = DefaultWorkflowSteps(req.TaskKey)
}
wf = WorkflowPlan{
WorkflowID: fmt.Sprintf("wf_%s_%d", req.TaskKey, now.UnixNano()),
Status: "pending",
CurrentStepIndex: 0,
Steps: steps,
}
}
}
if err := writeJSON(filepath.Join(workDir, "squad.json"), squadCfg); err != nil {
return nil, fmt.Errorf("write squad.json: %w", err)
}
if err := writeJSON(filepath.Join(workDir, "workflow.json"), wf); err != nil {
return nil, fmt.Errorf("write workflow.json: %w", err)
}
// 4. 写入自描述模板 template.md
if strings.TrimSpace(tmplContent) == "" {
var err error
tmplContent, err = template.GetDocTemplate(req.TaskKey)
if err != nil {
tmplContent = fmt.Sprintf("# %s\n\n## 1. 结论\n\n待确认\n\n## 2. 正文\n\n待确认\n", req.StageTitle)
}
}
vars := map[string]string{
"project_name": req.ProjectName,
"product_name": req.ProjectName,
"customer_name": req.CustomerName,
"device_type": req.DeviceType,
"intended_use": req.IntendedUse,
"target_market": req.TargetMarket,
"generated_at": now.Format("2006-01-02 15:04:05"),
"snapshot_id": wsID,
}
for k, v := range req.InitialFields {
vars[k] = v
}
renderedTmpl := template.RenderDoc(tmplContent, vars)
if err := os.WriteFile(filepath.Join(workDir, "template.md"), []byte(renderedTmpl), 0o644); err != nil {
return nil, fmt.Errorf("write template.md: %w", err)
}
// 5. 初始化 blackboard.json
bb := BlackboardState{
Facts: make([]FactItem, 0),
Gaps: make([]GapItem, 0),
Implications: make([]ImplicationItem, 0),
Assumptions: make([]AssumptionItem, 0),
Fields: make(map[string]string),
UpdatedAt: now,
}
if req.CustomerName != "" {
bb.Fields["customer_name"] = req.CustomerName
bb.Facts = append(bb.Facts, FactItem{
ID: "fact_01",
Claim: "客户名称为 " + req.CustomerName,
Source: "用户输入",
Verified: true,
})
}
if req.ProjectName != "" {
bb.Fields["project_name"] = req.ProjectName
}
if req.DeviceType != "" {
bb.Fields["device_type"] = req.DeviceType
}
if req.IntendedUse != "" {
bb.Fields["intended_use"] = req.IntendedUse
}
if req.TargetMarket != "" {
bb.Fields["target_market"] = req.TargetMarket
}
for k, v := range req.InitialFields {
bb.Fields[k] = v
}
if err := writeJSON(filepath.Join(workDir, "blackboard.json"), bb); err != nil {
return nil, fmt.Errorf("write blackboard.json: %w", err)
}
// 6. 构造自描述上下文体系 context/
if err := e.buildContextSystem(workDir, req); err != nil {
return nil, fmt.Errorf("build context system: %w", err)
}
// 7. 创建子工作区 tasks/<sub_dir> 并注入 TASK.md 与 AGENTS.md
if err := e.buildSubWorkspaces(workDir, wf, squadCfg, renderedTmpl); err != nil {
return nil, fmt.Errorf("build sub workspaces: %w", err)
}
// 8. 创建 artifacts/ 与基础日志
_ = os.MkdirAll(filepath.Join(workDir, "artifacts"), 0o755)
logEntry := fmt.Sprintf("[%s] 工作区创建完成: ID=%s, Task=%s\n", now.Format(time.RFC3339), wsID, req.TaskKey)
_ = os.WriteFile(filepath.Join(workDir, "execution.log"), []byte(logEntry), 0o644)
return &Workspace{
WorkDir: workDir,
Meta: meta,
Squad: squadCfg,
Workflow: wf,
}, nil
}