mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-14 18:17:29 +08:00
Refine agent config schema and prompt file loading
This commit is contained in:
@@ -14,36 +14,37 @@ import (
|
||||
)
|
||||
|
||||
type SubagentTask struct {
|
||||
ID string `json:"id"`
|
||||
Task string `json:"task"`
|
||||
Label string `json:"label"`
|
||||
Role string `json:"role"`
|
||||
AgentID string `json:"agent_id"`
|
||||
SessionKey string `json:"session_key"`
|
||||
MemoryNS string `json:"memory_ns"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty"`
|
||||
ToolAllowlist []string `json:"tool_allowlist,omitempty"`
|
||||
MaxRetries int `json:"max_retries,omitempty"`
|
||||
RetryBackoff int `json:"retry_backoff,omitempty"`
|
||||
TimeoutSec int `json:"timeout_sec,omitempty"`
|
||||
MaxTaskChars int `json:"max_task_chars,omitempty"`
|
||||
MaxResultChars int `json:"max_result_chars,omitempty"`
|
||||
RetryCount int `json:"retry_count,omitempty"`
|
||||
PipelineID string `json:"pipeline_id,omitempty"`
|
||||
PipelineTask string `json:"pipeline_task,omitempty"`
|
||||
ThreadID string `json:"thread_id,omitempty"`
|
||||
CorrelationID string `json:"correlation_id,omitempty"`
|
||||
ParentRunID string `json:"parent_run_id,omitempty"`
|
||||
LastMessageID string `json:"last_message_id,omitempty"`
|
||||
WaitingReply bool `json:"waiting_for_reply,omitempty"`
|
||||
SharedState map[string]interface{} `json:"shared_state,omitempty"`
|
||||
OriginChannel string `json:"origin_channel,omitempty"`
|
||||
OriginChatID string `json:"origin_chat_id,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Result string `json:"result,omitempty"`
|
||||
Steering []string `json:"steering,omitempty"`
|
||||
Created int64 `json:"created"`
|
||||
Updated int64 `json:"updated"`
|
||||
ID string `json:"id"`
|
||||
Task string `json:"task"`
|
||||
Label string `json:"label"`
|
||||
Role string `json:"role"`
|
||||
AgentID string `json:"agent_id"`
|
||||
SessionKey string `json:"session_key"`
|
||||
MemoryNS string `json:"memory_ns"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty"`
|
||||
SystemPromptFile string `json:"system_prompt_file,omitempty"`
|
||||
ToolAllowlist []string `json:"tool_allowlist,omitempty"`
|
||||
MaxRetries int `json:"max_retries,omitempty"`
|
||||
RetryBackoff int `json:"retry_backoff,omitempty"`
|
||||
TimeoutSec int `json:"timeout_sec,omitempty"`
|
||||
MaxTaskChars int `json:"max_task_chars,omitempty"`
|
||||
MaxResultChars int `json:"max_result_chars,omitempty"`
|
||||
RetryCount int `json:"retry_count,omitempty"`
|
||||
PipelineID string `json:"pipeline_id,omitempty"`
|
||||
PipelineTask string `json:"pipeline_task,omitempty"`
|
||||
ThreadID string `json:"thread_id,omitempty"`
|
||||
CorrelationID string `json:"correlation_id,omitempty"`
|
||||
ParentRunID string `json:"parent_run_id,omitempty"`
|
||||
LastMessageID string `json:"last_message_id,omitempty"`
|
||||
WaitingReply bool `json:"waiting_for_reply,omitempty"`
|
||||
SharedState map[string]interface{} `json:"shared_state,omitempty"`
|
||||
OriginChannel string `json:"origin_channel,omitempty"`
|
||||
OriginChatID string `json:"origin_chat_id,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Result string `json:"result,omitempty"`
|
||||
Steering []string `json:"steering,omitempty"`
|
||||
Created int64 `json:"created"`
|
||||
Updated int64 `json:"updated"`
|
||||
}
|
||||
|
||||
type SubagentManager struct {
|
||||
@@ -163,6 +164,7 @@ func (sm *SubagentManager) spawnTask(ctx context.Context, opts SubagentSpawnOpti
|
||||
}
|
||||
memoryNS := agentID
|
||||
systemPrompt := ""
|
||||
systemPromptFile := ""
|
||||
toolAllowlist := []string(nil)
|
||||
maxRetries := 0
|
||||
retryBackoff := 1000
|
||||
@@ -190,6 +192,7 @@ func (sm *SubagentManager) spawnTask(ctx context.Context, opts SubagentSpawnOpti
|
||||
memoryNS = ns
|
||||
}
|
||||
systemPrompt = strings.TrimSpace(profile.SystemPrompt)
|
||||
systemPromptFile = strings.TrimSpace(profile.SystemPromptFile)
|
||||
toolAllowlist = append([]string(nil), profile.ToolAllowlist...)
|
||||
maxRetries = profile.MaxRetries
|
||||
retryBackoff = profile.RetryBackoff
|
||||
@@ -257,31 +260,32 @@ func (sm *SubagentManager) spawnTask(ctx context.Context, opts SubagentSpawnOpti
|
||||
}
|
||||
}
|
||||
subagentTask := &SubagentTask{
|
||||
ID: taskID,
|
||||
Task: task,
|
||||
Label: label,
|
||||
Role: role,
|
||||
AgentID: agentID,
|
||||
SessionKey: sessionKey,
|
||||
MemoryNS: memoryNS,
|
||||
SystemPrompt: systemPrompt,
|
||||
ToolAllowlist: toolAllowlist,
|
||||
MaxRetries: maxRetries,
|
||||
RetryBackoff: retryBackoff,
|
||||
TimeoutSec: timeoutSec,
|
||||
MaxTaskChars: maxTaskChars,
|
||||
MaxResultChars: maxResultChars,
|
||||
RetryCount: 0,
|
||||
PipelineID: pipelineID,
|
||||
PipelineTask: pipelineTask,
|
||||
ThreadID: threadID,
|
||||
CorrelationID: correlationID,
|
||||
ParentRunID: parentRunID,
|
||||
OriginChannel: originChannel,
|
||||
OriginChatID: originChatID,
|
||||
Status: "running",
|
||||
Created: now,
|
||||
Updated: now,
|
||||
ID: taskID,
|
||||
Task: task,
|
||||
Label: label,
|
||||
Role: role,
|
||||
AgentID: agentID,
|
||||
SessionKey: sessionKey,
|
||||
MemoryNS: memoryNS,
|
||||
SystemPrompt: systemPrompt,
|
||||
SystemPromptFile: systemPromptFile,
|
||||
ToolAllowlist: toolAllowlist,
|
||||
MaxRetries: maxRetries,
|
||||
RetryBackoff: retryBackoff,
|
||||
TimeoutSec: timeoutSec,
|
||||
MaxTaskChars: maxTaskChars,
|
||||
MaxResultChars: maxResultChars,
|
||||
RetryCount: 0,
|
||||
PipelineID: pipelineID,
|
||||
PipelineTask: pipelineTask,
|
||||
ThreadID: threadID,
|
||||
CorrelationID: correlationID,
|
||||
ParentRunID: parentRunID,
|
||||
OriginChannel: originChannel,
|
||||
OriginChatID: originChatID,
|
||||
Status: "running",
|
||||
Created: now,
|
||||
Updated: now,
|
||||
}
|
||||
taskCtx, cancel := context.WithCancel(ctx)
|
||||
sm.tasks[taskID] = subagentTask
|
||||
@@ -469,19 +473,7 @@ func (sm *SubagentManager) executeTaskOnce(ctx context.Context, task *SubagentTa
|
||||
return "", fmt.Errorf("no llm provider configured for subagent execution")
|
||||
}
|
||||
|
||||
systemPrompt := "You are a subagent. Follow workspace AGENTS.md and complete the task independently."
|
||||
rolePrompt := strings.TrimSpace(task.SystemPrompt)
|
||||
if ws := strings.TrimSpace(sm.workspace); ws != "" {
|
||||
if data, err := os.ReadFile(filepath.Join(ws, "AGENTS.md")); err == nil {
|
||||
txt := strings.TrimSpace(string(data))
|
||||
if txt != "" {
|
||||
systemPrompt = "Workspace policy (AGENTS.md):\n" + txt + "\n\nComplete the given task independently and report the result."
|
||||
}
|
||||
}
|
||||
}
|
||||
if rolePrompt != "" {
|
||||
systemPrompt += "\n\nRole-specific profile prompt:\n" + rolePrompt
|
||||
}
|
||||
systemPrompt := sm.resolveSystemPrompt(task)
|
||||
messages := []providers.Message{
|
||||
{
|
||||
Role: "system",
|
||||
@@ -510,6 +502,44 @@ func (sm *SubagentManager) executeTaskOnce(ctx context.Context, task *SubagentTa
|
||||
return response.Content, nil
|
||||
}
|
||||
|
||||
func (sm *SubagentManager) resolveSystemPrompt(task *SubagentTask) string {
|
||||
systemPrompt := "You are a subagent. Follow workspace AGENTS.md and complete the task independently."
|
||||
workspacePrompt := sm.readWorkspacePromptFile("AGENTS.md")
|
||||
if workspacePrompt != "" {
|
||||
systemPrompt = "Workspace policy (AGENTS.md):\n" + workspacePrompt + "\n\nComplete the given task independently and report the result."
|
||||
}
|
||||
if task == nil {
|
||||
return systemPrompt
|
||||
}
|
||||
if promptFile := strings.TrimSpace(task.SystemPromptFile); promptFile != "" {
|
||||
if promptText := sm.readWorkspacePromptFile(promptFile); promptText != "" {
|
||||
return systemPrompt + "\n\nSubagent policy (" + promptFile + "):\n" + promptText
|
||||
}
|
||||
}
|
||||
if rolePrompt := strings.TrimSpace(task.SystemPrompt); rolePrompt != "" {
|
||||
return systemPrompt + "\n\nRole-specific profile prompt:\n" + rolePrompt
|
||||
}
|
||||
return systemPrompt
|
||||
}
|
||||
|
||||
func (sm *SubagentManager) readWorkspacePromptFile(relPath string) string {
|
||||
ws := strings.TrimSpace(sm.workspace)
|
||||
relPath = strings.TrimSpace(relPath)
|
||||
if ws == "" || relPath == "" || filepath.IsAbs(relPath) {
|
||||
return ""
|
||||
}
|
||||
fullPath := filepath.Clean(filepath.Join(ws, relPath))
|
||||
relToWorkspace, err := filepath.Rel(ws, fullPath)
|
||||
if err != nil || strings.HasPrefix(relToWorkspace, "..") {
|
||||
return ""
|
||||
}
|
||||
data, err := os.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(data))
|
||||
}
|
||||
|
||||
type SubagentRunFunc func(ctx context.Context, task *SubagentTask) (string, error)
|
||||
|
||||
func (sm *SubagentManager) SetRunFunc(f SubagentRunFunc) {
|
||||
|
||||
@@ -23,15 +23,16 @@ func DraftConfigSubagent(description, agentIDHint string) map[string]interface{}
|
||||
keywords := inferDraftKeywords(role, lower)
|
||||
systemPrompt := inferDraftSystemPrompt(role, desc)
|
||||
return map[string]interface{}{
|
||||
"agent_id": agentID,
|
||||
"role": role,
|
||||
"display_name": displayName,
|
||||
"description": desc,
|
||||
"system_prompt": systemPrompt,
|
||||
"memory_namespace": agentID,
|
||||
"tool_allowlist": toolAllowlist,
|
||||
"routing_keywords": keywords,
|
||||
"type": "worker",
|
||||
"agent_id": agentID,
|
||||
"role": role,
|
||||
"display_name": displayName,
|
||||
"description": desc,
|
||||
"system_prompt": systemPrompt,
|
||||
"system_prompt_file": "agents/" + agentID + "/AGENT.md",
|
||||
"memory_namespace": agentID,
|
||||
"tool_allowlist": toolAllowlist,
|
||||
"routing_keywords": keywords,
|
||||
"type": "worker",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +70,9 @@ func UpsertConfigSubagent(configPath string, args map[string]interface{}) (map[s
|
||||
if v := stringArgFromMap(args, "system_prompt"); v != "" {
|
||||
subcfg.SystemPrompt = v
|
||||
}
|
||||
if v := stringArgFromMap(args, "system_prompt_file"); v != "" {
|
||||
subcfg.SystemPromptFile = v
|
||||
}
|
||||
if v := stringArgFromMap(args, "memory_namespace"); v != "" {
|
||||
subcfg.MemoryNamespace = v
|
||||
}
|
||||
|
||||
@@ -39,12 +39,13 @@ func (t *SubagentConfigTool) Parameters() map[string]interface{} {
|
||||
"type": "string",
|
||||
"description": "Optional preferred agent id seed for draft.",
|
||||
},
|
||||
"agent_id": map[string]interface{}{"type": "string"},
|
||||
"role": map[string]interface{}{"type": "string"},
|
||||
"display_name": map[string]interface{}{"type": "string"},
|
||||
"system_prompt": map[string]interface{}{"type": "string"},
|
||||
"memory_namespace": map[string]interface{}{"type": "string"},
|
||||
"type": map[string]interface{}{"type": "string"},
|
||||
"agent_id": map[string]interface{}{"type": "string"},
|
||||
"role": map[string]interface{}{"type": "string"},
|
||||
"display_name": map[string]interface{}{"type": "string"},
|
||||
"system_prompt": map[string]interface{}{"type": "string"},
|
||||
"system_prompt_file": map[string]interface{}{"type": "string"},
|
||||
"memory_namespace": map[string]interface{}{"type": "string"},
|
||||
"type": map[string]interface{}{"type": "string"},
|
||||
"tool_allowlist": map[string]interface{}{
|
||||
"type": "array",
|
||||
"items": map[string]interface{}{"type": "string"},
|
||||
|
||||
@@ -16,21 +16,22 @@ import (
|
||||
)
|
||||
|
||||
type SubagentProfile struct {
|
||||
AgentID string `json:"agent_id"`
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role,omitempty"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty"`
|
||||
ToolAllowlist []string `json:"tool_allowlist,omitempty"`
|
||||
MemoryNamespace string `json:"memory_namespace,omitempty"`
|
||||
MaxRetries int `json:"max_retries,omitempty"`
|
||||
RetryBackoff int `json:"retry_backoff_ms,omitempty"`
|
||||
TimeoutSec int `json:"timeout_sec,omitempty"`
|
||||
MaxTaskChars int `json:"max_task_chars,omitempty"`
|
||||
MaxResultChars int `json:"max_result_chars,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
ManagedBy string `json:"managed_by,omitempty"`
|
||||
AgentID string `json:"agent_id"`
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role,omitempty"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty"`
|
||||
SystemPromptFile string `json:"system_prompt_file,omitempty"`
|
||||
ToolAllowlist []string `json:"tool_allowlist,omitempty"`
|
||||
MemoryNamespace string `json:"memory_namespace,omitempty"`
|
||||
MaxRetries int `json:"max_retries,omitempty"`
|
||||
RetryBackoff int `json:"retry_backoff_ms,omitempty"`
|
||||
TimeoutSec int `json:"timeout_sec,omitempty"`
|
||||
MaxTaskChars int `json:"max_task_chars,omitempty"`
|
||||
MaxResultChars int `json:"max_result_chars,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
ManagedBy string `json:"managed_by,omitempty"`
|
||||
}
|
||||
|
||||
type SubagentProfileStore struct {
|
||||
@@ -176,6 +177,7 @@ func normalizeSubagentProfile(in SubagentProfile) SubagentProfile {
|
||||
}
|
||||
p.Role = strings.TrimSpace(p.Role)
|
||||
p.SystemPrompt = strings.TrimSpace(p.SystemPrompt)
|
||||
p.SystemPromptFile = strings.TrimSpace(p.SystemPromptFile)
|
||||
p.MemoryNamespace = normalizeSubagentIdentifier(p.MemoryNamespace)
|
||||
if p.MemoryNamespace == "" {
|
||||
p.MemoryNamespace = p.AgentID
|
||||
@@ -343,19 +345,20 @@ func profileFromConfig(agentID string, subcfg config.SubagentConfig) SubagentPro
|
||||
status = "disabled"
|
||||
}
|
||||
return normalizeSubagentProfile(SubagentProfile{
|
||||
AgentID: agentID,
|
||||
Name: strings.TrimSpace(subcfg.DisplayName),
|
||||
Role: strings.TrimSpace(subcfg.Role),
|
||||
SystemPrompt: strings.TrimSpace(subcfg.SystemPrompt),
|
||||
ToolAllowlist: append([]string(nil), subcfg.Tools.Allowlist...),
|
||||
MemoryNamespace: strings.TrimSpace(subcfg.MemoryNamespace),
|
||||
MaxRetries: subcfg.Runtime.MaxRetries,
|
||||
RetryBackoff: subcfg.Runtime.RetryBackoffMs,
|
||||
TimeoutSec: subcfg.Runtime.TimeoutSec,
|
||||
MaxTaskChars: subcfg.Runtime.MaxTaskChars,
|
||||
MaxResultChars: subcfg.Runtime.MaxResultChars,
|
||||
Status: status,
|
||||
ManagedBy: "config.json",
|
||||
AgentID: agentID,
|
||||
Name: strings.TrimSpace(subcfg.DisplayName),
|
||||
Role: strings.TrimSpace(subcfg.Role),
|
||||
SystemPrompt: strings.TrimSpace(subcfg.SystemPrompt),
|
||||
SystemPromptFile: strings.TrimSpace(subcfg.SystemPromptFile),
|
||||
ToolAllowlist: append([]string(nil), subcfg.Tools.Allowlist...),
|
||||
MemoryNamespace: strings.TrimSpace(subcfg.MemoryNamespace),
|
||||
MaxRetries: subcfg.Runtime.MaxRetries,
|
||||
RetryBackoff: subcfg.Runtime.RetryBackoffMs,
|
||||
TimeoutSec: subcfg.Runtime.TimeoutSec,
|
||||
MaxTaskChars: subcfg.Runtime.MaxTaskChars,
|
||||
MaxResultChars: subcfg.Runtime.MaxResultChars,
|
||||
Status: status,
|
||||
ManagedBy: "config.json",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -382,11 +385,12 @@ func (t *SubagentProfileTool) Parameters() map[string]interface{} {
|
||||
"type": "string",
|
||||
"description": "Unique subagent id, e.g. coder/writer/tester",
|
||||
},
|
||||
"name": map[string]interface{}{"type": "string"},
|
||||
"role": map[string]interface{}{"type": "string"},
|
||||
"system_prompt": map[string]interface{}{"type": "string"},
|
||||
"memory_namespace": map[string]interface{}{"type": "string"},
|
||||
"status": map[string]interface{}{"type": "string", "description": "active|disabled"},
|
||||
"name": map[string]interface{}{"type": "string"},
|
||||
"role": map[string]interface{}{"type": "string"},
|
||||
"system_prompt": map[string]interface{}{"type": "string"},
|
||||
"system_prompt_file": map[string]interface{}{"type": "string"},
|
||||
"memory_namespace": map[string]interface{}{"type": "string"},
|
||||
"status": map[string]interface{}{"type": "string", "description": "active|disabled"},
|
||||
"tool_allowlist": map[string]interface{}{
|
||||
"type": "array",
|
||||
"description": "Tool allowlist entries. Supports tool names, '*'/'all', and grouped tokens like 'group:files_read' or '@pipeline'.",
|
||||
@@ -450,18 +454,19 @@ func (t *SubagentProfileTool) Execute(ctx context.Context, args map[string]inter
|
||||
return "subagent profile already exists", nil
|
||||
}
|
||||
p := SubagentProfile{
|
||||
AgentID: agentID,
|
||||
Name: stringArg(args, "name"),
|
||||
Role: stringArg(args, "role"),
|
||||
SystemPrompt: stringArg(args, "system_prompt"),
|
||||
MemoryNamespace: stringArg(args, "memory_namespace"),
|
||||
Status: stringArg(args, "status"),
|
||||
ToolAllowlist: parseStringList(args["tool_allowlist"]),
|
||||
MaxRetries: profileIntArg(args, "max_retries"),
|
||||
RetryBackoff: profileIntArg(args, "retry_backoff_ms"),
|
||||
TimeoutSec: profileIntArg(args, "timeout_sec"),
|
||||
MaxTaskChars: profileIntArg(args, "max_task_chars"),
|
||||
MaxResultChars: profileIntArg(args, "max_result_chars"),
|
||||
AgentID: agentID,
|
||||
Name: stringArg(args, "name"),
|
||||
Role: stringArg(args, "role"),
|
||||
SystemPrompt: stringArg(args, "system_prompt"),
|
||||
SystemPromptFile: stringArg(args, "system_prompt_file"),
|
||||
MemoryNamespace: stringArg(args, "memory_namespace"),
|
||||
Status: stringArg(args, "status"),
|
||||
ToolAllowlist: parseStringList(args["tool_allowlist"]),
|
||||
MaxRetries: profileIntArg(args, "max_retries"),
|
||||
RetryBackoff: profileIntArg(args, "retry_backoff_ms"),
|
||||
TimeoutSec: profileIntArg(args, "timeout_sec"),
|
||||
MaxTaskChars: profileIntArg(args, "max_task_chars"),
|
||||
MaxResultChars: profileIntArg(args, "max_result_chars"),
|
||||
}
|
||||
saved, err := t.store.Upsert(p)
|
||||
if err != nil {
|
||||
@@ -489,6 +494,9 @@ func (t *SubagentProfileTool) Execute(ctx context.Context, args map[string]inter
|
||||
if _, ok := args["system_prompt"]; ok {
|
||||
next.SystemPrompt = stringArg(args, "system_prompt")
|
||||
}
|
||||
if _, ok := args["system_prompt_file"]; ok {
|
||||
next.SystemPromptFile = stringArg(args, "system_prompt_file")
|
||||
}
|
||||
if _, ok := args["memory_namespace"]; ok {
|
||||
next.MemoryNamespace = stringArg(args, "memory_namespace")
|
||||
}
|
||||
|
||||
@@ -122,11 +122,12 @@ func TestSubagentProfileStoreReadsProfilesFromRuntimeConfig(t *testing.T) {
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Agents.Subagents["coder"] = config.SubagentConfig{
|
||||
Enabled: true,
|
||||
DisplayName: "Code Agent",
|
||||
Role: "coding",
|
||||
SystemPrompt: "write code",
|
||||
MemoryNamespace: "code-ns",
|
||||
Enabled: true,
|
||||
DisplayName: "Code Agent",
|
||||
Role: "coding",
|
||||
SystemPrompt: "write code",
|
||||
SystemPromptFile: "agents/coder/AGENT.md",
|
||||
MemoryNamespace: "code-ns",
|
||||
Tools: config.SubagentToolsConfig{
|
||||
Allowlist: []string{"read_file", "shell"},
|
||||
},
|
||||
@@ -154,6 +155,9 @@ func TestSubagentProfileStoreReadsProfilesFromRuntimeConfig(t *testing.T) {
|
||||
if profile.Name != "Code Agent" || profile.Role != "coding" {
|
||||
t.Fatalf("unexpected profile fields: %+v", profile)
|
||||
}
|
||||
if profile.SystemPromptFile != "agents/coder/AGENT.md" {
|
||||
t.Fatalf("expected system_prompt_file from config, got: %s", profile.SystemPromptFile)
|
||||
}
|
||||
if len(profile.ToolAllowlist) != 2 {
|
||||
t.Fatalf("expected merged allowlist, got: %v", profile.ToolAllowlist)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@ package tools
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
func TestSubagentSpawnEnforcesTaskQuota(t *testing.T) {
|
||||
@@ -480,3 +483,58 @@ func waitSubagentDone(t *testing.T, manager *SubagentManager, timeout time.Durat
|
||||
t.Fatalf("timeout waiting for subagent completion")
|
||||
return nil
|
||||
}
|
||||
|
||||
type captureProvider struct {
|
||||
messages []providers.Message
|
||||
}
|
||||
|
||||
func (p *captureProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, options map[string]interface{}) (*providers.LLMResponse, error) {
|
||||
p.messages = append([]providers.Message(nil), messages...)
|
||||
return &providers.LLMResponse{Content: "ok", FinishReason: "stop"}, nil
|
||||
}
|
||||
|
||||
func (p *captureProvider) GetDefaultModel() string { return "test-model" }
|
||||
|
||||
func TestSubagentUsesConfiguredSystemPromptFile(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.md"), []byte("workspace-policy"), 0644); err != nil {
|
||||
t.Fatalf("write workspace AGENTS failed: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(workspace, "agents", "coder", "AGENT.md"), []byte("coder-policy-from-file"), 0644); err != nil {
|
||||
t.Fatalf("write coder AGENT failed: %v", err)
|
||||
}
|
||||
provider := &captureProvider{}
|
||||
manager := NewSubagentManager(provider, workspace, nil, nil)
|
||||
if _, err := manager.ProfileStore().Upsert(SubagentProfile{
|
||||
AgentID: "coder",
|
||||
Status: "active",
|
||||
SystemPrompt: "inline-fallback",
|
||||
SystemPromptFile: "agents/coder/AGENT.md",
|
||||
}); err != nil {
|
||||
t.Fatalf("profile upsert failed: %v", err)
|
||||
}
|
||||
|
||||
_, err := manager.Spawn(context.Background(), SubagentSpawnOptions{
|
||||
Task: "implement feature",
|
||||
AgentID: "coder",
|
||||
OriginChannel: "cli",
|
||||
OriginChatID: "direct",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("spawn failed: %v", err)
|
||||
}
|
||||
_ = waitSubagentDone(t, manager, 4*time.Second)
|
||||
if len(provider.messages) == 0 {
|
||||
t.Fatalf("expected provider to receive messages")
|
||||
}
|
||||
systemPrompt := provider.messages[0].Content
|
||||
if !strings.Contains(systemPrompt, "coder-policy-from-file") {
|
||||
t.Fatalf("expected system prompt to include configured file content, got: %s", systemPrompt)
|
||||
}
|
||||
if strings.Contains(systemPrompt, "inline-fallback") {
|
||||
t.Fatalf("expected configured file content to take precedence over inline prompt, got: %s", systemPrompt)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user