Merge remote-tracking branch 'origin/main' into feature/chat-timeline-preview

This commit is contained in:
2026-10-01 06:17:45 +00:00
6 changed files with 93 additions and 6 deletions
+29
View File
@@ -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)
+22 -4
View File
@@ -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.<kind>);nil 或未命中 = 使用内联回退文案(测试与骨架兼容)。
var RoleInstructions func(kind string) (string, bool)
+19
View File
@@ -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
}
+21
View File
@@ -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)
}
}