ai-helper-agent 1.14.10 → 1.14.11

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/bin/agent.js CHANGED
@@ -372,20 +372,32 @@ if (command === 'start') {
372
372
 
373
373
  const running = await isRunning(config);
374
374
  if (running) {
375
+ let apiShutdownOk = false;
375
376
  try {
376
377
  const resp = await fetch(`http://${config.host}:${config.port}/api/shutdown`, {
377
378
  method: 'POST'
378
379
  });
379
380
  if (resp.ok) {
380
- const data = await resp.json();
381
- console.log(`[Agent] ${data.message || t('agentStopped')}`);
381
+ try {
382
+ const data = await resp.json();
383
+ console.log(`[Agent] ${data.message || t('agentStopped')}`);
384
+ } catch {
385
+ // 服务端在发送响应后关闭连接,json() 可能读不到完整数据
386
+ // 但 shutdown 请求已成功送达,服务正在关闭
387
+ console.log(`[Agent] ${t('agentStopped')}`);
388
+ }
389
+ apiShutdownOk = true;
382
390
  removePidFile();
383
391
  process.exit(0);
384
392
  }
385
- } catch (err) {
386
- console.error(`[Agent] ${t('apiShutdownFailed')}: ${err.message}`);
393
+ } catch {
394
+ // isRunning() 刚确认服务在线,fetch 出错说明服务端已关闭连接(shutdown 正在执行)
395
+ // 视为关闭成功,不打印错误
396
+ apiShutdownOk = true;
397
+ }
398
+ if (!apiShutdownOk) {
399
+ console.log(`[Agent] ${t('apiShutdownFallback')}`);
387
400
  }
388
- console.log(`[Agent] ${t('apiShutdownFallback')}`);
389
401
  }
390
402
 
391
403
  const pid = getPidFromFile();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-helper-agent",
3
- "version": "1.14.10",
3
+ "version": "1.14.11",
4
4
  "description": "AI Helper Agent - Local file access and system command execution proxy for Chrome extension / AI Helper 代理服务,为 Chrome 扩展提供本地文件读写和系统命令执行",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/src/server.js CHANGED
@@ -485,6 +485,39 @@ export function startServer() {
485
485
  });
486
486
  }
487
487
 
488
+ // Agent 关闭:本机来源(CLI stop)免认证,远程来源仍需认证(防止 0.0.0.0 监听时遭网络攻击)
489
+ if (req.method === 'POST' && pathname === '/api/shutdown') {
490
+ const remoteIp = req.socket.remoteAddress || '';
491
+ const isLocal = remoteIp === '127.0.0.1' || remoteIp === '::1' || remoteIp === '::ffff:127.0.0.1' || remoteIp === 'localhost';
492
+ if (!isLocal) {
493
+ // 远程来源:走正常认证流程
494
+ const authHeader = req.headers.authorization;
495
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
496
+ logSecurity('shutdown_auth_missing', { path: pathname, remote: remoteIp });
497
+ return jsonResponse(res, 401, { success: false, error: t('error.noToken') });
498
+ }
499
+ const extId = verifyToken(authHeader.slice(7));
500
+ if (!extId) {
501
+ logSecurity('shutdown_auth_invalid', { path: pathname, remote: remoteIp });
502
+ return jsonResponse(res, 403, { success: false, error: t('error.invalidToken') });
503
+ }
504
+ }
505
+ if (agentOperationInProgress) {
506
+ return jsonResponse(res, 409, { success: false, error: t('error.operationInProgress') });
507
+ }
508
+ if (Date.now() - lastOperationTime < OPERATION_COOLDOWN_MS) {
509
+ const wait = Math.ceil((OPERATION_COOLDOWN_MS - (Date.now() - lastOperationTime)) / 1000);
510
+ return jsonResponse(res, 429, { success: false, error: t('error.tooFrequent', { wait }) });
511
+ }
512
+ agentOperationInProgress = true;
513
+ lastOperationTime = Date.now();
514
+ logSystem('shutdown', { reason: 'api_request', local: isLocal, remote: remoteIp });
515
+ jsonResponse(res, 200, { success: true, message: t('message.shuttingDown') });
516
+ // 延迟 200ms 再关闭,确保响应数据已送达客户端(res.end() 仅写入缓冲区,不保证客户端已接收)
517
+ setTimeout(() => shutdown(), 200);
518
+ return;
519
+ }
520
+
488
521
  // ---------- 需要认证的接口 ----------
489
522
  const authHeader = req.headers.authorization;
490
523
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
@@ -503,23 +536,6 @@ export function startServer() {
503
536
  return jsonResponse(res, 200, { success: true, time: Date.now() });
504
537
  }
505
538
 
506
- // Agent 关闭(需认证)
507
- if (req.method === 'POST' && pathname === '/api/shutdown') {
508
- if (agentOperationInProgress) {
509
- return jsonResponse(res, 409, { success: false, error: t('error.operationInProgress') });
510
- }
511
- if (Date.now() - lastOperationTime < OPERATION_COOLDOWN_MS) {
512
- const wait = Math.ceil((OPERATION_COOLDOWN_MS - (Date.now() - lastOperationTime)) / 1000);
513
- return jsonResponse(res, 429, { success: false, error: t('error.tooFrequent', { wait }) });
514
- }
515
- agentOperationInProgress = true;
516
- lastOperationTime = Date.now();
517
- logSystem('shutdown', { reason: 'api_request', extId });
518
- jsonResponse(res, 200, { success: true, message: t('message.shuttingDown') });
519
- shutdown();
520
- return;
521
- }
522
-
523
539
  // 重启 Agent(需认证)
524
540
  if (req.method === 'POST' && pathname === '/api/agent/restart') {
525
541
  if (agentOperationInProgress) {