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
104 lines
4.3 KiB
Go
104 lines
4.3 KiB
Go
package runtime
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
|
||
nruntime "git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/runtime"
|
||
agentllm "git-ssh.u1680832.nyat.app/weichen/Agent-Native.git/runtime/llm"
|
||
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
|
||
)
|
||
|
||
// ---- F1:delegate_task 回传子代理全文,不再只有 160 字摘要 ----
|
||
|
||
func TestDelegateToolReturnsFullText(t *testing.T) {
|
||
full := strings.Repeat("子代理结论细节。", 60) // 480 字,远超 160
|
||
f := &fakeRunCtx{spawnAgentFn: func(agentID, name, task, hint string) (core.AgentResultPayload, error) {
|
||
return core.AgentResultPayload{Status: core.RunCompleted, Text: full, Summary: "完成(160字内摘要)"}, nil
|
||
}}
|
||
st := &directRun{l: &LLMRunner{}, rc: f, eff: f.Settings(), inflight: map[string]*toolSlot{}}
|
||
res := st.runTool("delegate_task", `{"name":"调研","task":"查证X"}`)
|
||
if res.IsError {
|
||
t.Fatalf("delegate failed: %s", res.Content)
|
||
}
|
||
if !strings.Contains(res.Content, "子代理结论细节。子代理结论细节。子代理结论细节。") {
|
||
t.Fatalf("delegate result must carry the full child text, got: %.120s…", res.Content)
|
||
}
|
||
if strings.Contains(res.Content, "160字内摘要") {
|
||
t.Fatalf("summary must be fallback only, got: %.120s…", res.Content)
|
||
}
|
||
}
|
||
|
||
// ---- F5:并行执行下工具事件按 CallID 配对 ----
|
||
|
||
func TestToolEventsPairByCallID(t *testing.T) {
|
||
f := &fakeRunCtx{}
|
||
st := &directRun{l: &LLMRunner{}, rc: f, eff: f.Settings(), inflight: map[string]*toolSlot{}}
|
||
st.queued = []agentllm.ToolCall{
|
||
{ID: "c1", Name: "knowledge_search", Arguments: `{}`},
|
||
{ID: "c2", Name: "web_search", Arguments: `{}`},
|
||
}
|
||
// 乱序进出:c1 先开始,c2 先结束(并行下的真实时序)。
|
||
st.onEvent(nruntime.Event{Type: "tool_execution_start", Tool: "knowledge_search", CallID: "c1"})
|
||
st.onEvent(nruntime.Event{Type: "tool_execution_start", Tool: "web_search", CallID: "c2"})
|
||
st.onEvent(nruntime.Event{Type: "tool_execution_end", Tool: "web_search", CallID: "c2"})
|
||
st.onEvent(nruntime.Event{Type: "tool_execution_end", Tool: "knowledge_search", CallID: "c1"})
|
||
|
||
if len(st.inflight) != 0 {
|
||
t.Fatalf("inflight must be empty after both ends: %v", st.inflight)
|
||
}
|
||
if len(st.finalParts) != 4 {
|
||
t.Fatalf("finalParts = %d (want 4: call+result ×2)", len(st.finalParts))
|
||
}
|
||
// 结束顺序决定部件顺序:先 c2 后 c1。
|
||
if got := st.finalParts[0].ToolCall.ID; got != "c2" {
|
||
t.Fatalf("first tool part = %s (want c2)", got)
|
||
}
|
||
if got := st.finalParts[1].ToolResult.CallID; got != "c2" {
|
||
t.Fatalf("first result part = %s (want c2)", got)
|
||
}
|
||
if got := st.finalParts[2].ToolCall.ID; got != "c1" {
|
||
t.Fatalf("second tool part = %s (want c1)", got)
|
||
}
|
||
if got := st.finalParts[3].ToolResult.CallID; got != "c1" {
|
||
t.Fatalf("second result part = %s (want c1)", got)
|
||
}
|
||
}
|
||
|
||
// ---- F5:执行模式与串行集合 ----
|
||
|
||
func TestToolExecModeAndSerialSet(t *testing.T) {
|
||
if got := toolExecMode(core.EffectiveSettings{}); got != nruntime.ExecParallel {
|
||
t.Fatalf("default must be parallel, got %q", got)
|
||
}
|
||
off := core.EffectiveSettings{"runtime": map[string]any{"parallelTools": false}}
|
||
if got := toolExecMode(off); got != "" {
|
||
t.Fatalf("parallelTools=false must fall back to sequential, got %q", got)
|
||
}
|
||
// 顺序敏感工具必须串行;可并行的读检索与委派不得误入。
|
||
for _, name := range []string{"update_plan", "update_todos", "submit_card", "card_begin", "card_append", "card_commit", "run_workflow", "squad_orchestrate", "run_shell", "run_python", "register_artifact"} {
|
||
if !serialTools[name] {
|
||
t.Fatalf("serialTools missing %s", name)
|
||
}
|
||
}
|
||
for _, name := range []string{"delegate_task", "review_findings", "knowledge_search", "web_search", "fs_read_file", "write_artifact"} {
|
||
if serialTools[name] {
|
||
t.Fatalf("serialTools must not contain %s", name)
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---- F2:MiniMax 前缀自动置位 reasoning_split ----
|
||
|
||
func TestOpenLLMClientReasoningSplitAuto(t *testing.T) {
|
||
if c := openLLMClient("http://x", "k", "MiniMax-M2.7-highspeed"); !c.ReasoningSplit {
|
||
t.Fatal("MiniMax model must set ReasoningSplit")
|
||
}
|
||
if c := openLLMClient("http://x", "k", "MiniMax-M3"); !c.ReasoningSplit {
|
||
t.Fatal("MiniMax M3 must set ReasoningSplit")
|
||
}
|
||
if c := openLLMClient("http://x", "k", "claude-sonnet-5"); c.ReasoningSplit {
|
||
t.Fatal("non-MiniMax model must not set ReasoningSplit")
|
||
}
|
||
}
|