@mclawnet/agent 0.2.0 → 0.2.1
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/cli.js +63 -71
- package/dist/backend-adapter.d.ts +53 -0
- package/dist/backend-adapter.d.ts.map +1 -0
- package/dist/chunk-YBQQZNRQ.js +780 -0
- package/dist/chunk-YBQQZNRQ.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/fs-handler.d.ts +39 -0
- package/dist/fs-handler.d.ts.map +1 -0
- package/dist/hub-connection.d.ts +60 -0
- package/dist/hub-connection.d.ts.map +1 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -726
- package/dist/index.js.map +1 -0
- package/dist/session-manager.d.ts +33 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/start.d.ts +15 -107
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +4 -713
- package/dist/start.js.map +1 -0
- package/package.json +23 -24
package/dist/start.js
CHANGED
|
@@ -1,716 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { homedir } from "os";
|
|
5
|
-
var CONFIG_DIR = join(homedir(), ".clawnet");
|
|
6
|
-
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7
|
-
var DEFAULT_HUB_URL = "wss://clawnet-hub.politeriver-28dd7708.eastasia.azurecontainerapps.io/ws/agent";
|
|
8
|
-
var DEFAULTS = {
|
|
9
|
-
hubUrl: DEFAULT_HUB_URL,
|
|
10
|
-
token: "",
|
|
11
|
-
port: 18789,
|
|
12
|
-
name: `agent-${process.pid}`
|
|
13
|
-
};
|
|
14
|
-
function loadConfig(cliOpts = {}) {
|
|
15
|
-
let fileConfig = {};
|
|
16
|
-
if (existsSync(CONFIG_FILE)) {
|
|
17
|
-
try {
|
|
18
|
-
fileConfig = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
19
|
-
} catch {
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return {
|
|
23
|
-
hubUrl: cliOpts.hubUrl ?? process.env.CLAWNET_HUB_URL ?? fileConfig.hubUrl ?? DEFAULTS.hubUrl,
|
|
24
|
-
token: cliOpts.token ?? process.env.CLAWNET_TOKEN ?? fileConfig.token ?? DEFAULTS.token,
|
|
25
|
-
port: Number(
|
|
26
|
-
cliOpts.port ?? process.env.CLAWNET_PORT ?? fileConfig.port ?? DEFAULTS.port
|
|
27
|
-
),
|
|
28
|
-
name: cliOpts.name ?? process.env.CLAWNET_NAME ?? fileConfig.name ?? DEFAULTS.name
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// src/gateway-launcher.ts
|
|
33
|
-
import { spawn, execSync } from "child_process";
|
|
34
|
-
import { dirname, join as join2 } from "path";
|
|
35
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
36
|
-
import { randomBytes } from "crypto";
|
|
37
|
-
import { fileURLToPath } from "url";
|
|
38
|
-
import { homedir as homedir2 } from "os";
|
|
39
|
-
import { createInterface } from "readline";
|
|
40
|
-
function launchGateway(port) {
|
|
41
|
-
return new Promise((resolvePromise, reject) => {
|
|
42
|
-
const openclawBin = findOpenClawBin();
|
|
43
|
-
console.log(`[openclaw] Using binary: ${openclawBin}`);
|
|
44
|
-
const child = spawn(openclawBin, buildGatewayArgs(port), {
|
|
45
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
46
|
-
env: {
|
|
47
|
-
...process.env,
|
|
48
|
-
OPENCLAW_GATEWAY_PORT: String(port)
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
let started = false;
|
|
52
|
-
const timeout = setTimeout(() => {
|
|
53
|
-
if (!started) {
|
|
54
|
-
started = true;
|
|
55
|
-
resolvePromise(handle);
|
|
56
|
-
}
|
|
57
|
-
}, 15e3);
|
|
58
|
-
const handle = {
|
|
59
|
-
process: child,
|
|
60
|
-
port,
|
|
61
|
-
async close() {
|
|
62
|
-
return new Promise((res) => {
|
|
63
|
-
if (child.killed || child.exitCode !== null) {
|
|
64
|
-
res();
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
child.on("exit", () => res());
|
|
68
|
-
child.kill("SIGTERM");
|
|
69
|
-
setTimeout(() => {
|
|
70
|
-
if (!child.killed) child.kill("SIGKILL");
|
|
71
|
-
res();
|
|
72
|
-
}, 5e3);
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
child.stdout?.on("data", (data) => {
|
|
77
|
-
const line = data.toString();
|
|
78
|
-
process.stdout.write(`[openclaw] ${line}`);
|
|
79
|
-
if (!started && (line.includes("ready") || line.includes("listening") || line.includes("Gateway"))) {
|
|
80
|
-
started = true;
|
|
81
|
-
clearTimeout(timeout);
|
|
82
|
-
resolvePromise(handle);
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
child.stderr?.on("data", (data) => {
|
|
86
|
-
process.stderr.write(`[openclaw] ${data.toString()}`);
|
|
87
|
-
});
|
|
88
|
-
child.on("error", (err) => {
|
|
89
|
-
clearTimeout(timeout);
|
|
90
|
-
if (!started) {
|
|
91
|
-
reject(new Error(`Failed to start OpenClaw: ${err.message}`));
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
child.on("exit", (code) => {
|
|
95
|
-
clearTimeout(timeout);
|
|
96
|
-
if (!started) {
|
|
97
|
-
reject(new Error(`OpenClaw exited with code ${code} before starting`));
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
function findOpenClawBin() {
|
|
103
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
104
|
-
const localBin = join2(__dirname, "..", "node_modules", ".bin", "openclaw");
|
|
105
|
-
if (existsSync2(localBin)) return localBin;
|
|
106
|
-
const hoistedBin = join2(__dirname, "..", "..", "..", "node_modules", ".bin", "openclaw");
|
|
107
|
-
if (existsSync2(hoistedBin)) return hoistedBin;
|
|
108
|
-
try {
|
|
109
|
-
const cmd = process.platform === "win32" ? "where openclaw" : "which openclaw";
|
|
110
|
-
const result = execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
111
|
-
if (result) return result;
|
|
112
|
-
} catch {
|
|
113
|
-
}
|
|
114
|
-
return "openclaw";
|
|
115
|
-
}
|
|
116
|
-
function buildGatewayArgs(port) {
|
|
117
|
-
return ["gateway", "run", "--port", String(port)];
|
|
118
|
-
}
|
|
119
|
-
var OPENCLAW_DIR_NAME = ".openclaw";
|
|
120
|
-
var OPENCLAW_CONFIG_NAME = "openclaw.json";
|
|
121
|
-
function openclawConfigPath() {
|
|
122
|
-
const dir = join2(homedir2(), OPENCLAW_DIR_NAME);
|
|
123
|
-
return { dir, file: join2(dir, OPENCLAW_CONFIG_NAME) };
|
|
124
|
-
}
|
|
125
|
-
function ensureGatewayToken() {
|
|
126
|
-
const { dir, file } = openclawConfigPath();
|
|
127
|
-
mkdirSync2(dir, { recursive: true });
|
|
128
|
-
let config = {};
|
|
129
|
-
if (existsSync2(file)) {
|
|
130
|
-
try {
|
|
131
|
-
config = JSON.parse(readFileSync2(file, "utf-8"));
|
|
132
|
-
} catch {
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
let dirty = false;
|
|
136
|
-
if (!config.gateway) config.gateway = {};
|
|
137
|
-
if (!config.gateway.auth) config.gateway.auth = {};
|
|
138
|
-
const existing = config.gateway.auth.token;
|
|
139
|
-
let token;
|
|
140
|
-
if (typeof existing === "string" && existing.length > 0) {
|
|
141
|
-
token = existing;
|
|
142
|
-
} else {
|
|
143
|
-
token = randomBytes(16).toString("hex");
|
|
144
|
-
config.gateway.auth.token = token;
|
|
145
|
-
dirty = true;
|
|
146
|
-
}
|
|
147
|
-
if (!config.gateway.mode) {
|
|
148
|
-
config.gateway.mode = "local";
|
|
149
|
-
dirty = true;
|
|
150
|
-
}
|
|
151
|
-
if (!config.gateway.controlUi) config.gateway.controlUi = {};
|
|
152
|
-
if (!config.gateway.controlUi.allowedOrigins) {
|
|
153
|
-
config.gateway.controlUi.allowedOrigins = ["*"];
|
|
154
|
-
dirty = true;
|
|
155
|
-
}
|
|
156
|
-
if (config.gateway.controlUi.allowInsecureAuth === void 0) {
|
|
157
|
-
config.gateway.controlUi.allowInsecureAuth = true;
|
|
158
|
-
dirty = true;
|
|
159
|
-
}
|
|
160
|
-
if (dirty) {
|
|
161
|
-
writeFileSync2(file, JSON.stringify(config, null, 2) + "\n");
|
|
162
|
-
}
|
|
163
|
-
return token;
|
|
164
|
-
}
|
|
165
|
-
var COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
|
|
166
|
-
var COPILOT_HEADERS = {
|
|
167
|
-
"content-type": "application/json",
|
|
168
|
-
"copilot-integration-id": "vscode-chat",
|
|
169
|
-
"editor-version": "vscode/1.104.3",
|
|
170
|
-
"editor-plugin-version": "copilot-chat/0.26.7",
|
|
171
|
-
"user-agent": "GitHubCopilotChat/0.26.7",
|
|
172
|
-
"openai-intent": "conversation-panel",
|
|
173
|
-
"x-github-api-version": "2025-04-01"
|
|
174
|
-
};
|
|
175
|
-
async function getCopilotApiToken(githubToken) {
|
|
176
|
-
const res = await fetch(COPILOT_TOKEN_URL, {
|
|
177
|
-
headers: {
|
|
178
|
-
...COPILOT_HEADERS,
|
|
179
|
-
Authorization: `token ${githubToken}`,
|
|
180
|
-
accept: "application/json"
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
if (!res.ok) throw new Error(`Copilot token exchange failed: ${res.status}`);
|
|
184
|
-
const data = await res.json();
|
|
185
|
-
const endpoints = data.endpoints;
|
|
186
|
-
let baseUrl = "https://api.githubcopilot.com";
|
|
187
|
-
if (endpoints && typeof endpoints === "object" && endpoints.api) {
|
|
188
|
-
baseUrl = endpoints.api;
|
|
189
|
-
} else {
|
|
190
|
-
const proxyEp = (data.token || "").match(/(?:^|;)\s*proxy-ep=([^;\s]+)/i)?.[1]?.trim();
|
|
191
|
-
if (proxyEp) {
|
|
192
|
-
baseUrl = proxyEp.startsWith("http") ? proxyEp : `https://${proxyEp}`;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return { token: data.token, baseUrl };
|
|
196
|
-
}
|
|
197
|
-
async function fetchCopilotModels(githubToken) {
|
|
198
|
-
try {
|
|
199
|
-
const { token, baseUrl } = await getCopilotApiToken(githubToken);
|
|
200
|
-
const res = await fetch(`${baseUrl}/models`, {
|
|
201
|
-
headers: {
|
|
202
|
-
...COPILOT_HEADERS,
|
|
203
|
-
Authorization: `Bearer ${token}`
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
if (!res.ok) throw new Error(`Models fetch failed: ${res.status}`);
|
|
207
|
-
const data = await res.json();
|
|
208
|
-
const models = (data.data || []).filter((m) => m.id && m.capabilities?.type === "chat").map((m) => ({
|
|
209
|
-
model: `github-copilot/${m.id}`,
|
|
210
|
-
display: `${m.name || m.id} (${m.vendor || "unknown"})`
|
|
211
|
-
}));
|
|
212
|
-
return models.length > 0 ? models : fallbackCopilotModels();
|
|
213
|
-
} catch (err) {
|
|
214
|
-
console.warn(` Could not fetch models from Copilot API: ${err.message}`);
|
|
215
|
-
console.warn(" Using default model list.");
|
|
216
|
-
return fallbackCopilotModels();
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
function fallbackCopilotModels() {
|
|
220
|
-
return [
|
|
221
|
-
{ model: "github-copilot/gpt-4o", display: "gpt-4o" },
|
|
222
|
-
{ model: "github-copilot/gpt-4.1", display: "gpt-4.1" },
|
|
223
|
-
{ model: "github-copilot/claude-sonnet-4", display: "Claude Sonnet 4" },
|
|
224
|
-
{ model: "github-copilot/o3-mini", display: "o3-mini" }
|
|
225
|
-
];
|
|
226
|
-
}
|
|
227
|
-
var GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
|
|
228
|
-
var GITHUB_BASE_URL = "https://github.com";
|
|
229
|
-
async function githubDeviceAuth() {
|
|
230
|
-
console.log("");
|
|
231
|
-
console.log("No GitHub Copilot auth found. Starting GitHub login...");
|
|
232
|
-
console.log("");
|
|
233
|
-
const codeRes = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {
|
|
234
|
-
method: "POST",
|
|
235
|
-
headers: { "content-type": "application/json", accept: "application/json" },
|
|
236
|
-
body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, scope: "read:user" })
|
|
237
|
-
});
|
|
238
|
-
if (!codeRes.ok) throw new Error(`Device code request failed: ${codeRes.status}`);
|
|
239
|
-
const codeData = await codeRes.json();
|
|
240
|
-
console.log(` Open: ${codeData.verification_uri}`);
|
|
241
|
-
console.log(` Code: ${codeData.user_code}`);
|
|
242
|
-
console.log("");
|
|
243
|
-
console.log(" Waiting for authorization...");
|
|
244
|
-
const interval = (codeData.interval + 1) * 1e3;
|
|
245
|
-
const deadline = Date.now() + codeData.expires_in * 1e3;
|
|
246
|
-
while (Date.now() < deadline) {
|
|
247
|
-
await new Promise((r) => setTimeout(r, interval));
|
|
248
|
-
const tokenRes = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {
|
|
249
|
-
method: "POST",
|
|
250
|
-
headers: { "content-type": "application/json", accept: "application/json" },
|
|
251
|
-
body: JSON.stringify({
|
|
252
|
-
client_id: GITHUB_CLIENT_ID,
|
|
253
|
-
device_code: codeData.device_code,
|
|
254
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
255
|
-
})
|
|
256
|
-
});
|
|
257
|
-
if (!tokenRes.ok) continue;
|
|
258
|
-
const tokenData = await tokenRes.json();
|
|
259
|
-
if (tokenData.access_token) {
|
|
260
|
-
console.log(" GitHub login successful!");
|
|
261
|
-
return tokenData.access_token;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
throw new Error("GitHub device flow timed out");
|
|
265
|
-
}
|
|
266
|
-
function saveGitHubToken(token) {
|
|
267
|
-
const agentDir = join2(homedir2(), ".openclaw", "agents", "main", "agent");
|
|
268
|
-
mkdirSync2(agentDir, { recursive: true });
|
|
269
|
-
const authFile = join2(agentDir, "auth-profiles.json");
|
|
270
|
-
let authData = { version: 1, profiles: {} };
|
|
271
|
-
if (existsSync2(authFile)) {
|
|
272
|
-
try {
|
|
273
|
-
authData = JSON.parse(readFileSync2(authFile, "utf-8"));
|
|
274
|
-
} catch {
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
authData.profiles = authData.profiles || {};
|
|
278
|
-
authData.profiles["github-copilot:github"] = {
|
|
279
|
-
type: "token",
|
|
280
|
-
provider: "github-copilot",
|
|
281
|
-
token
|
|
282
|
-
};
|
|
283
|
-
writeFileSync2(authFile, JSON.stringify(authData, null, 2) + "\n");
|
|
284
|
-
const { file } = openclawConfigPath();
|
|
285
|
-
if (existsSync2(file)) {
|
|
286
|
-
try {
|
|
287
|
-
const config = JSON.parse(readFileSync2(file, "utf-8"));
|
|
288
|
-
if (!config.auth) config.auth = {};
|
|
289
|
-
if (!config.auth.profiles) config.auth.profiles = {};
|
|
290
|
-
config.auth.profiles["github-copilot:github"] = {
|
|
291
|
-
provider: "github-copilot",
|
|
292
|
-
mode: "token"
|
|
293
|
-
};
|
|
294
|
-
writeFileSync2(file, JSON.stringify(config, null, 2) + "\n");
|
|
295
|
-
} catch {
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
function getGitHubToken(config) {
|
|
300
|
-
const configProfiles = config?.auth?.profiles;
|
|
301
|
-
if (configProfiles && typeof configProfiles === "object") {
|
|
302
|
-
for (const profile of Object.values(configProfiles)) {
|
|
303
|
-
if (profile?.provider === "github-copilot" && profile?.token) {
|
|
304
|
-
return profile.token;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
const authFile = join2(homedir2(), ".openclaw", "agents", "main", "agent", "auth-profiles.json");
|
|
309
|
-
if (existsSync2(authFile)) {
|
|
310
|
-
try {
|
|
311
|
-
const authData = JSON.parse(readFileSync2(authFile, "utf-8"));
|
|
312
|
-
const profiles = authData?.profiles;
|
|
313
|
-
if (profiles && typeof profiles === "object") {
|
|
314
|
-
for (const profile of Object.values(profiles)) {
|
|
315
|
-
if (profile?.provider === "github-copilot" && profile?.token) {
|
|
316
|
-
return profile.token;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
} catch {
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return process.env.COPILOT_GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN || null;
|
|
324
|
-
}
|
|
325
|
-
async function checkModelConfig() {
|
|
326
|
-
const { file } = openclawConfigPath();
|
|
327
|
-
if (!existsSync2(file)) return false;
|
|
328
|
-
let config;
|
|
329
|
-
try {
|
|
330
|
-
config = JSON.parse(readFileSync2(file, "utf-8"));
|
|
331
|
-
} catch {
|
|
332
|
-
return false;
|
|
333
|
-
}
|
|
334
|
-
const primary = config?.agents?.defaults?.model?.primary;
|
|
335
|
-
if (typeof primary === "string" && primary.length > 0) {
|
|
336
|
-
console.log(` Model: ${primary}`);
|
|
337
|
-
return true;
|
|
338
|
-
}
|
|
339
|
-
let githubToken = getGitHubToken(config);
|
|
340
|
-
if (!githubToken) {
|
|
341
|
-
try {
|
|
342
|
-
const accessToken = await githubDeviceAuth();
|
|
343
|
-
saveGitHubToken(accessToken);
|
|
344
|
-
githubToken = accessToken;
|
|
345
|
-
config = JSON.parse(readFileSync2(file, "utf-8"));
|
|
346
|
-
} catch (err) {
|
|
347
|
-
console.error(` GitHub auth failed: ${err.message}`);
|
|
348
|
-
console.warn(" You can retry or configure manually: openclaw onboard");
|
|
349
|
-
console.warn("");
|
|
350
|
-
return false;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
console.log("");
|
|
354
|
-
console.log("Fetching available models from GitHub Copilot...");
|
|
355
|
-
const models = await fetchCopilotModels(githubToken);
|
|
356
|
-
console.log("");
|
|
357
|
-
console.log("Select a model:");
|
|
358
|
-
console.log("");
|
|
359
|
-
for (let i = 0; i < models.length; i++) {
|
|
360
|
-
console.log(` ${i + 1}) ${models[i].display}`);
|
|
361
|
-
}
|
|
362
|
-
console.log(` 0) Skip (configure later with: openclaw onboard)`);
|
|
363
|
-
console.log("");
|
|
364
|
-
const choice = await promptChoice(models.length);
|
|
365
|
-
if (choice === 0) {
|
|
366
|
-
console.warn("Skipped. Gateway will start but cannot respond to messages.");
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
const selected = models[choice - 1];
|
|
370
|
-
if (!config.agents) config.agents = {};
|
|
371
|
-
if (!config.agents.defaults) config.agents.defaults = {};
|
|
372
|
-
config.agents.defaults.model = { primary: selected.model };
|
|
373
|
-
writeFileSync2(file, JSON.stringify(config, null, 2) + "\n");
|
|
374
|
-
console.log(` Model set to: ${selected.model}`);
|
|
375
|
-
console.log("");
|
|
376
|
-
return true;
|
|
377
|
-
}
|
|
378
|
-
function promptChoice(max) {
|
|
379
|
-
return new Promise((resolve2) => {
|
|
380
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
381
|
-
rl.question(` Choose [0-${max}]: `, (answer) => {
|
|
382
|
-
rl.close();
|
|
383
|
-
const num = parseInt(answer.trim(), 10);
|
|
384
|
-
if (isNaN(num) || num < 0 || num > max) {
|
|
385
|
-
resolve2(0);
|
|
386
|
-
} else {
|
|
387
|
-
resolve2(num);
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// src/hub-connection.ts
|
|
394
|
-
import { hostname as osHostname } from "os";
|
|
395
|
-
import WebSocket from "ws";
|
|
396
|
-
var DEFAULT_HEARTBEAT_MS = 3e4;
|
|
397
|
-
var DEFAULT_RECONNECT_MS = 1e3;
|
|
398
|
-
var DEFAULT_MAX_RECONNECT_MS = 3e4;
|
|
399
|
-
var HubConnection = class {
|
|
400
|
-
ws = null;
|
|
401
|
-
heartbeatTimer = null;
|
|
402
|
-
reconnectTimer = null;
|
|
403
|
-
reconnectDelay;
|
|
404
|
-
destroyed = false;
|
|
405
|
-
authState = "pending";
|
|
406
|
-
proxySessions = /* @__PURE__ */ new Map();
|
|
407
|
-
hubUrl;
|
|
408
|
-
token;
|
|
409
|
-
hostname;
|
|
410
|
-
gatewayPort;
|
|
411
|
-
gatewayToken;
|
|
412
|
-
heartbeatInterval;
|
|
413
|
-
maxReconnectDelay;
|
|
414
|
-
/** Agent ID assigned by Hub after successful auth */
|
|
415
|
-
agentId = null;
|
|
416
|
-
onMessage;
|
|
417
|
-
onConnectCb;
|
|
418
|
-
onDisconnect;
|
|
419
|
-
onError;
|
|
420
|
-
constructor(opts) {
|
|
421
|
-
this.hubUrl = opts.hubUrl;
|
|
422
|
-
this.token = opts.token;
|
|
423
|
-
this.hostname = opts.hostname ?? osHostname();
|
|
424
|
-
this.gatewayPort = opts.gatewayPort ?? 18789;
|
|
425
|
-
this.gatewayToken = opts.gatewayToken ?? "";
|
|
426
|
-
this.heartbeatInterval = opts.heartbeatInterval ?? DEFAULT_HEARTBEAT_MS;
|
|
427
|
-
this.reconnectDelay = opts.reconnectDelay ?? DEFAULT_RECONNECT_MS;
|
|
428
|
-
this.maxReconnectDelay = opts.maxReconnectDelay ?? DEFAULT_MAX_RECONNECT_MS;
|
|
429
|
-
this.onMessage = opts.onMessage;
|
|
430
|
-
this.onConnectCb = opts.onConnect;
|
|
431
|
-
this.onDisconnect = opts.onDisconnect;
|
|
432
|
-
this.onError = opts.onError;
|
|
433
|
-
}
|
|
434
|
-
/** Current WebSocket readyState (or CLOSED if no socket) */
|
|
435
|
-
get readyState() {
|
|
436
|
-
return this.ws?.readyState ?? WebSocket.CLOSED;
|
|
437
|
-
}
|
|
438
|
-
get isConnected() {
|
|
439
|
-
return this.ws?.readyState === WebSocket.OPEN && this.authState === "authenticated";
|
|
440
|
-
}
|
|
441
|
-
/** Open the connection to the hub (no token in URL) */
|
|
442
|
-
connect() {
|
|
443
|
-
if (this.destroyed) return;
|
|
444
|
-
this.cleanup();
|
|
445
|
-
this.authState = "pending";
|
|
446
|
-
this.ws = new WebSocket(this.hubUrl);
|
|
447
|
-
this.ws.on("open", () => {
|
|
448
|
-
this.reconnectDelay = DEFAULT_RECONNECT_MS;
|
|
449
|
-
});
|
|
450
|
-
this.ws.on("message", (raw) => {
|
|
451
|
-
let data;
|
|
452
|
-
try {
|
|
453
|
-
data = JSON.parse(raw.toString());
|
|
454
|
-
} catch {
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
if (this.authState === "pending" && data.type === "auth_required") {
|
|
458
|
-
this.authState = "authenticating";
|
|
459
|
-
this.ws.send(JSON.stringify({
|
|
460
|
-
type: "auth",
|
|
461
|
-
token: this.token,
|
|
462
|
-
hostname: this.hostname
|
|
463
|
-
}));
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
if (this.authState === "authenticating" && data.type === "registered") {
|
|
467
|
-
this.authState = "authenticated";
|
|
468
|
-
this.agentId = data.agentId ?? null;
|
|
469
|
-
this.startHeartbeat();
|
|
470
|
-
this.onConnectCb?.(this.agentId);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
if (this.authState === "authenticated") {
|
|
474
|
-
if (this.handleProxyMessage(data)) return;
|
|
475
|
-
this.onMessage?.(data);
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
this.ws.on("close", (code, reason) => {
|
|
479
|
-
this.stopHeartbeat();
|
|
480
|
-
this.authState = "pending";
|
|
481
|
-
this.onDisconnect?.(code, reason.toString());
|
|
482
|
-
if (code === 4002) {
|
|
483
|
-
console.error("[clawnet] Auth failed \u2014 not reconnecting. Check your token.");
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
this.scheduleReconnect();
|
|
487
|
-
});
|
|
488
|
-
this.ws.on("error", (err) => {
|
|
489
|
-
this.onError?.(err);
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
/** Send a JSON message to the hub */
|
|
493
|
-
send(data) {
|
|
494
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || this.authState !== "authenticated") return false;
|
|
495
|
-
this.ws.send(JSON.stringify(data));
|
|
496
|
-
return true;
|
|
497
|
-
}
|
|
498
|
-
/** Permanently close and stop reconnecting */
|
|
499
|
-
destroy() {
|
|
500
|
-
this.destroyed = true;
|
|
501
|
-
this.cleanup();
|
|
502
|
-
}
|
|
503
|
-
startHeartbeat() {
|
|
504
|
-
this.stopHeartbeat();
|
|
505
|
-
this.heartbeatTimer = setInterval(() => {
|
|
506
|
-
this.send({ type: "heartbeat", ts: Date.now() });
|
|
507
|
-
}, this.heartbeatInterval);
|
|
508
|
-
}
|
|
509
|
-
stopHeartbeat() {
|
|
510
|
-
if (this.heartbeatTimer) {
|
|
511
|
-
clearInterval(this.heartbeatTimer);
|
|
512
|
-
this.heartbeatTimer = null;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
scheduleReconnect() {
|
|
516
|
-
if (this.destroyed) return;
|
|
517
|
-
this.reconnectTimer = setTimeout(() => {
|
|
518
|
-
this.connect();
|
|
519
|
-
}, this.reconnectDelay);
|
|
520
|
-
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
|
|
521
|
-
}
|
|
522
|
-
cleanup() {
|
|
523
|
-
this.stopHeartbeat();
|
|
524
|
-
this.closeAllProxySessions();
|
|
525
|
-
if (this.reconnectTimer) {
|
|
526
|
-
clearTimeout(this.reconnectTimer);
|
|
527
|
-
this.reconnectTimer = null;
|
|
528
|
-
}
|
|
529
|
-
if (this.ws) {
|
|
530
|
-
this.ws.removeAllListeners();
|
|
531
|
-
if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
|
|
532
|
-
this.ws.close();
|
|
533
|
-
}
|
|
534
|
-
this.ws = null;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
// ── Proxy session management ──────────────────────────────────────
|
|
538
|
-
/**
|
|
539
|
-
* Handle proxy protocol messages from the Hub.
|
|
540
|
-
* Returns true if the message was handled.
|
|
541
|
-
*/
|
|
542
|
-
handleProxyMessage(msg) {
|
|
543
|
-
if (msg.type === "proxy.open" && msg.sessionId) {
|
|
544
|
-
this.openLocalGateway(msg.sessionId, msg.gatewayPort ?? this.gatewayPort);
|
|
545
|
-
return true;
|
|
546
|
-
}
|
|
547
|
-
if (msg.type === "proxy.data" && msg.sessionId) {
|
|
548
|
-
const session = this.proxySessions.get(msg.sessionId);
|
|
549
|
-
if (session && session.localWs.readyState === WebSocket.OPEN && msg.data) {
|
|
550
|
-
const patched = this.patchBrowserConnect(msg.data, session);
|
|
551
|
-
session.localWs.send(patched);
|
|
552
|
-
}
|
|
553
|
-
return true;
|
|
554
|
-
}
|
|
555
|
-
if (msg.type === "proxy.close" && msg.sessionId) {
|
|
556
|
-
this.closeProxySession(msg.sessionId);
|
|
557
|
-
return true;
|
|
558
|
-
}
|
|
559
|
-
return false;
|
|
560
|
-
}
|
|
561
|
-
openLocalGateway(sessionId, port) {
|
|
562
|
-
const localWs = new WebSocket(`ws://127.0.0.1:${port}/`, {
|
|
563
|
-
headers: { Origin: `http://127.0.0.1:${port}` }
|
|
564
|
-
});
|
|
565
|
-
const session = {
|
|
566
|
-
sessionId,
|
|
567
|
-
localWs,
|
|
568
|
-
gatewayNonce: null
|
|
569
|
-
};
|
|
570
|
-
this.proxySessions.set(sessionId, session);
|
|
571
|
-
localWs.on("open", () => {
|
|
572
|
-
this.send({ type: "proxy.opened", sessionId });
|
|
573
|
-
});
|
|
574
|
-
localWs.on("message", (raw) => {
|
|
575
|
-
const text = raw.toString();
|
|
576
|
-
try {
|
|
577
|
-
const frame = JSON.parse(text);
|
|
578
|
-
if (frame.type === "evt" && frame.event === "connect.challenge" && frame.payload?.nonce) {
|
|
579
|
-
session.gatewayNonce = frame.payload.nonce;
|
|
580
|
-
}
|
|
581
|
-
} catch {
|
|
582
|
-
}
|
|
583
|
-
this.send({ type: "proxy.data", sessionId, data: text });
|
|
584
|
-
});
|
|
585
|
-
localWs.on("close", () => {
|
|
586
|
-
this.proxySessions.delete(sessionId);
|
|
587
|
-
this.send({ type: "proxy.close", sessionId });
|
|
588
|
-
});
|
|
589
|
-
localWs.on("error", () => {
|
|
590
|
-
this.proxySessions.delete(sessionId);
|
|
591
|
-
this.send({ type: "proxy.close", sessionId });
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Intercept browser's "connect" request and patch it for the local gateway:
|
|
596
|
-
* - Inject gateway auth token
|
|
597
|
-
* - Remove `device` object (browser's device identity/signature doesn't apply
|
|
598
|
-
* through the proxy; the gateway will use token-only auth)
|
|
599
|
-
*/
|
|
600
|
-
patchBrowserConnect(raw, session) {
|
|
601
|
-
if (!this.gatewayToken) return raw;
|
|
602
|
-
try {
|
|
603
|
-
const frame = JSON.parse(raw);
|
|
604
|
-
if (frame.type === "req" && frame.method === "connect" && frame.params) {
|
|
605
|
-
if (!frame.params.auth) frame.params.auth = {};
|
|
606
|
-
frame.params.auth.token = this.gatewayToken;
|
|
607
|
-
delete frame.params.device;
|
|
608
|
-
return JSON.stringify(frame);
|
|
609
|
-
}
|
|
610
|
-
} catch {
|
|
611
|
-
}
|
|
612
|
-
return raw;
|
|
613
|
-
}
|
|
614
|
-
closeProxySession(sessionId) {
|
|
615
|
-
const session = this.proxySessions.get(sessionId);
|
|
616
|
-
if (!session) return;
|
|
617
|
-
this.proxySessions.delete(sessionId);
|
|
618
|
-
if (session.localWs.readyState === WebSocket.OPEN || session.localWs.readyState === WebSocket.CONNECTING) {
|
|
619
|
-
session.localWs.close();
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
closeAllProxySessions() {
|
|
623
|
-
for (const [sessionId] of this.proxySessions) {
|
|
624
|
-
this.closeProxySession(sessionId);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
// src/start.ts
|
|
630
|
-
var currentAgent = null;
|
|
631
|
-
async function startAgent(opts) {
|
|
632
|
-
const config = loadConfig({
|
|
633
|
-
hubUrl: opts.hubUrl,
|
|
634
|
-
token: opts.token,
|
|
635
|
-
port: opts.port ? Number(opts.port) : void 0,
|
|
636
|
-
name: opts.name
|
|
637
|
-
});
|
|
638
|
-
if (!config.token) {
|
|
639
|
-
console.error("Error: --token is required.");
|
|
640
|
-
console.error("");
|
|
641
|
-
console.error("Get your token from the Hub dashboard:");
|
|
642
|
-
console.error(" 1. Log in to the Hub");
|
|
643
|
-
console.error(" 2. Create a new instance");
|
|
644
|
-
console.error(" 3. Copy the instance token (clw_xxx)");
|
|
645
|
-
console.error("");
|
|
646
|
-
console.error("Usage: clawnet-agent start --token clw_xxx");
|
|
647
|
-
process.exit(1);
|
|
648
|
-
}
|
|
649
|
-
console.log("ClawNet Agent starting...");
|
|
650
|
-
console.log(` Hub: ${config.hubUrl}`);
|
|
651
|
-
console.log(` Port: ${config.port}`);
|
|
652
|
-
console.log(` Name: ${config.name}`);
|
|
653
|
-
console.log("");
|
|
654
|
-
const gatewayToken = ensureGatewayToken();
|
|
655
|
-
await checkModelConfig();
|
|
656
|
-
let gateway = null;
|
|
657
|
-
try {
|
|
658
|
-
console.log(`Starting OpenClaw gateway on port ${config.port}...`);
|
|
659
|
-
gateway = await launchGateway(config.port);
|
|
660
|
-
console.log(`OpenClaw gateway ready on http://localhost:${config.port}`);
|
|
661
|
-
console.log(` Dashboard: http://localhost:${config.port}/?token=${gatewayToken}`);
|
|
662
|
-
} catch (err) {
|
|
663
|
-
console.warn(
|
|
664
|
-
`Warning: Could not start OpenClaw gateway: ${err.message}`
|
|
665
|
-
);
|
|
666
|
-
console.warn("Continuing with Hub connection only...");
|
|
667
|
-
}
|
|
668
|
-
console.log(`Connecting to Hub at ${config.hubUrl}...`);
|
|
669
|
-
const hubConnection = new HubConnection({
|
|
670
|
-
hubUrl: config.hubUrl,
|
|
671
|
-
token: config.token,
|
|
672
|
-
gatewayPort: config.port,
|
|
673
|
-
gatewayToken,
|
|
674
|
-
onConnect: () => {
|
|
675
|
-
console.log("[clawnet] Connected to Hub");
|
|
676
|
-
},
|
|
677
|
-
onDisconnect: (code, reason) => {
|
|
678
|
-
console.log(`[clawnet] Disconnected from Hub: ${code} ${reason}`);
|
|
679
|
-
},
|
|
680
|
-
onError: (err) => {
|
|
681
|
-
console.error(`[clawnet] Hub connection error: ${err.message}`);
|
|
682
|
-
}
|
|
683
|
-
});
|
|
684
|
-
hubConnection.connect();
|
|
685
|
-
currentAgent = { config, gateway, hubConnection };
|
|
686
|
-
const shutdown = async () => {
|
|
687
|
-
console.log("\nShutting down...");
|
|
688
|
-
hubConnection.destroy();
|
|
689
|
-
if (gateway) {
|
|
690
|
-
await gateway.close();
|
|
691
|
-
}
|
|
692
|
-
console.log("ClawNet Agent stopped.");
|
|
693
|
-
process.exit(0);
|
|
694
|
-
};
|
|
695
|
-
process.on("SIGINT", shutdown);
|
|
696
|
-
process.on("SIGTERM", shutdown);
|
|
697
|
-
console.log("");
|
|
698
|
-
console.log("ClawNet Agent is running. Press Ctrl+C to stop.");
|
|
699
|
-
await new Promise(() => {
|
|
700
|
-
});
|
|
701
|
-
return currentAgent;
|
|
702
|
-
}
|
|
703
|
-
function showStatus() {
|
|
704
|
-
if (currentAgent) {
|
|
705
|
-
console.log("ClawNet Agent is running.");
|
|
706
|
-
console.log(` Hub: ${currentAgent.config.hubUrl}`);
|
|
707
|
-
console.log(` Connected: ${currentAgent.hubConnection.isConnected}`);
|
|
708
|
-
} else {
|
|
709
|
-
console.log("ClawNet Agent is not running in this process.");
|
|
710
|
-
console.log("Use 'clawnet-agent start' to start it.");
|
|
711
|
-
}
|
|
712
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
startAgent
|
|
3
|
+
} from "./chunk-YBQQZNRQ.js";
|
|
713
4
|
export {
|
|
714
|
-
showStatus,
|
|
715
5
|
startAgent
|
|
716
6
|
};
|
|
7
|
+
//# sourceMappingURL=start.js.map
|