Unify agent topology and subagent memory logging

This commit is contained in:
lpf
2026-03-06 15:14:58 +08:00
parent 86691f75d0
commit cc04d9ab3a
27 changed files with 1408 additions and 791 deletions

View File

@@ -3,6 +3,7 @@ package agent
import (
"context"
"fmt"
"path/filepath"
"strings"
"clawgo/pkg/bus"
@@ -21,12 +22,6 @@ func (al *AgentLoop) maybeHandleSubagentConfigIntent(ctx context.Context, msg bu
if content == "" {
return "", false, nil
}
if isSubagentConfigConfirm(content) {
return al.confirmPendingSubagentDraft(msg.SessionKey)
}
if isSubagentConfigCancel(content) {
return al.cancelPendingSubagentDraft(msg.SessionKey)
}
if !looksLikeSubagentCreateRequest(content) {
return "", false, nil
}
@@ -35,8 +30,11 @@ func (al *AgentLoop) maybeHandleSubagentConfigIntent(ctx context.Context, msg bu
return "", false, nil
}
draft := tools.DraftConfigSubagent(description, "")
al.storePendingSubagentDraft(msg.SessionKey, draft)
return formatSubagentDraftForUser(draft), true, nil
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 {
@@ -69,34 +67,6 @@ func looksLikeSubagentCreateRequest(content string) bool {
return false
}
func isSubagentConfigConfirm(content string) bool {
lower := strings.ToLower(strings.TrimSpace(content))
phrases := []string{
"确认创建", "确认保存", "确认生成", "保存这个子代理", "创建这个子代理",
"confirm create", "confirm save", "save it", "create it",
}
for _, phrase := range phrases {
if lower == phrase || strings.Contains(lower, phrase) {
return true
}
}
return false
}
func isSubagentConfigCancel(content string) bool {
lower := strings.ToLower(strings.TrimSpace(content))
phrases := []string{
"取消创建", "取消保存", "取消这个子代理", "放弃创建",
"cancel create", "cancel save", "discard draft", "never mind",
}
for _, phrase := range phrases {
if lower == phrase || strings.Contains(lower, phrase) {
return true
}
}
return false
}
func extractSubagentDescription(content string) string {
content = strings.TrimSpace(content)
replacers := []string{
@@ -117,93 +87,25 @@ func extractSubagentDescription(content string) string {
return out
}
func formatSubagentDraftForUser(draft map[string]interface{}) string {
func formatCreatedSubagentForUser(result map[string]interface{}, configPath string) string {
return fmt.Sprintf(
"已生成 subagent 草案。\nagent_id: %v\nrole: %v\ndisplay_name: %v\ntool_allowlist: %v\nrouting_keywords: %v\nsystem_prompt: %v\n\n回复“确认创建”会写入 config.json回复“取消创建”会丢弃这个草案。",
draft["agent_id"],
draft["role"],
draft["display_name"],
draft["tool_allowlist"],
draft["routing_keywords"],
draft["system_prompt"],
"subagent 已写入 config.json。\npath: %s\nagent_id: %v\nrole: %v\ndisplay_name: %v\ntool_allowlist: %v\nrouting_keywords: %v\nsystem_prompt_file: %v",
configPath,
result["agent_id"],
result["role"],
result["display_name"],
result["tool_allowlist"],
result["routing_keywords"],
result["system_prompt_file"],
)
}
func (al *AgentLoop) storePendingSubagentDraft(sessionKey string, draft map[string]interface{}) {
if al == nil || draft == nil {
return
func (al *AgentLoop) displayConfigPath() string {
if al == nil || strings.TrimSpace(al.configPath) == "" {
return "config path not configured"
}
if strings.TrimSpace(sessionKey) == "" {
sessionKey = "main"
}
al.streamMu.Lock()
defer al.streamMu.Unlock()
if al.pendingSubagentDraft == nil {
al.pendingSubagentDraft = map[string]map[string]interface{}{}
}
copied := make(map[string]interface{}, len(draft))
for k, v := range draft {
copied[k] = v
}
al.pendingSubagentDraft[sessionKey] = copied
if al.pendingDraftStore != nil {
_ = al.pendingDraftStore.Put(sessionKey, copied)
if abs, err := filepath.Abs(al.configPath); err == nil {
return abs
}
}
func (al *AgentLoop) loadPendingSubagentDraft(sessionKey string) map[string]interface{} {
if al == nil {
return nil
}
if strings.TrimSpace(sessionKey) == "" {
sessionKey = "main"
}
al.streamMu.Lock()
defer al.streamMu.Unlock()
draft := al.pendingSubagentDraft[sessionKey]
if draft == nil {
return nil
}
copied := make(map[string]interface{}, len(draft))
for k, v := range draft {
copied[k] = v
}
return copied
}
func (al *AgentLoop) deletePendingSubagentDraft(sessionKey string) {
if al == nil {
return
}
if strings.TrimSpace(sessionKey) == "" {
sessionKey = "main"
}
al.streamMu.Lock()
defer al.streamMu.Unlock()
delete(al.pendingSubagentDraft, sessionKey)
if al.pendingDraftStore != nil {
_ = al.pendingDraftStore.Delete(sessionKey)
}
}
func (al *AgentLoop) confirmPendingSubagentDraft(sessionKey string) (string, bool, error) {
draft := al.loadPendingSubagentDraft(sessionKey)
if draft == nil {
return "", false, nil
}
result, err := tools.UpsertConfigSubagent(al.configPath, draft)
if err != nil {
return "", true, err
}
al.deletePendingSubagentDraft(sessionKey)
return fmt.Sprintf("subagent 已写入 config.json。\nagent_id: %v\nrules: %v", result["agent_id"], result["rules"]), true, nil
}
func (al *AgentLoop) cancelPendingSubagentDraft(sessionKey string) (string, bool, error) {
draft := al.loadPendingSubagentDraft(sessionKey)
if draft == nil {
return "", false, nil
}
al.deletePendingSubagentDraft(sessionKey)
return "已取消这次 subagent 草案,不会写入 config.json。", true, nil
return strings.TrimSpace(al.configPath)
}