Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements. Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate. Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable. Refs: Versioned dependencies U1/U3; feature/dependency-workspace
133 lines
4.5 KiB
Go
133 lines
4.5 KiB
Go
package runmgr
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/bus"
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/profiles"
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/runtime"
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/settings"
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/store"
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/tools"
|
|
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/internal/workflow"
|
|
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
|
|
)
|
|
|
|
type launchCapture struct {
|
|
ch chan workflow.Launch
|
|
}
|
|
|
|
func (c launchCapture) Name() string { return "workflow-capture" }
|
|
|
|
func (c launchCapture) Execute(rc runtime.RunCtx) error {
|
|
src, ok := rc.(interface {
|
|
Workflow() (workflow.Launch, bool)
|
|
})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
launch, ok := src.Workflow()
|
|
if ok {
|
|
c.ch <- launch
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestStartWorkflowRunCopiesDefinition(t *testing.T) {
|
|
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "wf.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { st.Close() })
|
|
if err := st.MergeSettingsScope(store.OwnerDefault, settings.ScopeUser, map[string]any{"runtime": map[string]any{"mock": true}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reg := tools.NewRegistry()
|
|
prof := profiles.New(st)
|
|
if err := prof.EnsureDefault(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
direct := &gateRunner{parts: []core.Part{{Type: core.PartText, Text: "direct"}}}
|
|
cap := launchCapture{ch: make(chan workflow.Launch, 1)}
|
|
mgr := New(st, bus.New(), reg, prof, settings.NewService(st), direct, noopRunner{}, &runtime.MockRunner{}, &runtime.MockSubagent{})
|
|
mgr.SetWorkflowRunner(cap)
|
|
|
|
proj, err := st.CreateProject(store.OwnerDefault, "p")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
conv, err := st.CreateConversation(store.OwnerDefault, proj.ID, "c", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
msg := core.Message{ConversationID: conv.ID, Role: core.RoleUser, Parts: []core.Part{{Type: core.PartText, Text: "写简报"}}}
|
|
def := core.WorkflowDef{ID: "demo.brief", Title: "简报", Steps: []core.WorkflowStep{{ID: "s", Kind: "hitl"}}}
|
|
if _, err := mgr.StartWorkflowRun(conv, msg, def, map[string]string{"topic": "核能"}, []workflow.SlotQuestion{{Key: "audience", Question: "给谁看?"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
def.Title = "被改掉"
|
|
select {
|
|
case launch := <-cap.ch:
|
|
if launch.Def.ID != "demo.brief" || launch.Def.Title != "简报" || launch.Slots["topic"] != "核能" {
|
|
t.Fatalf("%+v", launch)
|
|
}
|
|
if len(launch.Missing) != 1 || launch.Missing[0].Question != "给谁看?" {
|
|
t.Fatalf("missing %+v", launch.Missing)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("workflow runner was not selected")
|
|
}
|
|
}
|
|
|
|
func TestStartWorkflowRunCopiesScriptBody(t *testing.T) {
|
|
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "wf.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { st.Close() })
|
|
if err := st.MergeSettingsScope(store.OwnerDefault, settings.ScopeUser, map[string]any{"runtime": map[string]any{"mock": true}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reg := tools.NewRegistry()
|
|
prof := profiles.New(st)
|
|
if err := prof.EnsureDefault(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
direct := &gateRunner{parts: []core.Part{{Type: core.PartText, Text: "direct"}}}
|
|
cap := launchCapture{ch: make(chan workflow.Launch, 1)}
|
|
mgr := New(st, bus.New(), reg, prof, settings.NewService(st), direct, noopRunner{}, &runtime.MockRunner{}, &runtime.MockSubagent{})
|
|
mgr.SetWorkflowRunner(cap)
|
|
wf := workflow.NewRegistry()
|
|
const path = "workflows/scripts/demo.py"
|
|
wf.Set([]core.WorkflowDef{{
|
|
ID: "demo.script", Title: "脚本",
|
|
Steps: []core.WorkflowStep{{ID: "run", Kind: "script", Script: path}},
|
|
}}, map[string]string{workflow.ScriptKey("demo.script", path): "print(1)\n"})
|
|
mgr.SetWorkflows(wf)
|
|
|
|
proj, err := st.CreateProject(store.OwnerDefault, "p")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
conv, err := st.CreateConversation(store.OwnerDefault, proj.ID, "c", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
msg := core.Message{ConversationID: conv.ID, Role: core.RoleUser, Parts: []core.Part{{Type: core.PartText, Text: "跑"}}}
|
|
def, _ := wf.Get("demo.script")
|
|
if _, err := mgr.StartWorkflowRun(conv, msg, def, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wf.Set(nil, nil)
|
|
select {
|
|
case launch := <-cap.ch:
|
|
if launch.Scripts[path] != "print(1)\n" {
|
|
t.Fatalf("%+v", launch.Scripts)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("workflow runner was not selected")
|
|
}
|
|
}
|