返回 DeepSeek-Reasonix
live.go
根目录 / internal / capdiag / live.go
1 package capdiag
2
3 import (
4 "context"
5 "fmt"
6 "os/exec"
7 "strings"
8 "time"
9
10 "reasonix/internal/boot"
11 "reasonix/internal/config"
12 "reasonix/internal/plugin"
13 )
14
15 func lookPath(cmd string) (string, error) {
16 return exec.LookPath(cmd)
17 }
18
19 // probeLiveMCP starts automatic-intent servers in an isolated Host, records
20 // connection results, and always closes the Host (including stdio children).
21 // Persistence (startup stats / schema cache) is disabled so --live stays
22 // free of cache/state side effects under Reasonix home.
23 func probeLiveMCP(rep *MCPReport, cfg *config.Config, root, home, reasonixHome string, timeout time.Duration) []Issue {
24 var issues []Issue
25 if cfg == nil {
26 return issues
27 }
28 if timeout <= 0 {
29 timeout = DefaultLiveTimeout
30 }
31 if timeout < MinLiveTimeout {
32 timeout = MinLiveTimeout
33 }
34 if timeout > MaxLiveTimeout {
35 timeout = MaxLiveTimeout
36 }
37
38 // Only probe automatic start intent servers.
39 var auto []config.PluginEntry
40 for _, p := range cfg.Plugins {
41 if p.ShouldAutoStart() {
42 auto = append(auto, p)
43 } else {
44 for i := range rep.Servers {
45 if rep.Servers[i].Name == p.Name {
46 rep.Servers[i].RuntimeStatus = "skipped"
47 }
48 }
49 }
50 }
51 if len(auto) == 0 {
52 return issues
53 }
54
55 specs := boot.PluginSpecsForRoot(auto, root)
56 ctx, cancel := context.WithTimeout(context.Background(), timeout*time.Duration(len(specs))+timeout)
57 defer cancel()
58
59 host, _, _ := plugin.Start(ctx, specs, plugin.StartPolicy{
60 PerPluginTimeout: timeout,
61 Concurrency: 4,
62 AbortOnError: false,
63 SkipPersistence: true,
64 })
65 if host == nil {
66 return issues
67 }
68 defer host.Close()
69
70 byName := map[string]int{}
71 for i, s := range rep.Servers {
72 byName[s.Name] = i
73 }
74
75 connected := map[string]bool{}
76 for _, s := range host.Servers() {
77 connected[s.Name] = true
78 tools := make([]MCPToolInfo, 0, len(s.ToolList))
79 for _, t := range s.ToolList {
80 tools = append(tools, MCPToolInfo{Name: t.Name, ReadOnlyHint: t.ReadOnlyHint, DestructiveHint: t.DestructiveHint})
81 }
82 if i, ok := byName[s.Name]; ok {
83 rep.Servers[i].RuntimeStatus = "probed"
84 rep.Servers[i].ToolCount = s.Tools
85 rep.Servers[i].Tools = tools
86 } else {
87 rep.Servers = append(rep.Servers, MCPServerInfo{
88 Name: s.Name, Transport: s.Transport, RuntimeStatus: "probed",
89 ToolCount: s.Tools, Tools: tools, StartIntent: "automatic",
90 })
91 }
92 if s.HasTools && s.Tools == 0 {
93 issues = append(issues, Issue{
94 Severity: "warning", Code: "mcp.no_tools", Subsystem: "mcp",
95 Name: s.Name, Message: "live probe: MCP server connected but exposes no tools",
96 Remediation: "Check server configuration and authentication",
97 SettingsTab: "mcp",
98 })
99 }
100 }
101 for _, f := range host.Failures() {
102 errText := sanitizeErrTextWithPaths(f.Error, root, home, reasonixHome)
103 if i, ok := byName[f.Name]; ok {
104 rep.Servers[i].RuntimeStatus = "failed"
105 rep.Servers[i].Error = errText
106 rep.Servers[i].StartupStage = f.Stage
107 rep.Servers[i].StartupElapsedMS = f.Elapsed.Milliseconds()
108 rep.Servers[i].Stderr = sanitizeErrTextWithPaths(f.Stderr, root, home, reasonixHome)
109 }
110 issues = append(issues, Issue{
111 Severity: "error", Code: "mcp.start_failed", Subsystem: "mcp",
112 Name: f.Name, Message: "live probe failed: " + errText,
113 Remediation: "Fix command/URL/auth; re-run with --live after changes",
114 SettingsTab: "mcp",
115 })
116 }
117 for _, p := range auto {
118 if connected[p.Name] {
119 continue
120 }
121 if i, ok := byName[p.Name]; ok {
122 if rep.Servers[i].RuntimeStatus == "failed" {
123 continue
124 }
125 if rep.Servers[i].RuntimeStatus == "" {
126 rep.Servers[i].RuntimeStatus = "failed"
127 rep.Servers[i].Error = "no connection result within timeout"
128 issues = append(issues, Issue{
129 Severity: "error", Code: "mcp.start_failed", Subsystem: "mcp",
130 Name: p.Name, Message: fmt.Sprintf("live probe: no result within %s", timeout),
131 Remediation: "Increase --timeout or fix a hanging MCP server",
132 SettingsTab: "mcp",
133 })
134 }
135 }
136 }
137 return issues
138 }
139
140 // HasErrorSeverity reports whether the report contains any error-level issue.
141 func HasErrorSeverity(r Report) bool {
142 for _, is := range r.Issues {
143 if is.Severity == "error" {
144 return true
145 }
146 }
147 return false
148 }
149
150 // LiveWarningMessage is printed to stderr before CLI --live starts MCP.
151 func LiveWarningMessage() string {
152 return strings.TrimSpace(`warning: --live will start third-party MCP servers in an isolated process.
153 They may access the network and receive configured environment variables and headers.
154 Tools are not registered into the agent registry. Startup stats/schema cache writes are disabled.
155 Host is always closed after the probe.`)
156 }
157
157 lines GO