U4 第五步:AssetProvider 端口注入,移除 squad→template 依赖 #8

Merged
weichen merged 1 commits from feature/squad-asset-provider-2026-09-29 into dev 2026-09-29 06:35:17 +00:00
22 changed files with 123 additions and 59 deletions
Showing only changes of commit 2c75dcaf69 - Show all commits
+1 -1
View File
@@ -50,6 +50,7 @@ imports:
- squad
- storage
- stream
- template
- textextract
- workflowdef
- workspace
@@ -288,7 +289,6 @@ imports:
- agent
- config
- stream
- template
- textextract
- workflowdef
storage:
+1
View File
@@ -211,6 +211,7 @@ func NewService(cfg *config.Config) (*Service, error) {
return agent.NewBackend(name, cfg)
},
TaskPreparer: svc.squadTaskPreparer,
Assets: templateAssetProvider{},
})
svc.parts = Parts{
Pool: pool,
+29
View File
@@ -0,0 +1,29 @@
package bizdev
import (
"chat-agent-system/internal/squad"
"chat-agent-system/internal/template"
)
// templateAssetProvider 把内容资产端口映射到产品模板包(KTD1 装配侧):
// 模板与指令的内容、版本与磁盘/内嵌来源由产品侧全权决定。
type templateAssetProvider struct{}
func (templateAssetProvider) DocTemplate(taskKey string) (string, error) {
return template.GetDocTemplate(taskKey)
}
func (templateAssetProvider) RenderDoc(tmpl string, vars map[string]string) string {
return template.RenderDoc(tmpl, vars)
}
func (templateAssetProvider) AgentInstruction(role string) (string, error) {
return template.GetAgentInstruction(role)
}
func (templateAssetProvider) AgentInstructionFile(file string) (string, error) {
return template.GetAgentInstructionFile(file)
}
// compile-time port conformance
var _ squad.AssetProvider = templateAssetProvider{}
+1 -1
View File
@@ -12,7 +12,7 @@ import (
func TestContextManager_InjectAndFormat(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: tmpDir,
TaskKey: "research",
+2 -2
View File
@@ -11,7 +11,7 @@ import (
func TestPrepareWorkspaceWritesDialogueContext(t *testing.T) {
dir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: dir,
TaskKey: "system_reqs",
@@ -104,7 +104,7 @@ func prepareDialogueWorkspace(t *testing.T, req squad.WorkspacePrepareRequest) s
ID: "s1", Title: "整理", Role: "organizer", Mode: "write",
SubWorkspace: "tasks/01_organizer", Status: "pending",
}}
if _, err := squad.NewWorkspaceEngine().PrepareWorkspace(req); err != nil {
if _, err := squad.NewWorkspaceEngine(nil).PrepareWorkspace(req); err != nil {
t.Fatal(err)
}
return req.WorkDir
+1 -1
View File
@@ -124,7 +124,7 @@ func TestWorkflowDriver_DynamicAdjustmentIntegration(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
engine := NewWorkspaceEngine()
engine := NewWorkspaceEngine(nil)
driver := NewWorkflowDriver(engine)
evaluator := NewStepEvaluator()
driver.SetEvaluator(evaluator)
+3 -3
View File
@@ -207,7 +207,7 @@ func TestDetectRepetition_NoFalsePositive(t *testing.T) {
func prepareGovWorkspace(t *testing.T, steps []squad.StepSpec) string {
t.Helper()
dir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
_, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: dir,
TaskKey: "intake",
@@ -272,7 +272,7 @@ func TestWorkflowDriver_GovernanceOnExecute(t *testing.T) {
{ID: "step_02", Role: "writer", Title: "成文", SubWorkspace: "tasks/02_draft"},
}
dir := prepareGovWorkspace(t, steps)
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine(nil))
res, err := driver.Execute(context.Background(), dir, &agent.StubBackend{}, squad.ExecuteOptions{Timeout: 10 * time.Second})
if err != nil || !res.Success {
@@ -324,7 +324,7 @@ func TestWorkflowDriver_BudgetHardStopKeepsPartialArchive(t *testing.T) {
{ID: "step_03", Role: "leader", Title: "终审", SubWorkspace: "tasks/03_review"},
}
dir := prepareGovWorkspace(t, steps)
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine(nil))
// 预算收紧为 2 步 → 第 3 步触发硬停
tight := squad.BudgetPolicy{MaxSteps: 2, MaxRepairRounds: 1, MaxAgentCalls: 10}
+1 -1
View File
@@ -32,7 +32,7 @@ func (b *failFirstBackend) Execute(ctx context.Context, req agent.Request) (*age
func prepareDegradeWS(t *testing.T) (string, *squad.WorkflowDriver) {
t.Helper()
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: tmpDir,
TaskKey: "degrade_test",
+12 -5
View File
@@ -5,7 +5,6 @@ import (
"strings"
"time"
"chat-agent-system/internal/template"
)
// PlannerRequest 任务规划请求
@@ -36,14 +35,15 @@ type PlannerResult struct {
// AdaptivePlanner 自适应任务规划器
type AdaptivePlanner struct {
registry *RoleRegistry
assets AssetProvider
}
// NewAdaptivePlanner 创建自适应任务规划器
func NewAdaptivePlanner(reg *RoleRegistry) *AdaptivePlanner {
func NewAdaptivePlanner(reg *RoleRegistry, assets AssetProvider) *AdaptivePlanner {
if reg == nil {
reg = NewRoleRegistry()
}
return &AdaptivePlanner{registry: reg}
return &AdaptivePlanner{registry: reg, assets: assets}
}
// Plan 执行动态规划:区分固定工作流自适应微调 vs 非标文档自主规划
@@ -157,8 +157,15 @@ func (p *AdaptivePlanner) planFixedAdaptive(req PlannerRequest) (*PlannerResult,
}
}
tmplContent, err := template.GetDocTemplate(req.TaskKey)
if err != nil {
var tmplContent string
if p.assets != nil {
var err error
tmplContent, err = p.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", title)
}
+3 -3
View File
@@ -7,7 +7,7 @@ import (
func TestPlanFixedAdaptive_Default(t *testing.T) {
reg := NewRoleRegistry()
planner := NewAdaptivePlanner(reg)
planner := NewAdaptivePlanner(reg, nil)
req := PlannerRequest{
TaskKey: "intake",
@@ -36,7 +36,7 @@ func TestPlanFixedAdaptive_Default(t *testing.T) {
func TestPlanFixedAdaptive_MaterialSnifferInsertStep(t *testing.T) {
reg := NewRoleRegistry()
planner := NewAdaptivePlanner(reg)
planner := NewAdaptivePlanner(reg, nil)
req := PlannerRequest{
TaskKey: "research",
@@ -87,7 +87,7 @@ func TestPlanFixedAdaptive_MaterialSnifferInsertStep(t *testing.T) {
func TestPlanAutonomousAdhoc_CleanroomDesign(t *testing.T) {
reg := NewRoleRegistry()
planner := NewAdaptivePlanner(reg)
planner := NewAdaptivePlanner(reg, nil)
req := PlannerRequest{
TaskKey: "cleanroom_spec",
+8 -8
View File
@@ -15,7 +15,7 @@ import (
func TestQualityGate_Validate(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: tmpDir,
TaskKey: "intake",
@@ -88,7 +88,7 @@ func TestQualityGate_Validate(t *testing.T) {
func TestQualityGate_AutoRepair(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: tmpDir,
TaskKey: "intake",
@@ -135,7 +135,7 @@ func TestQualityGate_AutoRepair(t *testing.T) {
func TestQualityGate_SystemReqsAndTestManager(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
gate := squad.NewQualityGate()
// 1. 测试 system_reqs
@@ -206,7 +206,7 @@ func TestQualityGate_SystemReqsAndTestManager(t *testing.T) {
// 现场证据 output.md:204 — 终稿自检表否定性引用禁词,不得误报为敷衍违规。
func TestQualityGate_NegatedSelfCheckCitation_NoFalsePositive(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: tmpDir,
TaskKey: "intake",
@@ -291,7 +291,7 @@ func TestQualityGate_NegatedSelfCheckCitation_NoFalsePositive(t *testing.T) {
func intakeTemplate(t *testing.T) (workDir string, gate *squad.QualityGate) {
t.Helper()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: t.TempDir(),
TaskKey: "intake",
@@ -463,7 +463,7 @@ func TestQualityGate_ActionPlanStatus_DoesNotTriggerRepair(t *testing.T) {
if err := os.WriteFile(filepath.Join(workDir, "output.md"), []byte(content), 0o644); err != nil {
t.Fatal(err)
}
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine(nil))
report, err := gate.EnsureQualityOrRepair(context.Background(), workDir, "intake", driver, &agent.StubBackend{}, 1, squad.ExecuteOptions{
Timeout: 5 * time.Second,
})
@@ -506,7 +506,7 @@ func TestCleanHeadingTitle_MultiDigitChineseOrdinal(t *testing.T) {
// 现场 nightly TestManager:LLM 写「## 十一、签署 / ## 十二、说明」,旧清洗只剥到「十、」会误报缺章。
func TestQualityGate_Validate_ChineseOrdinalElevenTwelve(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: filepath.Join(tmpDir, "ws_tm"),
TaskKey: "test_manager",
@@ -552,7 +552,7 @@ func TestNormalizeHeadingTitle_FoldsSpaceWidthAndCase(t *testing.T) {
func TestQualityGate_Validate_HeadingLevelAndSpaceDrift(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: filepath.Join(tmpDir, "ws_research"),
TaskKey: "research",
@@ -28,7 +28,7 @@ const repairGoodDraft = `# 用户初始需求收集表
func prepareRepairWS(t *testing.T) string {
t.Helper()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: t.TempDir(),
TaskKey: "intake",
@@ -102,7 +102,7 @@ func (b *localThenErrorBackend) Execute(_ context.Context, req agent.Request) (*
// Local repair is promoted only when backend succeeds, parents stay untouched, and the local target is trusted.
func TestEnsureQualityOrRepair_LocalTargetPromotesAndRevalidates(t *testing.T) {
workDir := prepareRepairWS(t)
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine(nil))
gate := squad.NewQualityGate()
report, err := gate.EnsureQualityOrRepair(context.Background(), workDir, "intake", driver, &localRepairBackend{body: repairGoodDraft}, 1, squad.ExecuteOptions{
Timeout: 5 * time.Second,
@@ -136,7 +136,7 @@ func TestEnsureQualityOrRepair_LocalTargetPromotesAndRevalidates(t *testing.T) {
func TestEnsureQualityOrRepair_ParentEscapeRestoresAndFailsClosed(t *testing.T) {
workDir := prepareRepairWS(t)
orig, _ := os.ReadFile(filepath.Join(workDir, "output.md"))
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine(nil))
gate := squad.NewQualityGate()
report, err := gate.EnsureQualityOrRepair(context.Background(), workDir, "intake", driver, &parentEscapeBackend{body: repairGoodDraft}, 1, squad.ExecuteOptions{
Timeout: 5 * time.Second,
@@ -166,7 +166,7 @@ func TestEnsureQualityOrRepair_ParentEscapeRestoresAndFailsClosed(t *testing.T)
func TestEnsureQualityOrRepair_BothChangedFailsClosedNoPromote(t *testing.T) {
workDir := prepareRepairWS(t)
orig, _ := os.ReadFile(filepath.Join(workDir, "output.md"))
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine(nil))
gate := squad.NewQualityGate()
parentDecoy := repairGoodDraft + "\n\n## 越界父级不应被采用\n"
report, err := gate.EnsureQualityOrRepair(context.Background(), workDir, "intake", driver, &bothWriteBackend{
@@ -195,7 +195,7 @@ func TestEnsureQualityOrRepair_BothChangedFailsClosedNoPromote(t *testing.T) {
func TestEnsureQualityOrRepair_RunErrorDoesNotPromoteLocalTarget(t *testing.T) {
workDir := prepareRepairWS(t)
orig, _ := os.ReadFile(filepath.Join(workDir, "output.md"))
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine(nil))
gate := squad.NewQualityGate()
report, err := gate.EnsureQualityOrRepair(context.Background(), workDir, "intake", driver, &localThenErrorBackend{body: repairGoodDraft}, 1, squad.ExecuteOptions{
Timeout: 5 * time.Second,
@@ -221,7 +221,7 @@ func TestEnsureQualityOrRepair_CreatedParentInputFailsClosed(t *testing.T) {
t.Fatal(err)
}
orig, _ := os.ReadFile(filepath.Join(workDir, "output.md"))
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine(nil))
gate := squad.NewQualityGate()
report, err := gate.EnsureQualityOrRepair(context.Background(), workDir, "intake", driver, &createParentInputBackend{body: repairGoodDraft}, 1, squad.ExecuteOptions{
Timeout: 5 * time.Second,
@@ -251,7 +251,7 @@ func TestEnsureQualityOrRepair_RestoreFailureStopsWithoutPromote(t *testing.T) {
return fmt.Errorf("restore output.md: injected")
})
defer reset()
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine(nil))
gate := squad.NewQualityGate()
report, err := gate.EnsureQualityOrRepair(context.Background(), workDir, "intake", driver, &bothWriteBackend{
local: repairGoodDraft,
@@ -275,7 +275,7 @@ func TestEnsureQualityOrRepair_RestoreFailureStopsWithoutPromote(t *testing.T) {
func TestEnsureQualityOrRepair_RepairBudgetNotRegressed(t *testing.T) {
workDir := prepareRepairWS(t)
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine())
driver := squad.NewWorkflowDriver(squad.NewWorkspaceEngine(nil))
gate := squad.NewQualityGate()
_, err := gate.EnsureQualityOrRepair(context.Background(), workDir, "intake", driver, &parentEscapeBackend{body: repairGoodDraft}, 1, squad.ExecuteOptions{
Timeout: 5 * time.Second,
@@ -290,7 +290,7 @@ func TestEnsureQualityOrRepair_RepairBudgetNotRegressed(t *testing.T) {
}
func TestAddStep_SnapshotLayoutCopiesNeedsLocally(t *testing.T) {
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: t.TempDir(),
TaskKey: "intake",
+1 -1
View File
@@ -69,7 +69,7 @@ func TestRDevRegulatoryFollowsComplexityTier(t *testing.T) {
t.Fatal("tasks outside the list stay without regulatory even at high")
}
planner := NewAdaptivePlanner(nil)
planner := NewAdaptivePlanner(nil, nil)
low, err := planner.Plan(PlannerRequest{
TaskKey: "rd_sw_plan",
Fields: map[string]string{"complexity_tier": "low"},
+2 -2
View File
@@ -11,7 +11,7 @@ import (
func TestRDevLeaderPrompt_UsesDesignControlNotBusiness(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: filepath.Join(tmpDir, "rd_design_input_spec"),
@@ -42,7 +42,7 @@ func TestRDevLeaderPrompt_UsesDesignControlNotBusiness(t *testing.T) {
func TestIntakeLeaderPrompt_StillBusiness(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: filepath.Join(tmpDir, "intake"),
+1 -1
View File
@@ -33,7 +33,7 @@ func TestRDevOverlayFiltersRoleAndComplexity(t *testing.T) {
func TestRDevWorkspaceWritesOverlay(t *testing.T) {
tmpDir := t.TempDir()
engine := NewWorkspaceEngine()
engine := NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(WorkspacePrepareRequest{
WorkDir: filepath.Join(tmpDir, "rd_planning_review"),
TaskKey: "rd_planning_review",
+19 -2
View File
@@ -122,6 +122,23 @@ type Options struct {
// 资产归 Core;Core 只做计算并返回产物,工作区文件由 squad 统一写入)。
// nil 或返回 nil 表示无预处理。
TaskPreparer func(workDir string, req SquadRunRequest) (*TaskPreparation, error)
// Assets 提供文档模板与成员指令等内容资产(KTD1:与业务绑定的模板、
// 提示词及版本选择归 Core)。nil 时相关能力失败关闭,不降级猜测。
Assets AssetProvider
}
// AssetProvider 是小队执行所需内容资产的唯一来源;磁盘/内嵌来源、
// 版本与业务绑定由产品侧装配决定。
type AssetProvider interface {
// DocTemplate 返回任务对应的文档模板正文。
DocTemplate(taskKey string) (string, error)
// RenderDoc 以变量渲染模板。
RenderDoc(tmpl string, vars map[string]string) string
// AgentInstruction 返回角色指令。
AgentInstruction(role string) (string, error)
// AgentInstructionFile 返回指定指令文件内容。
AgentInstructionFile(file string) (string, error)
}
// TaskPreparation 是领域准备钩子的产物:附件与工作区内回写内容。
@@ -153,7 +170,7 @@ type Service struct {
}
func NewService(opts Options) *Service {
engine := NewWorkspaceEngine()
engine := NewWorkspaceEngine(opts.Assets)
return &Service{
opts: opts.withDefaults(),
engine: engine,
@@ -215,7 +232,7 @@ func taskDisplayTitle(req SquadRunRequest) string {
// PlanWorkflow 动态规划生成任务工作流
func (s *Service) PlanWorkflow(ctx context.Context, req SquadRunRequest) (*WorkflowPlan, error) {
planner := NewAdaptivePlanner(nil)
planner := NewAdaptivePlanner(nil, s.opts.Assets)
res, err := planner.Plan(PlannerRequest{
TaskKey: req.TaskKey,
Title: taskDisplayTitle(req),
+1 -1
View File
@@ -100,7 +100,7 @@ func (b *countingBackend) Execute(ctx context.Context, req agent.Request) (*agen
func prepareSingleWriterWS(t *testing.T) (string, *squad.WorkflowDriver) {
t.Helper()
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: tmpDir,
TaskKey: "prelim_design",
+1 -1
View File
@@ -55,7 +55,7 @@ type WorkflowDriver struct {
func NewWorkflowDriver(engine *WorkspaceEngine) *WorkflowDriver {
if engine == nil {
engine = NewWorkspaceEngine()
engine = NewWorkspaceEngine(nil)
}
return &WorkflowDriver{engine: engine, evaluator: NewStepEvaluator()}
}
+1 -1
View File
@@ -15,7 +15,7 @@ import (
func TestWorkflowDriver_DynamicAndExecute(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
ws, err := engine.PrepareWorkspace(squad.WorkspacePrepareRequest{
WorkDir: tmpDir,
TaskKey: "intake",
+18 -9
View File
@@ -7,7 +7,6 @@ import (
"strings"
"time"
"chat-agent-system/internal/template"
)
// WorkspacePrepareRequest 工作区准备请求
@@ -60,10 +59,12 @@ type Workspace struct {
}
// WorkspaceEngine 工作区生命周期与沙箱构建引擎
type WorkspaceEngine struct{}
type WorkspaceEngine struct {
assets AssetProvider
}
func NewWorkspaceEngine() *WorkspaceEngine {
return &WorkspaceEngine{}
func NewWorkspaceEngine(assets AssetProvider) *WorkspaceEngine {
return &WorkspaceEngine{assets: assets}
}
// PrepareWorkspace 初始化自包含工作区目录
@@ -136,7 +137,7 @@ func (e *WorkspaceEngine) PrepareWorkspace(req WorkspacePrepareRequest) (*Worksp
Steps: steps,
}
} else {
planner := NewAdaptivePlanner(nil)
planner := NewAdaptivePlanner(nil, e.assets)
pRes, pErr := planner.Plan(PlannerRequest{
TaskKey: req.TaskKey,
Title: req.StageTitle,
@@ -187,13 +188,16 @@ func (e *WorkspaceEngine) PrepareWorkspace(req WorkspacePrepareRequest) (*Worksp
}
// 4. 写入自描述模板 template.md
if strings.TrimSpace(tmplContent) == "" {
if strings.TrimSpace(tmplContent) == "" && e.assets != nil {
var err error
tmplContent, err = template.GetDocTemplate(req.TaskKey)
tmplContent, err = e.assets.DocTemplate(req.TaskKey)
if err != nil {
tmplContent = fmt.Sprintf("# %s\n\n## 1. 结论\n\n待确认\n\n## 2. 正文\n\n待确认\n", req.StageTitle)
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,
@@ -207,7 +211,12 @@ func (e *WorkspaceEngine) PrepareWorkspace(req WorkspacePrepareRequest) (*Worksp
for k, v := range req.InitialFields {
vars[k] = v
}
renderedTmpl := template.RenderDoc(tmplContent, vars)
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)
}
+7 -6
View File
@@ -6,7 +6,6 @@ import (
"path/filepath"
"strings"
"chat-agent-system/internal/template"
)
// buildSubWorkspaces 为 workflow 中的各个子任务构建独立沙箱
@@ -27,11 +26,13 @@ func (e *WorkspaceEngine) buildSubWorkspaces(workDir string, wf WorkflowPlan, sq
spec := agentMap[step.Role]
var inst string
var err error
if file := strings.TrimSpace(spec.InstructionsFile); file != "" {
inst, err = template.GetAgentInstructionFile(file)
}
if err != nil || strings.TrimSpace(inst) == "" {
inst, err = template.GetAgentInstruction(step.Role)
if e.assets != nil {
if file := strings.TrimSpace(spec.InstructionsFile); file != "" {
inst, err = e.assets.AgentInstructionFile(file)
}
if err != nil || strings.TrimSpace(inst) == "" {
inst, err = e.assets.AgentInstruction(step.Role)
}
}
if err == nil && strings.TrimSpace(inst) != "" {
agentsBuilder.WriteString(inst)
+1 -1
View File
@@ -12,7 +12,7 @@ import (
func TestWorkspaceEngine_PrepareWorkspace(t *testing.T) {
tmpDir := t.TempDir()
engine := squad.NewWorkspaceEngine()
engine := squad.NewWorkspaceEngine(nil)
req := squad.WorkspacePrepareRequest{
WorkDir: filepath.Join(tmpDir, "test_workspace_01"),