返回 DeepSeek-Reasonix
doctor_runtime.go
根目录 / internal / boot / doctor_runtime.go
1 package boot
2
3 import (
4 "encoding/json"
5 "fmt"
6
7 "reasonix/internal/extension"
8 "reasonix/internal/skill/skillwatch"
9 )
10
11 // RuntimeDoctorReport is the structured runtime diagnostics document for
12 // `reasonix doctor runtime` and desktop status panels.
13 type RuntimeDoctorReport struct {
14 Status *extension.RuntimeStatus `json:"status,omitempty"`
15 Metrics extension.LifecycleMetricsSnapshot `json:"metrics"`
16 Recoverability extension.Recoverability `json:"recoverability"`
17 Resume extension.ResumeDecision `json:"resume"`
18 PublishedGen uint64 `json:"publishedGeneration"`
19 DrainingGens []uint64 `json:"drainingGenerations,omitempty"`
20 RuntimeOwnerFallbacks uint64 `json:"runtimeOwnerFallbacks"`
21 SkillWatch *skillwatch.Diagnostics `json:"skillWatch,omitempty"`
22 Text string `json:"-"`
23 }
24
25 // CollectRuntimeDoctor builds a report from an optional live BuildResult and
26 // its session-lineage owner. Also sweeps expired drains so doctor is a natural
27 // product trigger for drain-timeout force-expire.
28 func CollectRuntimeDoctor(res *BuildResult) RuntimeDoctorReport {
29 owner := extension.DefaultRuntimeOwner
30 if res != nil && res.Owner != nil {
31 owner = res.Owner
32 }
33 gate := owner.Gate
34 _ = gate.SweepAndForceExpire()
35 gen := gate.Published()
36 report := RuntimeDoctorReport{
37 Metrics: extension.DefaultLifecycleMetrics.Snapshot(),
38 PublishedGen: gen,
39 DrainingGens: gate.DrainingGenerations(),
40 RuntimeOwnerFallbacks: extension.RuntimeOwnerFallbackCount(),
41 Recoverability: owner.AssessRecoverability(gen),
42 Resume: owner.DecideResume(gen),
43 }
44 if res != nil {
45 if res.SkillWatchService != nil {
46 diagnostics := res.SkillWatchService.Diagnostics()
47 report.SkillWatch = &diagnostics
48 }
49 report.Status = res.Status
50 if res.Status != nil {
51 report.Text = FormatRuntimeStatus(res.Status)
52 }
53 if res.Snapshot != nil {
54 g := res.Snapshot.Generation()
55 report.Recoverability = owner.AssessRecoverability(g)
56 report.Resume = owner.DecideResume(g)
57 }
58 }
59 if report.Text == "" {
60 report.Text = FormatRuntimeStatus(report.Status)
61 }
62 return report
63 }
64
65 // RenderRuntimeDoctorJSON encodes the report.
66 func RenderRuntimeDoctorJSON(report RuntimeDoctorReport) ([]byte, error) {
67 return json.MarshalIndent(report, "", " ")
68 }
69
70 // RenderRuntimeDoctorText is the human-readable form.
71 func RenderRuntimeDoctorText(report RuntimeDoctorReport) string {
72 body := report.Text
73 if body == "" {
74 body = "runtime status: unavailable\n"
75 }
76 body += fmt.Sprintf("runtime owner fallbacks: %d\n", report.RuntimeOwnerFallbacks)
77 if watch := report.SkillWatch; watch != nil {
78 body += fmt.Sprintf("skill watch: physical=%d logical=%d scans=%d entries=%d events=%d notifications=%d degraded=%d helper-restarts=%d\n",
79 watch.PhysicalWatches, watch.LogicalSubscriptions, watch.Scans, watch.ScannedEntries,
80 watch.EventsReceived, watch.Notifications, watch.DegradedRoots, watch.HelperRestarts)
81 }
82 body += fmt.Sprintf("recoverability: clean=%v irreversible=%v\n", report.Recoverability.Clean, report.Recoverability.HasIrreversible)
83 body += fmt.Sprintf("resume: allow=%v cleanRollback=%v\n", report.Resume.AllowResume, report.Resume.CleanRollback)
84 for _, n := range report.Recoverability.Notes {
85 body += " note: " + n + "\n"
86 }
87 for _, n := range report.Resume.Notes {
88 body += " resume-note: " + n + "\n"
89 }
90 if len(report.DrainingGens) > 0 {
91 body += fmt.Sprintf("draining generations: %v\n", report.DrainingGens)
92 }
93 return body
94 }
95
95 lines GO