diff --git a/deps/agent-native b/deps/agent-native index 292142a..f9ab2ef 160000 --- a/deps/agent-native +++ b/deps/agent-native @@ -1 +1 @@ -Subproject commit 292142ac1a8b125488794e9374faee2cb1c8c579 +Subproject commit f9ab2ef698ff253b8914a081e8651da65de3b38d diff --git a/deps/agent-squad b/deps/agent-squad index 5454f74..9a402e4 160000 --- a/deps/agent-squad +++ b/deps/agent-squad @@ -1 +1 @@ -Subproject commit 5454f7485e7cf8b7ea7432b3c3c680da8a99056e +Subproject commit 9a402e468ec758b0f7d5af952e57ebde9248925d diff --git a/internal/app/app.go b/internal/app/app.go index 0d7454e..6ae6335 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -16,7 +16,9 @@ import ( "syscall" "time" + agentllm "agent-native/runtime/llm" agenttools "agent-native/runtime/tools" + squad "agent-squad/squad" "agent-system-backend/internal/bus" "agent-system-backend/internal/cardsvc" "agent-system-backend/internal/cmdbus" @@ -108,6 +110,33 @@ func Run(opt Options) error { body, err := promptSvc.AgentPrompt(store.OwnerDefault, "agent.squad."+kind) return body, err == nil } + // 小队的 agent-native 内核工厂(组合根装配,逐次运行时从 settings 解析): + // 模型走 settings.models.chat 同一路由;搜索/解释器复用产品同一套配置与白名单; + // 技能根取数据目录 skills/(存在才注入)。主模型未配置时返回未启用(不阻塞产品)。 + squadadapter.AgentNativeOptions = func() (squad.AgentNativeConfig, bool) { + effNow := setSvc.Effective(store.OwnerDefault, "") + route := runtime.ResolveChatModel(core.AgentProfile{}, effNow) + if route.Model == "" || route.Base == "" { + return squad.AgentNativeConfig{}, false + } + cfg := squad.AgentNativeConfig{ + Client: &agentllm.Client{BaseURL: route.Base, APIKey: route.Key}, + DefaultModel: route.Model, + MaxTurns: 12, + Timeout: 10 * time.Minute, + } + if keys := splitKeyPool(effNow.GetString("tools.tavily.apiKeys", "")); len(keys) > 0 { + cfg.Search = agenttools.NewTavilyProvider(effNow.GetString("tools.tavily.endpoint", ""), keys) + } + if effNow.GetBool("runtime.exec", true) { + cfg.Exec = tools.InterpreterExecConfig() + } + skillsRoot := filepath.Join(opt.DataDir, "skills") + if st2, err := os.Stat(skillsRoot); err == nil && st2.IsDir() { + cfg.SkillsRoots = []string{skillsRoot} + } + return cfg, true + } profSvc := profiles.New(st) if err := profSvc.EnsureDefault(); err != nil { return fmt.Errorf("default profile: %w", err) diff --git a/internal/squadadapter/adapter.go b/internal/squadadapter/adapter.go index 7055c3d..ee1ca09 100644 --- a/internal/squadadapter/adapter.go +++ b/internal/squadadapter/adapter.go @@ -68,10 +68,7 @@ func Orchestrate(rc Ctx, dataDir, goalText string) (string, error) { } } - backends := squad.NewBackendResolver() - backends.Register("agent-system", func(role squad.RoleSpec) (exec.Backend, error) { - return &stepBackend{rc: rc, role: role}, nil - }) + backends := buildBackends(rc) engine, err := squad.NewEngine(squad.Options{ DataDir: runDir, @@ -262,6 +259,27 @@ func isHoldStatus(s string) bool { return s == "waiting_human" || s == "awaiting_confirmation" || s == "awaiting_clarification" || s == "quality_review" } +// AgentNativeOptions 可选组合根注入:返回配置且启用时,为每次小队运行注册 +// "agent-native" 内核后端工厂(模型/搜索/执行/技能根由组合根从 settings 解析)。 +var AgentNativeOptions func() (squad.AgentNativeConfig, bool) + +// buildBackends 组装小队后端解析器:"agent-system"(SpawnSubagent 路径)恒注册; +// 组合根注入 AgentNativeOptions 时额外注册 "agent-native" 内核工厂——角色显式 +// 声明 Backend:"agent-native" 才会走内核执行;默认后端仍是 agent-system, +// 不因工厂注册而切换(对齐 parity 渐进启用条款)。 +func buildBackends(rc Ctx) *squad.BackendResolver { + backends := squad.NewBackendResolver() + backends.Register("agent-system", func(role squad.RoleSpec) (exec.Backend, error) { + return &stepBackend{rc: rc, role: role}, nil + }) + if AgentNativeOptions != nil { + if cfg, ok := AgentNativeOptions(); ok { + backends.RegisterAgentNativeFull(cfg) + } + } + return backends +} + // RoleInstructions 小队角色指令来源:app 装配时注入提示词库查找 // (agent.squad.);nil 或未命中 = 使用内联回退文案(测试与骨架兼容)。 var RoleInstructions func(kind string) (string, bool) diff --git a/internal/tools/exec_interpreter.go b/internal/tools/exec_interpreter.go index 433a838..ed0f98e 100644 --- a/internal/tools/exec_interpreter.go +++ b/internal/tools/exec_interpreter.go @@ -20,6 +20,8 @@ import ( "time" core "agent-system-core" + + agenttools "agent-native/runtime/tools" ) // RegisterInterpreterTools 注册解释器执行原语(run_shell / run_python)。 @@ -232,3 +234,20 @@ func firstPos(vals ...int) int { } return 0 } + +// InterpreterExecConfig 把产品解释器白名单换算为 agent-native 内核的 +// ExecConfig(squad 角色经 agent-native 后端执行时复用同一套约束与环境)。 +// 两个解释器都缺席时返回 nil(内核侧不注册执行工具,fail-closed)。 +func InterpreterExecConfig() *agenttools.ExecConfig { + cfg := &agenttools.ExecConfig{Env: execEnv()} + if p, err := exec.LookPath("bash"); err == nil { + cfg.Bash = p + } + if p, err := exec.LookPath("python3"); err == nil { + cfg.Python = p + } + if cfg.Bash == "" && cfg.Python == "" { + return nil + } + return cfg +} diff --git a/internal/tools/exec_interpreter_test.go b/internal/tools/exec_interpreter_test.go index 71ea4c3..5bde205 100644 --- a/internal/tools/exec_interpreter_test.go +++ b/internal/tools/exec_interpreter_test.go @@ -138,3 +138,24 @@ func TestRunPythonWritesZipMagic(t *testing.T) { t.Fatalf("docx magic: %q", b) } } + +func TestInterpreterExecConfigForKernel(t *testing.T) { + cfg := InterpreterExecConfig() + if cfg == nil { + t.Fatal("host has bash/python3; kernel exec config must resolve") + } + if cfg.Bash == "" && cfg.Python == "" { + t.Fatal("at least one interpreter path must be set") + } + if !filepath.IsAbs(cfg.Bash) && cfg.Bash != "" { + t.Fatalf("bash path must be absolute, got %q", cfg.Bash) + } + for _, kv := range cfg.Env { + if strings.HasPrefix(kv, "PATH=") || strings.HasPrefix(kv, "HOME=") || strings.HasPrefix(kv, "LANG=") || + strings.HasPrefix(kv, "LC_") || strings.HasPrefix(kv, "TZ=") || strings.HasPrefix(kv, "TERM=") || + kv == "PYTHONDONTWRITEBYTECODE=1" { + continue + } + t.Fatalf("env whitelist violated: %q", kv) + } +}