mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-14 00:17:34 +08:00
Refine agent config schema and prompt file loading
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
43
pkg/agent/subagent_prompt_test.go
Normal file
43
pkg/agent/subagent_prompt_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user