aws-runtime-bridge 1.9.2 → 1.9.6
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/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -0
- package/dist/index.js +28 -4
- package/dist/middleware/auth.d.ts +8 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +44 -0
- package/dist/routes/dashboard.d.ts +8 -0
- package/dist/routes/dashboard.d.ts.map +1 -0
- package/dist/routes/dashboard.js +1347 -0
- package/dist/routes/instance.d.ts +1 -0
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +53 -4
- package/dist/routes/instance.test.js +4 -0
- package/dist/routes/processes.js +1 -1
- package/dist/routes/runtime-binding.d.ts.map +1 -1
- package/dist/routes/runtime-binding.js +37 -0
- package/dist/routes/sessions.js +7 -7
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +137 -33
- package/dist/services/agent-process-manager.d.ts +12 -0
- package/dist/services/agent-process-manager.d.ts.map +1 -1
- package/dist/services/agent-process-manager.js +47 -13
- package/dist/services/orphan-monitor.js +2 -2
- package/dist/services/panel-auth.d.ts +26 -0
- package/dist/services/panel-auth.d.ts.map +1 -0
- package/dist/services/panel-auth.js +105 -0
- package/dist/services/process-detector.d.ts +16 -9
- package/dist/services/process-detector.d.ts.map +1 -1
- package/dist/services/process-detector.js +136 -66
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +106 -6
- 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 =
|
|
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
|
|
127
|
-
const lines =
|
|
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
|
|
164
|
-
const lines =
|
|
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
|
-
//
|
|
212
|
-
if (lowerCmd.includes('
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
690
|
-
const children =
|
|
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 (!
|
|
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 &&
|
|
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 (!
|
|
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 =
|
|
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
|
|
873
|
-
const parts =
|
|
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
|
|
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
|
|
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:
|
|
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) && !
|
|
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;
|
|
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"}
|