KTD1 fifth step (behavior preserved):
- squad.AssetProvider{DocTemplate,RenderDoc,AgentInstruction,
AgentInstructionFile} injected via Options.Assets into Service,
WorkspaceEngine and AdaptivePlanner
- bizdev templateAssetProvider maps port to internal/template (assembly side)
- nil-provider paths degrade to the same fallbacks the old error branches
used (placeholder template, role header) — fail-visible, no guessing
- squad -> template import eliminated; arch edge moved bizdev->template
- verified: build/vet/arch green, squad+bizdev short tests pass
283 lines
7.9 KiB
Go
283 lines
7.9 KiB
Go
package squad
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
)
|
|
|
|
// 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 {
|
|
assets AssetProvider
|
|
}
|
|
|
|
func NewWorkspaceEngine(assets AssetProvider) *WorkspaceEngine {
|
|
return &WorkspaceEngine{assets: assets}
|
|
}
|
|
|
|
// 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, e.assets)
|
|
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) == "" && e.assets != nil {
|
|
var err error
|
|
tmplContent, err = e.assets.DocTemplate(req.TaskKey)
|
|
if err != nil {
|
|
tmplContent = ""
|
|
}
|
|
}
|
|
if strings.TrimSpace(tmplContent) == "" {
|
|
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
|
|
}
|
|
var renderedTmpl string
|
|
if e.assets != nil {
|
|
renderedTmpl = e.assets.RenderDoc(tmplContent, vars)
|
|
} else {
|
|
renderedTmpl = tmplContent
|
|
}
|
|
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
|
|
}
|