@myassis/gateway 1.0.65 → 1.0.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -146,31 +146,123 @@ async function stopGatewayProcess() {
146
146
  fs_1.default.unlinkSync(GATEWAY_PID_FILE);
147
147
  }
148
148
  catch { /* ignore */ }
149
+ // 同时杀掉守护进程(防止它重新拉起 gateway)
150
+ if (process.platform === 'win32') {
151
+ try {
152
+ const daemonPidStr = fs_1.default.readFileSync(GATEWAY_DAEMON_PID_FILE, 'utf8').trim();
153
+ const daemonPid = parseInt(daemonPidStr, 10);
154
+ if (daemonPid && !isNaN(daemonPid)) {
155
+ try {
156
+ process.kill(daemonPid, 'SIGTERM');
157
+ }
158
+ catch { /* ignore */ }
159
+ await new Promise(r => setTimeout(r, 500));
160
+ try {
161
+ process.kill(daemonPid, 'SIGKILL');
162
+ }
163
+ catch { /* ignore */ }
164
+ }
165
+ }
166
+ catch { /* ignore */ }
167
+ try {
168
+ fs_1.default.unlinkSync(GATEWAY_DAEMON_PID_FILE);
169
+ }
170
+ catch { /* ignore */ }
171
+ }
149
172
  }
150
173
  /**
151
174
  * 写入 Gateway 启动脚本(用户级,不弹出窗口)
152
175
  */
153
176
  function writeGatewayLauncherScript(exePath, scriptPath, workDir) {
154
177
  // 写入 BOM + UTF-8 内容
178
+ const bs = (p) => p.replace(/\\/g, '\\\\');
155
179
  const content = [
156
- "$ErrorActionPreference = 'Stop'",
157
- "$exe = '" + exePath.replace(/\\/g, '\\\\') + "'",
158
- "$workDir = '" + workDir.replace(/\\/g, '\\\\') + "'",
159
- "$pidFile = '" + GATEWAY_PID_FILE.replace(/\\/g, '\\\\') + "'",
160
- "",
161
- "# 启动 Gateway(无参启动,直接进入 HTTP Server 模式,监听默认端口 3001)",
162
- "$psi = New-Object System.Diagnostics.ProcessStartInfo",
163
- "$psi.FileName = $exe",
164
- "$psi.WorkingDirectory = $workDir",
165
- "# 关键:UseShellExecute=$true ShellExecute API,子进程不继承父 PS 的 stdout/stderr/stdin 句柄",
166
- "# 防止 PowerShell 退出后句柄失效导致 gateway 写 stdout 时 EPIPE 崩溃(运行一段时间后自杀)",
167
- "$psi.UseShellExecute = $true",
168
- "$psi.WindowStyle = 'Hidden'",
169
- "$proc = [System.Diagnostics.Process]::Start($psi)",
170
- "if ($proc) {",
171
- " $proc.Id.ToString() | Out-File -FilePath $pidFile -Encoding UTF8",
172
- "}",
173
- "# fire-and-forget:不 WaitForExit,PowerShell 立即退出,省 ~30MB 常驻内存",
180
+ "$ErrorActionPreference = 'Continue'",
181
+ `$exe = '${bs(exePath)}'`,
182
+ `$workDir = '${bs(workDir)}'`,
183
+ `$pidFile = '${bs(GATEWAY_PID_FILE)}'`,
184
+ `$stopFlag = '${bs(GATEWAY_STOP_FLAG_FILE)}'`,
185
+ `$daemonPidFile = '${bs(GATEWAY_DAEMON_PID_FILE)}'`,
186
+ `$logFile = '${bs(path_1.default.join(GATEWAY_DATA_DIR, 'gateway-daemon.log'))}'`,
187
+ '',
188
+ '# 写入守护进程 PID',
189
+ '$PID.ToString() | Out-File -FilePath $daemonPidFile -Encoding UTF8',
190
+ '',
191
+ 'function Write-Log {',
192
+ ' param([string]$msg)',
193
+ " $ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'",
194
+ " $line = \"[$ts] $msg\"",
195
+ ' $line | Out-File -FilePath $logFile -Append -Encoding UTF8',
196
+ '}',
197
+ '',
198
+ 'function Start-Gateway {',
199
+ " Write-Log '启动 Gateway 进程...'",
200
+ ' $psi = New-Object System.Diagnostics.ProcessStartInfo',
201
+ ' $psi.FileName = $exe',
202
+ ' $psi.WorkingDirectory = $workDir',
203
+ ' $psi.UseShellExecute = $false',
204
+ ' $psi.RedirectStandardOutput = $true',
205
+ ' $psi.RedirectStandardError = $true',
206
+ ' $psi.CreateNoWindow = $true',
207
+ ' $proc = [System.Diagnostics.Process]::Start($psi)',
208
+ ' if ($proc) {',
209
+ ' $proc.Id.ToString() | Out-File -FilePath $pidFile -Encoding UTF8',
210
+ " Write-Log \"Gateway 已启动,PID=$($proc.Id)\"",
211
+ ' return $proc',
212
+ ' }',
213
+ " Write-Log 'Gateway 启动失败'",
214
+ ' return $null',
215
+ '}',
216
+ '',
217
+ '# 清理之前的停止标记',
218
+ 'if (Test-Path $stopFlag) { Remove-Item $stopFlag -Force }',
219
+ '',
220
+ '# 首次启动',
221
+ '$proc = Start-Gateway',
222
+ 'if (-not $proc) { exit 1 }',
223
+ '',
224
+ '# 守护循环:监控进程状态,异常退出则自动重启',
225
+ 'while ($true) {',
226
+ ' try {',
227
+ ' # 等待进程退出(每 5 秒检查一次,防止卡死)',
228
+ ' $exited = $proc.WaitForExit(5000)',
229
+ ' if ($exited) {',
230
+ ' $exitCode = $proc.ExitCode',
231
+ " Write-Log \"Gateway 进程已退出,退出码=$exitCode\"",
232
+ '',
233
+ ' # 检查是否是主动停止',
234
+ ' if (Test-Path $stopFlag) {',
235
+ " Write-Log '检测到停止标记,守护进程退出'",
236
+ ' Remove-Item $stopFlag -Force -ErrorAction SilentlyContinue',
237
+ ' break',
238
+ ' }',
239
+ '',
240
+ ' # 异常退出,等待 3 秒后重启',
241
+ " Write-Log 'Gateway 异常退出,3 秒后自动重启...'",
242
+ ' Start-Sleep -Seconds 3',
243
+ '',
244
+ ' # 再次检查停止标记(重启等待期间可能被标记停止)',
245
+ ' if (Test-Path $stopFlag) {',
246
+ " Write-Log '检测到停止标记,守护进程退出'",
247
+ ' Remove-Item $stopFlag -Force -ErrorAction SilentlyContinue',
248
+ ' break',
249
+ ' }',
250
+ '',
251
+ ' $proc = Start-Gateway',
252
+ ' if (-not $proc) {',
253
+ " Write-Log '重启失败,5 秒后重试...'",
254
+ ' Start-Sleep -Seconds 5',
255
+ ' continue',
256
+ ' }',
257
+ ' }',
258
+ ' } catch {',
259
+ " Write-Log \"守护循环异常: $($_.Exception.Message)\"",
260
+ ' Start-Sleep -Seconds 5',
261
+ ' }',
262
+ '}',
263
+ '',
264
+ '# 清理 PID 文件',
265
+ 'Remove-Item $daemonPidFile -Force -ErrorAction SilentlyContinue',
174
266
  ].join('\r\n');
175
267
  // 确保目标目录存在(%LOCALAPPDATA%\我的助手 首次启动时可能不存在)
176
268
  try {
@@ -244,6 +336,18 @@ async function uninstallWindows() {
244
336
  fs_1.default.unlinkSync(GATEWAY_PID_FILE);
245
337
  }
246
338
  catch { /* ignore */ }
339
+ try {
340
+ fs_1.default.unlinkSync(GATEWAY_DAEMON_PID_FILE);
341
+ }
342
+ catch { /* ignore */ }
343
+ try {
344
+ fs_1.default.unlinkSync(GATEWAY_STOP_FLAG_FILE);
345
+ }
346
+ catch { /* ignore */ }
347
+ try {
348
+ fs_1.default.unlinkSync(GATEWAY_LAUNCHER_FILE);
349
+ }
350
+ catch { /* ignore */ }
247
351
  return { success: true, message: 'Gateway 已卸载(用户级进程)' };
248
352
  }
249
353
  catch (err) {
@@ -260,14 +364,16 @@ async function startServiceWindows() {
260
364
  if (!fs_1.default.existsSync(GATEWAY_LAUNCHER_FILE)) {
261
365
  writeGatewayLauncherScript(exe, GATEWAY_LAUNCHER_FILE, workDir);
262
366
  }
263
- await execAsync(`powershell -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "${GATEWAY_LAUNCHER_FILE}"`, { timeout: 5000, windowsHide: true });
264
- const maxWait = 5000;
367
+ // 使用 Start-Process 在后台启动守护脚本(-WindowStyle Hidden 隐藏窗口)
368
+ // 守护脚本会持续运行并监控 gateway 进程,异常时自动重启
369
+ await execAsync(`powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath powershell.exe -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-WindowStyle','Hidden','-File','${GATEWAY_LAUNCHER_FILE}' -WindowStyle Hidden"`, { timeout: 5000, windowsHide: true });
370
+ const maxWait = 8000;
265
371
  const start = Date.now();
266
372
  while (!isGatewayRunning() && Date.now() - start < maxWait) {
267
373
  await new Promise(r => setTimeout(r, 500));
268
374
  }
269
375
  if (isGatewayRunning()) {
270
- return { success: true, message: 'Gateway 启动成功' };
376
+ return { success: true, message: 'Gateway 启动成功(已启用异常自动重启)' };
271
377
  }
272
378
  return { success: false, message: 'Gateway 进程未能启动,请检查日志' };
273
379
  }
@@ -288,7 +394,23 @@ async function restartServiceWindows() {
288
394
  }
289
395
  async function stopServiceWindows() {
290
396
  try {
397
+ // 写入停止标记,告知守护进程这是主动停止,不要重启
398
+ try {
399
+ fs_1.default.writeFileSync(GATEWAY_STOP_FLAG_FILE, '1', 'utf8');
400
+ }
401
+ catch { /* ignore */ }
402
+ // 给守护进程一点时间检测到标记
403
+ await new Promise(r => setTimeout(r, 200));
291
404
  await stopGatewayProcess();
405
+ // 清理停止标记和守护进程 PID 文件
406
+ try {
407
+ fs_1.default.unlinkSync(GATEWAY_STOP_FLAG_FILE);
408
+ }
409
+ catch { /* ignore */ }
410
+ try {
411
+ fs_1.default.unlinkSync(GATEWAY_DAEMON_PID_FILE);
412
+ }
413
+ catch { /* ignore */ }
292
414
  return { success: true, message: 'Gateway 已停止' };
293
415
  }
294
416
  catch (err) {
@@ -822,6 +944,10 @@ const GATEWAY_PID_FILE = path_1.default.join(os_1.default.tmpdir(), 'myassis-gat
822
944
  const GATEWAY_DATA_DIR = path_1.default.join(process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), '我的助手');
823
945
  /** Gateway 启动脚本路径 */
824
946
  const GATEWAY_LAUNCHER_FILE = path_1.default.join(GATEWAY_DATA_DIR, 'gateway-launcher.ps1');
947
+ /** Gateway 停止标记文件(存在时表示是主动停止,守护进程不应重启) */
948
+ const GATEWAY_STOP_FLAG_FILE = path_1.default.join(GATEWAY_DATA_DIR, '.gateway-stop-flag');
949
+ /** Gateway 守护进程 PID 文件 */
950
+ const GATEWAY_DAEMON_PID_FILE = path_1.default.join(GATEWAY_DATA_DIR, 'gateway-daemon.pid');
825
951
  /** 当前活跃的 Helper 进程 */
826
952
  let helperProcess = null;
827
953
  /** Helper 连接 socket */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myassis/gateway",
3
- "version": "1.0.65",
3
+ "version": "1.0.66",
4
4
  "description": "我的助手 Gateway Service - 本地 AI 网关服务,支持认证、WebSocket 实时通信和任务调度",
5
5
  "main": "dist/index.js",
6
6
  "bin": {