@myassis/gateway 1.0.51 → 1.0.53

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
@@ -33,6 +33,65 @@ const logger = (0, shared_1.getLogger)('index');
33
33
  // ─── CLI 模式 ─────────────────────────────────────────
34
34
  // gateway install | start | stop | uninstall | status | update
35
35
  const cliCommand = process.argv[2];
36
+ // ─── 崩溃防护 & stdout/stderr 重定向 ─────────────────────────────────────
37
+ // 打包为 exe 由 PowerShell 启动时,子进程会继承父 PS 的 stdout/stderr 句柄;
38
+ // PowerShell 立即退出后这些句柄失效,任何 console.log 都会触发 EPIPE/EBADF,
39
+ // Node 若无捕获会直接崩溃 → 表现为「gateway 运行一段时间就停止了」。
40
+ // 因此在业务代码启动前把 stdout/stderr 重定向到日志文件,并挂全局兜底。
41
+ if (!cliCommand && process.platform === 'win32') {
42
+ try {
43
+ // 使用 require 而非 import,避免打包器把 fs/path 提前解析导致副作用
44
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
45
+ const fs = require('fs');
46
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
47
+ const pathMod = require('path');
48
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
49
+ const osMod = require('os');
50
+ const logDir = pathMod.join(process.env.LOCALAPPDATA || osMod.tmpdir(), '我的助手', 'logs');
51
+ fs.mkdirSync(logDir, { recursive: true });
52
+ const logFile = pathMod.join(logDir, 'gateway.log');
53
+ // 简单滚动:>10MB 备份为 .old
54
+ try {
55
+ const st = fs.statSync(logFile);
56
+ if (st.size > 10 * 1024 * 1024) {
57
+ const oldFile = logFile + '.old';
58
+ try {
59
+ fs.unlinkSync(oldFile);
60
+ }
61
+ catch { /* ignore */ }
62
+ fs.renameSync(logFile, oldFile);
63
+ }
64
+ }
65
+ catch { /* 文件不存在,忽略 */ }
66
+ const out = fs.createWriteStream(logFile, { flags: 'a' });
67
+ out.on('error', () => { });
68
+ const writeFn = (chunk, enc, cb) => {
69
+ try {
70
+ return out.write(chunk, enc, cb);
71
+ }
72
+ catch {
73
+ return true;
74
+ }
75
+ };
76
+ process.stdout.write = writeFn;
77
+ process.stderr.write = writeFn;
78
+ // 全局兜底:任何未捕获异常都记录,不让进程静默退出
79
+ process.on('uncaughtException', (err) => {
80
+ try {
81
+ out.write(`[${new Date().toISOString()}] uncaughtException: ${err.stack || err.message}\r\n`);
82
+ }
83
+ catch { /* ignore */ }
84
+ });
85
+ process.on('unhandledRejection', (reason) => {
86
+ try {
87
+ out.write(`[${new Date().toISOString()}] unhandledRejection: ${reason?.stack || reason}\r\n`);
88
+ }
89
+ catch { /* ignore */ }
90
+ });
91
+ out.write(`\r\n[${new Date().toISOString()}] ===== gateway 启动 pid=${process.pid} =====\r\n`);
92
+ }
93
+ catch { /* 初始化失败不影响启动 */ }
94
+ }
36
95
  if (cliCommand) {
37
96
  (async () => {
38
97
  try {
@@ -162,10 +162,10 @@ function writeGatewayLauncherScript(exePath, scriptPath, workDir) {
162
162
  "$psi = New-Object System.Diagnostics.ProcessStartInfo",
163
163
  "$psi.FileName = $exe",
164
164
  "$psi.WorkingDirectory = $workDir",
165
- "$psi.UseShellExecute = $false",
166
- "$psi.CreateNoWindow = $true",
167
- "# 注意:不重定向 stdout/stderr,避免匿名管道 4KB 缓冲区打满后阻塞 gateway 的事件循环",
168
- "$psi.Environment['NODE_ENV'] = 'production'",
165
+ "# 关键:UseShellExecute=$true ShellExecute API,子进程不继承父 PS 的 stdout/stderr/stdin 句柄",
166
+ "# 防止 PowerShell 退出后句柄失效导致 gateway 写 stdout 时 EPIPE 崩溃(运行一段时间后自杀)",
167
+ "$psi.UseShellExecute = $true",
168
+ "$psi.WindowStyle = 'Hidden'",
169
169
  "$proc = [System.Diagnostics.Process]::Start($psi)",
170
170
  "if ($proc) {",
171
171
  " $proc.Id.ToString() | Out-File -FilePath $pidFile -Encoding UTF8",
@@ -440,14 +440,19 @@ async function ensureAutoStartHealthy() {
440
440
  const lower = currentValue.toLowerCase();
441
441
  const hasPowershell = lower.includes('powershell.exe');
442
442
  const pointsToNewPath = lower.includes(GATEWAY_LAUNCHER_FILE.toLowerCase());
443
- // 脚本内容健康:不能含 RedirectStandardOutput(旧版本会因管道阻塞卡死)
443
+ // 脚本内容健康:不能含 RedirectStandardOutput/WaitForExit(旧版本管道阻塞 bug),
444
+ // 也不能是 UseShellExecute=$false(会继承父 PS 句柄,PS 退出后 gateway 写 stdout 崩溃)
444
445
  let scriptHealthy = false;
445
446
  try {
446
447
  if (fs_1.default.existsSync(GATEWAY_LAUNCHER_FILE)) {
447
448
  const scriptContent = fs_1.default.readFileSync(GATEWAY_LAUNCHER_FILE, 'utf8');
448
- // 老版含 RedirectStandardOutput(管道阻塞 bug)或 WaitForExit(陪跑浪费内存)→ 判定不健康
449
+ // 老版包含以下任一特征就重写:
450
+ // 1) RedirectStandardOutput → 管道阻塞旧 bug
451
+ // 2) WaitForExit → PS 陆跑浪费内存
452
+ // 3) UseShellExecute = $false → 句柄继承导致进程崩溃(本次修复)
449
453
  scriptHealthy = !scriptContent.includes('RedirectStandardOutput')
450
- && !scriptContent.includes('WaitForExit');
454
+ && !scriptContent.includes('WaitForExit')
455
+ && !scriptContent.includes('UseShellExecute = $false');
451
456
  }
452
457
  }
453
458
  catch { /* ignore */ }
@@ -136,7 +136,15 @@ exports.fileTool = {
136
136
  },
137
137
  handler: async (args) => {
138
138
  try {
139
+ // 如果 args 是原始 JSON 字符串(上游 parseAruments 解析失败时原样返回),
140
+ // 先做轻量级检查:大文件写入时字符串极长,直接拦截避免 JSON.parse 爆内存
141
+ if (typeof args === 'string') {
142
+ return { success: false, errorMessage: `参数过大 (${(args.length / 1024).toFixed(1)}KB),大文件写入请分批进行或使用多次 edit 操作` };
143
+ }
139
144
  const { action, path: filePath, content, searchQuery, maxResults = 50, excludeDirs, caseSensitive = false, wholeWord = false } = args;
145
+ if (!action) {
146
+ return { success: false, errorMessage: '缺少必需参数: action' };
147
+ }
140
148
  const effectiveExcludeDirs = typeof excludeDirs === 'string' && excludeDirs.trim()
141
149
  ? [...new Set([...DEFAULT_EXCLUDE_DIRS, ...excludeDirs.split(',').map((d) => d.trim()).filter(Boolean)])]
142
150
  : DEFAULT_EXCLUDE_DIRS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myassis/gateway",
3
- "version": "1.0.51",
3
+ "version": "1.0.53",
4
4
  "description": "我的助手 Gateway Service - 本地 AI 网关服务,支持认证、WebSocket 实时通信和任务调度",
5
5
  "main": "dist/index.js",
6
6
  "bin": {