Files
MyGoNavi/main.go
Syngnat 155ba0f1f7 🐛 fix(mcp): 修复在线更新后外部客户端连接失效
- 启动时自动迁移 Claude Code 与 Codex 中失效的 GoNavi MCP 路径
- 兼容 Windows 版本化 EXE 改名及旧进程占用导致的残留路径
- 限制迁移范围,保留仍有效的稳定路径与自定义命令
- 补充路径迁移和误覆盖边界回归测试
2026-07-10 09:44:26 +08:00

189 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"context"
"fmt"
"os"
"runtime"
"runtime/debug"
"strings"
aiservice "GoNavi-Wails/internal/ai/service"
"GoNavi-Wails/internal/app"
"GoNavi-Wails/internal/logger"
"GoNavi-Wails/internal/mcpserver"
"GoNavi-Wails/internal/webserver"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2/pkg/options/windows"
wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime"
)
const nativeSelectCurrentLineEvent = "gonavi:native-select-current-line"
func main() {
// 大结果集导出88W+ 行JSON 编解码会产生 5-8 倍内存副本,
// Go 默认 GOGC=100 下堆翻倍才触发 GC叠加 Windows MADV_FREE 不归还 RSS
// 会导致 RSS 单调爬升到峰值后不下降。这里收紧到 50让 GC 更早触发。
// 代价是 CPU 开销略增,但导出/导入场景属 I/O 密集型GC 开销可忽略。
debug.SetGCPercent(50)
if runSpecialMode(os.Args[1:]) {
return
}
// Create an instance of the app structure
application := app.NewApp()
aiService := aiservice.NewService()
lowMemoryMode := isLowMemoryMode()
var runtimeCtx context.Context
backgroundColour := &options.RGBA{R: 0, G: 0, B: 0, A: 0}
windowsBackdrop := windows.Acrylic
if lowMemoryMode {
backgroundColour = &options.RGBA{R: 255, G: 255, B: 255, A: 255}
windowsBackdrop = windows.None
}
var appMenu *menu.Menu
if strings.EqualFold(strings.TrimSpace(runtime.GOOS), "darwin") {
appMenu = buildMacApplicationMenu(func() {
if runtimeCtx == nil {
return
}
wailsRuntime.EventsEmit(runtimeCtx, nativeSelectCurrentLineEvent)
}, true)
}
// Windows 冷启动:原生先最大化,避免 main 默认小窗先闪一帧;
// 前端 hydration 后再按用户记忆(最大化 / 普通尺寸)精细恢复。
// 其它平台仍用 Normal由前端恢复逻辑接管。
windowStartState := options.Normal
if strings.EqualFold(strings.TrimSpace(runtime.GOOS), "windows") {
windowStartState = options.Maximised
}
// Create application with options
err := wails.Run(&options.App{
Title: "GoNavi",
Width: 1440,
Height: 900,
MinWidth: 900,
MinHeight: 600,
WindowStartState: windowStartState,
Frameless: true,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: backgroundColour,
Menu: appMenu,
OnStartup: func(ctx context.Context) {
runtimeCtx = ctx
app.InitializeLifecycle(application, ctx)
aiservice.InitializeLifecycle(aiService, ctx)
if err := aiservice.RepairInstalledLocalMCPClientConfigs(aiService); err != nil {
logger.Warnf("自动修复本地 MCP 客户端配置失败:%v", err)
}
},
OnShutdown: func(ctx context.Context) {
aiService.Shutdown()
application.Shutdown()
},
OnBeforeClose: app.NewBeforeCloseHandler(application),
Bind: []interface{}{
application,
aiService,
},
Windows: &windows.Options{
WebviewIsTransparent: !lowMemoryMode,
WindowIsTranslucent: !lowMemoryMode,
BackdropType: windowsBackdrop,
DisableWindowIcon: false,
DisableFramelessWindowDecorations: false,
WebviewUserDataPath: resolveWindowsWebviewUserDataPath(),
},
Mac: &mac.Options{
WebviewIsTransparent: true,
WindowIsTranslucent: true,
},
})
if err != nil {
logger.Error(err, "应用启动失败")
}
}
func buildMacApplicationMenu(onNativeSelectCurrentLine func(), frameless bool) *menu.Menu {
result := menu.NewMenuFromItems(
menu.AppMenu(),
menu.EditMenu(),
)
if !frameless {
result.Append(menu.WindowMenu())
}
queryEditorMenu := result.AddSubmenu("SQL")
queryEditorMenu.AddText("Copy Current Line", keys.CmdOrCtrl("e"), func(_ *menu.CallbackData) {
if onNativeSelectCurrentLine != nil {
onNativeSelectCurrentLine()
}
})
return result
}
func runSpecialMode(args []string) bool {
if len(args) == 0 {
return false
}
mode := strings.ToLower(strings.TrimSpace(args[0]))
switch mode {
case "mcp-server", "--mcp-server":
if err := runMCPServerMode(context.Background(), args[1:]); err != nil {
logger.Error(err, "GoNavi MCP Server 退出")
}
return true
case "web-server", "--web-server":
if err := webserver.Run(context.Background(), assets, args[1:]); err != nil {
logger.Error(err, "GoNavi Web Server 退出")
}
return true
default:
return false
}
}
func runMCPServerMode(ctx context.Context, args []string) error {
if len(args) == 0 {
return mcpserver.RunAppStdioServer(ctx)
}
mode := strings.ToLower(strings.TrimSpace(args[0]))
switch mode {
case "stdio", "--stdio":
return mcpserver.RunAppStdioServer(ctx)
case "http", "--http", "streamable-http", "--streamable-http":
options, err := mcpserver.ParseHTTPServerOptions(args[1:])
if err != nil {
return err
}
logger.Infof("GoNavi MCP Streamable HTTP Server 启动addr=%s path=%s schemaOnly=%v", options.Addr, options.Path, options.SchemaOnly)
return mcpserver.RunAppStreamableHTTPServer(ctx, options)
case "remote-config", "--remote-config":
return mcpserver.WriteRemoteMCPClientConfig(os.Stdout, args[1:])
default:
return fmt.Errorf("未知 MCP server 模式: %s支持 stdio/http/remote-config", args[0])
}
}
func isLowMemoryMode() bool {
switch strings.ToLower(strings.TrimSpace(os.Getenv("GONAVI_LOW_MEMORY_MODE"))) {
case "1", "true", "yes", "on":
return true
default:
return false
}
}