Files
Agent-System/internal/squad/reconcile.go
T
WeiCHENandCursor e139b2b267 feat(api): observer task runs decoupled from SSE lifecycle
Add POST/GET/POST cancel task run APIs, background worker with event replay,
workspace reconcile, runner child cancel, and chat message metadata updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 02:50:22 +00:00

91 lines
2.3 KiB
Go

package squad
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
// ReconcileReport 工作区状态对账结果。
type ReconcileReport struct {
WorkDir string `json:"work_dir"`
PreviousStatus string `json:"previous_status"`
NewStatus string `json:"new_status"`
ExecutionOK bool `json:"execution_ok"`
HasOutput bool `json:"has_output"`
Reconciled bool `json:"reconciled"`
Reason string `json:"reason,omitempty"`
}
// ReconcileWorkspaceStatus 检测外层 failed 但 execution.log 为 SUCCESS 的孤儿任务并修复。
func ReconcileWorkspaceStatus(workDir string) (*ReconcileReport, error) {
workDir = strings.TrimSpace(workDir)
if workDir == "" {
return nil, fmt.Errorf("work_dir required")
}
metaPath := filepath.Join(workDir, "workspace.json")
metaBytes, err := os.ReadFile(metaPath)
if err != nil {
return nil, err
}
var meta WorkspaceMetadata
if err := json.Unmarshal(metaBytes, &meta); err != nil {
return nil, err
}
report := &ReconcileReport{
WorkDir: workDir,
PreviousStatus: meta.Status,
NewStatus: meta.Status,
}
execOK := scanExecutionSuccess(workDir)
report.ExecutionOK = execOK
report.HasOutput = fileExists(filepath.Join(workDir, "tasks", "01_organize", "output.md")) ||
fileExists(filepath.Join(workDir, "output.md"))
if meta.Status != "failed" || !execOK || !report.HasOutput {
report.Reason = "no reconciliation needed"
return report, nil
}
meta.Status = "completed"
now := time.Now()
meta.UpdatedAt = now
meta.CompletedAt = &now
if err := writeJSON(metaPath, meta); err != nil {
return nil, err
}
report.NewStatus = "completed"
report.Reconciled = true
report.Reason = "execution log SUCCESS with output present while workspace was failed"
return report, nil
}
func scanExecutionSuccess(workDir string) bool {
var found bool
_ = filepath.Walk(filepath.Join(workDir, "tasks"), func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() || info.Name() != "execution.log" {
return nil
}
b, readErr := os.ReadFile(path)
if readErr != nil {
return nil
}
if strings.Contains(string(b), "Outcome: SUCCESS") {
found = true
return filepath.SkipAll
}
return nil
})
return found
}
func fileExists(path string) bool {
fi, err := os.Stat(path)
return err == nil && !fi.IsDir()
}