register-remote-agent.js
6.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/usr/bin/env node
/**
* register-remote-agent.js — 远程 Agent 自动注册
*
* 用法:
* node scripts/register-remote-agent.js <BRIDGE_HOST> [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 <BRIDGE_HOST> [OPTIONS]
BRIDGE_HOST 远程 Bridge 地址 (IP / hostname / ws://...)
选项:
--port <PORT> Bridge WebSocket 端口 (默认 9001)
--name <NAME> Agent 名称 (默认 remote-agent)
--type <TYPE> Agent 类型: coder | validator | executor (默认 coder)
--protocol <PROTO> 协议: acp | claude-stream (默认 acp)
--server <URL> 主控 Server 地址 (默认 http://localhost:3000)
--description <D> 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);
});