diff --git a/.gitignore b/.gitignore index 042cb69..c7591ed 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ start-error.log # 临时文件 *.tmp *.log.* + +# WebView2 用户数据(仓库根下的缓存目录) +EBWebView/ diff --git a/app/dsh-app.exe b/app/dsh-app.exe index f611ab8..35c42c8 100644 Binary files a/app/dsh-app.exe and b/app/dsh-app.exe differ diff --git a/app/src/dsh-app.cs b/app/src/dsh-app.cs index a0af1b6..8f4b4b0 100644 --- a/app/src/dsh-app.cs +++ b/app/src/dsh-app.cs @@ -21,8 +21,9 @@ static class Program private const string RunKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Run"; private const string RunValueName = "DeepSeekHarnessService"; - private const string NodeExe = @"C:\Program Files\nodejs\node.exe"; - private const string NpmCli = @"C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js"; + // 标准安装路径(联网安装的 Node 默认位置);离线包使用应用旁的 runtime 目录 + private const string StdNodeExe = @"C:\Program Files\nodejs\node.exe"; + private const string StdNpmCli = @"C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js"; private static Process serviceProcess; // 本应用拉起的 dsh 服务进程 private static bool startedByUs; // 服务是否由本应用启动 @@ -175,6 +176,9 @@ static class Program // 后台检查 DSH 版本更新(有新版才提示,不阻塞启动) CheckUpdateAsync(); + // 刷新开机自启启动器(路径随当前环境,跨设备可用) + try { WriteServiceVbs(Path.Combine(AppDir(), "start-dsh-service.vbs")); } catch { } + Application.Run(mainForm); } @@ -183,6 +187,62 @@ static class Program return Path.GetDirectoryName(Application.ExecutablePath); } + // 应用根目录(AppDir 的上一级,离线包布局 /app、/runtime) + static string AppRoot() + { + string d = AppDir(); + return d != null ? Path.GetDirectoryName(d) : null; + } + + // ---- 运行时查找(标准安装 或 离线包 runtime 目录)---- + + static string FindNodeExe() + { + if (File.Exists(StdNodeExe)) return StdNodeExe; + string root = AppRoot(); + if (root != null) + { + string local = Path.Combine(root, "runtime", "node", "node.exe"); + if (File.Exists(local)) return local; + } + return null; + } + + static string GetNodeDir() + { + string exe = FindNodeExe(); + return exe != null ? Path.GetDirectoryName(exe) : null; + } + + static string FindNpmCli() + { + string nodeDir = GetNodeDir(); + if (nodeDir != null) + { + string cli = Path.Combine(nodeDir, "node_modules", "npm", "bin", "npm-cli.js"); + if (File.Exists(cli)) return cli; + } + if (File.Exists(StdNpmCli)) return StdNpmCli; + return null; + } + + static string FindDshBinJs() + { + // 1) npm 全局(联网安装 / 离线复制到 APPDATA 的情况) + string npmDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "npm"); + string p1 = Path.Combine(npmDir, "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"); + if (File.Exists(p1)) return p1; + // 2) 离线包布局 /runtime/dsh/node_modules + string root = AppRoot(); + if (root != null) + { + string p2 = Path.Combine(root, "runtime", "dsh", "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"); + if (File.Exists(p2)) return p2; + } + return null; + } + static bool IsPortOpen(int port) { try @@ -201,14 +261,18 @@ static class Program { string appDir = AppDir(); string logFile = Path.Combine(appDir, "web.log"); - string npmDir = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "npm"); - string binJs = Path.Combine(npmDir, "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"); + string nodeExe = FindNodeExe(); + string binJs = FindDshBinJs(); try { - if (!File.Exists(NodeExe) || !File.Exists(binJs)) return; + if (nodeExe == null || binJs == null) + { + try { File.WriteAllText(Path.Combine(AppDir(), "start-error.log"), + "Node 或 dsh 未找到。nodeExe=" + (nodeExe ?? "null") + " binJs=" + (binJs ?? "null")); } catch { } + return; + } - var psi = new ProcessStartInfo(NodeExe) + var psi = new ProcessStartInfo(nodeExe) { UseShellExecute = false, CreateNoWindow = true, @@ -220,10 +284,10 @@ static class Program // 确保 node 与 npm 命令可用(dsh 插件可能用到) string path = psi.EnvironmentVariables["PATH"] ?? ""; - if (path.IndexOf("nodejs", StringComparison.OrdinalIgnoreCase) < 0) - path = @"C:\Program Files\nodejs;" + path; - if (path.IndexOf("npm", StringComparison.OrdinalIgnoreCase) < 0) - path = npmDir + ";" + path; + string nodeDir = Path.GetDirectoryName(nodeExe); + if (nodeDir != null && path.IndexOf("nodejs", StringComparison.OrdinalIgnoreCase) < 0 + && path.IndexOf(nodeDir, StringComparison.OrdinalIgnoreCase) < 0) + path = nodeDir + ";" + path; psi.EnvironmentVariables["PATH"] = path; // 禁用 dsh 启动遥测(可能小幅加速冷启动) @@ -310,6 +374,7 @@ static class Program if (enable) { string vbs = Path.Combine(AppDir(), "start-dsh-service.vbs"); + WriteServiceVbs(vbs); key.SetValue(RunValueName, "wscript.exe \"" + vbs + "\""); } else @@ -321,6 +386,25 @@ static class Program catch { } } + // 按当前环境动态生成隐藏启动器 vbs(路径随用户/安装位置变化) + static void WriteServiceVbs(string path) + { + try + { + string nodeExe = FindNodeExe(); + string binJs = FindDshBinJs(); + if (nodeExe == null || binJs == null) return; + string logFile = Path.Combine(AppDir(), "web.log"); + // cmd /c ""node" "bin.js" web > "log" 2>&1" (CreateProcess 剥一层引号后 cmd 仍收到带引号命令) + string inner = "cmd /c \"\"" + nodeExe + "\" \"" + binJs + "\" web > \"" + logFile + "\" 2>&1\""; + string vbs = "' Start dsh service (hidden, auto-generated)\r\n" + + "Set ws = CreateObject(\"WScript.Shell\")\r\n" + + "ws.Run \"" + inner.Replace("\"", "\"\"") + "\", 0, False\r\n"; + File.WriteAllText(path, vbs); + } + catch { } + } + // ---- DSH 版本检查与升级引导(应用内完成,无需浏览器/终端)---- static void CheckUpdateAsync() @@ -369,14 +453,18 @@ static class Program { try { - string pkg = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "npm", "node_modules", "@deepseek-ai", "dsh", "package.json"); - if (File.Exists(pkg)) + string binJs = FindDshBinJs(); + if (binJs != null) { - var doc = new System.Text.RegularExpressions.Regex("\"version\"\\s*:\\s*\"([^\"]+)\""); - var m = doc.Match(File.ReadAllText(pkg)); - if (m.Success) return m.Groups[1].Value; + string pkg = Path.Combine( + Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(binJs))), + "package.json"); + if (File.Exists(pkg)) + { + var doc = new System.Text.RegularExpressions.Regex("\"version\"\\s*:\\s*\"([^\"]+)\""); + var m = doc.Match(File.ReadAllText(pkg)); + if (m.Success) return m.Groups[1].Value; + } } } catch { } @@ -387,14 +475,17 @@ static class Program { try { + string nodeExe = FindNodeExe(); + string cli = FindNpmCli(); + if (nodeExe == null || cli == null) return ""; string stdout = ""; - var psi = new ProcessStartInfo(NodeExe) + var psi = new ProcessStartInfo(nodeExe) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, - Arguments = "\"" + NpmCli + "\" view @deepseek-ai/dsh version", + Arguments = "\"" + cli + "\" view @deepseek-ai/dsh version", }; using (var p = Process.Start(psi)) { @@ -413,14 +504,17 @@ static class Program string logFile = Path.Combine(AppDir(), "upgrade.log"); try { - var psi = new ProcessStartInfo(NodeExe) + string nodeExe = FindNodeExe(); + string cli = FindNpmCli(); + if (nodeExe == null || cli == null) return false; + var psi = new ProcessStartInfo(nodeExe) { UseShellExecute = false, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, RedirectStandardOutput = true, RedirectStandardError = true, - Arguments = "\"" + NpmCli + "\" install -g @deepseek-ai/dsh@latest", + Arguments = "\"" + cli + "\" install -g @deepseek-ai/dsh@latest", }; using (var p = Process.Start(psi)) { diff --git a/app/start-dsh-service.vbs b/app/start-dsh-service.vbs index d765672..db8847b 100644 --- a/app/start-dsh-service.vbs +++ b/app/start-dsh-service.vbs @@ -1,4 +1,3 @@ -' Start dsh (DeepSeek Harness) service in background, no window. -' Used by "autostart" tray option (registry Run entry via wscript). +' Start dsh service (hidden, auto-generated) Set ws = CreateObject("WScript.Shell") ws.Run "cmd /c """"C:\Program Files\nodejs\node.exe"" ""C:\Users\VanuSystems-Mu\AppData\Roaming\npm\node_modules\@deepseek-ai\dsh\lib\bin.js"" web > ""C:\Users\VanuSystems-Mu\AppData\Local\DeepSeekHarness\app\web.log"" 2>&1""", 0, False