package main import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "time" ) var allowedTaskTypes = map[string]bool{ "wechat_reply": true, "wechat_observe": true, "noop": true, } var allowedActionTypes = map[string]bool{ "click": true, "type_text": true, "press_enter": true, "scroll": true, "wait": true, "noop": true, } func finalizeTask(task Task, summaries []RegionSummary, config Config, regionsPath string, screenshotPath string) Task { now := time.Now() if task.TaskID == "" { task.TaskID = "task_" + now.Format("20060102_150405") } if task.TaskName == "" { task.TaskName = "识别微信聊天并生成回复" } if task.TaskType == "" || !allowedTaskTypes[task.TaskType] { task.TaskType = "wechat_reply" } if task.TaskStatus == "" { task.TaskStatus = "planned" } if config.Agent.DryRun { task.TaskStatus = "dry_run" } if task.CreatedAt == "" { task.CreatedAt = now.Format(time.RFC3339) } task.Source = TaskSource{RegionsPath: regionsPath, ScreenshotPath: screenshotPath} task.Regions = summaries regionIndex := regionByType(summaries) if len(task.Actions) == 0 { task.Actions = defaultActions(task.LLMSummary.ReplyText) } for index := range task.Actions { action := &task.Actions[index] if action.ActionID == "" { action.ActionID = fmt.Sprintf("action_%03d", index+1) } if action.Name == "" { action.Name = action.Type } if !allowedActionTypes[action.Type] { action.Type = "noop" action.Reason = "unsupported action type replaced by noop" } if action.Type == "click" || action.Type == "scroll" { if summary, exists := regionIndex[action.TargetRegion]; exists { point := summary.CenterScreen action.Point = &point } else if action.Type == "click" { action.Type = "noop" action.Reason = "target_region not found" } } if action.Type == "type_text" && len([]rune(action.Text)) > 500 { runes := []rune(action.Text) action.Text = string(runes[:500]) } if config.Agent.DryRun { action.Status = "skipped" if action.Reason == "" { action.Reason = "dry_run enabled" } } else if action.Status == "" { action.Status = "pending" } task.ExecutionLog = append(task.ExecutionLog, ExecutionLog{ ActionID: action.ActionID, Status: action.Status, Message: executionMessage(*action), Time: time.Now().Format(time.RFC3339), }) } return task } func defaultActions(replyText string) []Action { if strings.TrimSpace(replyText) == "" { return []Action{{ActionID: "action_001", Type: "noop", Name: "无需回复", Status: "pending"}} } return []Action{ {ActionID: "action_001", Type: "click", Name: "点击输入框", Status: "pending", TargetRegion: "input_box"}, {ActionID: "action_002", Type: "type_text", Name: "输入回复内容", Status: "pending", Text: replyText}, {ActionID: "action_003", Type: "press_enter", Name: "按回车发送", Status: "pending"}, } } func executionMessage(action Action) string { switch action.Type { case "click": return fmt.Sprintf("dry_run: 跳过点击 %s", action.TargetRegion) case "type_text": return "dry_run: 跳过输入回复内容" case "press_enter": return "dry_run: 跳过按回车发送" case "scroll": return fmt.Sprintf("dry_run: 跳过滑动 %s delta_y=%d", action.TargetRegion, action.DeltaY) case "wait": return fmt.Sprintf("dry_run: 跳过等待 %dms", action.DurationMs) default: return "dry_run: noop" } } func saveTaskFiles(task Task, config Config) (string, string, error) { latestPath := appDataPath(config, config.Agent.TaskOutputPath) historyDir := appDataPath(config, config.Agent.TaskHistoryDir) if err := os.MkdirAll(filepath.Dir(latestPath), 0755); err != nil { return "", "", err } if err := os.MkdirAll(historyDir, 0755); err != nil { return "", "", err } data, err := json.MarshalIndent(task, "", " ") if err != nil { return "", "", err } if err := os.WriteFile(latestPath, data, 0644); err != nil { return "", "", err } historyPath := filepath.Join(historyDir, task.TaskID+".json") if err := os.WriteFile(historyPath, data, 0644); err != nil { return "", "", err } return latestPath, historyPath, nil } func saveRawResponse(config Config, raw string) error { path := appDataPath(config, "data/tasks/latest_task_raw.txt") if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return err } return os.WriteFile(path, []byte(raw), 0644) }