From 2df4a3d588b6f5f2d8b1f3f4cb85c6c99d19d93c Mon Sep 17 00:00:00 2001 From: lpf Date: Wed, 18 Feb 2026 01:15:01 +0800 Subject: [PATCH] fix start check --- cmd/clawgo/main.go | 45 ++++++++++++++++++++++++++ pkg/agent/loop.go | 73 ++++++++++++++++++++++++++++++++++++++++++ pkg/session/manager.go | 17 ++++++++++ 3 files changed, 135 insertions(+) diff --git a/cmd/clawgo/main.go b/cmd/clawgo/main.go index 60b66a1..9317ddf 100644 --- a/cmd/clawgo/main.go +++ b/cmd/clawgo/main.go @@ -857,6 +857,7 @@ func gatewayCmd() { } go agentLoop.Run(ctx) + go runGatewayStartupSelfCheck(ctx, agentLoop, cfg.WorkspacePath()) sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) @@ -965,6 +966,50 @@ func gatewayCmd() { } } +func runGatewayStartupSelfCheck(parent context.Context, agentLoop *agent.AgentLoop, workspace string) { + if agentLoop == nil { + return + } + + checkCtx, cancel := context.WithTimeout(parent, 10*time.Minute) + defer cancel() + + prompt := buildGatewayStartupSelfCheckPrompt(workspace) + report := agentLoop.RunStartupSelfCheckAllSessions(checkCtx, prompt, "gateway:startup-self-check") + logger.InfoCF("gateway", "Startup self-check completed", map[string]interface{}{ + "sessions_total": report.TotalSessions, + "sessions_compacted": report.CompactedSessions, + "sessions_checked": report.CheckedSessions, + "sessions_failed": report.FailedSessions, + }) +} + +func buildGatewayStartupSelfCheckPrompt(workspace string) string { + now := time.Now().Format(time.RFC3339) + notesPath := filepath.Join(workspace, "memory", "HEARTBEAT.md") + notes := "" + if data, err := os.ReadFile(notesPath); err == nil { + notes = strings.TrimSpace(string(data)) + } + + var sb strings.Builder + sb.WriteString("网关刚刚启动,请立即执行一次自检。\n") + sb.WriteString("目标:基于你自己的历史记录与记忆,判断是否有未完成任务需要继续执行,或是否需要立即采取其他行动。\n") + sb.WriteString("要求:\n") + sb.WriteString("1) 先给出结论(继续执行 / 暂无待续任务 / 其他行动)。\n") + sb.WriteString("2) 如果需要继续,请直接开始推进,并在关键节点自然汇报。\n") + sb.WriteString("3) 如果无需继续,也请给出下一步建议。\n") + sb.WriteString("4) 将本次结论简要写入 memory/MEMORY.md 便于下次启动继承。\n") + sb.WriteString("\n") + sb.WriteString("当前时间: ") + sb.WriteString(now) + if notes != "" { + sb.WriteString("\n\n参考 HEARTBEAT.md:\n") + sb.WriteString(notes) + } + return sb.String() +} + func maybePromptAndEscalateRoot(command string) { if os.Getenv(envRootPrompted) == "1" { return diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 25c4813..3bab6ca 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -127,6 +127,13 @@ type stageReporter struct { onUpdate func(content string) } +type StartupSelfCheckReport struct { + TotalSessions int + CompactedSessions int + CheckedSessions int + FailedSessions int +} + func (sr *stageReporter) Publish(stage int, total int, status string, detail string) { if sr == nil || sr.onUpdate == nil { return @@ -1536,6 +1543,72 @@ func (al *AgentLoop) GetStartupInfo() map[string]interface{} { return info } +func (al *AgentLoop) RunStartupSelfCheckAllSessions(ctx context.Context, prompt, fallbackSessionKey string) StartupSelfCheckReport { + report := StartupSelfCheckReport{} + if al == nil || al.sessions == nil { + return report + } + + fallbackSessionKey = strings.TrimSpace(fallbackSessionKey) + if fallbackSessionKey == "" { + fallbackSessionKey = "gateway:startup-self-check" + } + + keys := al.sessions.ListSessionKeys() + seen := make(map[string]struct{}, len(keys)+1) + sessions := make([]string, 0, len(keys)+1) + for _, key := range keys { + key = strings.TrimSpace(key) + if key == "" { + continue + } + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + sessions = append(sessions, key) + } + if _, ok := seen[fallbackSessionKey]; !ok { + sessions = append(sessions, fallbackSessionKey) + } + report.TotalSessions = len(sessions) + + for _, sessionKey := range sessions { + select { + case <-ctx.Done(): + return report + default: + } + + before := al.sessions.MessageCount(sessionKey) + if err := al.persistSessionWithCompaction(ctx, sessionKey); err != nil { + logger.WarnCF("agent", "Startup self-check pre-compaction failed", map[string]interface{}{ + "session_key": sessionKey, + logger.FieldError: err.Error(), + }) + } + after := al.sessions.MessageCount(sessionKey) + if after < before { + report.CompactedSessions++ + } + + runCtx, cancel := context.WithTimeout(ctx, 90*time.Second) + _, err := al.ProcessDirect(runCtx, prompt, sessionKey) + cancel() + if err != nil { + report.FailedSessions++ + logger.WarnCF("agent", "Startup self-check task failed", map[string]interface{}{ + "session_key": sessionKey, + logger.FieldError: err.Error(), + }) + continue + } + report.CheckedSessions++ + } + + return report +} + // formatMessagesForLog formats messages for logging func formatMessagesForLog(messages []providers.Message) string { if len(messages) == 0 { diff --git a/pkg/session/manager.go b/pkg/session/manager.go index 46a718a..71756c9 100644 --- a/pkg/session/manager.go +++ b/pkg/session/manager.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" "sync" "time" @@ -229,6 +230,22 @@ func (sm *SessionManager) MessageCount(key string) int { return len(session.Messages) } +func (sm *SessionManager) ListSessionKeys() []string { + sm.mu.RLock() + defer sm.mu.RUnlock() + + keys := make([]string, 0, len(sm.sessions)) + for k := range sm.sessions { + k = strings.TrimSpace(k) + if k == "" { + continue + } + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + func (sm *SessionManager) CompactHistory(key, summary string, keepLast int) (int, int, error) { sm.mu.RLock() session, ok := sm.sessions[key]