From 06c7f2b53f5a79c2ff04cf3e04a62fc869143fa0 Mon Sep 17 00:00:00 2001 From: lpf Date: Thu, 26 Feb 2026 15:45:51 +0800 Subject: [PATCH] fix --- pkg/agent/context.go | 4 +- pkg/agent/language.go | 6 +-- pkg/agent/loop.go | 41 +++++++++--------- pkg/autonomy/engine.go | 46 ++++++++++----------- pkg/config/config.go | 80 ++++++++++++++++++------------------ pkg/nodes/registry_server.go | 20 ++++----- pkg/session/manager.go | 6 +-- 7 files changed, 101 insertions(+), 102 deletions(-) diff --git a/pkg/agent/context.go b/pkg/agent/context.go index 0200961..b81af7f 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -29,8 +29,8 @@ func getGlobalConfigDir() string { } func NewContextBuilder(workspace string, toolsSummaryFunc func() []string) *ContextBuilder { - // builtin skills: 当前项目的 skills 目录 - // 使用当前工作目录下的 skills/ 目录 + // Built-in skills: use the current project's skills directory. + // Resolve from current working directory: skills/ wd, _ := os.Getwd() builtinSkillsDir := filepath.Join(wd, "skills") globalSkillsDir := filepath.Join(getGlobalConfigDir(), "skills") diff --git a/pkg/agent/language.go b/pkg/agent/language.go index e33a8de..3130bb5 100644 --- a/pkg/agent/language.go +++ b/pkg/agent/language.go @@ -79,9 +79,9 @@ func ExtractLanguagePreference(text string) string { return "" } - enHints := []string{"speak english", "reply in english", "use english", "以后用英文", "请用英文", "用英文"} - zhHints := []string{"说中文", "用中文", "请用中文", "reply in chinese", "speak chinese"} - jaHints := []string{"日本語", "reply in japanese", "speak japanese"} + enHints := []string{"speak english", "reply in english", "use english"} + zhHints := []string{"reply in chinese", "speak chinese", "use chinese"} + jaHints := []string{"reply in japanese", "speak japanese", "use japanese"} koHints := []string{"한국어", "reply in korean", "speak korean"} for _, h := range enHints { diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 24bba38..a24f152 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -235,9 +235,9 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers sessionRunLocks: map[string]*sync.Mutex{}, } - // 注入递归运行逻辑,使 subagent 具备 full tool-calling 能力 + // Inject recursive run logic so subagents can use full tool-calling flows. subagentManager.SetRunFunc(func(ctx context.Context, task, channel, chatID string) (string, error) { - sessionKey := fmt.Sprintf("subagent:%d", os.Getpid()) // 改用 PID 或随机数,避免 sessionKey 冲突 + sessionKey := fmt.Sprintf("subagent:%d", os.Getpid()) // Use PID/randomized key to reduce session key collisions. return loop.ProcessDirect(ctx, task, sessionKey) }) @@ -629,7 +629,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) }) } messages = append(messages, assistantMsg) - // 持久化包含 ToolCalls 的助手消息 + // Persist assistant message with tool calls. al.sessions.AddMessageFull(msg.SessionKey, assistantMsg) for _, tc := range response.ToolCalls { @@ -652,7 +652,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) ToolCallID: tc.ID, } messages = append(messages, toolResultMsg) - // 持久化工具返回结果 + // Persist tool result message. al.sessions.AddMessageFull(msg.SessionKey, toolResultMsg) } } @@ -673,7 +673,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) if iteration == 1 { fallback := strings.TrimSpace(al.thinkOnlyFallback) if fallback == "" { - fallback = "已完成思考流程。" + fallback = "Thinking process completed." } userContent = fallback } @@ -686,7 +686,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) } al.sessions.AddMessage(msg.SessionKey, "user", msg.Content) - // 使用 AddMessageFull 存储包含思考过程或工具调用的完整助手消息 + // Persist full assistant response (including reasoning/tool flow outcomes when present). al.sessions.AddMessageFull(msg.SessionKey, providers.Message{ Role: "assistant", Content: userContent, @@ -716,15 +716,15 @@ func (al *AgentLoop) updateIntentHint(sessionKey, content string) { lower := strings.ToLower(content) // Cron natural-language intent: avoid searching project files for user timer ops. - if strings.Contains(lower, "定时") || strings.Contains(lower, "定时任务") || strings.Contains(lower, "cron") || strings.Contains(lower, "schedule") { - hint := "优先使用 cron 工具处理定时任务:查看=action=list;删除=action=delete(id);启停=action=enable/disable。不要改为在项目目录中搜索 cron 文本。" + if strings.Contains(lower, "cron") || strings.Contains(lower, "schedule") || strings.Contains(lower, "timer") || strings.Contains(lower, "reminder") { + hint := "Prioritize the cron tool for timer operations: list=action=list; delete=action=delete(id); enable/disable=action=enable/disable. Do not switch to grepping project files for cron text." al.intentMu.Lock() - al.intentHints[sessionKey] = hint + " 用户补充=" + content + al.intentHints[sessionKey] = hint + " User details=" + content al.intentMu.Unlock() return } - if !strings.Contains(lower, "提交") && !strings.Contains(lower, "推送") && !strings.Contains(lower, "commit") && !strings.Contains(lower, "push") { + if !strings.Contains(lower, "commit") && !strings.Contains(lower, "push") { if strings.HasPrefix(content, "1.") || strings.HasPrefix(content, "2.") { al.intentMu.Lock() if prev := strings.TrimSpace(al.intentHints[sessionKey]); prev != "" { @@ -734,12 +734,12 @@ func (al *AgentLoop) updateIntentHint(sessionKey, content string) { } return } - hint := "执行事务: commit+push 一次闭环,包含分支/范围确认。" - if strings.Contains(lower, "所有分支") || strings.Contains(lower, "all branches") { - hint += " 范围=所有分支。" + hint := "Execute as one transaction: complete commit+push in one pass with branch/scope confirmation." + if strings.Contains(lower, "all branches") { + hint += " Scope=all branches." } al.intentMu.Lock() - al.intentHints[sessionKey] = hint + " 用户补充=" + content + al.intentHints[sessionKey] = hint + " User details=" + content al.intentMu.Unlock() } @@ -751,7 +751,7 @@ func (al *AgentLoop) applyIntentHint(sessionKey, content string) string { return content } lower := strings.ToLower(strings.TrimSpace(content)) - if strings.Contains(lower, "提交") || strings.Contains(lower, "推送") || strings.HasPrefix(content, "1.") || strings.HasPrefix(content, "2.") || strings.Contains(lower, "定时") || strings.Contains(lower, "cron") || strings.Contains(lower, "schedule") { + if strings.Contains(lower, "commit") || strings.Contains(lower, "push") || strings.HasPrefix(content, "1.") || strings.HasPrefix(content, "2.") || strings.Contains(lower, "cron") || strings.Contains(lower, "schedule") || strings.Contains(lower, "timer") || strings.Contains(lower, "reminder") { return "[Intent Slot]\n" + hint + "\n\n[User Message]\n" + content } return content @@ -887,7 +887,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe }) } messages = append(messages, assistantMsg) - // 持久化包含 ToolCalls 的助手消息 + // Persist assistant message with tool calls. al.sessions.AddMessageFull(sessionKey, assistantMsg) for _, tc := range response.ToolCalls { @@ -902,7 +902,7 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe ToolCallID: tc.ID, } messages = append(messages, toolResultMsg) - // 持久化工具返回结果 + // Persist tool result message. al.sessions.AddMessageFull(sessionKey, toolResultMsg) } } @@ -914,9 +914,8 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe // Save to session with system message marker al.sessions.AddMessage(sessionKey, "user", fmt.Sprintf("[System: %s] %s", msg.SenderID, msg.Content)) - // 如果 finalContent 中没有包含 tool calls (即最后一次 LLM 返回的结果) - // 我们已经通过循环内部的 AddMessageFull 存储了前面的步骤 - // 这里的 AddMessageFull 会存储最终回复 + // If finalContent has no tool calls (last LLM turn is direct text), + // earlier steps were already persisted in-loop; this stores the final reply. al.sessions.AddMessageFull(sessionKey, providers.Message{ Role: "assistant", Content: finalContent, @@ -1110,7 +1109,7 @@ func shouldRecallMemory(text string, keywords []string) bool { return false } if len(keywords) == 0 { - keywords = []string{"remember", "记得", "上次", "之前", "偏好", "preference", "todo", "待办", "决定", "decision", "日期", "when did", "what did we"} + keywords = []string{"remember", "preference", "todo", "decision", "date", "when did", "what did we"} } for _, k := range keywords { kk := strings.ToLower(strings.TrimSpace(k)) diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index 6af10a3..5ec520a 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -20,23 +20,23 @@ import ( ) type Options struct { - Enabled bool - TickIntervalSec int - MinRunIntervalSec int - MaxPendingDurationSec int - MaxConsecutiveStalls int - MaxDispatchPerTick int - Workspace string - DefaultNotifyChannel string - DefaultNotifyChatID string - NotifyCooldownSec int + Enabled bool + TickIntervalSec int + MinRunIntervalSec int + MaxPendingDurationSec int + MaxConsecutiveStalls int + MaxDispatchPerTick int + Workspace string + DefaultNotifyChannel string + DefaultNotifyChatID string + NotifyCooldownSec int NotifySameReasonCooldownSec int - QuietHours string - UserIdleResumeSec int - WaitingResumeDebounceSec int - ImportantKeywords []string - CompletionTemplate string - BlockedTemplate string + QuietHours string + UserIdleResumeSec int + WaitingResumeDebounceSec int + ImportantKeywords []string + CompletionTemplate string + BlockedTemplate string } type taskState struct { @@ -436,10 +436,10 @@ func normalizeResourceKeys(keys []string) []string { } type todoItem struct { - ID string - Content string - Priority string - DueAt string + ID string + Content string + Priority string + DueAt string DedupeHits int } @@ -549,7 +549,7 @@ func (e *Engine) sendCompletionNotification(st *taskState) { } tpl := strings.TrimSpace(e.opts.CompletionTemplate) if tpl == "" { - tpl = "✅ 已完成:%s\n下一步:如需我继续处理后续,直接回复“继续 %s”" + tpl = "✅ Completed: %s\nNext step: reply \"continue %s\" if you want me to proceed." } e.bus.PublishOutbound(bus.OutboundMessage{ Channel: e.opts.DefaultNotifyChannel, @@ -566,7 +566,7 @@ func (e *Engine) sendFailureNotification(st *taskState, reason string) { } tpl := strings.TrimSpace(e.opts.BlockedTemplate) if tpl == "" { - tpl = "⚠️ 任务受阻:%s\n原因:%s\n建议:回复“继续 %s”我会按当前状态重试。" + tpl = "⚠️ Task blocked: %s\nReason: %s\nSuggestion: reply \"continue %s\" and I will retry from current state." } e.bus.PublishOutbound(bus.OutboundMessage{ Channel: e.opts.DefaultNotifyChannel, @@ -942,7 +942,7 @@ func (e *Engine) isHighValueCompletion(st *taskState) bool { s := strings.ToLower(strings.TrimSpace(st.Content)) keywords := e.opts.ImportantKeywords if len(keywords) == 0 { - keywords = []string{"urgent", "重要", "付款", "payment", "上线", "release", "deadline", "截止"} + keywords = []string{"urgent", "payment", "release", "deadline", "p0", "asap"} } for _, k := range keywords { kk := strings.ToLower(strings.TrimSpace(k)) diff --git a/pkg/config/config.go b/pkg/config/config.go index f2d9dff..cbb9b09 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -45,19 +45,19 @@ type AgentDefaults struct { } type AutonomyConfig struct { - Enabled bool `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_ENABLED"` - TickIntervalSec int `json:"tick_interval_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_TICK_INTERVAL_SEC"` - MinRunIntervalSec int `json:"min_run_interval_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MIN_RUN_INTERVAL_SEC"` - MaxPendingDurationSec int `json:"max_pending_duration_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_PENDING_DURATION_SEC"` - MaxConsecutiveStalls int `json:"max_consecutive_stalls" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_CONSECUTIVE_STALLS"` - MaxDispatchPerTick int `json:"max_dispatch_per_tick" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_DISPATCH_PER_TICK"` - NotifyCooldownSec int `json:"notify_cooldown_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_COOLDOWN_SEC"` - NotifySameReasonCooldownSec int `json:"notify_same_reason_cooldown_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_SAME_REASON_COOLDOWN_SEC"` - QuietHours string `json:"quiet_hours" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_QUIET_HOURS"` - UserIdleResumeSec int `json:"user_idle_resume_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_USER_IDLE_RESUME_SEC"` - WaitingResumeDebounceSec int `json:"waiting_resume_debounce_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_WAITING_RESUME_DEBOUNCE_SEC"` - NotifyChannel string `json:"notify_channel" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHANNEL"` - NotifyChatID string `json:"notify_chat_id" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHAT_ID"` + Enabled bool `json:"enabled" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_ENABLED"` + TickIntervalSec int `json:"tick_interval_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_TICK_INTERVAL_SEC"` + MinRunIntervalSec int `json:"min_run_interval_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MIN_RUN_INTERVAL_SEC"` + MaxPendingDurationSec int `json:"max_pending_duration_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_PENDING_DURATION_SEC"` + MaxConsecutiveStalls int `json:"max_consecutive_stalls" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_CONSECUTIVE_STALLS"` + MaxDispatchPerTick int `json:"max_dispatch_per_tick" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_DISPATCH_PER_TICK"` + NotifyCooldownSec int `json:"notify_cooldown_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_COOLDOWN_SEC"` + NotifySameReasonCooldownSec int `json:"notify_same_reason_cooldown_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_SAME_REASON_COOLDOWN_SEC"` + QuietHours string `json:"quiet_hours" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_QUIET_HOURS"` + UserIdleResumeSec int `json:"user_idle_resume_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_USER_IDLE_RESUME_SEC"` + WaitingResumeDebounceSec int `json:"waiting_resume_debounce_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_WAITING_RESUME_DEBOUNCE_SEC"` + NotifyChannel string `json:"notify_channel" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHANNEL"` + NotifyChatID string `json:"notify_chat_id" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHAT_ID"` } type AgentTextConfig struct { @@ -304,42 +304,42 @@ func DefaultConfig() *Config { Temperature: 0.7, MaxToolIterations: 20, Heartbeat: HeartbeatConfig{ - Enabled: true, - EverySec: 30 * 60, - AckMaxChars: 64, + Enabled: true, + EverySec: 30 * 60, + AckMaxChars: 64, PromptTemplate: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.", }, Autonomy: AutonomyConfig{ - Enabled: false, - TickIntervalSec: 30, - MinRunIntervalSec: 20, - MaxPendingDurationSec: 180, - MaxConsecutiveStalls: 3, - MaxDispatchPerTick: 2, - NotifyCooldownSec: 300, + Enabled: false, + TickIntervalSec: 30, + MinRunIntervalSec: 20, + MaxPendingDurationSec: 180, + MaxConsecutiveStalls: 3, + MaxDispatchPerTick: 2, + NotifyCooldownSec: 300, NotifySameReasonCooldownSec: 900, - QuietHours: "23:00-08:00", - UserIdleResumeSec: 20, - WaitingResumeDebounceSec: 5, - NotifyChannel: "", - NotifyChatID: "", + QuietHours: "23:00-08:00", + UserIdleResumeSec: 20, + WaitingResumeDebounceSec: 5, + NotifyChannel: "", + NotifyChatID: "", }, Texts: AgentTextConfig{ - NoResponseFallback: "I've completed processing but have no response to give.", - ThinkOnlyFallback: "Thinking process completed.", - MemoryRecallKeywords: []string{"remember", "记得", "上次", "之前", "偏好", "preference", "todo", "待办", "决定", "decision", "日期", "when did", "what did we"}, - LangUsage: "Usage: /lang ", - LangInvalid: "Invalid language code.", - LangUpdatedTemplate: "Language preference updated to %s", - SubagentsNone: "No subagents.", - SessionsNone: "No sessions.", - UnsupportedAction: "unsupported action", + NoResponseFallback: "I've completed processing but have no response to give.", + ThinkOnlyFallback: "Thinking process completed.", + MemoryRecallKeywords: []string{"remember", "previous", "preference", "todo", "decision", "date", "when did", "what did we"}, + LangUsage: "Usage: /lang ", + LangInvalid: "Invalid language code.", + LangUpdatedTemplate: "Language preference updated to %s", + SubagentsNone: "No subagents.", + SessionsNone: "No sessions.", + UnsupportedAction: "unsupported action", SystemRewriteTemplate: "Rewrite the following internal system update in concise user-facing language:\n\n%s", RuntimeCompactionNote: "[runtime-compaction] removed %d old messages, kept %d recent messages", StartupCompactionNote: "[startup-compaction] removed %d old messages, kept %d recent messages", - AutonomyImportantKeywords: []string{"urgent", "重要", "付款", "payment", "上线", "release", "deadline", "截止"}, - AutonomyCompletionTemplate: "✅ 已完成:%s\n回复“继续 %s”可继续下一步。", - AutonomyBlockedTemplate: "⚠️ 任务受阻:%s(%s)\n回复“继续 %s”我会重试。", + AutonomyImportantKeywords: []string{"urgent", "payment", "release", "deadline", "p0", "asap"}, + AutonomyCompletionTemplate: "✅ Completed: %s\nReply \"continue %s\" to proceed to the next step.", + AutonomyBlockedTemplate: "⚠️ Task blocked: %s (%s)\nReply \"continue %s\" and I will retry.", }, ContextCompaction: ContextCompactionConfig{ Enabled: true, diff --git a/pkg/nodes/registry_server.go b/pkg/nodes/registry_server.go index 0f0e16c..9b9f9c0 100644 --- a/pkg/nodes/registry_server.go +++ b/pkg/nodes/registry_server.go @@ -1418,16 +1418,16 @@ func (s *RegistryServer) checkAuth(r *http.Request) bool { func hotReloadFieldInfo() []map[string]interface{} { return []map[string]interface{}{ - {"path": "logging.*", "name": "日志配置", "description": "日志等级、落盘等"}, - {"path": "sentinel.*", "name": "哨兵配置", "description": "健康检查与自动修复"}, - {"path": "agents.*", "name": "Agent 配置", "description": "模型、策略、默认行为"}, - {"path": "providers.*", "name": "Provider 配置", "description": "LLM 提供商与代理"}, - {"path": "tools.*", "name": "工具配置", "description": "工具开关、执行参数"}, - {"path": "channels.*", "name": "渠道配置", "description": "Telegram/其它渠道参数"}, - {"path": "cron.*", "name": "定时任务配置", "description": "cron 全局运行参数"}, - {"path": "agents.defaults.heartbeat.*", "name": "心跳策略", "description": "心跳频率、提示词"}, - {"path": "agents.defaults.autonomy.*", "name": "自治策略", "description": "自治任务开关与限流"}, - {"path": "gateway.*", "name": "网关配置", "description": "多数可热更,监听地址/端口可能需重启"}, + {"path": "logging.*", "name": "Logging", "description": "Log level, persistence, and related settings"}, + {"path": "sentinel.*", "name": "Sentinel", "description": "Health checks and auto-heal behavior"}, + {"path": "agents.*", "name": "Agent", "description": "Models, policies, and default behavior"}, + {"path": "providers.*", "name": "Providers", "description": "LLM providers and proxy settings"}, + {"path": "tools.*", "name": "Tools", "description": "Tool toggles and runtime options"}, + {"path": "channels.*", "name": "Channels", "description": "Telegram and other channel settings"}, + {"path": "cron.*", "name": "Cron", "description": "Global cron runtime settings"}, + {"path": "agents.defaults.heartbeat.*", "name": "Heartbeat", "description": "Heartbeat interval and prompt template"}, + {"path": "agents.defaults.autonomy.*", "name": "Autonomy", "description": "Autonomy toggles and throttling"}, + {"path": "gateway.*", "name": "Gateway", "description": "Mostly hot-reloadable; host/port may require restart"}, } } diff --git a/pkg/session/manager.go b/pkg/session/manager.go index bc70462..50cd8fb 100644 --- a/pkg/session/manager.go +++ b/pkg/session/manager.go @@ -108,7 +108,7 @@ func (sm *SessionManager) AddMessageFull(sessionKey string, msg providers.Messag session.Updated = time.Now() session.mu.Unlock() - // 立即持久化 (Append-only) + // Persist immediately (append-only). sm.appendMessage(sessionKey, msg) } @@ -323,8 +323,8 @@ func toOpenClawMessageEvent(msg providers.Message) openClawEvent { Type: "message", Timestamp: time.Now().UTC().Format(time.RFC3339Nano), Message: &struct { - Role string `json:"role"` - Content []struct { + Role string `json:"role"` + Content []struct { Type string `json:"type"` Text string `json:"text,omitempty"` } `json:"content,omitempty"`