Add a mock workflow script and load the example plugin
Why: Mock mode needs a path that drives the real step runner, and the example plugin should fail the host test if its workflow or slot question is wrong. Evidence: go test ./internal/plugins/ -run TestRepoExamplePluginLoads passed. The brief intent asks 主题是什么? and keeps the topic slot. Impact: #workflow is only the mock runner. The example plugin itself is in the Dev repo. Refs: feature/agent-harness-p4 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"agent-system-backend/internal/intentgate"
|
||||
@@ -208,3 +209,46 @@ func TestWorkflowScriptCannotEscape(t *testing.T) {
|
||||
t.Fatal("script path outside the plugin dir should be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoExamplePluginLoads(t *testing.T) {
|
||||
root := filepath.Join("..", "..", "..", "plugins")
|
||||
if _, err := os.Stat(filepath.Join(root, "example", "workflows", "demo-brief.json")); err != nil {
|
||||
t.Skip("example plugin is not beside this module")
|
||||
}
|
||||
dir := t.TempDir()
|
||||
st, err := store.OpenSQLite(filepath.Join(dir, "test.db"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { st.Close() })
|
||||
regTools := tools.NewRegistry()
|
||||
regTools.RegisterBuiltin()
|
||||
host := New(st, regTools, settings.NewService(st), root)
|
||||
gate := intentgate.New()
|
||||
wf := workflow.NewRegistry()
|
||||
host.OnIntents = func(overlay []json.RawMessage) error {
|
||||
return gate.Rebuild(context.Background(), overlay)
|
||||
}
|
||||
host.OnWorkflows = func(defs []core.WorkflowDef, scripts map[string]string) error {
|
||||
wf.Set(defs, scripts)
|
||||
return nil
|
||||
}
|
||||
if err := host.LoadDir()["example"]; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := wf.ByIntent("demo.brief"); !ok {
|
||||
t.Fatal("demo.brief is not bound")
|
||||
}
|
||||
body, ok := wf.Script("demo.script", "workflows/scripts/demo.py")
|
||||
if !ok || !strings.Contains(body, `wf.tool("time_now"`) {
|
||||
t.Fatalf("script %q", body)
|
||||
}
|
||||
got := gate.Recognize(context.Background(), "写一份简报")
|
||||
if got.ID != "demo.brief" || len(got.Missing) != 1 || got.Missing[0].Question != "主题是什么?" {
|
||||
t.Fatalf("%+v", got)
|
||||
}
|
||||
filled := gate.Recognize(context.Background(), "主题:核能 写一份简报")
|
||||
if filled.Slots["topic"] != "核能" || len(filled.Missing) != 0 {
|
||||
t.Fatalf("%+v", filled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"agent-system-backend/internal/docgen"
|
||||
"agent-system-backend/internal/squadadapter"
|
||||
"agent-system-backend/internal/workflow"
|
||||
core "agent-system-core"
|
||||
)
|
||||
|
||||
@@ -212,6 +213,17 @@ func (m *MockRunner) Execute(rc RunCtx) error {
|
||||
addText("Agent-Squad 编排完成:" + summary + "。步骤与子代理见右栏「计划」「代理」。")
|
||||
}
|
||||
|
||||
case strings.Contains(user, "#workflow"):
|
||||
res, err := (WorkflowRunner{}).run(rc, workflow.Launch{Def: mockWorkflow()})
|
||||
if err != nil {
|
||||
if res.Summary != "" {
|
||||
addText(res.Summary)
|
||||
}
|
||||
addText("演示工作流没有跑完:" + err.Error())
|
||||
break
|
||||
}
|
||||
parts = append(parts, res.Parts...)
|
||||
|
||||
case strings.Contains(user, "#error"):
|
||||
return fmt.Errorf("mock: 演示失败路径(#error 指令)")
|
||||
|
||||
@@ -229,7 +241,7 @@ func (m *MockRunner) Execute(rc RunCtx) error {
|
||||
core.Part{Type: core.PartToolResult, Process: true, ToolResult: &core.ToolResultPart{CallID: callID, Status: res.Status, Summary: truncateForJSON(res.Output, 120)}},
|
||||
)
|
||||
rc.Emit(core.EvRunPhase, core.RunPhasePayload{Phase: core.PhaseResponding})
|
||||
reply := fmt.Sprintf("(mock 回复)你说:%s\n\n这是 mock runner 的脚本化回复——未调用真实模型。可发送 #hitl / #agents / #doc / #error 触发对应演示路径。", user)
|
||||
reply := fmt.Sprintf("(mock 回复)你说:%s\n\n这是 mock runner 的脚本化回复——未调用真实模型。可发送 #hitl / #agents / #doc / #workflow / #error 触发对应演示路径。", user)
|
||||
idx := 0
|
||||
for _, r := range reply {
|
||||
rc.Emit(core.EvMessageDelta, core.MessageDeltaPayload{Index: idx, Text: string(r)})
|
||||
@@ -257,6 +269,23 @@ func (m *MockSubagent) Execute(rc RunCtx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func mockWorkflow() core.WorkflowDef {
|
||||
return core.WorkflowDef{
|
||||
ID: "mock.workflow", Title: "演示工作流",
|
||||
Steps: []core.WorkflowStep{
|
||||
{ID: "length", Title: "篇幅", Kind: "hitl", Hitl: &core.HITLCardTemplate{
|
||||
Question: "要多长?", Kind: core.HITLKindChoice, AllowFreeInput: false,
|
||||
Choices: []core.HITLChoice{
|
||||
{ID: "short", Label: "短", Description: "一段", Impact: "只写一段"},
|
||||
{ID: "long", Label: "长", Description: "几段", Impact: "会写得更长"},
|
||||
},
|
||||
}},
|
||||
{ID: "save", Title: "成稿", Kind: "artifact", Artifact: "mock-brief.md", Prompt: "篇幅:{{steps.length.output}}\n{{input}}"},
|
||||
{ID: "note", Title: "收件", Kind: "card", Card: &core.ActivityCardTemplate{Kind: core.CardKindResult, Title: "演示工作流完成"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func truncateForJSON(s string, n int) string {
|
||||
r := []rune(s)
|
||||
if len(r) <= n {
|
||||
|
||||
Reference in New Issue
Block a user