@myassis/gateway 1.0.64 → 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 = '
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
"$
|
|
170
|
-
"
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
264
|
-
|
|
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 */
|
|
@@ -531,6 +531,9 @@ class LLMClient {
|
|
|
531
531
|
if (d.finish_reason === 'stop' && !d.content) {
|
|
532
532
|
throw Error('model return empty content');
|
|
533
533
|
}
|
|
534
|
+
if (!d?.content && d?.finish_reason === '') {
|
|
535
|
+
throw new Error('model return empty content with unknown finish_reason');
|
|
536
|
+
}
|
|
534
537
|
const toolCalls = d.toolCalls?.map((tc) => ({
|
|
535
538
|
id: tc.id || '',
|
|
536
539
|
toolName: tc.function?.name || '',
|
|
@@ -552,6 +555,9 @@ class LLMClient {
|
|
|
552
555
|
if (messageData.finish_reason === 'stop' && !messageData.content) {
|
|
553
556
|
throw Error('model return empty content');
|
|
554
557
|
}
|
|
558
|
+
if (!messageData?.content && messageData.finish_reason === '') {
|
|
559
|
+
throw new Error('model return empty content with unknown finish_reason');
|
|
560
|
+
}
|
|
555
561
|
return {
|
|
556
562
|
content: messageData.content || '',
|
|
557
563
|
reasoningContent: messageData.reasoning_content || undefined,
|