返回 DeepSeek-Reasonix
heartbeat_sidecar.go
根目录 / desktop / heartbeat_sidecar.go
1 package main
2
3 import (
4 "encoding/json"
5 "fmt"
6 "log"
7 "path/filepath"
8
9 "reasonix/internal/config"
10 "reasonix/internal/fileutil"
11 )
12
13 // The top-level runs field remains readable by the previous sidecar reader;
14 // current readers use revision/previous to make the two-file commit consistent.
15 const heartbeatRunHistorySchemaVersion = 1
16
17 type heartbeatRunHistoryGeneration struct {
18 Revision uint64 `json:"revision"`
19 Runs map[string][]HeartbeatRun `json:"runs"`
20 }
21
22 type heartbeatRunHistorySidecar struct {
23 SchemaVersion int `json:"schemaVersion,omitempty"`
24 Revision uint64 `json:"revision,omitempty"`
25 Runs map[string][]HeartbeatRun `json:"runs"`
26 Previous *heartbeatRunHistoryGeneration `json:"previous,omitempty"`
27 }
28
29 func (e *HeartbeatEngine) runHistoryPath() string {
30 dir := config.MemoryUserDir()
31 if dir == "" {
32 dir = "."
33 }
34 return filepath.Join(dir, "heartbeat-tasks.runs.json")
35 }
36
37 // The previous generation keeps the old config readable if the process exits
38 // after publishing a new sidecar but before publishing the matching config.
39 func (e *HeartbeatEngine) readRunHistorySidecar(cfg heartbeatConfig) (map[string][]HeartbeatRun, error) {
40 b, err := readFileUTF8(e.runHistoryPath())
41 if err != nil {
42 return nil, nil
43 }
44 var sidecar heartbeatRunHistorySidecar
45 if err := json.Unmarshal(b, &sidecar); err != nil {
46 log.Printf("[heartbeat] invalid run-history sidecar: %v", err)
47 return nil, nil
48 }
49 if sidecar.SchemaVersion > heartbeatRunHistorySchemaVersion {
50 return nil, fmt.Errorf("heartbeat run-history sidecar schemaVersion %d is newer than this binary supports (%d); upgrade Reasonix", sidecar.SchemaVersion, heartbeatRunHistorySchemaVersion)
51 }
52 if sidecar.SchemaVersion == 0 && sidecar.Revision == 0 && sidecar.Previous == nil {
53 return sidecar.Runs, nil
54 }
55 if sidecar.Revision == cfg.Revision {
56 return sidecar.Runs, nil
57 }
58 if sidecar.Previous != nil && sidecar.Previous.Revision == cfg.Revision {
59 return sidecar.Previous.Runs, nil
60 }
61 // An older writer can replace only the main config and omit Revision.
62 if cfg.Revision == 0 || cfg.Revision > sidecar.Revision {
63 return sidecar.Runs, nil
64 }
65 log.Printf("[heartbeat] run-history sidecar revision %d does not match config revision %d; ignoring staged generation", sidecar.Revision, cfg.Revision)
66 return nil, nil
67 }
68
69 func (e *HeartbeatEngine) writeRunHistorySidecar(revision uint64, runs map[string][]HeartbeatRun, previous *heartbeatRunHistoryGeneration) error {
70 b, err := json.MarshalIndent(heartbeatRunHistorySidecar{
71 SchemaVersion: heartbeatRunHistorySchemaVersion,
72 Revision: revision,
73 Runs: runs,
74 Previous: previous,
75 }, "", " ")
76 if err != nil {
77 return err
78 }
79 return fileutil.AtomicWriteFile(e.runHistoryPath(), b, 0o644)
80 }
81
82 func heartbeatRunHistoryByTask(tasks []HeartbeatTask) map[string][]HeartbeatRun {
83 runs := make(map[string][]HeartbeatRun, len(tasks))
84 for _, task := range tasks {
85 if len(task.RunHistory) > 0 {
86 runs[task.ID] = task.RunHistory
87 }
88 }
89 return runs
90 }
91
91 lines GO