aws-runtime-bridge 1.9.1 → 1.9.5

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.
Files changed (66) hide show
  1. package/dist/config.d.ts +5 -4
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +25 -4
  4. package/dist/index.js +20 -4
  5. package/dist/middleware/auth.d.ts +8 -0
  6. package/dist/middleware/auth.d.ts.map +1 -1
  7. package/dist/middleware/auth.js +44 -0
  8. package/dist/routes/ai-sources.js +18 -14
  9. package/dist/routes/dashboard.d.ts +8 -0
  10. package/dist/routes/dashboard.d.ts.map +1 -0
  11. package/dist/routes/dashboard.js +1347 -0
  12. package/dist/routes/instance.d.ts +1 -0
  13. package/dist/routes/instance.d.ts.map +1 -1
  14. package/dist/routes/instance.js +53 -4
  15. package/dist/routes/instance.test.js +4 -0
  16. package/dist/routes/processes.js +1 -1
  17. package/dist/routes/runtime-binding.d.ts.map +1 -1
  18. package/dist/routes/runtime-binding.js +37 -0
  19. package/dist/routes/sessions.js +7 -7
  20. package/dist/routes/terminal.d.ts.map +1 -1
  21. package/dist/routes/terminal.js +143 -33
  22. package/dist/services/agent-process-manager.d.ts +12 -0
  23. package/dist/services/agent-process-manager.d.ts.map +1 -1
  24. package/dist/services/agent-process-manager.js +47 -13
  25. package/dist/services/auto-register.d.ts.map +1 -1
  26. package/dist/services/auto-register.js +4 -5
  27. package/dist/services/mcp-launch-binding-queue.d.ts +5 -0
  28. package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
  29. package/dist/services/mcp-launch-binding-queue.js +17 -0
  30. package/dist/services/mcp-launch-binding-queue.test.js +16 -1
  31. package/dist/services/orphan-monitor.js +2 -2
  32. package/dist/services/panel-auth.d.ts +26 -0
  33. package/dist/services/panel-auth.d.ts.map +1 -0
  34. package/dist/services/panel-auth.js +105 -0
  35. package/dist/services/process-detector.d.ts +16 -9
  36. package/dist/services/process-detector.d.ts.map +1 -1
  37. package/dist/services/process-detector.js +136 -66
  38. package/dist/services/session-output.d.ts.map +1 -1
  39. package/dist/services/session-output.js +106 -6
  40. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +20 -0
  41. package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
  42. package/package/aws-client-agent-mcp/dist/agent-client.js +71 -2
  43. package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
  44. package/package/aws-client-agent-mcp/dist/constants.d.ts +4 -1
  45. package/package/aws-client-agent-mcp/dist/constants.d.ts.map +1 -1
  46. package/package/aws-client-agent-mcp/dist/constants.js +5 -1
  47. package/package/aws-client-agent-mcp/dist/constants.js.map +1 -1
  48. package/package/aws-client-agent-mcp/dist/http-client.d.ts +17 -1
  49. package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
  50. package/package/aws-client-agent-mcp/dist/http-client.js +124 -28
  51. package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
  52. package/package/aws-client-agent-mcp/dist/http-client.test.js +108 -2
  53. package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -1
  54. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +5 -0
  55. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  56. package/package/aws-client-agent-mcp/dist/mcp-server.js +53 -3
  57. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  58. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
  59. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +79 -28
  60. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
  61. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +35 -0
  62. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js.map +1 -1
  63. package/package/aws-client-agent-mcp/dist/types.d.ts +9 -0
  64. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  65. package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
  66. package/package.json +1 -1
@@ -3,7 +3,9 @@
3
3
  *
4
4
  * 用于检测孤儿进程(当 runtime-bridge 异常关闭后仍在运行的 Agent 进程)
5
5
  */
6
- import { execSync } from 'child_process';
6
+ import { execSync, exec } from 'child_process';
7
+ import { promisify } from 'util';
8
+ const execAsync = promisify(exec);
7
9
  import os from 'os';
8
10
  import { createLogger } from '../utils/logger.js';
9
11
  const log = createLogger('process-detector');
@@ -26,6 +28,41 @@ function execPowerShell(psScript, timeout = 15000) {
26
28
  // CLIXML 格式:#< CLIXML <Objs ...>...</Objs>
27
29
  return output.replace(/#\s*<\s*CLIXML[\s\S]*?(?=#<\s*CLIXML|$)/g, '').trim();
28
30
  }
31
+ /**
32
+ * 异步版 PowerShell 命令执行(防止阻塞事件循环)
33
+ *
34
+ * @param psScript PowerShell 脚本
35
+ * @param timeout 超时时间(毫秒)
36
+ * @returns 命令输出
37
+ */
38
+ async function execPowerShellAsync(psScript, timeout = 15000) {
39
+ const buffer = Buffer.from(psScript, 'utf16le');
40
+ const encoded = buffer.toString('base64');
41
+ const { stdout } = await execAsync(`powershell -NoProfile -NonInteractive -EncodedCommand ${encoded}`, { encoding: 'utf8', timeout, windowsHide: true, maxBuffer: 10 * 1024 * 1024 });
42
+ // ★ 过滤 PowerShell CLIXML 噪音
43
+ return stdout.replace(/#\s*<\s*CLIXML[\s\S]*?(?=#<\s*CLIXML|$)/g, '').trim();
44
+ }
45
+ /**
46
+ * 异步检测进程是否存活(防止阻塞事件循环)
47
+ *
48
+ * @param pid 进程 ID
49
+ * @returns 是否存活
50
+ */
51
+ export async function isProcessRunningAsync(pid) {
52
+ try {
53
+ if (os.platform() === 'win32') {
54
+ const { stdout } = await execAsync(`tasklist /FI "PID eq ${pid}" /NH /FO CSV`, { encoding: 'utf8', timeout: 5000, windowsHide: true });
55
+ return stdout.includes(String(pid));
56
+ }
57
+ else {
58
+ process.kill(pid, 0);
59
+ return true;
60
+ }
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
29
66
  /**
30
67
  * 检测进程是否存活
31
68
  *
@@ -57,17 +94,17 @@ export function isProcessRunning(pid) {
57
94
  *
58
95
  * @returns 匹配的进程列表
59
96
  */
60
- export function findClaudeCodeProcesses() {
97
+ export async function findClaudeCodeProcesses() {
61
98
  const platform = os.platform();
62
99
  const results = [];
63
100
  try {
64
101
  if (platform === 'win32') {
65
102
  // Windows: 优先使用 PowerShell(wmic 已弃用)
66
- results.push(...findProcessesWindows());
103
+ results.push(...await findProcessesWindows());
67
104
  }
68
105
  else {
69
106
  // macOS/Linux: 使用 ps
70
- results.push(...findProcessesUnix());
107
+ results.push(...await findProcessesUnix());
71
108
  }
72
109
  }
73
110
  catch (error) {
@@ -80,11 +117,11 @@ export function findClaudeCodeProcesses() {
80
117
  * Windows: 使用 PowerShell 查找 Agent 进程
81
118
  * PowerShell 比 wmic 更可靠(wmic 在 Win10 21H1+ 已弃用)
82
119
  */
83
- function findProcessesWindows() {
120
+ async function findProcessesWindows() {
84
121
  const results = [];
85
122
  try {
86
123
  const psScript = `Get-CimInstance Win32_Process -Filter "Name LIKE '%node%' OR Name LIKE '%claude%' OR Name LIKE '%opencode%' OR Name LIKE '%python%'" | Select-Object ProcessId, CommandLine | ConvertTo-Json -Compress`;
87
- const output = execPowerShell(psScript, 10000);
124
+ const output = await execPowerShellAsync(psScript, 10000);
88
125
  if (!output || !output.trim())
89
126
  return results;
90
127
  let processes;
@@ -113,18 +150,18 @@ function findProcessesWindows() {
113
150
  catch (error) {
114
151
  // PowerShell 失败,回退到 wmic
115
152
  log.debug('PowerShell process query failed, trying wmic fallback:', error);
116
- results.push(...findProcessesWindowsWmic());
153
+ results.push(...await findProcessesWindowsWmic());
117
154
  }
118
155
  return results;
119
156
  }
120
157
  /**
121
158
  * Windows: wmic 回退方案
122
159
  */
123
- function findProcessesWindowsWmic() {
160
+ async function findProcessesWindowsWmic() {
124
161
  const results = [];
125
162
  try {
126
- const output = execSync('wmic process where "name like \'%node%\' or name like \'%claude%\' or name like \'%opencode%\'" get processid,commandline /format:list', { encoding: 'utf8', timeout: 5000, windowsHide: true });
127
- const lines = output.split('\n');
163
+ const { stdout } = await execAsync('wmic process where "name like \'%node%\' or name like \'%claude%\' or name like \'%opencode%\'" get processid,commandline /format:list', { encoding: 'utf8', timeout: 5000, windowsHide: true });
164
+ const lines = stdout.split('\n');
128
165
  let currentPid = null;
129
166
  let currentCmd = null;
130
167
  for (const line of lines) {
@@ -157,11 +194,11 @@ function findProcessesWindowsWmic() {
157
194
  /**
158
195
  * Unix: 使用 ps 查找 Agent 进程
159
196
  */
160
- function findProcessesUnix() {
197
+ async function findProcessesUnix() {
161
198
  const results = [];
162
199
  try {
163
- const output = execSync('ps aux | grep -E "claude|claude-code|opencode" | grep -v grep', { encoding: 'utf8', timeout: 5000 });
164
- const lines = output.split('\n').filter(l => l.trim());
200
+ const { stdout } = await execAsync('ps aux | grep -E "claude|claude-code|opencode" | grep -v grep', { encoding: 'utf8', timeout: 5000 });
201
+ const lines = stdout.split('\n').filter((l) => l.trim());
165
202
  for (const line of lines) {
166
203
  const parts = line.trim().split(/\s+/);
167
204
  if (parts.length >= 2) {
@@ -194,11 +231,9 @@ function isAgentProcess(cmd) {
194
231
  if (!cmd)
195
232
  return false;
196
233
  const lowerCmd = cmd.toLowerCase();
197
- // Claude Code 相关特征
234
+ // Claude Code 相关特征(精确匹配,避免误匹配普通 node 进程)
198
235
  if (lowerCmd.includes('claude') ||
199
- lowerCmd.includes('claude-code') ||
200
- lowerCmd.includes('@anthropic-ai/claude-code') ||
201
- lowerCmd.includes('cli.js')) {
236
+ lowerCmd.includes('@anthropic-ai/claude-code')) {
202
237
  return true;
203
238
  }
204
239
  // OpenCode 相关特征
@@ -208,8 +243,17 @@ function isAgentProcess(cmd) {
208
243
  lowerCmd.includes('opencode.cmd')) {
209
244
  return true;
210
245
  }
211
- // 通用特征:包含 serve --port 模式的 node 进程(OpenCode SDK 的 opencode serve --port)
212
- if (lowerCmd.includes('serve') && lowerCmd.includes('--port')) {
246
+ // AWS Agent MCP 客户端特征(aws-client-agent-mcp 包)
247
+ if (lowerCmd.includes('aws-client-agent-mcp') ||
248
+ lowerCmd.includes('aws-client-agent')) {
249
+ return true;
250
+ }
251
+ // ACode SDK 特征
252
+ if (lowerCmd.includes('acode') && lowerCmd.includes('sdk')) {
253
+ return true;
254
+ }
255
+ // CC-Switch SDK 特征
256
+ if (lowerCmd.includes('cc-switch-sdk') || lowerCmd.includes('cc-switch')) {
213
257
  return true;
214
258
  }
215
259
  return false;
@@ -217,6 +261,11 @@ function isAgentProcess(cmd) {
217
261
  /**
218
262
  * 从命令行提取 Agent ID
219
263
  *
264
+ * 支持多种提取模式:
265
+ * - 环境变量形式:AWS_AGENT_ID=xxx
266
+ * - 命令行参数:--agent-id=xxx 或 --agent-id xxx
267
+ * - 配置/标识参数:--id=xxx(agent CLI 常用)
268
+ *
220
269
  * @param cmd 命令行
221
270
  * @returns Agent ID 或 undefined
222
271
  */
@@ -226,15 +275,33 @@ function extractAgentId(cmd) {
226
275
  // 尝试匹配 AWS_AGENT_ID 环境变量(某些情况下 wmic/PowerShell 会包含环境变量)
227
276
  const envMatch = cmd.match(/AWS_AGENT_ID=(\S+)/);
228
277
  if (envMatch) {
229
- return envMatch[1];
278
+ return stripQuotes(envMatch[1]);
230
279
  }
231
- // 尝试匹配 --agent-id 参数
280
+ // 尝试匹配 --agent-id 参数(= 或空格分隔)
232
281
  const argMatch = cmd.match(/--agent-id[=\s]+(\S+)/);
233
282
  if (argMatch) {
234
- return argMatch[1];
283
+ return stripQuotes(argMatch[1]);
284
+ }
285
+ // 尝试匹配 --id 参数(部分 CLI 使用简短形式)
286
+ const idMatch = cmd.match(/--id[=\s]+(\S+)/);
287
+ if (idMatch) {
288
+ return stripQuotes(idMatch[1]);
235
289
  }
236
290
  return undefined;
237
291
  }
292
+ /**
293
+ * 去除字符串首尾引号
294
+ */
295
+ function stripQuotes(value) {
296
+ if (!value)
297
+ return value;
298
+ const first = value[0];
299
+ const last = value[value.length - 1];
300
+ if ((first === '"' || first === "'") && first === last) {
301
+ return value.slice(1, -1);
302
+ }
303
+ return value;
304
+ }
238
305
  /**
239
306
  * 根据 agentId 查找孤儿进程
240
307
  *
@@ -246,20 +313,20 @@ function extractAgentId(cmd) {
246
313
  * @param agentId Agent ID
247
314
  * @returns 进程信息或 undefined
248
315
  */
249
- export function findOrphanProcessByAgentId(agentId) {
316
+ export async function findOrphanProcessByAgentId(agentId) {
250
317
  // 1. 先通过命令行匹配
251
- const processes = findClaudeCodeProcesses();
318
+ const processes = await findClaudeCodeProcesses();
252
319
  const cmdMatch = processes.find(p => p.agentId === agentId);
253
320
  if (cmdMatch)
254
321
  return cmdMatch;
255
322
  // 2. Windows 特有:在命令行中模糊搜索 agentId
256
323
  if (os.platform() === 'win32') {
257
- const envMatch = findProcessByEnvVarWindows(agentId);
324
+ const envMatch = await findProcessByEnvVarWindows(agentId);
258
325
  if (envMatch)
259
326
  return envMatch;
260
327
  // ★ 3. Windows: 进程树搜索 —— 遍历所有 Agent 进程,
261
328
  // 检查其祖先进程中是否有 node(runtime-bridge) → shell 的模式
262
- const treeMatch = findOrphanByAncestorEnvSearch(agentId);
329
+ const treeMatch = await findOrphanByAncestorEnvSearch(agentId);
263
330
  if (treeMatch)
264
331
  return treeMatch;
265
332
  }
@@ -270,10 +337,10 @@ export function findOrphanProcessByAgentId(agentId) {
270
337
  *
271
338
  * 策略:在所有 node/claude/opencode 进程的命令行中模糊搜索 agentId
272
339
  */
273
- function findProcessByEnvVarWindows(agentId) {
340
+ async function findProcessByEnvVarWindows(agentId) {
274
341
  try {
275
342
  const psScript = `$targetId = '${agentId}'; $procs = Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and ($_.Name -like '*node*' -or $_.Name -like '*claude*' -or $_.Name -like '*opencode*') }; foreach ($proc in $procs) { if ($proc.CommandLine -like "*$targetId*") { @{ProcessId=$proc.ProcessId; CommandLine=$proc.CommandLine; AgentId=$targetId} | ConvertTo-Json -Compress } }`;
276
- const output = execPowerShell(psScript, 10000);
343
+ const output = await execPowerShellAsync(psScript, 10000);
277
344
  if (!output || !output.trim())
278
345
  return undefined;
279
346
  const lines = output.trim().split('\n').filter(l => l.trim());
@@ -311,15 +378,15 @@ function findProcessByEnvVarWindows(agentId) {
311
378
  *
312
379
  * 策略:用极简 PowerShell 获取全量进程树数据 → 在 Node.js 层做祖先链匹配
313
380
  */
314
- function findOrphanByAncestorEnvSearch(agentId) {
381
+ async function findOrphanByAncestorEnvSearch(agentId) {
315
382
  try {
316
383
  // ★ 极简 PowerShell —— 只做数据采集,不做任何逻辑判断
317
384
  const psScript = `Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId, Name, CommandLine | ConvertTo-Json -Compress`;
318
- const output = execPowerShell(psScript, 15000);
385
+ const output = await execPowerShellAsync(psScript, 15000);
319
386
  if (!output || !output.trim())
320
387
  return undefined;
321
388
  // ★ 所有逻辑在 Node.js 层执行
322
- return searchOrphanInProcessTree(output.trim(), agentId);
389
+ return await searchOrphanInProcessTree(output.trim(), agentId);
323
390
  }
324
391
  catch (error) {
325
392
  log.debug('[findOrphanByAncestorEnvSearch] Failed:', error);
@@ -335,7 +402,7 @@ function findOrphanByAncestorEnvSearch(agentId) {
335
402
  * 3. 对每个候选向上遍历祖先链
336
403
  * 4. 如果祖先链中出现 node(runtime-bridge) → powershell/cmd 的模式 → 判定为孤儿
337
404
  */
338
- function searchOrphanInProcessTree(rawJson, agentId) {
405
+ async function searchOrphanInProcessTree(rawJson, agentId) {
339
406
  let allProcs;
340
407
  try {
341
408
  // ★ 过滤 CLIXML 噪音后再解析 JSON
@@ -455,7 +522,7 @@ function searchOrphanInProcessTree(rawJson, agentId) {
455
522
  const candidateWithoutBridge = allProcs.filter(p => isCandidate(p) && !findAncestorShell(p.processId));
456
523
  for (const proc of candidateWithoutBridge) {
457
524
  // 尝试通过读取进程环境变量来验证 agentId
458
- const verified = verifyProcessAgentId(proc.processId, agentId);
525
+ const verified = await verifyProcessAgentId(proc.processId, agentId);
459
526
  if (verified) {
460
527
  log.info(`[searchOrphanInProcessTree] Found orphan by env verification: agentId=${agentId}, pid=${proc.processId}, name=${proc.name}`);
461
528
  return {
@@ -501,7 +568,7 @@ function extractJsonFromOutput(output) {
501
568
  * @param targetAgentId 目标 Agent ID
502
569
  * @returns 是否匹配
503
570
  */
504
- function verifyProcessAgentId(pid, targetAgentId) {
571
+ async function verifyProcessAgentId(pid, targetAgentId) {
505
572
  try {
506
573
  // 策略1:检查进程命令行是否包含 agentId
507
574
  const psScript = `
@@ -510,7 +577,7 @@ function verifyProcessAgentId(pid, targetAgentId) {
510
577
  if ($proc -and $proc.CommandLine -like '*${targetAgentId}*') { 'MATCH' } else { 'NOMATCH' }
511
578
  } catch { 'ERROR' }
512
579
  `;
513
- const result = execPowerShell(psScript, 5000).trim();
580
+ const result = (await execPowerShellAsync(psScript, 5000)).trim();
514
581
  if (result === 'MATCH')
515
582
  return true;
516
583
  // 策略2:检查父进程命令行是否包含 agentId
@@ -524,7 +591,7 @@ function verifyProcessAgentId(pid, targetAgentId) {
524
591
  } else { 'NOPROC' }
525
592
  } catch { 'ERROR' }
526
593
  `;
527
- const parentResult = execPowerShell(parentPsScript, 5000).trim();
594
+ const parentResult = (await execPowerShellAsync(parentPsScript, 5000)).trim();
528
595
  if (parentResult === 'MATCH')
529
596
  return true;
530
597
  // 策略3:检查祖父进程(node → powershell → claude 三级链中,agentId 在 shell 命令行中)
@@ -542,7 +609,7 @@ function verifyProcessAgentId(pid, targetAgentId) {
542
609
  } else { 'NOPROC' }
543
610
  } catch { 'ERROR' }
544
611
  `;
545
- const grandParentResult = execPowerShell(grandParentPsScript, 5000).trim();
612
+ const grandParentResult = (await execPowerShellAsync(grandParentPsScript, 5000)).trim();
546
613
  return grandParentResult === 'MATCH';
547
614
  }
548
615
  catch (error) {
@@ -562,11 +629,11 @@ function verifyProcessAgentId(pid, targetAgentId) {
562
629
  * @param workspacePath 工作目录路径
563
630
  * @returns 匹配的进程信息或 undefined
564
631
  */
565
- export function findOrphanProcessByWorkspace(workspacePath) {
632
+ export async function findOrphanProcessByWorkspace(workspacePath) {
566
633
  if (!workspacePath)
567
634
  return undefined;
568
635
  try {
569
- const processes = findClaudeCodeProcesses();
636
+ const processes = await findClaudeCodeProcesses();
570
637
  // 在匹配的进程命令行中搜索工作目录路径
571
638
  const normalizedPath = workspacePath.replace(/\\/g, '/').toLowerCase();
572
639
  for (const proc of processes) {
@@ -591,7 +658,7 @@ export function findOrphanProcessByWorkspace(workspacePath) {
591
658
  }
592
659
  }
593
660
  `;
594
- const output = execPowerShell(psScript, 10000);
661
+ const output = await execPowerShellAsync(psScript, 10000);
595
662
  if (output && output.trim()) {
596
663
  const lines = output.trim().split('\n').filter(l => l.trim());
597
664
  for (const line of lines) {
@@ -643,12 +710,12 @@ export function terminateProcess(pid) {
643
710
  * @param pid 进程 ID
644
711
  * @returns { terminated: 成功终止的进程数,failed: 失败的进程数 }
645
712
  */
646
- export function terminateProcessTree(pid) {
713
+ export async function terminateProcessTree(pid) {
647
714
  const result = { terminated: 0, failed: 0 };
648
715
  if (os.platform() === 'win32') {
649
716
  // Windows: 使用 taskkill /T /F 终止进程树
650
717
  try {
651
- execSync(`taskkill /PID ${pid} /T /F`, { encoding: 'utf8', timeout: 5000, windowsHide: true });
718
+ await execAsync(`taskkill /PID ${pid} /T /F`, { encoding: 'utf8', timeout: 5000, windowsHide: true });
652
719
  result.terminated++;
653
720
  log.info(`Terminated process tree for pid=${pid}`);
654
721
  }
@@ -660,11 +727,11 @@ export function terminateProcessTree(pid) {
660
727
  }
661
728
  else {
662
729
  // Unix: 递归获取所有子进程并终止
663
- const childPids = getChildProcesses(pid);
730
+ const childPids = await getChildProcesses(pid);
664
731
  log.debug(`Found ${childPids.length} child processes for pid=${pid}`);
665
732
  // 先终止所有子进程
666
733
  for (const childPid of childPids) {
667
- const childResult = terminateProcessTree(childPid);
734
+ const childResult = await terminateProcessTree(childPid);
668
735
  result.terminated += childResult.terminated;
669
736
  result.failed += childResult.failed;
670
737
  }
@@ -684,15 +751,15 @@ export function terminateProcessTree(pid) {
684
751
  * @param pid 父进程 ID
685
752
  * @returns 子进程 PID 列表
686
753
  */
687
- function getChildProcesses(pid) {
754
+ async function getChildProcesses(pid) {
688
755
  try {
689
- const output = execSync(`pgrep -P ${pid}`, { encoding: 'utf8', timeout: 3000 });
690
- const children = output.trim().split('\n').filter(Boolean).map(Number);
756
+ const { stdout } = await execAsync(`pgrep -P ${pid}`, { encoding: 'utf8', timeout: 3000 });
757
+ const children = stdout.trim().split('\n').filter(Boolean).map(Number);
691
758
  // 递归获取孙进程
692
759
  const allDescendants = [];
693
760
  for (const childPid of children) {
694
761
  allDescendants.push(childPid);
695
- allDescendants.push(...getChildProcesses(childPid));
762
+ allDescendants.push(...await getChildProcesses(childPid));
696
763
  }
697
764
  return allDescendants;
698
765
  }
@@ -711,7 +778,7 @@ function getChildProcesses(pid) {
711
778
  export async function waitForProcessExit(pid, timeoutMs = 5000) {
712
779
  const startTime = Date.now();
713
780
  while (Date.now() - startTime < timeoutMs) {
714
- if (!isProcessRunning(pid)) {
781
+ if (!await isProcessRunningAsync(pid)) {
715
782
  return true;
716
783
  }
717
784
  // 等待 100ms 后重试
@@ -734,13 +801,13 @@ export async function waitForProcessExit(pid, timeoutMs = 5000) {
734
801
  * @param persistedSessions 持久化的会话列表(包含 PID 信息)
735
802
  * @returns 孤儿进程列表
736
803
  */
737
- export function detectOrphanProcesses(persistedSessions) {
804
+ export async function detectOrphanProcesses(persistedSessions) {
738
805
  const orphans = [];
739
806
  // 1. 首先检查持久化会话中的 PID 是否仍在运行
740
807
  for (const session of persistedSessions) {
741
- if (session.pid && isProcessRunning(session.pid)) {
808
+ if (session.pid && await isProcessRunningAsync(session.pid)) {
742
809
  // PID 存活,尝试获取详细进程信息
743
- const processes = findClaudeCodeProcesses();
810
+ const processes = await findClaudeCodeProcesses();
744
811
  const matched = processes.find(p => p.pid === session.pid);
745
812
  if (matched) {
746
813
  orphans.push({
@@ -775,7 +842,7 @@ export function detectOrphanProcesses(persistedSessions) {
775
842
  // 2. 对于没有 PID 但有 agentId 的会话,尝试通过 agentId 搜索进程
776
843
  for (const session of persistedSessions) {
777
844
  if (!session.pid && session.agentId) {
778
- const process = findOrphanProcessByAgentId(session.agentId);
845
+ const process = await findOrphanProcessByAgentId(session.agentId);
779
846
  if (process && !orphans.some(o => o.agentId === session.agentId)) {
780
847
  orphans.push({
781
848
  agentId: session.agentId,
@@ -810,7 +877,7 @@ export async function checkProcessHealth(pid, agentId) {
810
877
  };
811
878
  try {
812
879
  // 检查进程是否存在
813
- if (!isProcessRunning(pid)) {
880
+ if (!await isProcessRunningAsync(pid)) {
814
881
  status.status = 'dead';
815
882
  log.debug(`Process ${pid} is not running`);
816
883
  return status;
@@ -851,7 +918,7 @@ async function getProcessResources(pid) {
851
918
  // Windows: 使用 PowerShell 获取进程资源
852
919
  const psScript = `Get-CimInstance Win32_Process -Filter "ProcessId=${pid}" | Select-Object @{Name='CPU';Expression={$_.WorkingSetSize/1MB}}, @{Name='Mem';Expression={$_.WorkingSetSize/1MB}} | ConvertTo-Json -Compress`;
853
920
  try {
854
- const output = execPowerShell(psScript, 5000);
921
+ const output = await execPowerShellAsync(psScript, 5000);
855
922
  if (!output || !output.trim()) {
856
923
  return { cpu: 0, memoryMB: 0 };
857
924
  }
@@ -869,8 +936,8 @@ async function getProcessResources(pid) {
869
936
  else {
870
937
  // Unix: 使用 ps 获取资源
871
938
  try {
872
- const output = execSync(`ps -p ${pid} -o %cpu=,rss=`, { encoding: 'utf8', timeout: 5000 });
873
- const parts = output.trim().split(/\s+/);
939
+ const { stdout } = await execAsync(`ps -p ${pid} -o %cpu=,rss=`, { encoding: 'utf8', timeout: 5000 });
940
+ const parts = stdout.trim().split(/\s+/);
874
941
  if (parts.length >= 2) {
875
942
  return {
876
943
  cpu: parseFloat(parts[0]) || 0,
@@ -913,10 +980,10 @@ export async function checkAllSessionsHealth(sessions) {
913
980
  * @param managedPids 已知被管理的 PID 集合(来自 ProcessRegistry)
914
981
  * @returns 发现的进程列表
915
982
  */
916
- export function discoverAllProcesses(managedPids = new Set(), managedAgentIds = new Set()) {
983
+ export async function discoverAllProcesses(managedPids = new Set(), managedAgentIds = new Set()) {
917
984
  const results = [];
918
985
  // 1. 扫描机器上所有 Agent 进程
919
- const allAgentProcesses = findClaudeCodeProcesses();
986
+ const allAgentProcesses = await findClaudeCodeProcesses();
920
987
  for (const proc of allAgentProcesses) {
921
988
  const isManaged = managedPids.has(proc.pid);
922
989
  const isAgentManaged = proc.agentId ? managedAgentIds.has(proc.agentId) : false;
@@ -938,30 +1005,33 @@ export function discoverAllProcesses(managedPids = new Set(), managedAgentIds =
938
1005
  * @param registryExport 注册表导出的进程记录(含 agentId, pid)
939
1006
  * @returns 完整的发现结果
940
1007
  */
941
- export function discoverAllProcessesFull(registryExport) {
1008
+ export async function discoverAllProcessesFull(registryExport) {
942
1009
  const managedPids = new Set(registryExport.map(r => r.pid));
943
1010
  const managedAgentIds = new Set(registryExport.map(r => r.agentId));
944
- const agentIdToRecord = new Map(registryExport.map(r => [r.agentId, r]));
1011
+ const pidToRecord = new Map(registryExport.map(r => [r.pid, r]));
945
1012
  const results = [];
946
1013
  const seenPids = new Set();
947
1014
  // 1. 扫描实际运行的进程
948
- const runningProcesses = findClaudeCodeProcesses();
1015
+ const runningProcesses = await findClaudeCodeProcesses();
949
1016
  for (const proc of runningProcesses) {
950
1017
  seenPids.add(proc.pid);
951
1018
  const isManaged = managedPids.has(proc.pid);
952
1019
  const isAgentManaged = proc.agentId ? managedAgentIds.has(proc.agentId) : false;
953
- const record = proc.agentId ? agentIdToRecord.get(proc.agentId) : undefined;
1020
+ const recordByPid = pidToRecord.get(proc.pid);
1021
+ // ★ 关键修复:当命令行中无法提取 agentId 但 PID 匹配注册表时,继承注册表的 agentId
1022
+ const resolvedAgentId = proc.agentId || recordByPid?.agentId;
954
1023
  results.push({
955
1024
  ...proc,
1025
+ agentId: resolvedAgentId,
956
1026
  isRunning: true,
957
1027
  managementStatus: isManaged || isAgentManaged ? 'managed' : 'orphan',
958
- sessionId: record ? undefined : undefined,
1028
+ sessionId: undefined,
959
1029
  lastSeen: new Date().toISOString(),
960
1030
  });
961
1031
  }
962
- // 2. 注册表中存在但进程已死亡的记录
1032
+ // 2. 注册表中存在但进程已死亡的记录(仅保留近期死亡的,过滤陈旧条目)
963
1033
  for (const record of registryExport) {
964
- if (!seenPids.has(record.pid) && !isProcessRunning(record.pid)) {
1034
+ if (!seenPids.has(record.pid) && !await isProcessRunningAsync(record.pid)) {
965
1035
  results.push({
966
1036
  pid: record.pid,
967
1037
  command: '(process exited)',
@@ -1 +1 @@
1
- {"version":3,"file":"session-output.d.ts","sourceRoot":"","sources":["../../src/services/session-output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C,UAAU,uBAAuB;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AA8BD,aAAa;AACb,eAAO,MAAM,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAa,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,uBAAuB,EACpC,KAAK,CAAC,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EACrD,WAAW,CAAC,EAAE,0BAA0B,GACvC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CA4C7C;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,qBAAqB,EAChC,WAAW,CAAC,EAAE,0BAA0B,GACvC,OAAO,CAAC,IAAI,CAAC,CA2Bf;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAAE,EACpB,WAAW,CAAC,EAAE,0BAA0B,GACvC,OAAO,CAAC,IAAI,CAAC,CAef;AAED,MAAM,WAAW,yBAAyB;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAgBzE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAa3D;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCzE"}
1
+ {"version":3,"file":"session-output.d.ts","sourceRoot":"","sources":["../../src/services/session-output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAI3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C,UAAU,uBAAuB;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAgED,aAAa;AACb,eAAO,MAAM,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAa,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,uBAAuB,EACpC,KAAK,CAAC,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EACrD,WAAW,CAAC,EAAE,0BAA0B,GACvC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAgE7C;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,qBAAqB,EAChC,WAAW,CAAC,EAAE,0BAA0B,GACvC,OAAO,CAAC,IAAI,CAAC,CA6Cf;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAAE,EACpB,WAAW,CAAC,EAAE,0BAA0B,GACvC,OAAO,CAAC,IAAI,CAAC,CAkCf;AAED,MAAM,WAAW,yBAAyB;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAgBzE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAa3D;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCzE"}