#!/usr/bin/env node /** * register-remote-agent.js — 远程 Agent 自动注册 * * 用法: * node scripts/register-remote-agent.js [OPTIONS] * * 示例: * node scripts/register-remote-agent.js 192.168.1.100 * node scripts/register-remote-agent.js 10.0.0.5 --port 9002 --name remote-claude --type coder * node scripts/register-remote-agent.js ws://10.0.0.5:9001 --server http://localhost:3000 * * 功能: * 1. 探测远程 Bridge 健康状态 * 2. 向主控 Server 注册 Agent * 3. 验证注册结果 */ const http = require('http'); const url = require('url'); // ─── CLI 解析 ─── const args = process.argv.slice(2); if (args.length === 0 || args.includes('--help')) { console.log(` 用法: node register-remote-agent.js [OPTIONS] BRIDGE_HOST 远程 Bridge 地址 (IP / hostname / ws://...) 选项: --port Bridge WebSocket 端口 (默认 9001) --name Agent 名称 (默认 remote-agent) --type Agent 类型: coder | validator | executor (默认 coder) --protocol 协议: acp | claude-stream (默认 acp) --server 主控 Server 地址 (默认 http://localhost:3000) --description Agent 描述 --skip-health 跳过 Bridge 健康检查 --unregister 注销而非注册 `); process.exit(0); } function opt(name, fallback) { const i = args.indexOf(`--${name}`); return i >= 0 && args[i + 1] ? args[i + 1] : fallback; } const BRIDGE_HOST_RAW = args.find(a => !a.startsWith('--')); const PORT = opt('port', '9001'); const NAME = opt('name', 'remote-agent'); const TYPE = opt('type', 'coder'); const PROTOCOL = opt('protocol', 'acp'); const SERVER = opt('server', 'http://localhost:3000'); const DESC = opt('description', `远程 Agent (${BRIDGE_HOST_RAW}:${PORT})`); const SKIP_HEALTH = args.includes('--skip-health'); const UNREGISTER = args.includes('--unregister'); // 解析 Bridge 地址 let bridgeHost, bridgePort; if (BRIDGE_HOST_RAW.startsWith('ws://') || BRIDGE_HOST_RAW.startsWith('wss://')) { const parsed = new URL(BRIDGE_HOST_RAW); bridgeHost = parsed.hostname; bridgePort = parsed.port || PORT; } else if (BRIDGE_HOST_RAW.includes(':')) { [bridgeHost, bridgePort] = BRIDGE_HOST_RAW.split(':'); } else { bridgeHost = BRIDGE_HOST_RAW; bridgePort = PORT; } const WS_URL = `ws://${bridgeHost}:${bridgePort}`; // ─── HTTP 工具 ─── function request(method, urlStr, body = null) { return new Promise((resolve, reject) => { const { hostname, port, pathname } = new URL(urlStr); const data = body ? JSON.stringify(body) : null; const req = http.request({ hostname, port, path: pathname, method, headers: { 'Content-Type': 'application/json', ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}), }, timeout: 5000, }, (res) => { let buf = ''; res.on('data', c => buf += c); res.on('end', () => { try { resolve({ status: res.statusCode, data: JSON.parse(buf) }); } catch { resolve({ status: res.statusCode, data: buf }); } }); }); req.on('error', reject); req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); }); if (data) req.write(data); req.end(); }); } function ok(msg) { console.log(` \x1b[32m✅\x1b[0m ${msg}`); } function fail(msg) { console.log(` \x1b[31m❌\x1b[0m ${msg}`); } function step(n, msg) { console.log(`\n\x1b[36m[${n}]\x1b[0m ${msg}`); } // ─── 主流程 ─── async function main() { console.log(` ╔═══════════════════════════════════════╗ ║ 远程 Agent ${UNREGISTER ? '注销' : '注册'} ║ ╠═══════════════════════════════════════╣ ║ Bridge: ${WS_URL.padEnd(27)}║ ║ Agent: ${NAME.padEnd(27)}║ ║ Server: ${SERVER.padEnd(27)}║ ╚═══════════════════════════════════════╝`); // ── 注销模式 ── if (UNREGISTER) { step(1, `注销 ${NAME}`); try { const res = await request('DELETE', `${SERVER}/api/agents/${NAME}`); if (res.status === 200) { ok(`${NAME} 已注销`); } else { fail(`注销失败: ${JSON.stringify(res.data)}`); process.exit(1); } } catch (e) { fail(`Server 不可达: ${e.message}`); process.exit(1); } step(2, '验证'); const check = await request('GET', `${SERVER}/api/agents/${NAME}`); if (check.status === 404 || check.data?.error) { ok('确认已注销'); } else { fail('仍然存在'); } return; } // ── 注册模式 ── // Step 1: 检查 Server 可达 step(1, '检查主控 Server'); try { const res = await request('GET', `${SERVER}/api/agents`); ok(`Server 可达,当前 ${res.data?.length || '?'} 个 Agent`); } catch (e) { fail(`Server 不可达 (${SERVER}): ${e.message}`); process.exit(1); } // Step 2: 探测 Bridge if (!SKIP_HEALTH) { step(2, `探测 Bridge (${bridgeHost}:${bridgePort})`); try { const res = await request('GET', `http://${bridgeHost}:${bridgePort}/health`); ok(`Bridge 在线: ${JSON.stringify(res.data)}`); } catch (e) { fail(`Bridge 不可达: ${e.message}`); console.log(' 提示: 使用 --skip-health 跳过此检查'); process.exit(1); } } else { step(2, '跳过 Bridge 健康检查 (--skip-health)'); } // Step 3: 注册 step(3, `注册 Agent: ${NAME}`); const payload = { name: NAME, type: TYPE, protocol: PROTOCOL, transport: 'websocket', transportConfig: { url: WS_URL }, launcher: 'none', description: DESC, }; try { const res = await request('POST', `${SERVER}/api/agents`, payload); if (res.status === 200 || res.status === 201) { ok(`注册成功`); } else { fail(`注册失败: ${JSON.stringify(res.data)}`); process.exit(1); } } catch (e) { fail(`请求失败: ${e.message}`); process.exit(1); } // Step 4: 验证 step(4, '验证注册结果'); try { const res = await request('GET', `${SERVER}/api/agents/${NAME}`); if (res.data?.name === NAME || res.data?.agent) { ok(`Agent "${NAME}" 已注册`); console.log(` 类型: ${TYPE}`); console.log(` 协议: ${PROTOCOL}`); console.log(` 地址: ${WS_URL}`); } else { fail(`验证失败: ${JSON.stringify(res.data)}`); } } catch (e) { fail(`验证请求失败: ${e.message}`); } console.log(` \x1b[32m注册完成!\x1b[0m 后续操作: # 查看所有 Agent curl ${SERVER}/api/agents # 健康检查 curl ${SERVER}/api/agents/${NAME}/health # 注销 node scripts/register-remote-agent.js ${BRIDGE_HOST_RAW} --name ${NAME} --unregister `); } main().catch(e => { console.error('💥', e.message); process.exit(1); });