mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-12 06:17:31 +08:00
Remove heuristic subagent config shortcut
This commit is contained in:
@@ -19,7 +19,7 @@ import (
|
||||
//go:embed workspace
|
||||
var embeddedFiles embed.FS
|
||||
|
||||
var version = "0.2.1"
|
||||
var version = "0.0.2"
|
||||
var buildTime = "unknown"
|
||||
|
||||
const logo = ">"
|
||||
|
||||
@@ -1201,25 +1201,6 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||
} else {
|
||||
specTaskRef = normalizeSpecCodingTaskRef(taskRef)
|
||||
}
|
||||
if configAction, handled, configErr := al.maybeHandleSubagentConfigIntent(ctx, msg); handled {
|
||||
if configErr != nil && specTaskRef.Summary != "" {
|
||||
if err := al.maybeReopenSpecCodingTask(specTaskRef, msg.Content, configErr.Error()); err != nil {
|
||||
logger.WarnCF("agent", logger.C0172, map[string]interface{}{
|
||||
"session_key": msg.SessionKey,
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
if configErr == nil && specTaskRef.Summary != "" {
|
||||
if err := al.maybeCompleteSpecCodingTask(specTaskRef, configAction); err != nil {
|
||||
logger.WarnCF("agent", logger.C0172, map[string]interface{}{
|
||||
"session_key": msg.SessionKey,
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return configAction, configErr
|
||||
}
|
||||
if routed, ok, routeErr := al.maybeAutoRoute(ctx, msg); ok {
|
||||
if routeErr != nil && specTaskRef.Summary != "" {
|
||||
if err := al.maybeReopenSpecCodingTask(specTaskRef, msg.Content, routeErr.Error()); err != nil {
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
)
|
||||
|
||||
func (al *AgentLoop) maybeHandleSubagentConfigIntent(ctx context.Context, msg bus.InboundMessage) (string, bool, error) {
|
||||
_ = ctx
|
||||
if al == nil {
|
||||
return "", false, nil
|
||||
}
|
||||
if msg.Channel == "system" || msg.Channel == "internal" {
|
||||
return "", false, nil
|
||||
}
|
||||
content := strings.TrimSpace(msg.Content)
|
||||
if content == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
if !looksLikeSubagentCreateRequest(content) {
|
||||
return "", false, nil
|
||||
}
|
||||
description := extractSubagentDescription(content)
|
||||
if description == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
draft := tools.DraftConfigSubagent(description, "")
|
||||
result, err := tools.UpsertConfigSubagent(al.configPath, draft)
|
||||
if err != nil {
|
||||
return "", true, fmt.Errorf("persist subagent config to %s failed: %w", al.displayConfigPath(), err)
|
||||
}
|
||||
return formatCreatedSubagentForUser(result, al.displayConfigPath()), true, nil
|
||||
}
|
||||
|
||||
func looksLikeSubagentCreateRequest(content string) bool {
|
||||
lower := strings.ToLower(strings.TrimSpace(content))
|
||||
if lower == "" {
|
||||
return false
|
||||
}
|
||||
createMarkers := []string{
|
||||
"创建", "新建", "增加", "添加", "配置一个", "生成一个",
|
||||
"create", "add", "new",
|
||||
}
|
||||
subagentMarkers := []string{
|
||||
"subagent", "sub-agent", "agent", "子代理", "子 agent", "工作代理",
|
||||
}
|
||||
hasCreate := false
|
||||
for _, item := range createMarkers {
|
||||
if strings.Contains(lower, item) {
|
||||
hasCreate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasCreate {
|
||||
return false
|
||||
}
|
||||
for _, item := range subagentMarkers {
|
||||
if strings.Contains(lower, item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func extractSubagentDescription(content string) string {
|
||||
content = strings.TrimSpace(content)
|
||||
replacers := []string{
|
||||
"请", "帮我", "给我", "创建", "新建", "增加", "添加", "配置", "生成",
|
||||
"a ", "an ", "new ", "create ", "add ",
|
||||
}
|
||||
out := content
|
||||
for _, item := range replacers {
|
||||
out = strings.ReplaceAll(out, item, "")
|
||||
}
|
||||
out = strings.ReplaceAll(out, "子代理", "")
|
||||
out = strings.ReplaceAll(out, "subagent", "")
|
||||
out = strings.ReplaceAll(out, "sub-agent", "")
|
||||
out = strings.TrimSpace(out)
|
||||
if out == "" {
|
||||
return strings.TrimSpace(content)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func formatCreatedSubagentForUser(result map[string]interface{}, configPath string) string {
|
||||
return fmt.Sprintf(
|
||||
"subagent 已写入 config.json。\npath: %s\nagent_id: %v",
|
||||
configPath,
|
||||
result["agent_id"],
|
||||
)
|
||||
}
|
||||
|
||||
func (al *AgentLoop) displayConfigPath() string {
|
||||
if al == nil || strings.TrimSpace(al.configPath) == "" {
|
||||
return "config path not configured"
|
||||
}
|
||||
if abs, err := filepath.Abs(al.configPath); err == nil {
|
||||
return abs
|
||||
}
|
||||
return strings.TrimSpace(al.configPath)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatCreatedSubagentForUserReadsNestedFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
out := formatCreatedSubagentForUser(map[string]interface{}{
|
||||
"agent_id": "coder",
|
||||
"subagent": map[string]interface{}{
|
||||
"role": "coding",
|
||||
"display_name": "Code Agent",
|
||||
"system_prompt_file": "agents/coder/AGENT.md",
|
||||
"tools": map[string]interface{}{
|
||||
"allowlist": []interface{}{"filesystem", "shell"},
|
||||
},
|
||||
},
|
||||
"rules": []interface{}{
|
||||
map[string]interface{}{
|
||||
"agent_id": "coder",
|
||||
"keywords": []interface{}{"code", "fix"},
|
||||
},
|
||||
},
|
||||
}, "/tmp/config.json")
|
||||
|
||||
for _, want := range []string{
|
||||
"subagent 已写入 config.json。",
|
||||
"path: /tmp/config.json",
|
||||
"agent_id: coder",
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Fatalf("expected output to contain %q, got:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
for _, unwanted := range []string{
|
||||
"role:",
|
||||
"display_name:",
|
||||
"tool_allowlist:",
|
||||
"routing_keywords:",
|
||||
"system_prompt_file:",
|
||||
"<nil>",
|
||||
} {
|
||||
if strings.Contains(out, unwanted) {
|
||||
t.Fatalf("did not expect %q in output, got:\n%s", unwanted, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,31 +10,6 @@ import (
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
)
|
||||
|
||||
func DraftConfigSubagent(description, agentIDHint string) map[string]interface{} {
|
||||
desc := strings.TrimSpace(description)
|
||||
lower := strings.ToLower(desc)
|
||||
role := inferDraftRole(lower)
|
||||
agentID := strings.TrimSpace(agentIDHint)
|
||||
if agentID == "" {
|
||||
agentID = inferDraftAgentID(role, lower)
|
||||
}
|
||||
displayName := inferDraftDisplayName(role, agentID)
|
||||
toolAllowlist := inferDraftToolAllowlist(role)
|
||||
keywords := inferDraftKeywords(role, lower)
|
||||
return map[string]interface{}{
|
||||
"agent_id": agentID,
|
||||
"role": role,
|
||||
"display_name": displayName,
|
||||
"description": desc,
|
||||
"notify_main_policy": "final_only",
|
||||
"system_prompt_file": "agents/" + agentID + "/AGENT.md",
|
||||
"memory_namespace": agentID,
|
||||
"tool_allowlist": toolAllowlist,
|
||||
"routing_keywords": keywords,
|
||||
"type": "worker",
|
||||
}
|
||||
}
|
||||
|
||||
func UpsertConfigSubagent(configPath string, args map[string]interface{}) (map[string]interface{}, error) {
|
||||
configPath = strings.TrimSpace(configPath)
|
||||
if configPath == "" {
|
||||
@@ -284,113 +259,3 @@ func normalizeKeywords(items []string) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func inferDraftRole(lower string) string {
|
||||
switch {
|
||||
case containsDraftKeyword(lower, "test", "regression", "qa", "鍥炲綊", "娴嬭瘯", "楠岃瘉"):
|
||||
return "testing"
|
||||
case containsDraftKeyword(lower, "doc", "docs", "readme", "鏂囨。", "璇存槑"):
|
||||
return "docs"
|
||||
case containsDraftKeyword(lower, "research", "investigate", "analyze", "璋冪爺", "鍒嗘瀽", "鐮旂┒"):
|
||||
return "research"
|
||||
default:
|
||||
return "coding"
|
||||
}
|
||||
}
|
||||
|
||||
func inferDraftAgentID(role, lower string) string {
|
||||
switch role {
|
||||
case "testing":
|
||||
if containsDraftKeyword(lower, "review", "瀹℃煡", "reviewer") {
|
||||
return "reviewer"
|
||||
}
|
||||
return "tester"
|
||||
case "docs":
|
||||
return "doc_writer"
|
||||
case "research":
|
||||
return "researcher"
|
||||
default:
|
||||
if containsDraftKeyword(lower, "frontend", "ui", "鍓嶇") {
|
||||
return "frontend-coder"
|
||||
}
|
||||
if containsDraftKeyword(lower, "backend", "api", "鍚庣") {
|
||||
return "backend-coder"
|
||||
}
|
||||
return "coder"
|
||||
}
|
||||
}
|
||||
|
||||
func inferDraftDisplayName(role, agentID string) string {
|
||||
switch role {
|
||||
case "testing":
|
||||
return "Test Agent"
|
||||
case "docs":
|
||||
return "Docs Agent"
|
||||
case "research":
|
||||
return "Research Agent"
|
||||
default:
|
||||
if strings.Contains(agentID, "frontend") {
|
||||
return "Frontend Code Agent"
|
||||
}
|
||||
if strings.Contains(agentID, "backend") {
|
||||
return "Backend Code Agent"
|
||||
}
|
||||
return "Code Agent"
|
||||
}
|
||||
}
|
||||
|
||||
func inferDraftToolAllowlist(role string) []string {
|
||||
switch role {
|
||||
case "testing":
|
||||
return []string{"shell", "filesystem", "process_manager", "sessions"}
|
||||
case "docs":
|
||||
return []string{"filesystem", "read_file", "write_file", "edit_file", "repo_map", "sessions"}
|
||||
case "research":
|
||||
return []string{"web_search", "web_fetch", "repo_map", "sessions", "memory_search"}
|
||||
default:
|
||||
return []string{"filesystem", "shell", "repo_map", "sessions"}
|
||||
}
|
||||
}
|
||||
|
||||
func inferDraftKeywords(role, lower string) []string {
|
||||
seed := []string{}
|
||||
switch role {
|
||||
case "testing":
|
||||
seed = []string{"test", "regression", "verify", "鍥炲綊", "娴嬭瘯", "楠岃瘉"}
|
||||
case "docs":
|
||||
seed = []string{"docs", "readme", "document", "鏂囨。", "璇存槑"}
|
||||
case "research":
|
||||
seed = []string{"research", "analyze", "investigate", "璋冪爺", "鍒嗘瀽", "鐮旂┒"}
|
||||
default:
|
||||
seed = []string{"code", "implement", "fix", "refactor", "浠g爜", "瀹炵幇", "淇", "閲嶆瀯"}
|
||||
}
|
||||
if containsDraftKeyword(lower, "frontend", "鍓嶇", "ui") {
|
||||
seed = append(seed, "frontend", "ui", "鍓嶇")
|
||||
}
|
||||
if containsDraftKeyword(lower, "backend", "鍚庣", "api") {
|
||||
seed = append(seed, "backend", "api", "鍚庣")
|
||||
}
|
||||
return normalizeKeywords(seed)
|
||||
}
|
||||
|
||||
func inferDraftSystemPrompt(role, description string) string {
|
||||
switch role {
|
||||
case "testing":
|
||||
return "浣犺礋璐f祴璇曘€侀獙璇併€佸洖褰掓鏌ヤ笌椋庨櫓鍙嶉銆備换鍔℃弿杩帮細" + description
|
||||
case "docs":
|
||||
return "浣犺礋璐f枃妗g紪鍐欍€佺粨鏋勬暣鐞嗗拰璇存槑琛ュ叏銆備换鍔℃弿杩帮細" + description
|
||||
case "research":
|
||||
return "浣犺礋璐h皟鐮斻€佸垎鏋愩€佹瘮杈冩柟妗堬紝骞惰緭鍑虹粨璁轰笌渚濇嵁銆備换鍔℃弿杩帮細" + description
|
||||
default:
|
||||
return "浣犺礋璐d唬鐮佸疄鐜颁笌閲嶆瀯锛岃緭鍑哄叿浣撲慨鏀瑰缓璁拰鍙樻洿缁撴灉銆備换鍔℃弿杩帮細" + description
|
||||
}
|
||||
}
|
||||
|
||||
func containsDraftKeyword(text string, items ...string) bool {
|
||||
for _, item := range items {
|
||||
if strings.Contains(text, strings.ToLower(strings.TrimSpace(item))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user