@myassis/gateway 1.0.48 → 1.0.50

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.
package/dist/main.js CHANGED
@@ -250,6 +250,9 @@ else {
250
250
  schedulerStarted = true;
251
251
  }
252
252
  logger.info(`我的助手 Gateway Service running on port ${port}`);
253
+ // 启动自愈:检查开机自启 Run key 是否健康,不健康则自动 rewrite。
254
+ // 异步执行,失败不影响主流程。
255
+ (0, ServiceManager_js_1.ensureAutoStartHealthy)().catch(() => { });
253
256
  });
254
257
  };
255
258
  startServer(configuredPort);
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.execAsUser = exports.isHelperReady = exports.stopHelper = exports.updateService = exports.checkForUpdates = exports.restartService = exports.stopService = exports.startService = exports.uninstallService = exports.installService = exports.getServiceInfo = exports.SERVICE_DISPLAY_NAME = exports.SERVICE_NAME = void 0;
6
+ exports.execAsUser = exports.isHelperReady = exports.stopHelper = exports.updateService = exports.checkForUpdates = exports.restartService = exports.stopService = exports.startService = exports.uninstallService = exports.installService = exports.ensureAutoStartHealthy = exports.getServiceInfo = exports.SERVICE_DISPLAY_NAME = exports.SERVICE_NAME = void 0;
7
7
  const child_process_1 = require("child_process");
8
8
  const util_1 = require("util");
9
9
  const path_1 = __importDefault(require("path"));
@@ -158,21 +158,25 @@ function writeGatewayLauncherScript(exePath, scriptPath, workDir) {
158
158
  "$workDir = '" + workDir.replace(/\\/g, '\\\\') + "'",
159
159
  "$pidFile = '" + GATEWAY_PID_FILE.replace(/\\/g, '\\\\') + "'",
160
160
  "",
161
- "# 启动 Gateway(使用 CLI start 命令,监听默认端口 3001)",
161
+ "# 启动 Gateway(无参启动,直接进入 HTTP Server 模式,监听默认端口 3001)",
162
162
  "$psi = New-Object System.Diagnostics.ProcessStartInfo",
163
163
  "$psi.FileName = $exe",
164
164
  "$psi.WorkingDirectory = $workDir",
165
165
  "$psi.UseShellExecute = $false",
166
- "$psi.RedirectStandardOutput = $true",
167
- "$psi.RedirectStandardError = $true",
168
166
  "$psi.CreateNoWindow = $true",
167
+ "# 注意:不重定向 stdout/stderr,避免匿名管道 4KB 缓冲区打满后阻塞 gateway 的事件循环",
169
168
  "$psi.Environment['NODE_ENV'] = 'production'",
170
169
  "$proc = [System.Diagnostics.Process]::Start($psi)",
171
170
  "if ($proc) {",
172
171
  " $proc.Id.ToString() | Out-File -FilePath $pidFile -Encoding UTF8",
173
- " $proc.WaitForExit()",
174
172
  "}",
173
+ "# fire-and-forget:不 WaitForExit,PowerShell 立即退出,省 ~30MB 常驻内存",
175
174
  ].join('\r\n');
175
+ // 确保目标目录存在(%LOCALAPPDATA%\我的助手 首次启动时可能不存在)
176
+ try {
177
+ fs_1.default.mkdirSync(path_1.default.dirname(scriptPath), { recursive: true });
178
+ }
179
+ catch { /* ignore */ }
176
180
  fs_1.default.writeFileSync(scriptPath, '\uFEFF' + content, 'utf8');
177
181
  }
178
182
  /**
@@ -193,7 +197,15 @@ function isRegisteredInRunKey() {
193
197
  * 写入注册表 Run key(开机自启)
194
198
  */
195
199
  async function registerRunKey(scriptPath) {
196
- const regCmd = `powershell -NoProfile -Command "Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name '${exports.SERVICE_NAME}' -Value '\"${scriptPath}\"' -Type String"`;
200
+ // Run key value 必须是一条完整的命令行,Windows 登录时不会按 .ps1 关联去执行;
201
+ // 因此这里把 powershell.exe 的完整调用串(含执行策略与隐藏窗口参数)作为 value 写入。
202
+ const powershellExe = path_1.default.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
203
+ // 最终写入注册表的字符串形如:
204
+ // "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "<scriptPath>"
205
+ const runValue = `"${powershellExe}" -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "${scriptPath}"`;
206
+ // PowerShell 单引号字符串里的单引号需要用两个单引号转义
207
+ const runValueForPs = runValue.replace(/'/g, "''");
208
+ const regCmd = `powershell -NoProfile -Command "Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name '${exports.SERVICE_NAME}' -Value '${runValueForPs}' -Type String"`;
197
209
  await execAsync(regCmd, { timeout: 10000, windowsHide: true });
198
210
  }
199
211
  /**
@@ -402,6 +414,68 @@ async function getServiceInfo() {
402
414
  };
403
415
  }
404
416
  exports.getServiceInfo = getServiceInfo;
417
+ /**
418
+ * 启动自愈:检查 Run key 值是否健康,不健康则自动 rewrite。
419
+ *
420
+ * 历史版本把 launcher 存到了 %TEMP%,且 Run key 里直接写的是 .ps1 裸路径(Windows 登录时不会当脚本执行)。
421
+ * 本函数在 gateway 启动时无侵入地修复这一情况:
422
+ * - 仅 Windows 平台生效
423
+ * - 若 Run key 未注册(说明从未走过 install 流程,例如开发机)→ 直接跳过
424
+ * - 若已注册但值不含 powershell.exe,或 launcher 不在新路径 → rewrite
425
+ * - 顺手清理 %TEMP% 里的旧 launcher 残留
426
+ * 失败不抛异常,仅记录日志,避免影响主流程。
427
+ */
428
+ async function ensureAutoStartHealthy() {
429
+ if (process.platform !== 'win32')
430
+ return;
431
+ try {
432
+ // 1) 读取当前 Run key 值
433
+ const readCmd = `powershell -NoProfile -Command "try { (Get-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name '${exports.SERVICE_NAME}' -ErrorAction Stop).'${exports.SERVICE_NAME}' } catch { '' }"`;
434
+ const { stdout } = await execAsync(readCmd, { timeout: 5000, windowsHide: true });
435
+ const currentValue = stdout.toString().trim();
436
+ // 2) 未注册→开发机或从未安装,不做任何事
437
+ if (!currentValue)
438
+ return;
439
+ // 3) 评估健康度(Run key + 脚本内容双重检查)
440
+ const lower = currentValue.toLowerCase();
441
+ const hasPowershell = lower.includes('powershell.exe');
442
+ const pointsToNewPath = lower.includes(GATEWAY_LAUNCHER_FILE.toLowerCase());
443
+ // 脚本内容健康:不能含 RedirectStandardOutput(旧版本会因管道阻塞卡死)
444
+ let scriptHealthy = false;
445
+ try {
446
+ if (fs_1.default.existsSync(GATEWAY_LAUNCHER_FILE)) {
447
+ const scriptContent = fs_1.default.readFileSync(GATEWAY_LAUNCHER_FILE, 'utf8');
448
+ // 老版含 RedirectStandardOutput(管道阻塞 bug)或 WaitForExit(陪跑浪费内存)→ 判定不健康
449
+ scriptHealthy = !scriptContent.includes('RedirectStandardOutput')
450
+ && !scriptContent.includes('WaitForExit');
451
+ }
452
+ }
453
+ catch { /* ignore */ }
454
+ if (hasPowershell && pointsToNewPath && scriptHealthy)
455
+ return; // 已健康
456
+ logger.warn(`检测到 Run key 不健康,自动修复。current='${currentValue}'`);
457
+ // 4) 重写 launcher 脚本到新路径
458
+ const exe = getGatewayExePath();
459
+ const workDir = path_1.default.dirname(exe);
460
+ writeGatewayLauncherScript(exe, GATEWAY_LAUNCHER_FILE, workDir);
461
+ // 5) 重新注册完整 powershell 命令行
462
+ await registerRunKey(GATEWAY_LAUNCHER_FILE);
463
+ // 6) 清理 %TEMP% 旧 launcher 残留
464
+ try {
465
+ const legacyPath = path_1.default.join(os_1.default.tmpdir(), 'myassis-gateway-launcher.ps1');
466
+ if (fs_1.default.existsSync(legacyPath) && legacyPath.toLowerCase() !== GATEWAY_LAUNCHER_FILE.toLowerCase()) {
467
+ fs_1.default.unlinkSync(legacyPath);
468
+ logger.info(`已清理旧 launcher: ${legacyPath}`);
469
+ }
470
+ }
471
+ catch { /* ignore */ }
472
+ logger.info(`Run key 修复完成 → ${GATEWAY_LAUNCHER_FILE}`);
473
+ }
474
+ catch (err) {
475
+ logger.warn(`ensureAutoStartHealthy 失败(忽略): ${err?.message || err}`);
476
+ }
477
+ }
478
+ exports.ensureAutoStartHealthy = ensureAutoStartHealthy;
405
479
  async function installService() {
406
480
  const platform = process.platform;
407
481
  if (platform === 'win32')
@@ -739,8 +813,10 @@ const HELPER_PORT = 19630;
739
813
  const HELPER_TIMEOUT_MS = 60000;
740
814
  /** Gateway PID 文件路径(用户级进程跟踪) */
741
815
  const GATEWAY_PID_FILE = path_1.default.join(os_1.default.tmpdir(), 'myassis-gateway-pid.txt');
816
+ /** Gateway 启动脚本存放目录(用户级持久化目录,避免被 %TEMP% 清理策略删除) */
817
+ const GATEWAY_DATA_DIR = path_1.default.join(process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), '我的助手');
742
818
  /** Gateway 启动脚本路径 */
743
- const GATEWAY_LAUNCHER_FILE = path_1.default.join(os_1.default.tmpdir(), 'myassis-gateway-launcher.ps1');
819
+ const GATEWAY_LAUNCHER_FILE = path_1.default.join(GATEWAY_DATA_DIR, 'gateway-launcher.ps1');
744
820
  /** 当前活跃的 Helper 进程 */
745
821
  let helperProcess = null;
746
822
  /** Helper 连接 socket */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myassis/gateway",
3
- "version": "1.0.48",
3
+ "version": "1.0.50",
4
4
  "description": "我的助手 Gateway Service - 本地 AI 网关服务,支持认证、WebSocket 实时通信和任务调度",
5
5
  "main": "dist/index.js",
6
6
  "bin": {