Merge pull request 'U4 第五步:AssetProvider 端口注入,移除 squad→template 依赖' (#8) from feature/squad-asset-provider-2026-09-29 into dev
This commit was merged in pull request #8.
This commit is contained in:
@@ -50,6 +50,7 @@ imports:
|
||||
- squad
|
||||
- storage
|
||||
- stream
|
||||
- template
|
||||
- textextract
|
||||
- workflowdef
|
||||
- workspace
|
||||
@@ -288,7 +289,6 @@ imports:
|
||||
- agent
|
||||
- config
|
||||
- stream
|
||||
- template
|
||||
- textextract
|
||||
- workflowdef
|
||||
storage:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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{}
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user