Refine agent config schema and prompt file loading

This commit is contained in:
lpf
2026-03-06 13:56:38 +08:00
parent 1fec90643b
commit 2bc71870db
16 changed files with 483 additions and 250 deletions

View File

@@ -148,12 +148,12 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
toolsRegistry.Register(tools.NewCronTool(cs))
}
maxParallelCalls := cfg.Agents.Defaults.RuntimeControl.ToolMaxParallelCalls
maxParallelCalls := cfg.Agents.Defaults.Execution.ToolMaxParallelCalls
if maxParallelCalls <= 0 {
maxParallelCalls = 4
}
parallelSafe := make(map[string]struct{})
for _, name := range cfg.Agents.Defaults.RuntimeControl.ToolParallelSafeNames {
for _, name := range cfg.Agents.Defaults.Execution.ToolParallelSafeNames {
trimmed := strings.TrimSpace(name)
if trimmed != "" {
parallelSafe[trimmed] = struct{}{}
@@ -313,16 +313,50 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
if sessionKey == "" {
sessionKey = fmt.Sprintf("subagent:%s", strings.TrimSpace(task.ID))
}
taskInput := task.Task
if p := strings.TrimSpace(task.SystemPrompt); p != "" {
taskInput = fmt.Sprintf("Role Profile Prompt:\n%s\n\nTask:\n%s", p, task.Task)
}
taskInput := loop.buildSubagentTaskInput(task)
return loop.ProcessDirectWithOptions(ctx, taskInput, sessionKey, task.OriginChannel, task.OriginChatID, task.MemoryNS, task.ToolAllowlist)
})
return loop
}
func (al *AgentLoop) buildSubagentTaskInput(task *tools.SubagentTask) string {
if task == nil {
return ""
}
taskText := strings.TrimSpace(task.Task)
if promptFile := strings.TrimSpace(task.SystemPromptFile); promptFile != "" {
if promptText := al.readSubagentPromptFile(promptFile); promptText != "" {
return fmt.Sprintf("Role Profile Policy (%s):\n%s\n\nTask:\n%s", promptFile, promptText, taskText)
}
}
if prompt := strings.TrimSpace(task.SystemPrompt); prompt != "" {
return fmt.Sprintf("Role Profile Prompt:\n%s\n\nTask:\n%s", prompt, taskText)
}
return taskText
}
func (al *AgentLoop) readSubagentPromptFile(relPath string) string {
if al == nil {
return ""
}
workspace := strings.TrimSpace(al.workspace)
relPath = strings.TrimSpace(relPath)
if workspace == "" || relPath == "" || filepath.IsAbs(relPath) {
return ""
}
fullPath := filepath.Clean(filepath.Join(workspace, relPath))
relToWorkspace, err := filepath.Rel(workspace, fullPath)
if err != nil || strings.HasPrefix(relToWorkspace, "..") {
return ""
}
data, err := os.ReadFile(fullPath)
if err != nil {
return ""
}
return strings.TrimSpace(string(data))
}
func (al *AgentLoop) Run(ctx context.Context) error {
al.running = true

View File

@@ -125,16 +125,17 @@ func (al *AgentLoop) HandleSubagentRuntime(ctx context.Context, action string, a
items := make([]map[string]interface{}, 0, len(cfg.Agents.Subagents))
for agentID, subcfg := range cfg.Agents.Subagents {
items = append(items, map[string]interface{}{
"agent_id": agentID,
"enabled": subcfg.Enabled,
"type": subcfg.Type,
"display_name": subcfg.DisplayName,
"role": subcfg.Role,
"description": subcfg.Description,
"system_prompt": subcfg.SystemPrompt,
"memory_namespace": subcfg.MemoryNamespace,
"tool_allowlist": append([]string(nil), subcfg.Tools.Allowlist...),
"routing_keywords": routeKeywordsForRegistry(cfg.Agents.Router.Rules, agentID),
"agent_id": agentID,
"enabled": subcfg.Enabled,
"type": subcfg.Type,
"display_name": subcfg.DisplayName,
"role": subcfg.Role,
"description": subcfg.Description,
"system_prompt": subcfg.SystemPrompt,
"system_prompt_file": subcfg.SystemPromptFile,
"memory_namespace": subcfg.MemoryNamespace,
"tool_allowlist": append([]string(nil), subcfg.Tools.Allowlist...),
"routing_keywords": routeKeywordsForRegistry(cfg.Agents.Router.Rules, agentID),
})
}
sort.Slice(items, func(i, j int) bool {

View File

@@ -72,12 +72,13 @@ func TestHandleSubagentRuntimeUpsertConfigSubagent(t *testing.T) {
subagentRouter: tools.NewSubagentRouter(manager),
}
out, err := loop.HandleSubagentRuntime(context.Background(), "upsert_config_subagent", map[string]interface{}{
"agent_id": "reviewer",
"role": "testing",
"display_name": "Review Agent",
"system_prompt": "review changes",
"routing_keywords": []interface{}{"review", "regression"},
"tool_allowlist": []interface{}{"shell", "sessions"},
"agent_id": "reviewer",
"role": "testing",
"display_name": "Review Agent",
"system_prompt": "review changes",
"system_prompt_file": "agents/reviewer/AGENT.md",
"routing_keywords": []interface{}{"review", "regression"},
"tool_allowlist": []interface{}{"shell", "sessions"},
})
if err != nil {
t.Fatalf("upsert config subagent failed: %v", err)
@@ -94,6 +95,9 @@ func TestHandleSubagentRuntimeUpsertConfigSubagent(t *testing.T) {
if !ok || subcfg.DisplayName != "Review Agent" {
t.Fatalf("expected reviewer subagent in config, got %+v", reloaded.Agents.Subagents)
}
if subcfg.SystemPromptFile != "agents/reviewer/AGENT.md" {
t.Fatalf("expected system_prompt_file to persist, got %+v", subcfg)
}
if len(reloaded.Agents.Router.Rules) == 0 {
t.Fatalf("expected router rules to be persisted")
}

View File

@@ -0,0 +1,43 @@
package agent
import (
"os"
"path/filepath"
"strings"
"testing"
"clawgo/pkg/tools"
)
func TestBuildSubagentTaskInputPrefersPromptFile(t *testing.T) {
workspace := t.TempDir()
if err := os.MkdirAll(filepath.Join(workspace, "agents", "coder"), 0755); err != nil {
t.Fatalf("mkdir failed: %v", err)
}
if err := os.WriteFile(filepath.Join(workspace, "agents", "coder", "AGENT.md"), []byte("coder-file-policy"), 0644); err != nil {
t.Fatalf("write AGENT failed: %v", err)
}
loop := &AgentLoop{workspace: workspace}
input := loop.buildSubagentTaskInput(&tools.SubagentTask{
Task: "implement login flow",
SystemPrompt: "inline-fallback",
SystemPromptFile: "agents/coder/AGENT.md",
})
if !strings.Contains(input, "coder-file-policy") {
t.Fatalf("expected prompt file content, got: %s", input)
}
if strings.Contains(input, "inline-fallback") {
t.Fatalf("expected file prompt to take precedence, got: %s", input)
}
}
func TestBuildSubagentTaskInputFallsBackToInlinePrompt(t *testing.T) {
loop := &AgentLoop{workspace: t.TempDir()}
input := loop.buildSubagentTaskInput(&tools.SubagentTask{
Task: "run regression",
SystemPrompt: "test inline prompt",
})
if !strings.Contains(input, "test inline prompt") {
t.Fatalf("expected inline prompt in task input, got: %s", input)
}
}