echoclaw-relay-agent 0.22.7 → 0.23.0
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/ConfigureHandler-NXQ2MDTI.js +342 -0
- package/dist/SessionStore-YHBFPQV7.js +6 -0
- package/dist/chunk-H67DFP5T.js +6077 -0
- package/dist/chunk-STI2237I.js +66 -0
- package/dist/cli.js +897 -729
- package/dist/index.d.ts +3 -0
- package/dist/index.js +626 -35
- package/dist/launchd-TQ5G4GUM.js +140 -0
- package/dist/local/LocalAgent.d.ts +1 -0
- package/dist/studio/StudioHandler.d.ts +91 -0
- package/dist/studio/types.d.ts +113 -0
- package/dist/systemd-MM76PMPH.js +170 -0
- package/dist/windows-MFD3SSHE.js +138 -0
- package/package.json +5 -2
- package/dist/RelayAgent.js +0 -527
- package/dist/RelayClient.js +0 -457
- package/dist/chat/ChatHandler.js +0 -1067
- package/dist/configure/ConfigureHandler.js +0 -196
- package/dist/configure/ModelVerifier.js +0 -80
- package/dist/configure/OpenClawConfig.js +0 -152
- package/dist/crypto/FrameCrypto.js +0 -104
- package/dist/gateway/AppRequestPrompt.js +0 -250
- package/dist/gateway/DeviceIdentity.js +0 -113
- package/dist/gateway/GatewayBridge.js +0 -425
- package/dist/gateway/GatewayManager.js +0 -247
- package/dist/gateway/GatewayWatchdog.js +0 -275
- package/dist/gateway/OpenClawWsClient.js +0 -755
- package/dist/gateway/ProcessRestart.js +0 -139
- package/dist/gateway/TokenDiscovery.js +0 -91
- package/dist/gateway/WorkspaceSync.js +0 -93
- package/dist/gateway/index.js +0 -12
- package/dist/gateway/types.js +0 -43
- package/dist/install/InstallHandler.js +0 -647
- package/dist/install/MarkerParser.js +0 -229
- package/dist/install/index.js +0 -6
- package/dist/install/types.js +0 -40
- package/dist/local/LocalAgent.js +0 -200
- package/dist/local/LocalDetector.js +0 -77
- package/dist/local/index.js +0 -5
- package/dist/ota/SelfUpdater.js +0 -217
- package/dist/relay/IdentityStore.js +0 -166
- package/dist/relay/PairingProtocol.js +0 -232
- package/dist/relay/ReconnectPolicy.js +0 -75
- package/dist/relay/RelayTransport.js +0 -318
- package/dist/relay/SessionStore.js +0 -83
- package/dist/service/launchd.js +0 -164
- package/dist/service/platform.js +0 -30
- package/dist/service/systemd.js +0 -218
- package/dist/service/windows.js +0 -172
- package/dist/types.js +0 -12
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// src/configure/ConfigureHandler.ts
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
5
|
+
// src/configure/OpenClawConfig.ts
|
|
6
|
+
import { readFile, writeFile, mkdir, rename } from "node:fs/promises";
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { randomBytes } from "node:crypto";
|
|
11
|
+
var OPENCLAW_CONFIG_DIR = join(homedir(), ".openclaw");
|
|
12
|
+
var OPENCLAW_CONFIG_PATH = join(OPENCLAW_CONFIG_DIR, "openclaw.json");
|
|
13
|
+
function detectProvider(apiKey, explicitProvider) {
|
|
14
|
+
if (!apiKey || typeof apiKey !== "string" || apiKey.trim().length < 10) {
|
|
15
|
+
throw new Error("API Key \u65E0\u6548\uFF1A\u957F\u5EA6\u8FC7\u77ED");
|
|
16
|
+
}
|
|
17
|
+
if (/[\x00-\x1f\x7f]/.test(apiKey)) {
|
|
18
|
+
throw new Error("API Key \u65E0\u6548\uFF1A\u5305\u542B\u63A7\u5236\u5B57\u7B26");
|
|
19
|
+
}
|
|
20
|
+
if (explicitProvider) {
|
|
21
|
+
return {
|
|
22
|
+
provider: explicitProvider,
|
|
23
|
+
modelId: getDefaultModel(explicitProvider),
|
|
24
|
+
apiKey
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (apiKey.startsWith("sk-ant-")) {
|
|
28
|
+
return { provider: "anthropic", modelId: "anthropic/claude-sonnet-4-5", apiKey };
|
|
29
|
+
}
|
|
30
|
+
if (apiKey.startsWith("sk-proj-") || apiKey.startsWith("sk-svcacct-") || apiKey.startsWith("sk-") && apiKey.length > 40) {
|
|
31
|
+
return { provider: "openai", modelId: "openai/gpt-4o", apiKey };
|
|
32
|
+
}
|
|
33
|
+
throw new Error(
|
|
34
|
+
"\u65E0\u6CD5\u81EA\u52A8\u8BC6\u522B API Key \u63D0\u4F9B\u5546\u3002\u8BF7\u6307\u5B9A provider \u53C2\u6570 (deepseek / anthropic / openai / openai-compatible)"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function getDefaultModel(provider) {
|
|
38
|
+
switch (provider) {
|
|
39
|
+
case "anthropic":
|
|
40
|
+
return "anthropic/claude-sonnet-4-5";
|
|
41
|
+
case "openai":
|
|
42
|
+
return "openai/gpt-4o";
|
|
43
|
+
case "deepseek":
|
|
44
|
+
return "deepseek/deepseek-chat";
|
|
45
|
+
default:
|
|
46
|
+
return `${provider}/default`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function readOpenClawConfig() {
|
|
50
|
+
if (!existsSync(OPENCLAW_CONFIG_PATH)) {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const raw = await readFile(OPENCLAW_CONFIG_PATH, "utf-8");
|
|
55
|
+
return JSON.parse(raw);
|
|
56
|
+
} catch {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function writeOpenClawConfig(config) {
|
|
61
|
+
await mkdir(OPENCLAW_CONFIG_DIR, { recursive: true, mode: 448 });
|
|
62
|
+
const tmpPath = `${OPENCLAW_CONFIG_PATH}.${randomBytes(4).toString("hex")}.tmp`;
|
|
63
|
+
await writeFile(tmpPath, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
64
|
+
await rename(tmpPath, OPENCLAW_CONFIG_PATH);
|
|
65
|
+
}
|
|
66
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
67
|
+
function setNestedValue(obj, path, value) {
|
|
68
|
+
const parts = path.split(".");
|
|
69
|
+
let current = obj;
|
|
70
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
71
|
+
if (FORBIDDEN_KEYS.has(parts[i])) throw new Error(`Invalid config path: ${path}`);
|
|
72
|
+
if (current[parts[i]] === void 0 || typeof current[parts[i]] !== "object") {
|
|
73
|
+
current[parts[i]] = {};
|
|
74
|
+
}
|
|
75
|
+
current = current[parts[i]];
|
|
76
|
+
}
|
|
77
|
+
const lastKey = parts[parts.length - 1];
|
|
78
|
+
if (FORBIDDEN_KEYS.has(lastKey)) throw new Error(`Invalid config path: ${path}`);
|
|
79
|
+
current[lastKey] = value;
|
|
80
|
+
}
|
|
81
|
+
function getNestedValue(obj, path) {
|
|
82
|
+
const parts = path.split(".");
|
|
83
|
+
let current = obj;
|
|
84
|
+
for (const part of parts) {
|
|
85
|
+
if (current === void 0 || current === null) return void 0;
|
|
86
|
+
current = current[part];
|
|
87
|
+
}
|
|
88
|
+
return current;
|
|
89
|
+
}
|
|
90
|
+
async function configureModel(providerConfig) {
|
|
91
|
+
const config = await readOpenClawConfig();
|
|
92
|
+
setNestedValue(config, "agents.defaults.model", providerConfig.modelId);
|
|
93
|
+
setNestedValue(
|
|
94
|
+
config,
|
|
95
|
+
`models.providers.${providerConfig.provider}.apiKey`,
|
|
96
|
+
providerConfig.apiKey
|
|
97
|
+
);
|
|
98
|
+
if (providerConfig.baseUrl) {
|
|
99
|
+
setNestedValue(
|
|
100
|
+
config,
|
|
101
|
+
`models.providers.${providerConfig.provider}.baseUrl`,
|
|
102
|
+
providerConfig.baseUrl
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
setNestedValue(config, "gateway.http.endpoints.chatCompletions.enabled", true);
|
|
106
|
+
await writeOpenClawConfig(config);
|
|
107
|
+
}
|
|
108
|
+
async function getGatewayToken() {
|
|
109
|
+
const config = await readOpenClawConfig();
|
|
110
|
+
return getNestedValue(config, "gateway.auth.token");
|
|
111
|
+
}
|
|
112
|
+
async function hasApiKeyConfigured() {
|
|
113
|
+
const config = await readOpenClawConfig();
|
|
114
|
+
const providers = getNestedValue(config, "models.providers");
|
|
115
|
+
if (!providers || typeof providers !== "object") return false;
|
|
116
|
+
for (const p of Object.values(providers)) {
|
|
117
|
+
if (typeof p === "object" && p !== null && "apiKey" in p && p.apiKey) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/configure/ModelVerifier.ts
|
|
125
|
+
async function verifyModel(gatewayPort, gatewayToken, model, timeoutMs = 3e4) {
|
|
126
|
+
const url = `http://localhost:${gatewayPort}/v1/chat/completions`;
|
|
127
|
+
try {
|
|
128
|
+
const res = await fetch(url, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: {
|
|
131
|
+
"Content-Type": "application/json",
|
|
132
|
+
Authorization: `Bearer ${gatewayToken}`
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({
|
|
135
|
+
model,
|
|
136
|
+
messages: [{ role: "user", content: "hi" }],
|
|
137
|
+
max_tokens: 5
|
|
138
|
+
}),
|
|
139
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
140
|
+
});
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
const body = await res.text().catch(() => "");
|
|
143
|
+
if (res.status === 401) {
|
|
144
|
+
return { success: false, error: "Gateway token \u65E0\u6548" };
|
|
145
|
+
}
|
|
146
|
+
if (res.status === 400 && body.includes("model")) {
|
|
147
|
+
return { success: false, error: `\u6A21\u578B ${model} \u4E0D\u53EF\u7528` };
|
|
148
|
+
}
|
|
149
|
+
if (res.status === 402 || body.includes("insufficient") || body.includes("quota")) {
|
|
150
|
+
return { success: false, error: "API \u4F59\u989D\u4E0D\u8DB3" };
|
|
151
|
+
}
|
|
152
|
+
return { success: false, error: `Gateway \u8FD4\u56DE ${res.status}: ${body.slice(0, 200)}` };
|
|
153
|
+
}
|
|
154
|
+
const data = await res.json();
|
|
155
|
+
if (data.choices && data.choices.length > 0) {
|
|
156
|
+
return { success: true, model };
|
|
157
|
+
}
|
|
158
|
+
return { success: false, error: "\u6A21\u578B\u8FD4\u56DE\u4E86\u7A7A\u54CD\u5E94" };
|
|
159
|
+
} catch (err) {
|
|
160
|
+
if (err.name === "AbortError" || err.name === "TimeoutError") {
|
|
161
|
+
return { success: false, error: `\u6A21\u578B\u8BF7\u6C42\u8D85\u65F6 (${timeoutMs / 1e3}s)` };
|
|
162
|
+
}
|
|
163
|
+
if (err.code === "ECONNREFUSED") {
|
|
164
|
+
return { success: false, error: `Gateway \u672A\u8FD0\u884C (localhost:${gatewayPort})` };
|
|
165
|
+
}
|
|
166
|
+
return { success: false, error: `\u8FDE\u63A5\u5931\u8D25: ${err.message}` };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function waitForGateway(gatewayPort, gatewayToken, timeoutMs = 3e4, pollIntervalMs = 2e3) {
|
|
170
|
+
const deadline = Date.now() + timeoutMs;
|
|
171
|
+
while (Date.now() < deadline) {
|
|
172
|
+
try {
|
|
173
|
+
const res = await fetch(`http://localhost:${gatewayPort}/v1/models`, {
|
|
174
|
+
headers: { Authorization: `Bearer ${gatewayToken}` },
|
|
175
|
+
signal: AbortSignal.timeout(5e3)
|
|
176
|
+
});
|
|
177
|
+
if (res.ok) return true;
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/configure/ConfigureHandler.ts
|
|
186
|
+
var execFileAsync = promisify(execFile);
|
|
187
|
+
var DEFAULT_GATEWAY_PORT = 18789;
|
|
188
|
+
async function findOpenClaw() {
|
|
189
|
+
try {
|
|
190
|
+
const { stdout } = await execFileAsync("which", ["openclaw"]);
|
|
191
|
+
return stdout.trim() || null;
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function runOpenClaw(args, timeoutMs = 6e4) {
|
|
197
|
+
const openclawBin = await findOpenClaw();
|
|
198
|
+
if (!openclawBin) throw new Error("OpenClaw binary not found");
|
|
199
|
+
return execFileAsync(openclawBin, args, {
|
|
200
|
+
timeout: timeoutMs,
|
|
201
|
+
env: { ...process.env }
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
async function checkOpenClawInstalled() {
|
|
205
|
+
try {
|
|
206
|
+
const { stdout } = await runOpenClaw(["--version"], 1e4);
|
|
207
|
+
const version = stdout.trim();
|
|
208
|
+
if (version) return { installed: true, version };
|
|
209
|
+
return { installed: false };
|
|
210
|
+
} catch {
|
|
211
|
+
return { installed: false };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function checkNeedsApiKey() {
|
|
215
|
+
return !await hasApiKeyConfigured();
|
|
216
|
+
}
|
|
217
|
+
async function handleConfigure(request, sendStatus) {
|
|
218
|
+
try {
|
|
219
|
+
sendStatus({
|
|
220
|
+
type: "agent_setup_status",
|
|
221
|
+
state: "configuring",
|
|
222
|
+
detail: "\u6B63\u5728\u68C0\u6D4B\u6A21\u578B\u63D0\u4F9B\u5546..."
|
|
223
|
+
}).catch(() => {
|
|
224
|
+
});
|
|
225
|
+
const providerConfig = detectProvider(request.apiKey, request.provider);
|
|
226
|
+
if (request.baseUrl) {
|
|
227
|
+
providerConfig.baseUrl = request.baseUrl;
|
|
228
|
+
}
|
|
229
|
+
sendStatus({
|
|
230
|
+
type: "agent_setup_status",
|
|
231
|
+
state: "configuring",
|
|
232
|
+
detail: `\u6B63\u5728\u914D\u7F6E ${providerConfig.provider} \u6A21\u578B...`
|
|
233
|
+
}).catch(() => {
|
|
234
|
+
});
|
|
235
|
+
await configureModel(providerConfig);
|
|
236
|
+
let gatewayToken = await getGatewayToken();
|
|
237
|
+
if (!gatewayToken) {
|
|
238
|
+
sendStatus({
|
|
239
|
+
type: "agent_setup_status",
|
|
240
|
+
state: "configuring",
|
|
241
|
+
detail: "\u6B63\u5728\u751F\u6210 Gateway \u8BA4\u8BC1\u4EE4\u724C..."
|
|
242
|
+
}).catch(() => {
|
|
243
|
+
});
|
|
244
|
+
try {
|
|
245
|
+
await runOpenClaw(["doctor", "--generate-gateway-token", "--non-interactive"], 3e4);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
return {
|
|
248
|
+
type: "agent_setup_status",
|
|
249
|
+
state: "error",
|
|
250
|
+
error: `\u751F\u6210 Gateway Token \u5931\u8D25: ${err.message}`
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
gatewayToken = await getGatewayToken();
|
|
254
|
+
if (!gatewayToken) {
|
|
255
|
+
return {
|
|
256
|
+
type: "agent_setup_status",
|
|
257
|
+
state: "error",
|
|
258
|
+
error: "Gateway Token \u751F\u6210\u540E\u4ECD\u4E0D\u53EF\u7528"
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
sendStatus({
|
|
263
|
+
type: "agent_setup_status",
|
|
264
|
+
state: "configuring",
|
|
265
|
+
detail: "\u6B63\u5728\u542F\u52A8 OpenClaw Gateway..."
|
|
266
|
+
}).catch(() => {
|
|
267
|
+
});
|
|
268
|
+
try {
|
|
269
|
+
await runOpenClaw(["gateway", "install"], 3e4);
|
|
270
|
+
} catch (installErr) {
|
|
271
|
+
sendStatus({
|
|
272
|
+
type: "agent_setup_status",
|
|
273
|
+
state: "configuring",
|
|
274
|
+
detail: `Gateway install: ${installErr.message?.slice(0, 100) ?? "skipped (may already be installed)"}`
|
|
275
|
+
}).catch(() => {
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
await runOpenClaw(["gateway", "start"], 15e3);
|
|
280
|
+
} catch (startErr) {
|
|
281
|
+
sendStatus({
|
|
282
|
+
type: "agent_setup_status",
|
|
283
|
+
state: "configuring",
|
|
284
|
+
detail: `Gateway start: ${startErr.message?.slice(0, 100) ?? "skipped (may already be running)"}`
|
|
285
|
+
}).catch(() => {
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
sendStatus({
|
|
289
|
+
type: "agent_setup_status",
|
|
290
|
+
state: "configuring",
|
|
291
|
+
detail: "\u7B49\u5F85 Gateway \u5C31\u7EEA..."
|
|
292
|
+
}).catch(() => {
|
|
293
|
+
});
|
|
294
|
+
const gatewayReady = await waitForGateway(DEFAULT_GATEWAY_PORT, gatewayToken, 3e4);
|
|
295
|
+
if (!gatewayReady) {
|
|
296
|
+
try {
|
|
297
|
+
await runOpenClaw(["gateway", "status", "--require-rpc"], 1e4);
|
|
298
|
+
} catch {
|
|
299
|
+
return {
|
|
300
|
+
type: "agent_setup_status",
|
|
301
|
+
state: "error",
|
|
302
|
+
error: "Gateway \u542F\u52A8\u8D85\u65F6\uFF0830s\uFF09\uFF0C\u8BF7\u68C0\u67E5 OpenClaw \u5B89\u88C5"
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
sendStatus({
|
|
307
|
+
type: "agent_setup_status",
|
|
308
|
+
state: "configuring",
|
|
309
|
+
detail: "\u6B63\u5728\u9A8C\u8BC1\u6A21\u578B\u8FDE\u63A5..."
|
|
310
|
+
}).catch(() => {
|
|
311
|
+
});
|
|
312
|
+
const verifyResult = await verifyModel(
|
|
313
|
+
DEFAULT_GATEWAY_PORT,
|
|
314
|
+
gatewayToken,
|
|
315
|
+
providerConfig.modelId
|
|
316
|
+
);
|
|
317
|
+
if (!verifyResult.success) {
|
|
318
|
+
return {
|
|
319
|
+
type: "agent_setup_status",
|
|
320
|
+
state: "error",
|
|
321
|
+
error: verifyResult.error ?? "\u6A21\u578B\u9A8C\u8BC1\u5931\u8D25"
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
type: "agent_setup_status",
|
|
326
|
+
state: "ready",
|
|
327
|
+
detail: `${providerConfig.provider} \u6A21\u578B\u5DF2\u5C31\u7EEA`,
|
|
328
|
+
gatewayPort: DEFAULT_GATEWAY_PORT
|
|
329
|
+
};
|
|
330
|
+
} catch (err) {
|
|
331
|
+
return {
|
|
332
|
+
type: "agent_setup_status",
|
|
333
|
+
state: "error",
|
|
334
|
+
error: `\u914D\u7F6E\u5931\u8D25: ${err.message}`
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
export {
|
|
339
|
+
checkNeedsApiKey,
|
|
340
|
+
checkOpenClawInstalled,
|
|
341
|
+
handleConfigure
|
|
342
|
+
};
|