Files
weichenandCursor e4020d297c
ci / vet-and-test (pull_request) Successful in 49s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
Add a script host, deferred tools, and background MCP connect
Why: models need a capped way to call several tools from one script, and a slow MCP server must not block the first prompt.
Evidence: GOWORK=off go test ./runtime/codeorch/ ./runtime/tools/ ./runtime/mcp/ ./runtime/policy/ ./adapters/native/
Impact: run_tool_script stays unregistered until the caller turns it on. Deferred tools stay out of the model list. An unready MCP server connects in the background.
Refs: feature/code-orchestration
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 18:02:53 +00:00

90 lines
2.7 KiB
Go

package codeorch
import (
"context"
"errors"
"os"
"strings"
"testing"
"time"
"unicode/utf8"
)
func TestScriptCallsTwoToolsAndReportsTheOneThatRan(t *testing.T) {
body := "import agentwf as wf\nprint(wf.tool('alpha', {}))\nprint(wf.tool('beta', {}))\n"
out := Run(context.Background(), t.TempDir(), body, Limits{}, nil, func(_ context.Context, call Call) (string, error) {
if call.Name == "beta" {
return "", errors.New("beta 失败")
}
return "A", nil
})
text := out.ModelText()
if len(out.Calls) != 1 || out.Calls[0] != "alpha" {
t.Fatalf("calls %+v", out.Calls)
}
if !strings.Contains(text, "已执行:alpha") || !strings.Contains(text, "beta 失败") {
t.Fatalf("text %q err %v", text, out.Err)
}
}
func TestForbiddenToolIsRejectedBeforeDispatchRunsIt(t *testing.T) {
body := "import agentwf as wf\nprint(wf.tool('read_file', {}))\nprint(wf.tool('run_shell', {}))\n"
var ran []string
out := Run(context.Background(), t.TempDir(), body, Limits{}, nil, func(_ context.Context, call Call) (string, error) {
if Forbidden(call.Name) {
return "", errors.New("不能调用 " + call.Name)
}
ran = append(ran, call.Name)
return "ok", nil
})
if len(ran) != 1 || ran[0] != "read_file" {
t.Fatalf("ran %+v", ran)
}
if !strings.Contains(out.ModelText(), "不能调用 run_shell") {
t.Fatalf("text %q", out.ModelText())
}
}
func TestLongOutputIsClippedAndStored(t *testing.T) {
body := "import agentwf as wf\nprint(wf.tool('big', {}))\n"
dir := t.TempDir()
out := Run(context.Background(), dir, body, Limits{MaxModelChars: 8, FullDir: dir}, nil, func(_ context.Context, call Call) (string, error) {
return strings.Repeat("测", 20), nil
})
if out.FullPath == "" {
t.Fatal("expected a full-text file")
}
full, err := os.ReadFile(out.FullPath)
if err != nil {
t.Fatal(err)
}
if utf8.RuneCount(full) < 20 {
t.Fatalf("full text too short: %q", full)
}
if !strings.HasSuffix(out.Output, "…(超长截断)") || utf8.RuneCountInString(strings.TrimSuffix(out.Output, "…(超长截断)")) != 8 {
t.Fatalf("model output not clipped: %q", out.Output)
}
if !strings.Contains(out.ModelText(), out.FullPath) {
t.Fatalf("model text missing path: %q", out.ModelText())
}
}
func TestResultGivenToScriptIsCapped(t *testing.T) {
got := clipBytes(strings.Repeat("x", 20), 8)
if got != "xxxxxxxx" {
t.Fatalf("clip %q", got)
}
if !utf8.ValidString(clipBytes("测测测", 4)) {
t.Fatal("clip broke utf-8")
}
}
func TestTimeoutIsCapped(t *testing.T) {
if (Limits{Timeout: 900 * time.Second, MaxTimeout: 600 * time.Second}).bounded() != 600*time.Second {
t.Fatal("timeout must clamp to the max")
}
if (Limits{}).bounded() != defaultTimeout {
t.Fatal("empty limits must use the 120s default")
}
}