@myassis/gateway 1.0.47 → 1.0.49

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"));
@@ -103,11 +103,35 @@ function getStoredPid() {
103
103
  }
104
104
  /**
105
105
  * 停止 Gateway 用户进程(优雅终止)
106
+ *
107
+ * 注意:当 pid 指向当前进程自己时,绝不能使用 process.kill(pid)。
108
+ * - Windows 上 Node 的 process.kill 等同 TerminateProcess,无清理机会,
109
+ * server.close() / WebSocket / 文件句柄都不会被释放,会导致后续 exe 解锁超时。
110
+ * - 因此自杀场景改走 process.emit('SIGTERM'),复用 main.ts 已注册的优雅关闭逻辑,
111
+ * 并在响应返回后异步退出,避免 HTTP 调用方读不到结果。
106
112
  */
107
113
  async function stopGatewayProcess() {
108
114
  const pid = getStoredPid();
109
115
  if (!pid)
110
116
  return;
117
+ // —— 场景 1:要停的就是自己 —— 走优雅退出
118
+ if (pid === process.pid) {
119
+ try {
120
+ fs_1.default.unlinkSync(GATEWAY_PID_FILE);
121
+ }
122
+ catch { /* ignore */ }
123
+ // 异步触发,确保当前调用栈(含 HTTP 响应)有机会先返回
124
+ setTimeout(() => {
125
+ try {
126
+ process.emit('SIGTERM');
127
+ }
128
+ catch { /* ignore */ }
129
+ // 兜底:5s 内 SIGTERM handler 没把进程关掉,再强退
130
+ setTimeout(() => process.exit(0), 5000).unref();
131
+ }, 100).unref();
132
+ return;
133
+ }
134
+ // —— 场景 2:停的是别人(孤儿进程 / 旧实例)—— 才真正发信号
111
135
  try {
112
136
  process.kill(pid, 'SIGTERM');
113
137
  await new Promise(r => setTimeout(r, 2000));
@@ -149,6 +173,11 @@ function writeGatewayLauncherScript(exePath, scriptPath, workDir) {
149
173
  " $proc.WaitForExit()",
150
174
  "}",
151
175
  ].join('\r\n');
176
+ // 确保目标目录存在(%LOCALAPPDATA%\我的助手 首次启动时可能不存在)
177
+ try {
178
+ fs_1.default.mkdirSync(path_1.default.dirname(scriptPath), { recursive: true });
179
+ }
180
+ catch { /* ignore */ }
152
181
  fs_1.default.writeFileSync(scriptPath, '\uFEFF' + content, 'utf8');
153
182
  }
154
183
  /**
@@ -169,7 +198,15 @@ function isRegisteredInRunKey() {
169
198
  * 写入注册表 Run key(开机自启)
170
199
  */
171
200
  async function registerRunKey(scriptPath) {
172
- const regCmd = `powershell -NoProfile -Command "Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name '${exports.SERVICE_NAME}' -Value '\"${scriptPath}\"' -Type String"`;
201
+ // Run key value 必须是一条完整的命令行,Windows 登录时不会按 .ps1 关联去执行;
202
+ // 因此这里把 powershell.exe 的完整调用串(含执行策略与隐藏窗口参数)作为 value 写入。
203
+ const powershellExe = path_1.default.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
204
+ // 最终写入注册表的字符串形如:
205
+ // "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "<scriptPath>"
206
+ const runValue = `"${powershellExe}" -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "${scriptPath}"`;
207
+ // PowerShell 单引号字符串里的单引号需要用两个单引号转义
208
+ const runValueForPs = runValue.replace(/'/g, "''");
209
+ const regCmd = `powershell -NoProfile -Command "Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name '${exports.SERVICE_NAME}' -Value '${runValueForPs}' -Type String"`;
173
210
  await execAsync(regCmd, { timeout: 10000, windowsHide: true });
174
211
  }
175
212
  /**
@@ -378,6 +415,57 @@ async function getServiceInfo() {
378
415
  };
379
416
  }
380
417
  exports.getServiceInfo = getServiceInfo;
418
+ /**
419
+ * 启动自愈:检查 Run key 值是否健康,不健康则自动 rewrite。
420
+ *
421
+ * 历史版本把 launcher 存到了 %TEMP%,且 Run key 里直接写的是 .ps1 裸路径(Windows 登录时不会当脚本执行)。
422
+ * 本函数在 gateway 启动时无侵入地修复这一情况:
423
+ * - 仅 Windows 平台生效
424
+ * - 若 Run key 未注册(说明从未走过 install 流程,例如开发机)→ 直接跳过
425
+ * - 若已注册但值不含 powershell.exe,或 launcher 不在新路径 → rewrite
426
+ * - 顺手清理 %TEMP% 里的旧 launcher 残留
427
+ * 失败不抛异常,仅记录日志,避免影响主流程。
428
+ */
429
+ async function ensureAutoStartHealthy() {
430
+ if (process.platform !== 'win32')
431
+ return;
432
+ try {
433
+ // 1) 读取当前 Run key 值
434
+ 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 { '' }"`;
435
+ const { stdout } = await execAsync(readCmd, { timeout: 5000, windowsHide: true });
436
+ const currentValue = stdout.toString().trim();
437
+ // 2) 未注册→开发机或从未安装,不做任何事
438
+ if (!currentValue)
439
+ return;
440
+ // 3) 评估健康度
441
+ const lower = currentValue.toLowerCase();
442
+ const hasPowershell = lower.includes('powershell.exe');
443
+ const pointsToNewPath = lower.includes(GATEWAY_LAUNCHER_FILE.toLowerCase());
444
+ if (hasPowershell && pointsToNewPath)
445
+ return; // 已健康
446
+ logger.warn(`检测到 Run key 不健康,自动修复。current='${currentValue}'`);
447
+ // 4) 重写 launcher 脚本到新路径
448
+ const exe = getGatewayExePath();
449
+ const workDir = path_1.default.dirname(exe);
450
+ writeGatewayLauncherScript(exe, GATEWAY_LAUNCHER_FILE, workDir);
451
+ // 5) 重新注册完整 powershell 命令行
452
+ await registerRunKey(GATEWAY_LAUNCHER_FILE);
453
+ // 6) 清理 %TEMP% 旧 launcher 残留
454
+ try {
455
+ const legacyPath = path_1.default.join(os_1.default.tmpdir(), 'myassis-gateway-launcher.ps1');
456
+ if (fs_1.default.existsSync(legacyPath) && legacyPath.toLowerCase() !== GATEWAY_LAUNCHER_FILE.toLowerCase()) {
457
+ fs_1.default.unlinkSync(legacyPath);
458
+ logger.info(`已清理旧 launcher: ${legacyPath}`);
459
+ }
460
+ }
461
+ catch { /* ignore */ }
462
+ logger.info(`Run key 修复完成 → ${GATEWAY_LAUNCHER_FILE}`);
463
+ }
464
+ catch (err) {
465
+ logger.warn(`ensureAutoStartHealthy 失败(忽略): ${err?.message || err}`);
466
+ }
467
+ }
468
+ exports.ensureAutoStartHealthy = ensureAutoStartHealthy;
381
469
  async function installService() {
382
470
  const platform = process.platform;
383
471
  if (platform === 'win32')
@@ -715,8 +803,10 @@ const HELPER_PORT = 19630;
715
803
  const HELPER_TIMEOUT_MS = 60000;
716
804
  /** Gateway PID 文件路径(用户级进程跟踪) */
717
805
  const GATEWAY_PID_FILE = path_1.default.join(os_1.default.tmpdir(), 'myassis-gateway-pid.txt');
806
+ /** Gateway 启动脚本存放目录(用户级持久化目录,避免被 %TEMP% 清理策略删除) */
807
+ const GATEWAY_DATA_DIR = path_1.default.join(process.env.LOCALAPPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), '我的助手');
718
808
  /** Gateway 启动脚本路径 */
719
- const GATEWAY_LAUNCHER_FILE = path_1.default.join(os_1.default.tmpdir(), 'myassis-gateway-launcher.ps1');
809
+ const GATEWAY_LAUNCHER_FILE = path_1.default.join(GATEWAY_DATA_DIR, 'gateway-launcher.ps1');
720
810
  /** 当前活跃的 Helper 进程 */
721
811
  let helperProcess = null;
722
812
  /** Helper 连接 socket */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myassis/gateway",
3
- "version": "1.0.47",
3
+ "version": "1.0.49",
4
4
  "description": "我的助手 Gateway Service - 本地 AI 网关服务,支持认证、WebSocket 实时通信和任务调度",
5
5
  "main": "dist/index.js",
6
6
  "bin": {