ai-helper-agent 1.1.0 → 1.3.0

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/README.md CHANGED
@@ -13,14 +13,15 @@ npm install -g ai-helper-agent
13
13
  ## 快速开始
14
14
 
15
15
  ```bash
16
- # 启动 Agent(默认工作目录为当前目录)
16
+ # 前台启动(终端显示实时运行日志,适合调试)
17
17
  ai-helper-agent start
18
18
 
19
- # 指定工作目录
20
- ai-helper-agent start --workdir /path/to/your/project
19
+ # 后台启动(守护进程模式,终端立即返回)
20
+ ai-helper-agent start --background
21
+ ai-helper-agent start -b
21
22
 
22
- # 指定端口
23
- ai-helper-agent start --port 18911
23
+ # 指定工作目录和端口
24
+ ai-helper-agent start --workdir /path/to/your/project --port 18911
24
25
  ```
25
26
 
26
27
  启动后终端会显示 4 位配对码,在 Chrome 扩展设置页「本地Agent」标签中填入即可完成配对。
@@ -29,9 +30,10 @@ ai-helper-agent start --port 18911
29
30
 
30
31
  | 命令 | 说明 |
31
32
  |------|------|
32
- | `start` | 启动 Agent 服务 |
33
+ | `start` | 启动 Agent 服务(前台运行,显示实时日志) |
34
+ | `start --background` / `start -b` | 后台启动(守护进程模式) |
33
35
  | `stop` | 停止正在运行的 Agent |
34
- | `restart` | 重启 Agent 服务 |
36
+ | `restart` | 重启 Agent 服务(支持 -b 后台重启) |
35
37
  | `status` | 查看运行状态 |
36
38
  | `paircode` | 查看配对码提示 |
37
39
  | `config` | 查看当前配置 |
@@ -41,14 +43,40 @@ ai-helper-agent start --port 18911
41
43
  ### 启动选项
42
44
 
43
45
  ```
44
- --port <端口> 监听端口,默认 18910
45
- --host <地址> 监听地址,默认 127.0.0.1
46
- --workdir <目录> 工作目录(文件读写限制在此范围内)
46
+ --background, -b 后台运行(守护进程模式)
47
+ --port <端口> 监听端口,默认 18910
48
+ --host <地址> 监听地址,默认 127.0.0.1
49
+ --workdir <目录> 工作目录(文件读写限制在此范围内)
47
50
  ```
48
51
 
52
+ ### 前台 vs 后台运行
53
+
54
+ | 模式 | 命令 | 终端行为 | 适用场景 |
55
+ |------|------|----------|----------|
56
+ | 前台 | `start` | 阻塞终端,实时显示运行日志 | 开发调试、问题排查 |
57
+ | 后台 | `start --background` | 立即返回,终端无日志输出 | 生产环境、日常使用 |
58
+
59
+ 前台运行时,所有文件读写、命令执行、安全拦截等操作日志会以格式化形式实时输出到终端:
60
+
61
+ ```
62
+ [12:30:01] [INFO] [文件:read] path=/project/src/index.js size=2048
63
+ [12:30:05] [INFO] [命令:started] command="npm test" cwd=/project execId=a1b2c3d4
64
+ [12:30:12] [INFO] [命令:completed] execId=a1b2c3d4 exitCode=0 killed=false
65
+ [12:31:00] [WARN] [安全:exec_denied] command="rm -rf /" reason=高危命令被拦截
66
+ ```
67
+
68
+ ### 进程管理
69
+
70
+ Agent 通过 PID 文件(`~/.ai-helper-agent/agent.pid`)管理进程生命周期:
71
+
72
+ - 启动时自动写入 PID 文件
73
+ - `stop` 命令优先通过 API 优雅关闭,失败则通过 PID 文件 kill 进程
74
+ - 正常关闭时自动清理 PID 文件
75
+
49
76
  ## 安全机制
50
77
 
51
78
  ### 文件沙箱
79
+
52
80
  所有文件操作限制在配置的 `allowedPaths` 白名单目录内。通过 `realpath` 解析防止符号链接绕过,确保物理路径安全。
53
81
 
54
82
  ### 命令分级管控
@@ -105,10 +133,32 @@ ai-helper-agent start --port 18911
105
133
  | `commandTimeout` | 命令执行超时(毫秒) | 300000(5分钟) |
106
134
  | `fileMaxSize` | 文件读写最大字节数 | 52428800(50MB) |
107
135
 
136
+ ## 文件搜索
137
+
138
+ Agent 优先使用系统原生搜索工具,不可用时自动回退到 Node.js 实现:
139
+
140
+ | 引擎 | 用途 | 检测命令 |
141
+ |------|------|----------|
142
+ | `fd` | 文件名搜索(快速) | `fd --version` |
143
+ | `rg` (ripgrep) | 文件内容搜索(快速) | `rg --version` |
144
+
145
+ 状态接口返回当前可用的搜索工具:
146
+
147
+ ```json
148
+ { "searchTools": { "fd": true, "rg": true } }
149
+ ```
150
+
108
151
  ## 审计日志
109
152
 
110
153
  所有操作自动记录到审计日志中,日志文件位于 `~/.ai-helper-agent/logs/`。
111
154
 
155
+ ### 双通道输出
156
+
157
+ - **终端输出**(前台模式):格式化可读格式,输出到 stderr
158
+ - **文件输出**:JSON Lines 格式,写入日志文件
159
+
160
+ 两种通道同时工作,互不影响。
161
+
112
162
  ### 日志格式
113
163
 
114
164
  JSON Lines 格式,每行一条记录,文件按日命名 `agent-YYYY-MM-DD.log`。
@@ -124,10 +174,10 @@ JSON Lines 格式,每行一条记录,文件按日命名 `agent-YYYY-MM-DD.lo
124
174
  | category | 说明 | 包含的操作 |
125
175
  |----------|------|-----------|
126
176
  | `auth` | 认证事件 | 配对成功/失败 |
127
- | `fs` | 文件操作 | read, write, list, delete |
177
+ | `fs` | 文件操作 | read, write, list, delete, search_files, search_content |
128
178
  | `exec` | 命令执行 | started, completed, stopped, error |
129
179
  | `security` | 安全事件 | deny, confirm, auth 失败, 路径越权拦截 |
130
- | `system` | 系统事件 | server_start, server_stop, shutdown |
180
+ | `system` | 系统事件 | server_start, server_stop, shutdown, server_error, uncaught_exception, unhandled_rejection |
131
181
 
132
182
  ### 日志查询 API
133
183
 
@@ -156,7 +206,8 @@ GET /api/logs/dates
156
206
  | 方法 | 路径 | 说明 |
157
207
  |------|------|------|
158
208
  | POST | `/api/pair` | 配对认证 |
159
- | GET | `/api/status` | 健康检查(版本号、运行状态) |
209
+ | GET | `/api/status` | 健康检查(版本号、平台信息、搜索工具可用性) |
210
+ | POST | `/api/shutdown` | 关闭 Agent 服务(仅限本地访问) |
160
211
 
161
212
  ### 需要认证(Bearer Token)
162
213
 
@@ -166,11 +217,12 @@ GET /api/logs/dates
166
217
  | POST | `/api/fs/write` | 写入文件 |
167
218
  | POST | `/api/fs/list` | 列出目录 |
168
219
  | POST | `/api/fs/delete` | 删除文件/目录 |
220
+ | POST | `/api/fs/search_files` | 按文件名模式搜索(glob) |
221
+ | POST | `/api/fs/search_content` | 搜索文件内容(rg 优先,Node.js 回退) |
169
222
  | POST | `/api/exec` | 执行系统命令 |
170
223
  | POST | `/api/exec/stop` | 停止命令执行 |
171
224
  | GET | `/api/exec/running` | 运行中的进程列表 |
172
- | GET | `/api/status/detail` | 详细信息(含工作目录) |
173
- | POST | `/api/shutdown` | 关闭 Agent 服务 |
225
+ | GET | `/api/status/detail` | 详细信息(含工作目录、配对码、搜索工具) |
174
226
  | GET | `/api/logs` | 查询审计日志(支持 date/category/limit/offset 参数) |
175
227
  | GET | `/api/logs/dates` | 获取可用日志日期列表 |
176
228
 
@@ -180,12 +232,24 @@ GET /api/logs/dates
180
232
  |------|------|
181
233
  | `ws://127.0.0.1:18910/ws/exec/:execId` | 命令执行实时输出流 |
182
234
 
235
+ ## 健壮性
236
+
237
+ Agent 内置多层异常保护,防止单个错误导致整个服务崩溃:
238
+
239
+ - **请求级保护**:每个 HTTP 请求处理外层包裹异常捕获,异常时返回 500 而非崩溃
240
+ - **URL 解析保护**:畸形 URL 不会导致进程崩溃,返回 400
241
+ - **服务器级保护**:端口占用等服务器错误有专门处理,优雅报错退出
242
+ - **全局兜底**:`uncaughtException` 和 `unhandledRejection` 全局捕获,记录错误日志但不退出进程
243
+ - **文件 I/O 保护**:配置文件读写失败不影响服务运行
244
+ - **进程管理保护**:`SIGTERM`/`SIGKILL` 发送对已退出进程有 try-catch 保护
245
+
183
246
  ## 技术栈
184
247
 
185
248
  - Node.js >= 18
186
249
  - 原生 `http` 模块(HTTP 服务)
187
250
  - `ws` 库(WebSocket 服务)
188
251
  - 零外部框架依赖
252
+ - 可选依赖:`fd`、`rg`(ripgrep)— 用于加速文件搜索
189
253
 
190
254
  ## License
191
255
 
package/bin/agent.js CHANGED
@@ -2,10 +2,13 @@
2
2
  // agent/bin/agent.js - CLI 入口
3
3
  // 用法: ai-helper-agent <start|stop|restart|status|paircode|config> [options]
4
4
  import { join } from 'path';
5
- import { existsSync, readFileSync } from 'fs';
5
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
6
+ import { spawn } from 'child_process';
7
+ import { homedir } from 'os';
6
8
 
7
- const AGENT_DIR = join(process.env.HOME || '~', '.ai-helper-agent');
9
+ const AGENT_DIR = join(homedir(), '.ai-helper-agent');
8
10
  const CONFIG_FILE = join(AGENT_DIR, 'config.json');
11
+ const PID_FILE = join(AGENT_DIR, 'agent.pid');
9
12
 
10
13
  /**
11
14
  * 读取 Agent 配置文件
@@ -16,22 +19,100 @@ function readAgentConfig() {
16
19
  const raw = readFileSync(CONFIG_FILE, 'utf-8');
17
20
  return JSON.parse(raw);
18
21
  }
19
- } catch {}
22
+ } catch (err) {
23
+ console.error(`[Agent] 配置文件读取失败: ${err.message}`);
24
+ }
20
25
  return { port: 18910, host: '127.0.0.1' };
21
26
  }
22
27
 
23
28
  /**
24
- * 检查 Agent 是否在运行
29
+ * 确保 Agent 配置目录存在
30
+ */
31
+ function ensureAgentDir() {
32
+ if (!existsSync(AGENT_DIR)) {
33
+ try {
34
+ mkdirSync(AGENT_DIR, { recursive: true });
35
+ } catch (err) {
36
+ console.error(`[Agent] 无法创建配置目录 ${AGENT_DIR}: ${err.message}`);
37
+ }
38
+ }
39
+ }
40
+
41
+ /**
42
+ * 从 PID 文件读取进程 ID(原子化读取,无 TOCTOU)
25
43
  */
26
- async function isRunning(config) {
44
+ function getPidFromFile() {
27
45
  try {
28
- const resp = await fetch(`http://${config.host}:${config.port}/api/status`);
29
- return resp.ok;
46
+ const raw = readFileSync(PID_FILE, 'utf-8').trim();
47
+ const pid = parseInt(raw, 10);
48
+ if (isNaN(pid) || pid <= 0) {
49
+ console.error(`[Agent] PID 文件内容无效: "${raw}"`);
50
+ return null;
51
+ }
52
+ return pid;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 删除 PID 文件
60
+ */
61
+ function removePidFile() {
62
+ try {
63
+ if (existsSync(PID_FILE)) unlinkSync(PID_FILE);
64
+ } catch (err) {
65
+ console.error(`[Agent] 删除 PID 文件失败: ${err.message}`);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * 通过 PID 终止进程
71
+ */
72
+ function killByPid(pid) {
73
+ if (!pid || typeof pid !== 'number' || pid <= 0) return false;
74
+ try {
75
+ process.kill(pid, 'SIGTERM');
76
+ return true;
30
77
  } catch {
31
78
  return false;
32
79
  }
33
80
  }
34
81
 
82
+ /**
83
+ * 校验端口号
84
+ */
85
+ function isValidPort(port) {
86
+ return Number.isInteger(port) && port >= 1 && port <= 65535;
87
+ }
88
+
89
+ /**
90
+ * 检查 Agent 是否在运行(轮询直到确认)
91
+ */
92
+ async function isRunning(config, retries = 2) {
93
+ for (let i = 0; i < retries; i++) {
94
+ try {
95
+ const resp = await fetch(`http://${config.host}:${config.port}/api/status`);
96
+ if (resp.ok) return true;
97
+ } catch {}
98
+ if (i < retries - 1) await new Promise(r => setTimeout(r, 300));
99
+ }
100
+ return false;
101
+ }
102
+
103
+ /**
104
+ * 等待 Agent 退出(轮询端口释放)
105
+ */
106
+ async function waitForExit(config, timeoutMs = 5000) {
107
+ const start = Date.now();
108
+ while (Date.now() - start < timeoutMs) {
109
+ const alive = await isRunning(config, 1);
110
+ if (!alive) return true;
111
+ await new Promise(r => setTimeout(r, 200));
112
+ }
113
+ return false;
114
+ }
115
+
35
116
  /**
36
117
  * 打印帮助信息
37
118
  */
@@ -41,7 +122,7 @@ function printHelp() {
41
122
  console.log('用法: ai-helper-agent <命令> [选项]');
42
123
  console.log('');
43
124
  console.log('命令:');
44
- console.log(' start 启动 Agent 服务');
125
+ console.log(' start 启动 Agent 服务(前台运行)');
45
126
  console.log(' stop 停止正在运行的 Agent');
46
127
  console.log(' restart 重启 Agent 服务');
47
128
  console.log(' status 查看 Agent 运行状态');
@@ -50,51 +131,131 @@ function printHelp() {
50
131
  console.log(' help 显示此帮助信息');
51
132
  console.log('');
52
133
  console.log('启动选项:');
134
+ console.log(' --background, -b 后台运行(守护进程模式)');
53
135
  console.log(' --port <端口> 设置监听端口 (默认: 18910)');
54
136
  console.log(' --host <地址> 设置监听地址 (默认: 127.0.0.1)');
55
137
  console.log(' --workdir <目录> 设置工作目录 (默认: 当前目录)');
56
138
  console.log('');
57
139
  console.log('示例:');
58
140
  console.log(' ai-helper-agent start');
59
- console.log(' ai-helper-agent start --port 18911 --workdir /path/to/project');
141
+ console.log(' ai-helper-agent start --background');
142
+ console.log(' ai-helper-agent start -b --port 18911');
60
143
  console.log(' ai-helper-agent stop');
61
- console.log(' ai-helper-agent restart');
144
+ console.log(' ai-helper-agent restart -b');
62
145
  console.log(' ai-helper-agent status');
63
146
  }
64
147
 
148
+ /**
149
+ * 从命令行参数提取端口配置,校验后覆盖 config
150
+ */
151
+ function applyCliArgs(config, args) {
152
+ for (let i = 0; i < args.length; i++) {
153
+ const arg = args[i];
154
+ if (arg === '--port' && args[i + 1]) {
155
+ const port = parseInt(args[i + 1], 10);
156
+ if (!isValidPort(port)) {
157
+ console.error(`[Agent] 无效端口号: ${args[i + 1]},使用默认端口 ${config.port}`);
158
+ } else {
159
+ config.port = port;
160
+ }
161
+ i++;
162
+ } else if (arg === '--host' && args[i + 1]) {
163
+ config.host = args[i + 1];
164
+ i++;
165
+ } else if (arg === '--workdir' && args[i + 1]) {
166
+ config.workdir = args[i + 1];
167
+ i++;
168
+ }
169
+ }
170
+ return config;
171
+ }
172
+
65
173
  const command = process.argv[2] || 'help';
66
174
 
67
175
  // --version / -v
68
176
  if (command === '--version' || command === '-v') {
69
- const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
70
- console.log(`ai-helper-agent v${pkg.version}`);
177
+ try {
178
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
179
+ console.log(`ai-helper-agent v${pkg.version}`);
180
+ } catch (err) {
181
+ console.error(`[Agent] 无法读取版本信息: ${err.message}`);
182
+ }
71
183
  process.exit(0);
72
184
  }
73
185
 
74
186
  // ==================== start ====================
75
187
  if (command === 'start') {
76
- const { startServer } = await import('../src/server.js');
77
- const { loadConfig } = await import('../src/config.js');
188
+ const isBg = process.argv.includes('--background') || process.argv.includes('-b');
78
189
 
79
- console.log('[Agent] AI Helper Agent 启动中...');
190
+ // 过滤掉 --background/-b,其余参数传给子进程
191
+ const passArgs = process.argv.slice(3).filter(a => a !== '--background' && a !== '-b');
80
192
 
81
- const args = process.argv.slice(3);
82
- const config = loadConfig();
193
+ if (isBg) {
194
+ ensureAgentDir();
83
195
 
84
- for (let i = 0; i < args.length; i++) {
85
- const arg = args[i];
86
- if (arg === '--port' && args[i + 1]) {
87
- config.port = parseInt(args[i + 1], 10);
88
- i++;
89
- } else if (arg === '--host' && args[i + 1]) {
90
- config.host = args[i + 1];
91
- i++;
92
- } else if (arg === '--workdir' && args[i + 1]) {
93
- config.workdir = args[i + 1];
94
- i++;
196
+ // 检查是否已有 Agent 在运行
197
+ const existingConfig = readAgentConfig();
198
+ if (await isRunning(existingConfig)) {
199
+ console.log('[Agent] Agent 已在运行中,无需重复启动');
200
+ process.exit(0);
201
+ }
202
+
203
+ // 以 detached 模式启动子进程
204
+ const child = spawn(process.execPath, [
205
+ ...process.execArgv,
206
+ process.argv[1],
207
+ 'start',
208
+ ...passArgs
209
+ ], {
210
+ detached: true,
211
+ stdio: 'ignore',
212
+ cwd: process.cwd(),
213
+ env: { ...process.env }
214
+ });
215
+
216
+ // 监听 spawn 错误
217
+ let spawnFailed = false;
218
+ child.on('error', (err) => {
219
+ spawnFailed = true;
220
+ console.error(`[Agent] 后台启动失败: ${err.message}`);
221
+ try { unlinkSync(PID_FILE); } catch {}
222
+ process.exit(1);
223
+ });
224
+
225
+ // 等待进程启动确认
226
+ await new Promise(resolve => setTimeout(resolve, 500));
227
+
228
+ if (!spawnFailed && child.pid) {
229
+ try {
230
+ writeFileSync(PID_FILE, String(child.pid));
231
+ } catch (err) {
232
+ console.error(`[Agent] PID 文件写入失败: ${err.message}`);
233
+ process.exit(1);
234
+ }
235
+ child.unref();
236
+ console.log(`[Agent] Agent 已在后台启动 (PID: ${child.pid})`);
237
+ console.log(`[Agent] 使用 'ai-helper-agent stop' 停止服务`);
238
+ console.log(`[Agent] 使用 'ai-helper-agent status' 查看状态`);
95
239
  }
240
+
241
+ process.exit(spawnFailed ? 1 : 0);
242
+ }
243
+
244
+ // 前台模式:写入 PID 文件以便 stop 能找到
245
+ ensureAgentDir();
246
+ try {
247
+ writeFileSync(PID_FILE, String(process.pid));
248
+ } catch (err) {
249
+ console.error(`[Agent] PID 文件写入失败: ${err.message}`);
96
250
  }
97
251
 
252
+ const { startServer } = await import('../src/server.js');
253
+ const { loadConfig } = await import('../src/config.js');
254
+
255
+ console.log('[Agent] AI Helper Agent 启动中...');
256
+
257
+ const config = applyCliArgs(loadConfig(), passArgs);
258
+
98
259
  console.log(`[Agent] 工作目录: ${config.workdir}`);
99
260
  console.log(`[Agent] 监听地址: ${config.host}:${config.port}`);
100
261
 
@@ -106,23 +267,44 @@ if (command === 'start') {
106
267
  console.log('[Agent] 正在停止 Agent...');
107
268
 
108
269
  const running = await isRunning(config);
109
- if (!running) {
110
- console.log('[Agent] Agent 未在运行');
111
- process.exit(0);
270
+ if (running) {
271
+ // 优先通过 API 优雅关闭
272
+ try {
273
+ const resp = await fetch(`http://${config.host}:${config.port}/api/shutdown`, {
274
+ method: 'POST'
275
+ });
276
+ if (resp.ok) {
277
+ const data = await resp.json();
278
+ console.log(`[Agent] ${data.message || 'Agent 已停止'}`);
279
+ removePidFile();
280
+ process.exit(0);
281
+ }
282
+ } catch (err) {
283
+ console.error(`[Agent] API 关闭请求失败: ${err.message}`);
284
+ }
285
+ console.log('[Agent] API 关闭失败,尝试通过 PID 终止...');
112
286
  }
113
287
 
114
- try {
115
- const resp = await fetch(`http://${config.host}:${config.port}/api/shutdown`, {
116
- method: 'POST'
117
- });
118
- const data = await resp.json();
119
- console.log(`[Agent] ${data.message || 'Agent 已停止'}`);
120
- } catch {
121
- console.log('[Agent] 无法发送停止请求,Agent 可能已退出');
288
+ // 回退:通过 PID 文件终止
289
+ const pid = getPidFromFile();
290
+ if (pid) {
291
+ if (killByPid(pid)) {
292
+ console.log(`[Agent] 已向 PID ${pid} 发送终止信号`);
293
+ } else {
294
+ console.log(`[Agent] 进程 ${pid} 已不存在`);
295
+ }
296
+ removePidFile();
297
+ } else if (!running) {
298
+ console.log('[Agent] Agent 未在运行');
122
299
  }
123
300
 
301
+ process.exit(0);
302
+
124
303
  // ==================== restart ====================
125
304
  } else if (command === 'restart') {
305
+ const isBg = process.argv.includes('--background') || process.argv.includes('-b');
306
+
307
+ // 先停止现有实例
126
308
  const config = readAgentConfig();
127
309
  const running = await isRunning(config);
128
310
 
@@ -130,23 +312,79 @@ if (command === 'start') {
130
312
  console.log('[Agent] 正在停止 Agent...');
131
313
  try {
132
314
  await fetch(`http://${config.host}:${config.port}/api/shutdown`, { method: 'POST' });
133
- } catch {}
134
- // 等待进程退出
135
- await new Promise(resolve => setTimeout(resolve, 1500));
315
+ } catch (err) {
316
+ console.error(`[Agent] API 关闭请求失败: ${err.message}`);
317
+ }
318
+ // 等待进程退出(轮询而非硬延迟)
319
+ const exited = await waitForExit(config, 8000);
320
+ if (!exited) {
321
+ console.log('[Agent] 等待超时,尝试强制终止...');
322
+ }
323
+ }
324
+
325
+ // 再通过 PID 兜底
326
+ const pid = getPidFromFile();
327
+ if (pid) {
328
+ killByPid(pid);
329
+ removePidFile();
330
+ await new Promise(resolve => setTimeout(resolve, 500));
136
331
  }
137
332
 
333
+ // 重新启动
334
+ if (isBg) {
335
+ ensureAgentDir();
336
+ const passArgs = process.argv.slice(3).filter(a => a !== '--background' && a !== '-b');
337
+
338
+ const child = spawn(process.execPath, [
339
+ ...process.execArgv,
340
+ process.argv[1],
341
+ 'start',
342
+ ...passArgs
343
+ ], {
344
+ detached: true,
345
+ stdio: 'ignore',
346
+ cwd: process.cwd(),
347
+ env: { ...process.env }
348
+ });
349
+
350
+ let spawnFailed = false;
351
+ child.on('error', (err) => {
352
+ spawnFailed = true;
353
+ console.error(`[Agent] 后台重启失败: ${err.message}`);
354
+ try { unlinkSync(PID_FILE); } catch {}
355
+ process.exit(1);
356
+ });
357
+
358
+ await new Promise(resolve => setTimeout(resolve, 500));
359
+
360
+ if (!spawnFailed && child.pid) {
361
+ try {
362
+ writeFileSync(PID_FILE, String(child.pid));
363
+ } catch (err) {
364
+ console.error(`[Agent] PID 文件写入失败: ${err.message}`);
365
+ process.exit(1);
366
+ }
367
+ child.unref();
368
+ console.log(`[Agent] Agent 已在后台重启 (PID: ${child.pid})`);
369
+ console.log(`[Agent] 使用 'ai-helper-agent stop' 停止服务`);
370
+ }
371
+
372
+ process.exit(spawnFailed ? 1 : 0);
373
+ }
374
+
375
+ // 前台模式
138
376
  console.log('[Agent] 正在启动 Agent...');
377
+ ensureAgentDir();
378
+ try {
379
+ writeFileSync(PID_FILE, String(process.pid));
380
+ } catch (err) {
381
+ console.error(`[Agent] PID 文件写入失败: ${err.message}`);
382
+ }
383
+
139
384
  const { startServer: restartServer } = await import('../src/server.js');
140
385
  const { loadConfig: restartConfig } = await import('../src/config.js');
141
- const cfg = restartConfig();
142
-
143
- // 应用命令行参数
144
- const rargs = process.argv.slice(3);
145
- for (let i = 0; i < rargs.length; i++) {
146
- if (rargs[i] === '--port' && rargs[i + 1]) { cfg.port = parseInt(rargs[i + 1], 10); i++; }
147
- else if (rargs[i] === '--host' && rargs[i + 1]) { cfg.host = rargs[i + 1]; i++; }
148
- else if (rargs[i] === '--workdir' && rargs[i + 1]) { cfg.workdir = rargs[i + 1]; i++; }
149
- }
386
+ const cfg = applyCliArgs(restartConfig(),
387
+ process.argv.slice(3).filter(a => a !== '--background' && a !== '-b'));
150
388
 
151
389
  console.log(`[Agent] 工作目录: ${cfg.workdir}`);
152
390
  restartServer();
@@ -166,7 +404,9 @@ if (command === 'start') {
166
404
  const resp = await fetch(`http://${config.host}:${config.port}/api/status`);
167
405
  const data = await resp.json();
168
406
  console.log(`[Agent] 版本: ${data.version}`);
169
- } catch {}
407
+ } catch (err) {
408
+ console.error(`[Agent] 获取状态失败: ${err.message}`);
409
+ }
170
410
  }
171
411
 
172
412
  // ==================== paircode ====================
@@ -179,7 +419,6 @@ if (command === 'start') {
179
419
  process.exit(1);
180
420
  }
181
421
 
182
- // 配对码需要认证,如果没有 token 则直接查看(Agent 启动控制台会打印)
183
422
  console.log('[Agent] 配对码已显示在 Agent 启动终端中');
184
423
  console.log('[Agent] 请在启动 Agent 的终端窗口中查看配对码');
185
424
  console.log('');
@@ -190,7 +429,9 @@ if (command === 'start') {
190
429
  const data = await resp.json();
191
430
  console.log(`[Agent] 版本: ${data.version}`);
192
431
  }
193
- } catch {}
432
+ } catch (err) {
433
+ console.error(`[Agent] 获取状态失败: ${err.message}`);
434
+ }
194
435
 
195
436
  // ==================== config ====================
196
437
  } else if (command === 'config') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-helper-agent",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "AI Helper 本地代理服务 - 为 Chrome 扩展提供本地文件读写和系统命令执行能力",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",