| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "reasonix/internal/boot" |
| 5 | "reasonix/internal/control" |
| 6 | "reasonix/internal/skill/skillwatch" |
| 7 | ) |
| 8 | |
| 9 | func rebuildTabRuntime(a *App, tab *WorkspaceTab, old *control.Controller, opts boot.Options) (*boot.BuildResult, error) { |
| 10 | var res *boot.BuildResult |
| 11 | var err error |
| 12 | previous := a.tabBuildResultForController(tab, old) |
| 13 | if previous != nil { |
| 14 | res, err = boot.RebuildFrom(a.bootContext(), previous, opts) |
| 15 | } else { |
| 16 | res, err = boot.Rebuild(a.bootContext(), old, opts) |
| 17 | } |
| 18 | if err != nil { |
| 19 | return nil, err |
| 20 | } |
| 21 | a.setTabLastBuildResult(tab, res) |
| 22 | return res, nil |
| 23 | } |
| 24 | |
| 25 | func (a *App) tabBuildResultForController(tab *WorkspaceTab, ctrl *control.Controller) *boot.BuildResult { |
| 26 | if a == nil || tab == nil || ctrl == nil { |
| 27 | return nil |
| 28 | } |
| 29 | a.mu.RLock() |
| 30 | defer a.mu.RUnlock() |
| 31 | if tab.ID != "" && !a.ownsRuntimeTabLocked(tab) { |
| 32 | return nil |
| 33 | } |
| 34 | res := tab.lastBuildResult |
| 35 | if res == nil || res.Controller != ctrl || tab.Ctrl != ctrl { |
| 36 | return nil |
| 37 | } |
| 38 | return res |
| 39 | } |
| 40 | |
| 41 | func (a *App) setTabLastBuildResult(tab *WorkspaceTab, res *boot.BuildResult) { |
| 42 | if a == nil || tab == nil { |
| 43 | return |
| 44 | } |
| 45 | a.mu.Lock() |
| 46 | defer a.mu.Unlock() |
| 47 | if tab.ID != "" && !a.ownsRuntimeTabLocked(tab) { |
| 48 | return |
| 49 | } |
| 50 | tab.lastBuildResult = res |
| 51 | } |
| 52 | |
| 53 | // RuntimeDoctorReport is the desktop/Wails view of extension runtime diagnostics. |
| 54 | type RuntimeDoctorReport struct { |
| 55 | Text string `json:"text"` |
| 56 | PublishedGen uint64 `json:"publishedGeneration"` |
| 57 | AllowResume bool `json:"allowResume"` |
| 58 | CleanRollback bool `json:"cleanRollback"` |
| 59 | HasIrreversible bool `json:"hasIrreversible"` |
| 60 | NoOpRebuilds uint64 `json:"noOpRebuilds"` |
| 61 | FullRebuilds uint64 `json:"fullRebuilds"` |
| 62 | SubgraphRebuilds uint64 `json:"subgraphRebuilds"` |
| 63 | StaleDrops uint64 `json:"staleDrops"` |
| 64 | AdmissionRejected uint64 `json:"admissionRejected"` |
| 65 | RuntimeOwnerFallbacks uint64 `json:"runtimeOwnerFallbacks"` |
| 66 | SkillWatch *skillwatch.Diagnostics `json:"skillWatch,omitempty"` |
| 67 | } |
| 68 | |
| 69 | // RuntimeDoctor returns process-wide + active-tab extension runtime diagnostics |
| 70 | // for the settings/status panel (mirrors `reasonix doctor runtime`). |
| 71 | func (a *App) RuntimeDoctor() RuntimeDoctorReport { |
| 72 | var res *boot.BuildResult |
| 73 | if a != nil { |
| 74 | // Snapshot the active tab and build result under the same lock: Wails can |
| 75 | // query diagnostics while another goroutine finishes a runtime rebuild. |
| 76 | a.mu.RLock() |
| 77 | if tab := a.activeTabLocked(); tab != nil { |
| 78 | candidate := tab.lastBuildResult |
| 79 | if candidate != nil && candidate.Controller == tab.Ctrl { |
| 80 | res = candidate |
| 81 | } |
| 82 | } |
| 83 | a.mu.RUnlock() |
| 84 | } |
| 85 | report := boot.CollectRuntimeDoctor(res) |
| 86 | return RuntimeDoctorReport{ |
| 87 | Text: boot.RenderRuntimeDoctorText(report), |
| 88 | PublishedGen: report.PublishedGen, |
| 89 | AllowResume: report.Resume.AllowResume, |
| 90 | CleanRollback: report.Resume.CleanRollback, |
| 91 | HasIrreversible: report.Resume.HasIrreversible, |
| 92 | NoOpRebuilds: report.Metrics.NoOpRebuilds, |
| 93 | FullRebuilds: report.Metrics.FullRebuilds, |
| 94 | SubgraphRebuilds: report.Metrics.SubgraphRebuilds, |
| 95 | StaleDrops: report.Metrics.StaleDrops, |
| 96 | AdmissionRejected: report.Metrics.AdmissionRejected, |
| 97 | RuntimeOwnerFallbacks: report.RuntimeOwnerFallbacks, |
| 98 | SkillWatch: report.SkillWatch, |
| 99 | } |
| 100 | } |
| 101 |