Skip to content

Commit d7621a9

Browse files
author
SqlRush
committed
Manage LSP server processes
1 parent 1bc3a82 commit d7621a9

6 files changed

Lines changed: 433 additions & 3 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1456,7 +1456,7 @@ M7 补充:terminal input parser 和 configurable keybinding name parser 现在
14561456
- Chrome/computer-use/voice/native integrations。
14571457
- enterprise/gated/platform-specific behavior。
14581458

1459-
当前状态:已新增 `advanced` settings gate 地基,覆盖 bridge/LSP/telemetry/Chrome/voice/computer-use/native integrations 的独立 bool 开关解析、settings merge、headless `/config show advanced` 和 `/config search` 审计;`advanced.telemetry=true` 时会在 session 目录写入安全摘要 JSONL 诊断事件,记录事件类型、session/model、tool/progress keys、token/compact/error 摘要,不写入用户/助手正文或工具结果内容,并已有安全 JSONL 读取、类型/模型过滤、汇总统计和 headless `/status show telemetry` 地基;`advanced.lsp=true` 时才向模型暴露只读 `LSPDiagnostics` 工具,用于读取 session-scoped diagnostics snapshot,并支持 file/severity/limit 过滤,底层已能解析 LSP `textDocument/publishDiagnostics` params/notification payload、按文件替换 snapshot、按 LSP 空 diagnostics 语义清空旧文件诊断;同时会写出 session-scoped LSP manager status,按默认 server definitions 解析 workspace/root marker/file extension 命中情况,将已匹配但未启动的 server 标为 `not_started`,并可通过 headless `/status show lsp` 审计 diagnostics 与 manager runtime state;LSP Content-Length framed JSON-RPC reader/writer 与 diagnostics stream processor 已能持续消费 `textDocument/publishDiagnostics` notification 并更新 session snapshot;`advanced.bridge=true` 时会写出 session-scoped bridge manifest,列出 bridge-safe slash/local command 元数据,提供 command/display/alias 安全解析,并可通过 headless `/status show bridge` 审计;bridge direct HTTP handler 已可复用同一 manifest/registry,提供 `/health`、`/manifest`、`/resolve` 和 `/execute` JSON 端点,执行前强制 bridge-safe 白名单并默认不回传展开后的 prompt 正文;bridge loopback-only direct listener 已有可嵌入启动/关闭 API,默认 `127.0.0.1:0`、拒绝非 loopback 地址,并支持可选 bearer/`X-Bridge-Token` token guard;`advanced.nativeIntegrations=true` 时会写出 session-scoped native capability manifest,记录平台和安全能力状态,并可通过 headless `/status show native` 审计;任一 `advanced.chrome`/`advanced.voice`/`advanced.computerUse=true` 时会写出 session-scoped integrations manifest,将 Chrome/voice/computer-use 的 enabled 与 `runtime_state` 分开记录,当前启用项明确标为 `not_wired`,并可通过 headless `/status show integrations` 审计;未启用时仍不注册或泄露 gated 工具 schema。实际 bridge websocket、session websocket/direct connect 外部接入、LSP server process lifecycle、telemetry exporter/tracing、Chrome/voice/computer-use/native runtime 仍未完成。
1459+
当前状态:已新增 `advanced` settings gate 地基,覆盖 bridge/LSP/telemetry/Chrome/voice/computer-use/native integrations 的独立 bool 开关解析、settings merge、headless `/config show advanced` 和 `/config search` 审计;`advanced.telemetry=true` 时会在 session 目录写入安全摘要 JSONL 诊断事件,记录事件类型、session/model、tool/progress keys、token/compact/error 摘要,不写入用户/助手正文或工具结果内容,并已有安全 JSONL 读取、类型/模型过滤、汇总统计和 headless `/status show telemetry` 地基;`advanced.lsp=true` 时才向模型暴露只读 `LSPDiagnostics` 工具,用于读取 session-scoped diagnostics snapshot,并支持 file/severity/limit 过滤,底层已能解析 LSP `textDocument/publishDiagnostics` params/notification payload、按文件替换 snapshot、按 LSP 空 diagnostics 语义清空旧文件诊断;同时会写出 session-scoped LSP manager status,按默认 server definitions 解析 workspace/root marker/file extension 命中情况,将已匹配但未启动的 server 标为 `not_started`,并可通过 headless `/status show lsp` 审计 diagnostics 与 manager runtime state;LSP Content-Length framed JSON-RPC reader/writer 与 diagnostics stream processor 已能持续消费 `textDocument/publishDiagnostics` notification 并更新 session snapshot;LSP server process lifecycle API 已可启动配置命令、消费 stdout framed diagnostics stream,并把 `running`/`exited`/`failed`、PID 和时间戳写回 manager status;`advanced.bridge=true` 时会写出 session-scoped bridge manifest,列出 bridge-safe slash/local command 元数据,提供 command/display/alias 安全解析,并可通过 headless `/status show bridge` 审计;bridge direct HTTP handler 已可复用同一 manifest/registry,提供 `/health`、`/manifest`、`/resolve` 和 `/execute` JSON 端点,执行前强制 bridge-safe 白名单并默认不回传展开后的 prompt 正文;bridge loopback-only direct listener 已有可嵌入启动/关闭 API,默认 `127.0.0.1:0`、拒绝非 loopback 地址,并支持可选 bearer/`X-Bridge-Token` token guard;`advanced.nativeIntegrations=true` 时会写出 session-scoped native capability manifest,记录平台和安全能力状态,并可通过 headless `/status show native` 审计;任一 `advanced.chrome`/`advanced.voice`/`advanced.computerUse=true` 时会写出 session-scoped integrations manifest,将 Chrome/voice/computer-use 的 enabled 与 `runtime_state` 分开记录,当前启用项明确标为 `not_wired`,并可通过 headless `/status show integrations` 审计;未启用时仍不注册或泄露 gated 工具 schema。实际 bridge websocket、session websocket/direct connect 外部接入、LSP initialize/didOpen 编排和 runner 自动启动、telemetry exporter/tracing、Chrome/voice/computer-use/native runtime 仍未完成。
14601460

14611461
## Recommended Next Steps
14621462

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ test/parity/ # golden tests against TS/official behavior
745745
- 每个 gated feature 独立开关测试。
746746
- 不启用 feature 时二进制行为和可见 schema 不泄露 gated 工具/命令。
747747

748-
- 本轮补充:新增 `advanced` settings gate 地基,覆盖 bridge/LSP/telemetry/Chrome/voice/computer-use/native integrations 的独立 bool 开关解析、settings merge、headless `/config show advanced` 和 `/config search` 审计;`advanced.telemetry=true` 时会在 session 目录写入安全摘要 JSONL 诊断事件,记录事件类型、session/model、tool/progress keys、token/compact/error 摘要,不写入用户/助手正文或工具结果内容,并已有安全 JSONL 读取、类型/模型过滤、汇总统计和 headless `/status show telemetry` 地基;`advanced.lsp=true` 时才向模型暴露只读 `LSPDiagnostics` 工具,用于读取 session-scoped diagnostics snapshot,并支持 file/severity/limit 过滤,底层已能解析 LSP `textDocument/publishDiagnostics` params/notification payload、按文件替换 snapshot、按 LSP 空 diagnostics 语义清空旧文件诊断;同时会写出 session-scoped LSP manager status,按默认 server definitions 解析 workspace/root marker/file extension 命中情况,将已匹配但未启动的 server 标为 `not_started`,并可通过 headless `/status show lsp` 审计 diagnostics 与 manager runtime state;LSP Content-Length framed JSON-RPC reader/writer 与 diagnostics stream processor 已能持续消费 `textDocument/publishDiagnostics` notification 并更新 session snapshot;`advanced.bridge=true` 时会写出 session-scoped bridge manifest,列出 bridge-safe slash/local command 元数据,提供 command/display/alias 安全解析,并可通过 headless `/status show bridge` 审计;bridge direct HTTP handler 已可复用同一 manifest/registry,提供 `/health`、`/manifest`、`/resolve` 和 `/execute` JSON 端点,执行前强制 bridge-safe 白名单并默认不回传展开后的 prompt 正文;bridge loopback-only direct listener 已有可嵌入启动/关闭 API,默认 `127.0.0.1:0`、拒绝非 loopback 地址,并支持可选 bearer/`X-Bridge-Token` token guard;`advanced.nativeIntegrations=true` 时会写出 session-scoped native capability manifest,记录平台和安全能力状态,并可通过 headless `/status show native` 审计;任一 `advanced.chrome`/`advanced.voice`/`advanced.computerUse=true` 时会写出 session-scoped integrations manifest,将 Chrome/voice/computer-use 的 enabled 与 `runtime_state` 分开记录,当前启用项明确标为 `not_wired`,并可通过 headless `/status show integrations` 审计;未启用时仍不注册或泄露 gated 工具 schema。实际 bridge websocket、session websocket/direct connect 外部接入、LSP server process lifecycle、telemetry exporter/tracing、Chrome/voice/computer-use/native runtime 仍未完成。
748+
- 本轮补充:新增 `advanced` settings gate 地基,覆盖 bridge/LSP/telemetry/Chrome/voice/computer-use/native integrations 的独立 bool 开关解析、settings merge、headless `/config show advanced` 和 `/config search` 审计;`advanced.telemetry=true` 时会在 session 目录写入安全摘要 JSONL 诊断事件,记录事件类型、session/model、tool/progress keys、token/compact/error 摘要,不写入用户/助手正文或工具结果内容,并已有安全 JSONL 读取、类型/模型过滤、汇总统计和 headless `/status show telemetry` 地基;`advanced.lsp=true` 时才向模型暴露只读 `LSPDiagnostics` 工具,用于读取 session-scoped diagnostics snapshot,并支持 file/severity/limit 过滤,底层已能解析 LSP `textDocument/publishDiagnostics` params/notification payload、按文件替换 snapshot、按 LSP 空 diagnostics 语义清空旧文件诊断;同时会写出 session-scoped LSP manager status,按默认 server definitions 解析 workspace/root marker/file extension 命中情况,将已匹配但未启动的 server 标为 `not_started`,并可通过 headless `/status show lsp` 审计 diagnostics 与 manager runtime state;LSP Content-Length framed JSON-RPC reader/writer 与 diagnostics stream processor 已能持续消费 `textDocument/publishDiagnostics` notification 并更新 session snapshot;LSP server process lifecycle API 已可启动配置命令、消费 stdout framed diagnostics stream,并把 `running`/`exited`/`failed`、PID 和时间戳写回 manager status;`advanced.bridge=true` 时会写出 session-scoped bridge manifest,列出 bridge-safe slash/local command 元数据,提供 command/display/alias 安全解析,并可通过 headless `/status show bridge` 审计;bridge direct HTTP handler 已可复用同一 manifest/registry,提供 `/health`、`/manifest`、`/resolve` 和 `/execute` JSON 端点,执行前强制 bridge-safe 白名单并默认不回传展开后的 prompt 正文;bridge loopback-only direct listener 已有可嵌入启动/关闭 API,默认 `127.0.0.1:0`、拒绝非 loopback 地址,并支持可选 bearer/`X-Bridge-Token` token guard;`advanced.nativeIntegrations=true` 时会写出 session-scoped native capability manifest,记录平台和安全能力状态,并可通过 headless `/status show native` 审计;任一 `advanced.chrome`/`advanced.voice`/`advanced.computerUse=true` 时会写出 session-scoped integrations manifest,将 Chrome/voice/computer-use 的 enabled 与 `runtime_state` 分开记录,当前启用项明确标为 `not_wired`,并可通过 headless `/status show integrations` 审计;未启用时仍不注册或泄露 gated 工具 schema。实际 bridge websocket、session websocket/direct connect 外部接入、LSP initialize/didOpen 编排和 runner 自动启动、telemetry exporter/tracing、Chrome/voice/computer-use/native runtime 仍未完成。
749749

750750
### M12: Parity hardening
751751

internal/lsp/manager.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ const managerStatusFileName = "lsp-manager.json"
1717

1818
const (
1919
ServerRuntimeNotStarted = "not_started"
20+
ServerRuntimeRunning = "running"
21+
ServerRuntimeExited = "exited"
22+
ServerRuntimeFailed = "failed"
2023
ServerRuntimeNoWorkspaceMatch = "no_workspace_match"
2124
ServerRuntimeInvalid = "invalid"
2225
)
@@ -47,6 +50,9 @@ type ServerStatus struct {
4750
RuntimeState string `json:"runtime_state"`
4851
Reason string `json:"reason,omitempty"`
4952
MatchReasons []string `json:"match_reasons,omitempty"`
53+
ProcessID int `json:"process_id,omitempty"`
54+
StartedAt string `json:"started_at,omitempty"`
55+
EndedAt string `json:"ended_at,omitempty"`
5056
}
5157

5258
func SessionManagerStatusPath(sessionPath string, sessionID contracts.ID) string {
@@ -178,6 +184,30 @@ func CountMatchedServers(servers []ServerStatus) int {
178184
return count
179185
}
180186

187+
func UpsertServerStatus(status ManagerStatus, server ServerStatus) ManagerStatus {
188+
servers := normalizeServerStatuses([]ServerStatus{server})
189+
if len(servers) == 0 {
190+
return status
191+
}
192+
server = servers[0]
193+
replaced := false
194+
for i, existing := range status.Servers {
195+
if existing.Name == server.Name {
196+
status.Servers[i] = server
197+
replaced = true
198+
break
199+
}
200+
}
201+
if !replaced {
202+
status.Servers = append(status.Servers, server)
203+
}
204+
status.Servers = normalizeServerStatuses(status.Servers)
205+
if status.GeneratedAt == "" {
206+
status.GeneratedAt = time.Now().UTC().Format(time.RFC3339Nano)
207+
}
208+
return status
209+
}
210+
181211
func resolveServerStatus(cwd string, definition ServerDefinition, files []string) ServerStatus {
182212
status := ServerStatus{
183213
Name: definition.Name,
@@ -199,7 +229,7 @@ func resolveServerStatus(cwd string, definition ServerDefinition, files []string
199229
return status
200230
}
201231
status.RuntimeState = ServerRuntimeNotStarted
202-
status.Reason = "language server process runtime is not wired"
232+
status.Reason = "language server process has not been started"
203233
return status
204234
}
205235

@@ -269,6 +299,8 @@ func normalizeServerStatuses(servers []ServerStatus) []ServerStatus {
269299
server.RuntimeState = strings.TrimSpace(server.RuntimeState)
270300
server.Reason = strings.TrimSpace(server.Reason)
271301
server.MatchReasons = sortedTrimmedStrings(server.MatchReasons)
302+
server.StartedAt = strings.TrimSpace(server.StartedAt)
303+
server.EndedAt = strings.TrimSpace(server.EndedAt)
272304
if server.Name == "" {
273305
continue
274306
}

internal/lsp/manager_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,19 @@ func TestWriteAndLoadManagerStatus(t *testing.T) {
9797
}
9898
}
9999

100+
func TestUpsertServerStatusIgnoresEmptyServer(t *testing.T) {
101+
status := ManagerStatus{
102+
Servers: []ServerStatus{{
103+
Name: "gopls",
104+
RuntimeState: ServerRuntimeNotStarted,
105+
}},
106+
}
107+
got := UpsertServerStatus(status, ServerStatus{})
108+
if len(got.Servers) != 1 || got.Servers[0].Name != "gopls" {
109+
t.Fatalf("status = %#v", got)
110+
}
111+
}
112+
100113
func TestManagerStatusIORejectsEmptyPath(t *testing.T) {
101114
if err := WriteManagerStatus("", ManagerStatus{}); !errors.Is(err, os.ErrInvalid) {
102115
t.Fatalf("WriteManagerStatus empty path err = %v", err)

0 commit comments

Comments
 (0)