@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 +59 -0
- package/dist/services/ServiceManager.js +12 -7
- package/dist/services/tools/file.js +8 -0
- package/package.json +1 -1
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
|
-
"
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"$psi.
|
|
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
|
-
//
|
|
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;
|