@hypercli.com/sdk 2026.3.13-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/README.md +205 -0
- package/dist/agent.d.ts +63 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +128 -0
- package/dist/agent.js.map +1 -0
- package/dist/agents.d.ts +245 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +602 -0
- package/dist/agents.js.map +1 -0
- package/dist/billing.d.ts +38 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +53 -0
- package/dist/billing.js.map +1 -0
- package/dist/claw.d.ts +83 -0
- package/dist/claw.d.ts.map +1 -0
- package/dist/claw.js +148 -0
- package/dist/claw.js.map +1 -0
- package/dist/client.d.ts +62 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +82 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +107 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +9 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +15 -0
- package/dist/errors.js.map +1 -0
- package/dist/files.d.ts +60 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +128 -0
- package/dist/files.js.map +1 -0
- package/dist/gateway.d.ts +216 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +1306 -0
- package/dist/gateway.js.map +1 -0
- package/dist/http.d.ts +41 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +206 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/instances.d.ts +93 -0
- package/dist/instances.d.ts.map +1 -0
- package/dist/instances.js +196 -0
- package/dist/instances.js.map +1 -0
- package/dist/job/base.d.ts +80 -0
- package/dist/job/base.d.ts.map +1 -0
- package/dist/job/base.js +200 -0
- package/dist/job/base.js.map +1 -0
- package/dist/job/comfyui.d.ts +75 -0
- package/dist/job/comfyui.d.ts.map +1 -0
- package/dist/job/comfyui.js +454 -0
- package/dist/job/comfyui.js.map +1 -0
- package/dist/job/gradio.d.ts +48 -0
- package/dist/job/gradio.d.ts.map +1 -0
- package/dist/job/gradio.js +90 -0
- package/dist/job/gradio.js.map +1 -0
- package/dist/jobs.d.ts +132 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +244 -0
- package/dist/jobs.js.map +1 -0
- package/dist/keys.d.ts +39 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +53 -0
- package/dist/keys.js.map +1 -0
- package/dist/logs.d.ts +54 -0
- package/dist/logs.d.ts.map +1 -0
- package/dist/logs.js +256 -0
- package/dist/logs.js.map +1 -0
- package/dist/renders.d.ts +168 -0
- package/dist/renders.d.ts.map +1 -0
- package/dist/renders.js +228 -0
- package/dist/renders.js.map +1 -0
- package/dist/user.d.ts +20 -0
- package/dist/user.d.ts.map +1 -0
- package/dist/user.js +23 -0
- package/dist/user.js.map +1 -0
- package/package.json +65 -0
package/dist/gateway.js
ADDED
|
@@ -0,0 +1,1306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Gateway WebSocket Client
|
|
3
|
+
*
|
|
4
|
+
* Connects to an agent's OpenClaw gateway over WebSocket for real-time
|
|
5
|
+
* configuration, chat, session management, and file operations.
|
|
6
|
+
*
|
|
7
|
+
* Protocol: OpenClaw Gateway v3
|
|
8
|
+
*/
|
|
9
|
+
import { getPublicKeyAsync, signAsync, utils as edUtils } from "@noble/ed25519";
|
|
10
|
+
class GatewayRequestError extends Error {
|
|
11
|
+
gatewayCode;
|
|
12
|
+
details;
|
|
13
|
+
constructor(error) {
|
|
14
|
+
super(error.message);
|
|
15
|
+
this.name = "GatewayRequestError";
|
|
16
|
+
this.gatewayCode = error.code;
|
|
17
|
+
this.details = error.details;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const PROTOCOL_VERSION = 3;
|
|
21
|
+
const DEFAULT_TIMEOUT = 15_000;
|
|
22
|
+
const CHAT_TIMEOUT = 120_000;
|
|
23
|
+
const RECONNECT_CLOSE_CODE = 4008;
|
|
24
|
+
const DEFAULT_CLIENT_ID = "openclaw-control-ui";
|
|
25
|
+
const DEFAULT_CLIENT_MODE = "webchat";
|
|
26
|
+
const DEFAULT_CLIENT_VERSION = "@hypercli/sdk";
|
|
27
|
+
const DEFAULT_CAPS = ["tool-events"];
|
|
28
|
+
const CONNECT_TIMER_MS = 750;
|
|
29
|
+
const INITIAL_BACKOFF_MS = 800;
|
|
30
|
+
const MAX_BACKOFF_MS = 15_000;
|
|
31
|
+
const BACKOFF_MULTIPLIER = 1.7;
|
|
32
|
+
const OPERATOR_ROLE = "operator";
|
|
33
|
+
const OPERATOR_SCOPES = ["operator.admin", "operator.approvals", "operator.pairing"];
|
|
34
|
+
const STORAGE_KEY = "openclaw.device.auth.v1";
|
|
35
|
+
const CONNECT_ERROR_PAIRING_REQUIRED = "PAIRING_REQUIRED";
|
|
36
|
+
const CONNECT_ERROR_DEVICE_TOKEN_MISMATCH = "AUTH_DEVICE_TOKEN_MISMATCH";
|
|
37
|
+
const VALID_CLIENT_IDS = new Set([
|
|
38
|
+
"webchat-ui",
|
|
39
|
+
"openclaw-control-ui",
|
|
40
|
+
"webchat",
|
|
41
|
+
"cli",
|
|
42
|
+
"gateway-client",
|
|
43
|
+
"openclaw-macos",
|
|
44
|
+
"openclaw-ios",
|
|
45
|
+
"openclaw-android",
|
|
46
|
+
"node-host",
|
|
47
|
+
"test",
|
|
48
|
+
"fingerprint",
|
|
49
|
+
"openclaw-probe",
|
|
50
|
+
]);
|
|
51
|
+
const VALID_CLIENT_MODES = new Set([
|
|
52
|
+
"webchat",
|
|
53
|
+
"cli",
|
|
54
|
+
"ui",
|
|
55
|
+
"backend",
|
|
56
|
+
"node",
|
|
57
|
+
"probe",
|
|
58
|
+
"test",
|
|
59
|
+
]);
|
|
60
|
+
let memoryDeviceAuthStore = null;
|
|
61
|
+
function makeId() {
|
|
62
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
63
|
+
return crypto.randomUUID();
|
|
64
|
+
}
|
|
65
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
|
|
66
|
+
const random = Math.random() * 16 | 0;
|
|
67
|
+
return (char === "x" ? random : (random & 0x3) | 0x8).toString(16);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function asRecord(value) {
|
|
71
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
72
|
+
? value
|
|
73
|
+
: null;
|
|
74
|
+
}
|
|
75
|
+
function splitConfigPath(path) {
|
|
76
|
+
return path
|
|
77
|
+
.split(".")
|
|
78
|
+
.map((part) => part.trim())
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.flatMap((part) => part.endsWith("[]") && part !== "[]"
|
|
81
|
+
? [part.slice(0, -2), "[]"]
|
|
82
|
+
: [part]);
|
|
83
|
+
}
|
|
84
|
+
export function normalizeOpenClawConfigSchemaNode(value) {
|
|
85
|
+
const schema = asRecord(value) ?? {};
|
|
86
|
+
const oneOf = Array.isArray(schema.oneOf) ? schema.oneOf : [];
|
|
87
|
+
const anyOf = Array.isArray(schema.anyOf) ? schema.anyOf : [];
|
|
88
|
+
const union = [...oneOf, ...anyOf];
|
|
89
|
+
if (union.length === 0)
|
|
90
|
+
return schema;
|
|
91
|
+
const primary = union.find((entry) => {
|
|
92
|
+
const obj = asRecord(entry);
|
|
93
|
+
if (!obj)
|
|
94
|
+
return false;
|
|
95
|
+
const t = obj.type;
|
|
96
|
+
if (typeof t === "string")
|
|
97
|
+
return t !== "null";
|
|
98
|
+
if (Array.isArray(t))
|
|
99
|
+
return t.some((v) => v !== "null");
|
|
100
|
+
return true;
|
|
101
|
+
});
|
|
102
|
+
return asRecord(primary) ?? schema;
|
|
103
|
+
}
|
|
104
|
+
export function describeOpenClawConfigNode(value) {
|
|
105
|
+
const schema = normalizeOpenClawConfigSchemaNode(value);
|
|
106
|
+
const rawType = schema.type;
|
|
107
|
+
const type = Array.isArray(rawType)
|
|
108
|
+
? rawType.find((entry) => entry !== "null")
|
|
109
|
+
: (typeof rawType === "string" ? rawType : undefined);
|
|
110
|
+
const rawProperties = asRecord(schema.properties) ?? {};
|
|
111
|
+
const properties = {};
|
|
112
|
+
for (const [key, child] of Object.entries(rawProperties)) {
|
|
113
|
+
const childSchema = asRecord(child);
|
|
114
|
+
if (childSchema) {
|
|
115
|
+
properties[key] = childSchema;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const additionalPropertySchema = asRecord(schema.additionalProperties);
|
|
119
|
+
const additionalProperties = schema.additionalProperties === true || Boolean(additionalPropertySchema);
|
|
120
|
+
return {
|
|
121
|
+
schema,
|
|
122
|
+
type,
|
|
123
|
+
properties,
|
|
124
|
+
additionalProperties,
|
|
125
|
+
additionalPropertySchema,
|
|
126
|
+
isDynamicMap: (type === "object" || Object.keys(properties).length > 0 || additionalProperties) &&
|
|
127
|
+
additionalProperties,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function createOpenClawConfigValue(value) {
|
|
131
|
+
const descriptor = describeOpenClawConfigNode(value);
|
|
132
|
+
switch (descriptor.type) {
|
|
133
|
+
case "object":
|
|
134
|
+
return {};
|
|
135
|
+
case "array":
|
|
136
|
+
return [];
|
|
137
|
+
case "boolean":
|
|
138
|
+
return false;
|
|
139
|
+
case "number":
|
|
140
|
+
case "integer":
|
|
141
|
+
return 0;
|
|
142
|
+
default:
|
|
143
|
+
return "";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export function normalizeOpenClawConfigSchema(value) {
|
|
147
|
+
const raw = asRecord(value);
|
|
148
|
+
if (!raw)
|
|
149
|
+
return null;
|
|
150
|
+
const wrappedSchema = asRecord(raw.schema);
|
|
151
|
+
const uiHints = asRecord(raw.uiHints) ?? {};
|
|
152
|
+
const normalized = {
|
|
153
|
+
schema: wrappedSchema ?? raw,
|
|
154
|
+
uiHints: uiHints,
|
|
155
|
+
};
|
|
156
|
+
if (typeof raw.version === "string") {
|
|
157
|
+
normalized.version = raw.version;
|
|
158
|
+
}
|
|
159
|
+
if (typeof raw.generatedAt === "string") {
|
|
160
|
+
normalized.generatedAt = raw.generatedAt;
|
|
161
|
+
}
|
|
162
|
+
return normalized;
|
|
163
|
+
}
|
|
164
|
+
export function resolveOpenClawConfigUiHint(source, path) {
|
|
165
|
+
if (!path.trim())
|
|
166
|
+
return null;
|
|
167
|
+
const sourceRecord = asRecord(source);
|
|
168
|
+
const uiHints = sourceRecord && asRecord(sourceRecord.uiHints)
|
|
169
|
+
? sourceRecord.uiHints
|
|
170
|
+
: sourceRecord;
|
|
171
|
+
if (!uiHints || typeof uiHints !== "object")
|
|
172
|
+
return null;
|
|
173
|
+
const targetParts = splitConfigPath(path);
|
|
174
|
+
let best = null;
|
|
175
|
+
for (const [hintPath, hint] of Object.entries(uiHints)) {
|
|
176
|
+
const hintParts = splitConfigPath(hintPath);
|
|
177
|
+
if (hintParts.length !== targetParts.length)
|
|
178
|
+
continue;
|
|
179
|
+
let wildcardCount = 0;
|
|
180
|
+
let matches = true;
|
|
181
|
+
for (let index = 0; index < hintParts.length; index += 1) {
|
|
182
|
+
const hintPart = hintParts[index];
|
|
183
|
+
const targetPart = targetParts[index];
|
|
184
|
+
if (hintPart === targetPart)
|
|
185
|
+
continue;
|
|
186
|
+
if (hintPart === "*" || hintPart === "[]") {
|
|
187
|
+
wildcardCount += 1;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
matches = false;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
if (!matches)
|
|
194
|
+
continue;
|
|
195
|
+
if (!best || wildcardCount < best.wildcardCount) {
|
|
196
|
+
best = { path: hintPath, hint, wildcardCount };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return best ? { path: best.path, hint: best.hint } : null;
|
|
200
|
+
}
|
|
201
|
+
function normalizeClientId(value) {
|
|
202
|
+
const normalized = value?.trim().toLowerCase();
|
|
203
|
+
return normalized && VALID_CLIENT_IDS.has(normalized) ? normalized : DEFAULT_CLIENT_ID;
|
|
204
|
+
}
|
|
205
|
+
function normalizeClientMode(value) {
|
|
206
|
+
const normalized = value?.trim().toLowerCase();
|
|
207
|
+
return normalized && VALID_CLIENT_MODES.has(normalized) ? normalized : DEFAULT_CLIENT_MODE;
|
|
208
|
+
}
|
|
209
|
+
function getNavigatorLike() {
|
|
210
|
+
const maybeNavigator = globalThis.navigator;
|
|
211
|
+
return maybeNavigator ?? null;
|
|
212
|
+
}
|
|
213
|
+
function resolvePlatform(value) {
|
|
214
|
+
const normalized = value?.trim();
|
|
215
|
+
if (normalized)
|
|
216
|
+
return normalized;
|
|
217
|
+
const browserNavigator = getNavigatorLike();
|
|
218
|
+
if (browserNavigator?.platform)
|
|
219
|
+
return browserNavigator.platform;
|
|
220
|
+
if (typeof process !== "undefined" && process.platform)
|
|
221
|
+
return process.platform;
|
|
222
|
+
return "web";
|
|
223
|
+
}
|
|
224
|
+
function inferBrowserName(userAgent) {
|
|
225
|
+
const ua = userAgent ?? "";
|
|
226
|
+
if (!ua)
|
|
227
|
+
return null;
|
|
228
|
+
if (/Firefox\//i.test(ua))
|
|
229
|
+
return "Firefox";
|
|
230
|
+
if (/Edg\//i.test(ua))
|
|
231
|
+
return "Edge";
|
|
232
|
+
if (/Chrome\//i.test(ua) && !/Edg\//i.test(ua))
|
|
233
|
+
return "Chrome";
|
|
234
|
+
if (/Safari\//i.test(ua) && !/Chrome\//i.test(ua))
|
|
235
|
+
return "Safari";
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
function inferPlatformName(platform) {
|
|
239
|
+
const normalized = platform.trim().toLowerCase();
|
|
240
|
+
if (!normalized)
|
|
241
|
+
return null;
|
|
242
|
+
if (normalized.includes("mac"))
|
|
243
|
+
return "macOS";
|
|
244
|
+
if (normalized.includes("win"))
|
|
245
|
+
return "Windows";
|
|
246
|
+
if (normalized.includes("linux"))
|
|
247
|
+
return "Linux";
|
|
248
|
+
if (normalized.includes("iphone") || normalized.includes("ipad") || normalized.includes("ios")) {
|
|
249
|
+
return "iOS";
|
|
250
|
+
}
|
|
251
|
+
if (normalized.includes("android"))
|
|
252
|
+
return "Android";
|
|
253
|
+
return platform.trim();
|
|
254
|
+
}
|
|
255
|
+
function resolveBrowserHost() {
|
|
256
|
+
const browserWindow = globalThis.window;
|
|
257
|
+
const hostname = browserWindow?.location?.hostname?.trim();
|
|
258
|
+
return hostname || null;
|
|
259
|
+
}
|
|
260
|
+
function resolveClientDisplayName(value, platform) {
|
|
261
|
+
const provided = value?.trim();
|
|
262
|
+
if (provided)
|
|
263
|
+
return provided;
|
|
264
|
+
const browserName = inferBrowserName(resolveUserAgent());
|
|
265
|
+
const platformName = inferPlatformName(platform);
|
|
266
|
+
const host = resolveBrowserHost();
|
|
267
|
+
const details = [browserName, platformName ? `on ${platformName}` : null]
|
|
268
|
+
.filter(Boolean)
|
|
269
|
+
.join(" ");
|
|
270
|
+
if (details && host) {
|
|
271
|
+
return `Hyper Agent Web (${details}, ${host})`;
|
|
272
|
+
}
|
|
273
|
+
if (details) {
|
|
274
|
+
return `Hyper Agent Web (${details})`;
|
|
275
|
+
}
|
|
276
|
+
if (host) {
|
|
277
|
+
return `Hyper Agent Web (${host})`;
|
|
278
|
+
}
|
|
279
|
+
return "Hyper Agent Web";
|
|
280
|
+
}
|
|
281
|
+
function normalizeScopes(scopes) {
|
|
282
|
+
if (!Array.isArray(scopes)) {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
const unique = new Set();
|
|
286
|
+
for (const scope of scopes) {
|
|
287
|
+
const normalized = scope.trim();
|
|
288
|
+
if (normalized)
|
|
289
|
+
unique.add(normalized);
|
|
290
|
+
}
|
|
291
|
+
return [...unique].sort();
|
|
292
|
+
}
|
|
293
|
+
function getStorage() {
|
|
294
|
+
const storage = globalThis.localStorage;
|
|
295
|
+
return storage ?? null;
|
|
296
|
+
}
|
|
297
|
+
function readDeviceAuthStore() {
|
|
298
|
+
const storage = getStorage();
|
|
299
|
+
if (!storage) {
|
|
300
|
+
return memoryDeviceAuthStore;
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
const raw = storage.getItem(STORAGE_KEY);
|
|
304
|
+
if (!raw)
|
|
305
|
+
return null;
|
|
306
|
+
const parsed = JSON.parse(raw);
|
|
307
|
+
return parsed?.version === 1 && typeof parsed === "object" && !Array.isArray(parsed)
|
|
308
|
+
? parsed
|
|
309
|
+
: null;
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function writeDeviceAuthStore(store) {
|
|
316
|
+
const normalized = {
|
|
317
|
+
version: 1,
|
|
318
|
+
...(store.deviceId ? { deviceId: store.deviceId } : {}),
|
|
319
|
+
...(store.publicKey ? { publicKey: store.publicKey } : {}),
|
|
320
|
+
...(store.privateKey ? { privateKey: store.privateKey } : {}),
|
|
321
|
+
...(typeof store.createdAtMs === "number" ? { createdAtMs: store.createdAtMs } : {}),
|
|
322
|
+
...(store.tokens ? { tokens: store.tokens } : {}),
|
|
323
|
+
...(store.pendingPairings ? { pendingPairings: store.pendingPairings } : {}),
|
|
324
|
+
};
|
|
325
|
+
const storage = getStorage();
|
|
326
|
+
if (!storage) {
|
|
327
|
+
memoryDeviceAuthStore = normalized;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
storage.setItem(STORAGE_KEY, JSON.stringify(normalized));
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
memoryDeviceAuthStore = normalized;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function storageScopeKey(scope, role) {
|
|
338
|
+
return `${scope.trim()}|${role.trim()}`;
|
|
339
|
+
}
|
|
340
|
+
function loadStoredDeviceToken(deviceId, scope, role) {
|
|
341
|
+
const store = readDeviceAuthStore();
|
|
342
|
+
if (!store || store.deviceId !== deviceId || !store.tokens)
|
|
343
|
+
return null;
|
|
344
|
+
const entry = store.tokens[storageScopeKey(scope, role)];
|
|
345
|
+
return entry && typeof entry.token === "string" ? entry : null;
|
|
346
|
+
}
|
|
347
|
+
function storeStoredDeviceToken(params) {
|
|
348
|
+
const role = params.role.trim();
|
|
349
|
+
const key = storageScopeKey(params.scope, role);
|
|
350
|
+
const existing = readDeviceAuthStore();
|
|
351
|
+
const next = {
|
|
352
|
+
version: 1,
|
|
353
|
+
...(existing?.deviceId ? { deviceId: existing.deviceId } : {}),
|
|
354
|
+
...(existing?.publicKey ? { publicKey: existing.publicKey } : {}),
|
|
355
|
+
...(existing?.privateKey ? { privateKey: existing.privateKey } : {}),
|
|
356
|
+
...(typeof existing?.createdAtMs === "number" ? { createdAtMs: existing.createdAtMs } : {}),
|
|
357
|
+
...(existing?.pendingPairings ? { pendingPairings: existing.pendingPairings } : {}),
|
|
358
|
+
tokens: {
|
|
359
|
+
...(existing?.tokens ?? {}),
|
|
360
|
+
[key]: {
|
|
361
|
+
token: params.token,
|
|
362
|
+
role,
|
|
363
|
+
scopes: normalizeScopes(params.scopes),
|
|
364
|
+
updatedAtMs: Date.now(),
|
|
365
|
+
...(params.gatewayUrl ? { gatewayUrl: params.gatewayUrl } : {}),
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
if (!next.deviceId) {
|
|
370
|
+
next.deviceId = params.deviceId;
|
|
371
|
+
}
|
|
372
|
+
writeDeviceAuthStore(next);
|
|
373
|
+
return next.tokens?.[key];
|
|
374
|
+
}
|
|
375
|
+
function clearStoredDeviceToken(deviceId, scope, role) {
|
|
376
|
+
const store = readDeviceAuthStore();
|
|
377
|
+
if (!store || store.deviceId !== deviceId || !store.tokens)
|
|
378
|
+
return;
|
|
379
|
+
const key = storageScopeKey(scope, role);
|
|
380
|
+
if (!store.tokens[key])
|
|
381
|
+
return;
|
|
382
|
+
const nextTokens = { ...store.tokens };
|
|
383
|
+
delete nextTokens[key];
|
|
384
|
+
writeDeviceAuthStore({
|
|
385
|
+
version: 1,
|
|
386
|
+
...(store.deviceId ? { deviceId: store.deviceId } : {}),
|
|
387
|
+
...(store.publicKey ? { publicKey: store.publicKey } : {}),
|
|
388
|
+
...(store.privateKey ? { privateKey: store.privateKey } : {}),
|
|
389
|
+
...(typeof store.createdAtMs === "number" ? { createdAtMs: store.createdAtMs } : {}),
|
|
390
|
+
...(store.pendingPairings ? { pendingPairings: store.pendingPairings } : {}),
|
|
391
|
+
tokens: nextTokens,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
function pairingStoreKey(scope, role) {
|
|
395
|
+
return storageScopeKey(scope, role);
|
|
396
|
+
}
|
|
397
|
+
function loadPendingPairing(scope, role) {
|
|
398
|
+
const store = readDeviceAuthStore();
|
|
399
|
+
const key = pairingStoreKey(scope, role);
|
|
400
|
+
return store?.pendingPairings?.[key] ?? null;
|
|
401
|
+
}
|
|
402
|
+
function storePendingPairing(pairing, scope) {
|
|
403
|
+
const existing = readDeviceAuthStore();
|
|
404
|
+
const key = pairingStoreKey(scope, pairing.role);
|
|
405
|
+
writeDeviceAuthStore({
|
|
406
|
+
version: 1,
|
|
407
|
+
...(existing?.deviceId ? { deviceId: existing.deviceId } : {}),
|
|
408
|
+
...(existing?.publicKey ? { publicKey: existing.publicKey } : {}),
|
|
409
|
+
...(existing?.privateKey ? { privateKey: existing.privateKey } : {}),
|
|
410
|
+
...(typeof existing?.createdAtMs === "number" ? { createdAtMs: existing.createdAtMs } : {}),
|
|
411
|
+
...(existing?.tokens ? { tokens: existing.tokens } : {}),
|
|
412
|
+
pendingPairings: {
|
|
413
|
+
...(existing?.pendingPairings ?? {}),
|
|
414
|
+
[key]: pairing,
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
return pairing;
|
|
418
|
+
}
|
|
419
|
+
function clearPendingPairing(scope, role) {
|
|
420
|
+
const store = readDeviceAuthStore();
|
|
421
|
+
if (!store?.pendingPairings)
|
|
422
|
+
return;
|
|
423
|
+
const key = pairingStoreKey(scope, role);
|
|
424
|
+
if (!store.pendingPairings[key])
|
|
425
|
+
return;
|
|
426
|
+
const nextPendingPairings = { ...store.pendingPairings };
|
|
427
|
+
delete nextPendingPairings[key];
|
|
428
|
+
writeDeviceAuthStore({
|
|
429
|
+
version: 1,
|
|
430
|
+
...(store.deviceId ? { deviceId: store.deviceId } : {}),
|
|
431
|
+
...(store.publicKey ? { publicKey: store.publicKey } : {}),
|
|
432
|
+
...(store.privateKey ? { privateKey: store.privateKey } : {}),
|
|
433
|
+
...(typeof store.createdAtMs === "number" ? { createdAtMs: store.createdAtMs } : {}),
|
|
434
|
+
...(store.tokens ? { tokens: store.tokens } : {}),
|
|
435
|
+
...(Object.keys(nextPendingPairings).length > 0 ? { pendingPairings: nextPendingPairings } : {}),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
function bytesToHex(bytes) {
|
|
439
|
+
return Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
|
|
440
|
+
}
|
|
441
|
+
function bytesToBase64Url(bytes) {
|
|
442
|
+
if (typeof Buffer !== "undefined") {
|
|
443
|
+
return Buffer.from(bytes)
|
|
444
|
+
.toString("base64")
|
|
445
|
+
.replace(/\+/g, "-")
|
|
446
|
+
.replace(/\//g, "_")
|
|
447
|
+
.replace(/=+$/g, "");
|
|
448
|
+
}
|
|
449
|
+
const encoder = globalThis;
|
|
450
|
+
if (typeof encoder.btoa !== "function") {
|
|
451
|
+
throw new Error("base64 encoder unavailable");
|
|
452
|
+
}
|
|
453
|
+
let binary = "";
|
|
454
|
+
for (const byte of bytes) {
|
|
455
|
+
binary += String.fromCharCode(byte);
|
|
456
|
+
}
|
|
457
|
+
return encoder
|
|
458
|
+
.btoa(binary)
|
|
459
|
+
.replace(/\+/g, "-")
|
|
460
|
+
.replace(/\//g, "_")
|
|
461
|
+
.replace(/=+$/g, "");
|
|
462
|
+
}
|
|
463
|
+
function base64UrlToBytes(value) {
|
|
464
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
465
|
+
const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
|
|
466
|
+
if (typeof Buffer !== "undefined") {
|
|
467
|
+
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
468
|
+
}
|
|
469
|
+
const decoder = globalThis;
|
|
470
|
+
if (typeof decoder.atob !== "function") {
|
|
471
|
+
throw new Error("base64 decoder unavailable");
|
|
472
|
+
}
|
|
473
|
+
const binary = decoder.atob(padded);
|
|
474
|
+
const output = new Uint8Array(binary.length);
|
|
475
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
476
|
+
output[index] = binary.charCodeAt(index);
|
|
477
|
+
}
|
|
478
|
+
return output;
|
|
479
|
+
}
|
|
480
|
+
async function sha256Hex(bytes) {
|
|
481
|
+
if (!globalThis.crypto?.subtle) {
|
|
482
|
+
throw new Error("crypto.subtle is required for device auth");
|
|
483
|
+
}
|
|
484
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
485
|
+
return bytesToHex(new Uint8Array(digest));
|
|
486
|
+
}
|
|
487
|
+
async function loadOrCreateDeviceIdentity() {
|
|
488
|
+
const store = readDeviceAuthStore();
|
|
489
|
+
if (store?.version === 1 &&
|
|
490
|
+
typeof store.deviceId === "string" &&
|
|
491
|
+
typeof store.publicKey === "string" &&
|
|
492
|
+
typeof store.privateKey === "string") {
|
|
493
|
+
const derivedId = await sha256Hex(base64UrlToBytes(store.publicKey));
|
|
494
|
+
if (derivedId !== store.deviceId) {
|
|
495
|
+
writeDeviceAuthStore({
|
|
496
|
+
...store,
|
|
497
|
+
version: 1,
|
|
498
|
+
deviceId: derivedId,
|
|
499
|
+
});
|
|
500
|
+
return {
|
|
501
|
+
deviceId: derivedId,
|
|
502
|
+
publicKey: store.publicKey,
|
|
503
|
+
privateKey: store.privateKey,
|
|
504
|
+
createdAtMs: store.createdAtMs,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
deviceId: store.deviceId,
|
|
509
|
+
publicKey: store.publicKey,
|
|
510
|
+
privateKey: store.privateKey,
|
|
511
|
+
createdAtMs: store.createdAtMs,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
const privateKeyBytes = edUtils.randomSecretKey();
|
|
515
|
+
const publicKeyBytes = await getPublicKeyAsync(privateKeyBytes);
|
|
516
|
+
const deviceId = await sha256Hex(publicKeyBytes);
|
|
517
|
+
const identity = {
|
|
518
|
+
deviceId,
|
|
519
|
+
publicKey: bytesToBase64Url(publicKeyBytes),
|
|
520
|
+
privateKey: bytesToBase64Url(privateKeyBytes),
|
|
521
|
+
createdAtMs: Date.now(),
|
|
522
|
+
};
|
|
523
|
+
writeDeviceAuthStore({
|
|
524
|
+
version: 1,
|
|
525
|
+
...identity,
|
|
526
|
+
...(store?.tokens ? { tokens: store.tokens } : {}),
|
|
527
|
+
});
|
|
528
|
+
return identity;
|
|
529
|
+
}
|
|
530
|
+
async function signDevicePayload(privateKey, payload) {
|
|
531
|
+
const signature = await signAsync(new TextEncoder().encode(payload), base64UrlToBytes(privateKey));
|
|
532
|
+
return bytesToBase64Url(signature);
|
|
533
|
+
}
|
|
534
|
+
function buildDeviceAuthPayload(params) {
|
|
535
|
+
return [
|
|
536
|
+
"v2",
|
|
537
|
+
params.deviceId,
|
|
538
|
+
params.clientId,
|
|
539
|
+
params.clientMode,
|
|
540
|
+
params.role,
|
|
541
|
+
params.scopes.join(","),
|
|
542
|
+
String(params.signedAtMs),
|
|
543
|
+
params.token ?? "",
|
|
544
|
+
params.nonce,
|
|
545
|
+
].join("|");
|
|
546
|
+
}
|
|
547
|
+
function toCloseError(error) {
|
|
548
|
+
if (error instanceof GatewayRequestError) {
|
|
549
|
+
return {
|
|
550
|
+
code: error.gatewayCode,
|
|
551
|
+
message: error.message,
|
|
552
|
+
details: error.details,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
if (error instanceof Error) {
|
|
556
|
+
return {
|
|
557
|
+
code: "UNAVAILABLE",
|
|
558
|
+
message: error.message,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
function readConnectErrorCode(error) {
|
|
564
|
+
if (!(error instanceof GatewayRequestError))
|
|
565
|
+
return null;
|
|
566
|
+
const details = error.details;
|
|
567
|
+
if (!details || typeof details !== "object" || Array.isArray(details))
|
|
568
|
+
return null;
|
|
569
|
+
const code = details.code;
|
|
570
|
+
return typeof code === "string" && code.trim() ? code.trim() : null;
|
|
571
|
+
}
|
|
572
|
+
function readConnectPairingRequestId(error) {
|
|
573
|
+
if (!(error instanceof GatewayRequestError))
|
|
574
|
+
return null;
|
|
575
|
+
const details = error.details;
|
|
576
|
+
if (!details || typeof details !== "object" || Array.isArray(details))
|
|
577
|
+
return null;
|
|
578
|
+
const requestId = details.requestId;
|
|
579
|
+
return typeof requestId === "string" && requestId.trim() ? requestId.trim() : null;
|
|
580
|
+
}
|
|
581
|
+
function isSocketOpen(ws) {
|
|
582
|
+
return Boolean(ws && ws.readyState === 1);
|
|
583
|
+
}
|
|
584
|
+
function resolveUserAgent() {
|
|
585
|
+
return getNavigatorLike()?.userAgent;
|
|
586
|
+
}
|
|
587
|
+
function resolveLocale() {
|
|
588
|
+
return getNavigatorLike()?.language;
|
|
589
|
+
}
|
|
590
|
+
export class GatewayClient {
|
|
591
|
+
url;
|
|
592
|
+
token;
|
|
593
|
+
gatewayToken;
|
|
594
|
+
deploymentId;
|
|
595
|
+
apiKey;
|
|
596
|
+
apiBase;
|
|
597
|
+
autoApprovePairing;
|
|
598
|
+
clientId;
|
|
599
|
+
clientMode;
|
|
600
|
+
clientDisplayName;
|
|
601
|
+
clientVersion;
|
|
602
|
+
clientPlatform;
|
|
603
|
+
clientInstanceId;
|
|
604
|
+
caps;
|
|
605
|
+
origin;
|
|
606
|
+
defaultTimeout;
|
|
607
|
+
ws = null;
|
|
608
|
+
pending = new Map();
|
|
609
|
+
eventHandlers = new Set();
|
|
610
|
+
connected = false;
|
|
611
|
+
closed = false;
|
|
612
|
+
reconnectTimer = null;
|
|
613
|
+
connectTimer = null;
|
|
614
|
+
backoffMs = INITIAL_BACKOFF_MS;
|
|
615
|
+
connectNonce = null;
|
|
616
|
+
connectSent = false;
|
|
617
|
+
pendingConnectError = null;
|
|
618
|
+
pairingState = null;
|
|
619
|
+
autoApproveAttemptedRequestIds = new Set();
|
|
620
|
+
lastSeq = null;
|
|
621
|
+
connectPromise = null;
|
|
622
|
+
resolveConnectPromise = null;
|
|
623
|
+
rejectConnectPromise = null;
|
|
624
|
+
_version = null;
|
|
625
|
+
_protocol = null;
|
|
626
|
+
onDisconnect = null;
|
|
627
|
+
constructor(options) {
|
|
628
|
+
this.url = options.url;
|
|
629
|
+
this.token = options.token?.trim() || undefined;
|
|
630
|
+
this.gatewayToken = options.gatewayToken?.trim() || undefined;
|
|
631
|
+
this.deploymentId = options.deploymentId?.trim() || undefined;
|
|
632
|
+
this.apiKey = options.apiKey?.trim() || undefined;
|
|
633
|
+
this.apiBase = options.apiBase?.trim().replace(/\/$/, "") || undefined;
|
|
634
|
+
this.autoApprovePairing = options.autoApprovePairing === true;
|
|
635
|
+
this.clientId = normalizeClientId(options.clientId);
|
|
636
|
+
this.clientMode = normalizeClientMode(options.clientMode);
|
|
637
|
+
this.clientVersion = options.clientVersion?.trim() || DEFAULT_CLIENT_VERSION;
|
|
638
|
+
this.clientPlatform = resolvePlatform(options.platform);
|
|
639
|
+
this.clientDisplayName = resolveClientDisplayName(options.clientDisplayName, this.clientPlatform);
|
|
640
|
+
this.clientInstanceId = options.instanceId?.trim() || makeId();
|
|
641
|
+
this.caps = Array.isArray(options.caps)
|
|
642
|
+
? options.caps.map((cap) => cap.trim()).filter(Boolean)
|
|
643
|
+
: [...DEFAULT_CAPS];
|
|
644
|
+
this.origin = options.origin ?? "https://hypercli.com";
|
|
645
|
+
this.defaultTimeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
646
|
+
this.onHello = options.onHello;
|
|
647
|
+
this.onClose = options.onClose;
|
|
648
|
+
this.onGap = options.onGap;
|
|
649
|
+
this.onPairing = options.onPairing;
|
|
650
|
+
this.pairingState = loadPendingPairing(this.storageScope(), OPERATOR_ROLE);
|
|
651
|
+
}
|
|
652
|
+
onHello;
|
|
653
|
+
onClose;
|
|
654
|
+
onGap;
|
|
655
|
+
onPairing;
|
|
656
|
+
get version() {
|
|
657
|
+
return this._version;
|
|
658
|
+
}
|
|
659
|
+
get protocol() {
|
|
660
|
+
return this._protocol;
|
|
661
|
+
}
|
|
662
|
+
get isConnected() {
|
|
663
|
+
return this.connected;
|
|
664
|
+
}
|
|
665
|
+
get pendingPairing() {
|
|
666
|
+
return this.pairingState;
|
|
667
|
+
}
|
|
668
|
+
storageScope() {
|
|
669
|
+
return this.deploymentId || this.url;
|
|
670
|
+
}
|
|
671
|
+
/** Subscribe to server-sent events */
|
|
672
|
+
onEvent(handler) {
|
|
673
|
+
this.eventHandlers.add(handler);
|
|
674
|
+
return () => this.eventHandlers.delete(handler);
|
|
675
|
+
}
|
|
676
|
+
/** Connect and keep reconnecting until stopped */
|
|
677
|
+
connect() {
|
|
678
|
+
return this.start();
|
|
679
|
+
}
|
|
680
|
+
start() {
|
|
681
|
+
this.closed = false;
|
|
682
|
+
if (this.connected) {
|
|
683
|
+
return Promise.resolve();
|
|
684
|
+
}
|
|
685
|
+
if (!this.connectPromise) {
|
|
686
|
+
this.connectPromise = new Promise((resolve, reject) => {
|
|
687
|
+
this.resolveConnectPromise = resolve;
|
|
688
|
+
this.rejectConnectPromise = reject;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
this.openSocket();
|
|
692
|
+
return this.connectPromise;
|
|
693
|
+
}
|
|
694
|
+
/** Close permanently and stop reconnecting */
|
|
695
|
+
close() {
|
|
696
|
+
this.stop();
|
|
697
|
+
}
|
|
698
|
+
stop() {
|
|
699
|
+
this.closed = true;
|
|
700
|
+
this.connected = false;
|
|
701
|
+
this.connectSent = false;
|
|
702
|
+
this.connectNonce = null;
|
|
703
|
+
this.pendingConnectError = null;
|
|
704
|
+
if (this.reconnectTimer) {
|
|
705
|
+
clearTimeout(this.reconnectTimer);
|
|
706
|
+
this.reconnectTimer = null;
|
|
707
|
+
}
|
|
708
|
+
if (this.connectTimer) {
|
|
709
|
+
clearTimeout(this.connectTimer);
|
|
710
|
+
this.connectTimer = null;
|
|
711
|
+
}
|
|
712
|
+
const ws = this.ws;
|
|
713
|
+
this.ws = null;
|
|
714
|
+
if (ws) {
|
|
715
|
+
ws.close();
|
|
716
|
+
}
|
|
717
|
+
this.flushPending(new Error("gateway client stopped"));
|
|
718
|
+
if (this.rejectConnectPromise) {
|
|
719
|
+
this.rejectConnectPromise(new Error("gateway client stopped"));
|
|
720
|
+
}
|
|
721
|
+
this.connectPromise = null;
|
|
722
|
+
this.resolveConnectPromise = null;
|
|
723
|
+
this.rejectConnectPromise = null;
|
|
724
|
+
}
|
|
725
|
+
updatePairingState(pairing) {
|
|
726
|
+
this.pairingState = pairing;
|
|
727
|
+
if (pairing) {
|
|
728
|
+
storePendingPairing(pairing, this.storageScope());
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
clearPendingPairing(this.storageScope(), OPERATOR_ROLE);
|
|
732
|
+
}
|
|
733
|
+
this.onPairing?.(pairing);
|
|
734
|
+
}
|
|
735
|
+
canAutoApprovePairing() {
|
|
736
|
+
return Boolean(this.autoApprovePairing &&
|
|
737
|
+
this.deploymentId &&
|
|
738
|
+
this.apiKey &&
|
|
739
|
+
this.apiBase &&
|
|
740
|
+
typeof fetch === "function");
|
|
741
|
+
}
|
|
742
|
+
async approvePairingRequest(requestId) {
|
|
743
|
+
if (!this.canAutoApprovePairing()) {
|
|
744
|
+
throw new Error("autoApprovePairing requires deploymentId, apiKey, apiBase, and fetch()");
|
|
745
|
+
}
|
|
746
|
+
const response = await fetch(`${this.apiBase}/deployments/${encodeURIComponent(this.deploymentId)}/exec`, {
|
|
747
|
+
method: "POST",
|
|
748
|
+
headers: {
|
|
749
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
750
|
+
"Content-Type": "application/json",
|
|
751
|
+
},
|
|
752
|
+
body: JSON.stringify({
|
|
753
|
+
command: `openclaw devices approve ${requestId}`,
|
|
754
|
+
timeout: 30,
|
|
755
|
+
}),
|
|
756
|
+
});
|
|
757
|
+
if (!response.ok) {
|
|
758
|
+
const errorText = await response.text();
|
|
759
|
+
throw new Error(`Pairing approval failed: ${response.status} ${errorText}`);
|
|
760
|
+
}
|
|
761
|
+
const payload = await response.json();
|
|
762
|
+
if ((payload.exitCode ?? payload.exit_code ?? 1) !== 0) {
|
|
763
|
+
throw new Error(payload.stderr?.trim() || payload.stdout?.trim() || "pairing approval command failed");
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
openSocket() {
|
|
767
|
+
if (this.closed || this.ws)
|
|
768
|
+
return;
|
|
769
|
+
if (typeof WebSocket === "undefined") {
|
|
770
|
+
throw new Error("WebSocket is not available in this environment");
|
|
771
|
+
}
|
|
772
|
+
if (this.reconnectTimer) {
|
|
773
|
+
clearTimeout(this.reconnectTimer);
|
|
774
|
+
this.reconnectTimer = null;
|
|
775
|
+
}
|
|
776
|
+
const wsUrl = this.token
|
|
777
|
+
? `${this.url}${this.url.includes("?") ? "&" : "?"}token=${encodeURIComponent(this.token)}`
|
|
778
|
+
: this.url;
|
|
779
|
+
const ws = new WebSocket(wsUrl);
|
|
780
|
+
this.ws = ws;
|
|
781
|
+
ws.onopen = () => {
|
|
782
|
+
this.queueConnect();
|
|
783
|
+
};
|
|
784
|
+
ws.onmessage = (event) => {
|
|
785
|
+
this.handleMessage(String(event.data ?? ""));
|
|
786
|
+
};
|
|
787
|
+
ws.onerror = () => {
|
|
788
|
+
// Close handling covers retries and surfaced errors.
|
|
789
|
+
};
|
|
790
|
+
ws.onclose = (event) => {
|
|
791
|
+
this.handleClose(ws, event.code ?? 1006, String(event.reason ?? ""));
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
queueConnect() {
|
|
795
|
+
this.connectNonce = null;
|
|
796
|
+
this.connectSent = false;
|
|
797
|
+
if (this.connectTimer) {
|
|
798
|
+
clearTimeout(this.connectTimer);
|
|
799
|
+
}
|
|
800
|
+
this.connectTimer = setTimeout(() => {
|
|
801
|
+
if (!this.ws || !isSocketOpen(this.ws) || this.closed || this.connectSent) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (!this.connectNonce) {
|
|
805
|
+
this.pendingConnectError = {
|
|
806
|
+
code: "CONNECT_CHALLENGE_TIMEOUT",
|
|
807
|
+
message: "gateway connect challenge timeout",
|
|
808
|
+
};
|
|
809
|
+
this.ws.close(RECONNECT_CLOSE_CODE, "connect challenge timeout");
|
|
810
|
+
}
|
|
811
|
+
}, CONNECT_TIMER_MS);
|
|
812
|
+
}
|
|
813
|
+
scheduleReconnect() {
|
|
814
|
+
if (this.closed || this.reconnectTimer) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const delay = this.backoffMs;
|
|
818
|
+
this.backoffMs = Math.min(this.backoffMs * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
|
|
819
|
+
this.reconnectTimer = setTimeout(() => {
|
|
820
|
+
this.reconnectTimer = null;
|
|
821
|
+
this.openSocket();
|
|
822
|
+
}, delay);
|
|
823
|
+
}
|
|
824
|
+
flushPending(error) {
|
|
825
|
+
for (const [, pending] of this.pending) {
|
|
826
|
+
clearTimeout(pending.timer);
|
|
827
|
+
pending.reject(error);
|
|
828
|
+
}
|
|
829
|
+
this.pending.clear();
|
|
830
|
+
}
|
|
831
|
+
handleClose(ws, code, reason) {
|
|
832
|
+
if (this.ws !== ws) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
this.ws = null;
|
|
836
|
+
this.connected = false;
|
|
837
|
+
this.connectSent = false;
|
|
838
|
+
this.connectNonce = null;
|
|
839
|
+
if (this.connectTimer) {
|
|
840
|
+
clearTimeout(this.connectTimer);
|
|
841
|
+
this.connectTimer = null;
|
|
842
|
+
}
|
|
843
|
+
const error = this.pendingConnectError;
|
|
844
|
+
this.pendingConnectError = null;
|
|
845
|
+
this.flushPending(new Error(`gateway closed (${code}): ${reason || "no reason"}`));
|
|
846
|
+
this.onClose?.({ code, reason, error });
|
|
847
|
+
if (!this.closed) {
|
|
848
|
+
this.onDisconnect?.();
|
|
849
|
+
this.scheduleReconnect();
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
async sendConnect() {
|
|
853
|
+
if (this.connectSent || !this.ws || !isSocketOpen(this.ws)) {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const nonce = this.connectNonce?.trim() ?? "";
|
|
857
|
+
if (!nonce) {
|
|
858
|
+
this.pendingConnectError = {
|
|
859
|
+
code: "DEVICE_AUTH_NONCE_REQUIRED",
|
|
860
|
+
message: "gateway connect challenge missing nonce",
|
|
861
|
+
};
|
|
862
|
+
this.ws.close(RECONNECT_CLOSE_CODE, "connect challenge missing nonce");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
this.connectSent = true;
|
|
866
|
+
if (this.connectTimer) {
|
|
867
|
+
clearTimeout(this.connectTimer);
|
|
868
|
+
this.connectTimer = null;
|
|
869
|
+
}
|
|
870
|
+
let identity = null;
|
|
871
|
+
try {
|
|
872
|
+
identity = await loadOrCreateDeviceIdentity();
|
|
873
|
+
const storedDeviceToken = loadStoredDeviceToken(identity.deviceId, this.storageScope(), OPERATOR_ROLE)?.token;
|
|
874
|
+
const authToken = storedDeviceToken ?? this.gatewayToken;
|
|
875
|
+
const signedAtMs = Date.now();
|
|
876
|
+
const payload = buildDeviceAuthPayload({
|
|
877
|
+
deviceId: identity.deviceId,
|
|
878
|
+
clientId: this.clientId,
|
|
879
|
+
clientMode: this.clientMode,
|
|
880
|
+
role: OPERATOR_ROLE,
|
|
881
|
+
scopes: OPERATOR_SCOPES,
|
|
882
|
+
signedAtMs,
|
|
883
|
+
token: authToken ?? null,
|
|
884
|
+
nonce,
|
|
885
|
+
});
|
|
886
|
+
const signature = await signDevicePayload(identity.privateKey, payload);
|
|
887
|
+
const params = {
|
|
888
|
+
minProtocol: PROTOCOL_VERSION,
|
|
889
|
+
maxProtocol: PROTOCOL_VERSION,
|
|
890
|
+
client: {
|
|
891
|
+
id: this.clientId,
|
|
892
|
+
...(this.clientDisplayName ? { displayName: this.clientDisplayName } : {}),
|
|
893
|
+
version: this.clientVersion,
|
|
894
|
+
platform: this.clientPlatform,
|
|
895
|
+
mode: this.clientMode,
|
|
896
|
+
...(this.clientInstanceId ? { instanceId: this.clientInstanceId } : {}),
|
|
897
|
+
},
|
|
898
|
+
role: OPERATOR_ROLE,
|
|
899
|
+
scopes: [...OPERATOR_SCOPES],
|
|
900
|
+
device: {
|
|
901
|
+
id: identity.deviceId,
|
|
902
|
+
publicKey: identity.publicKey,
|
|
903
|
+
signature,
|
|
904
|
+
signedAt: signedAtMs,
|
|
905
|
+
nonce,
|
|
906
|
+
},
|
|
907
|
+
caps: this.caps,
|
|
908
|
+
...(authToken
|
|
909
|
+
? {
|
|
910
|
+
auth: {
|
|
911
|
+
...(authToken ? { token: authToken } : {}),
|
|
912
|
+
},
|
|
913
|
+
}
|
|
914
|
+
: {}),
|
|
915
|
+
...(resolveUserAgent() ? { userAgent: resolveUserAgent() } : {}),
|
|
916
|
+
...(resolveLocale() ? { locale: resolveLocale() } : {}),
|
|
917
|
+
};
|
|
918
|
+
const hello = await this.sendRawRequest("connect", params, this.defaultTimeout, true);
|
|
919
|
+
if (hello?.auth?.deviceToken) {
|
|
920
|
+
storeStoredDeviceToken({
|
|
921
|
+
deviceId: identity.deviceId,
|
|
922
|
+
scope: this.storageScope(),
|
|
923
|
+
gatewayUrl: this.url,
|
|
924
|
+
role: hello.auth.role ?? OPERATOR_ROLE,
|
|
925
|
+
token: hello.auth.deviceToken,
|
|
926
|
+
scopes: hello.auth.scopes ?? [],
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
this._version = hello?.server?.version ?? hello?.version ?? null;
|
|
930
|
+
this._protocol = hello?.protocol ?? null;
|
|
931
|
+
this.connected = true;
|
|
932
|
+
this.pendingConnectError = null;
|
|
933
|
+
this.backoffMs = INITIAL_BACKOFF_MS;
|
|
934
|
+
this.updatePairingState(null);
|
|
935
|
+
if (this.resolveConnectPromise) {
|
|
936
|
+
this.resolveConnectPromise();
|
|
937
|
+
}
|
|
938
|
+
this.connectPromise = null;
|
|
939
|
+
this.resolveConnectPromise = null;
|
|
940
|
+
this.rejectConnectPromise = null;
|
|
941
|
+
this.onHello?.(hello);
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
this.pendingConnectError = toCloseError(error);
|
|
945
|
+
const detailCode = readConnectErrorCode(error);
|
|
946
|
+
const requestId = readConnectPairingRequestId(error);
|
|
947
|
+
if (identity &&
|
|
948
|
+
(detailCode === CONNECT_ERROR_PAIRING_REQUIRED ||
|
|
949
|
+
detailCode === CONNECT_ERROR_DEVICE_TOKEN_MISMATCH)) {
|
|
950
|
+
clearStoredDeviceToken(identity.deviceId, this.storageScope(), OPERATOR_ROLE);
|
|
951
|
+
}
|
|
952
|
+
if (detailCode === CONNECT_ERROR_PAIRING_REQUIRED && requestId) {
|
|
953
|
+
this.updatePairingState({
|
|
954
|
+
requestId,
|
|
955
|
+
role: OPERATOR_ROLE,
|
|
956
|
+
gatewayUrl: this.url,
|
|
957
|
+
...(identity ? { deviceId: identity.deviceId } : {}),
|
|
958
|
+
status: "pending",
|
|
959
|
+
updatedAtMs: Date.now(),
|
|
960
|
+
});
|
|
961
|
+
if (this.canAutoApprovePairing() && !this.autoApproveAttemptedRequestIds.has(requestId)) {
|
|
962
|
+
this.autoApproveAttemptedRequestIds.add(requestId);
|
|
963
|
+
try {
|
|
964
|
+
this.updatePairingState({
|
|
965
|
+
requestId,
|
|
966
|
+
role: OPERATOR_ROLE,
|
|
967
|
+
gatewayUrl: this.url,
|
|
968
|
+
...(identity ? { deviceId: identity.deviceId } : {}),
|
|
969
|
+
status: "approving",
|
|
970
|
+
updatedAtMs: Date.now(),
|
|
971
|
+
});
|
|
972
|
+
await this.approvePairingRequest(requestId);
|
|
973
|
+
this.updatePairingState({
|
|
974
|
+
requestId,
|
|
975
|
+
role: OPERATOR_ROLE,
|
|
976
|
+
gatewayUrl: this.url,
|
|
977
|
+
...(identity ? { deviceId: identity.deviceId } : {}),
|
|
978
|
+
status: "approved",
|
|
979
|
+
updatedAtMs: Date.now(),
|
|
980
|
+
});
|
|
981
|
+
this.pendingConnectError = {
|
|
982
|
+
code: "PAIRING_APPROVED",
|
|
983
|
+
message: "Pairing approved, reconnecting",
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
catch (approvalError) {
|
|
987
|
+
this.pendingConnectError = toCloseError(approvalError);
|
|
988
|
+
this.updatePairingState({
|
|
989
|
+
requestId,
|
|
990
|
+
role: OPERATOR_ROLE,
|
|
991
|
+
gatewayUrl: this.url,
|
|
992
|
+
...(identity ? { deviceId: identity.deviceId } : {}),
|
|
993
|
+
status: "failed",
|
|
994
|
+
updatedAtMs: Date.now(),
|
|
995
|
+
error: approvalError instanceof Error ? approvalError.message : String(approvalError),
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (this.ws) {
|
|
1001
|
+
this.ws.close(RECONNECT_CLOSE_CODE, "connect failed");
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
handleMessage(raw) {
|
|
1006
|
+
let message;
|
|
1007
|
+
try {
|
|
1008
|
+
message = JSON.parse(raw);
|
|
1009
|
+
}
|
|
1010
|
+
catch {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (message.type === "event") {
|
|
1014
|
+
const gatewayEvent = message;
|
|
1015
|
+
if (gatewayEvent.event === "connect.challenge") {
|
|
1016
|
+
const nonce = gatewayEvent.payload && typeof gatewayEvent.payload.nonce === "string"
|
|
1017
|
+
? gatewayEvent.payload.nonce.trim()
|
|
1018
|
+
: "";
|
|
1019
|
+
if (!nonce) {
|
|
1020
|
+
this.pendingConnectError = {
|
|
1021
|
+
code: "DEVICE_AUTH_NONCE_REQUIRED",
|
|
1022
|
+
message: "gateway connect challenge missing nonce",
|
|
1023
|
+
};
|
|
1024
|
+
this.ws?.close(RECONNECT_CLOSE_CODE, "connect challenge missing nonce");
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
this.connectNonce = nonce;
|
|
1028
|
+
void this.sendConnect();
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
if (typeof gatewayEvent.seq === "number") {
|
|
1032
|
+
if (this.lastSeq !== null && gatewayEvent.seq > this.lastSeq + 1) {
|
|
1033
|
+
this.onGap?.({ expected: this.lastSeq + 1, received: gatewayEvent.seq });
|
|
1034
|
+
}
|
|
1035
|
+
this.lastSeq = gatewayEvent.seq;
|
|
1036
|
+
}
|
|
1037
|
+
for (const handler of this.eventHandlers) {
|
|
1038
|
+
try {
|
|
1039
|
+
handler(gatewayEvent);
|
|
1040
|
+
}
|
|
1041
|
+
catch {
|
|
1042
|
+
// Event handlers are isolated from the socket lifecycle.
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
if (message.type !== "res") {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
const pending = this.pending.get(message.id);
|
|
1051
|
+
if (!pending) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
this.pending.delete(message.id);
|
|
1055
|
+
clearTimeout(pending.timer);
|
|
1056
|
+
if (message.ok) {
|
|
1057
|
+
pending.resolve(message.payload);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
pending.reject(new GatewayRequestError({
|
|
1061
|
+
code: message.error?.code ?? "UNAVAILABLE",
|
|
1062
|
+
message: message.error?.message ?? "gateway request failed",
|
|
1063
|
+
details: message.error?.details,
|
|
1064
|
+
}));
|
|
1065
|
+
}
|
|
1066
|
+
sendRawRequest(method, params = {}, timeout = this.defaultTimeout, allowBeforeHello = false) {
|
|
1067
|
+
if (!this.ws || !isSocketOpen(this.ws)) {
|
|
1068
|
+
return Promise.reject(new Error("gateway not connected"));
|
|
1069
|
+
}
|
|
1070
|
+
if (!allowBeforeHello && !this.connected) {
|
|
1071
|
+
return Promise.reject(new Error("gateway not connected"));
|
|
1072
|
+
}
|
|
1073
|
+
const id = makeId();
|
|
1074
|
+
const request = { type: "req", id, method, params };
|
|
1075
|
+
return new Promise((resolve, reject) => {
|
|
1076
|
+
const timer = setTimeout(() => {
|
|
1077
|
+
this.pending.delete(id);
|
|
1078
|
+
reject(new Error(`RPC timeout: ${method}`));
|
|
1079
|
+
}, timeout);
|
|
1080
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
1081
|
+
this.ws?.send(JSON.stringify(request));
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
// ---------------------------------------------------------------------------
|
|
1085
|
+
// RPC
|
|
1086
|
+
// ---------------------------------------------------------------------------
|
|
1087
|
+
rpc(method, params = {}, timeout) {
|
|
1088
|
+
return this.sendRawRequest(method, params, timeout ?? this.defaultTimeout);
|
|
1089
|
+
}
|
|
1090
|
+
request(method, params = {}, timeout) {
|
|
1091
|
+
return this.rpc(method, params, timeout);
|
|
1092
|
+
}
|
|
1093
|
+
// ---------------------------------------------------------------------------
|
|
1094
|
+
// Config
|
|
1095
|
+
// ---------------------------------------------------------------------------
|
|
1096
|
+
async configGet() {
|
|
1097
|
+
const res = await this.rpc("config.get");
|
|
1098
|
+
if (res?.parsed)
|
|
1099
|
+
return res.parsed;
|
|
1100
|
+
if (res?.raw) {
|
|
1101
|
+
try {
|
|
1102
|
+
return JSON.parse(res.raw);
|
|
1103
|
+
}
|
|
1104
|
+
catch {
|
|
1105
|
+
// Fall through to the raw payload.
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return res?.config ?? res ?? {};
|
|
1109
|
+
}
|
|
1110
|
+
async configSchema() {
|
|
1111
|
+
const res = await this.rpc("config.schema");
|
|
1112
|
+
return normalizeOpenClawConfigSchema(res) ?? { schema: {}, uiHints: {} };
|
|
1113
|
+
}
|
|
1114
|
+
async configPatch(patch) {
|
|
1115
|
+
await this.rpc("config.patch", { patch });
|
|
1116
|
+
}
|
|
1117
|
+
async configSet(config) {
|
|
1118
|
+
await this.rpc("config.set", { config });
|
|
1119
|
+
}
|
|
1120
|
+
async modelsList() {
|
|
1121
|
+
const res = await this.rpc("models.list");
|
|
1122
|
+
return res?.models ?? res ?? [];
|
|
1123
|
+
}
|
|
1124
|
+
// ---------------------------------------------------------------------------
|
|
1125
|
+
// Sessions
|
|
1126
|
+
// ---------------------------------------------------------------------------
|
|
1127
|
+
async sessionsList() {
|
|
1128
|
+
const res = await this.rpc("sessions.list");
|
|
1129
|
+
return res?.sessions ?? res ?? [];
|
|
1130
|
+
}
|
|
1131
|
+
async sessionsPreview(sessionKey, limit = 20) {
|
|
1132
|
+
const res = await this.rpc("sessions.preview", { sessionKey, limit });
|
|
1133
|
+
return res?.messages ?? res ?? [];
|
|
1134
|
+
}
|
|
1135
|
+
async chatHistory(sessionKey, limit = 50) {
|
|
1136
|
+
const params = { limit };
|
|
1137
|
+
if (sessionKey)
|
|
1138
|
+
params.sessionKey = sessionKey;
|
|
1139
|
+
const res = await this.rpc("chat.history", params);
|
|
1140
|
+
return res?.messages ?? res ?? [];
|
|
1141
|
+
}
|
|
1142
|
+
async chatAbort(sessionKey) {
|
|
1143
|
+
const params = {};
|
|
1144
|
+
if (sessionKey)
|
|
1145
|
+
params.sessionKey = sessionKey;
|
|
1146
|
+
await this.rpc("chat.abort", params);
|
|
1147
|
+
}
|
|
1148
|
+
async sendChat(message, sessionKey = "main", agentId, attachments) {
|
|
1149
|
+
const params = {
|
|
1150
|
+
message,
|
|
1151
|
+
sessionKey,
|
|
1152
|
+
idempotencyKey: makeId(),
|
|
1153
|
+
};
|
|
1154
|
+
if (agentId)
|
|
1155
|
+
params.agentId = agentId;
|
|
1156
|
+
if (attachments && attachments.length > 0)
|
|
1157
|
+
params.attachments = attachments;
|
|
1158
|
+
return this.rpc("chat.send", params, CHAT_TIMEOUT);
|
|
1159
|
+
}
|
|
1160
|
+
async sessionsReset(sessionKey) {
|
|
1161
|
+
await this.rpc("sessions.reset", { sessionKey });
|
|
1162
|
+
}
|
|
1163
|
+
// ---------------------------------------------------------------------------
|
|
1164
|
+
// Chat (streaming via events)
|
|
1165
|
+
// ---------------------------------------------------------------------------
|
|
1166
|
+
async *chatSend(message, sessionKey) {
|
|
1167
|
+
if (!this.connected || !this.ws) {
|
|
1168
|
+
throw new Error("Not connected");
|
|
1169
|
+
}
|
|
1170
|
+
const id = makeId();
|
|
1171
|
+
const req = {
|
|
1172
|
+
type: "req",
|
|
1173
|
+
id,
|
|
1174
|
+
method: "chat.send",
|
|
1175
|
+
params: {
|
|
1176
|
+
message,
|
|
1177
|
+
sessionKey,
|
|
1178
|
+
idempotencyKey: makeId(),
|
|
1179
|
+
},
|
|
1180
|
+
};
|
|
1181
|
+
const events = [];
|
|
1182
|
+
let resolveWait = null;
|
|
1183
|
+
let done = false;
|
|
1184
|
+
let error = null;
|
|
1185
|
+
// Temporary event handler for chat events
|
|
1186
|
+
const handler = (evt) => {
|
|
1187
|
+
if (evt.event?.startsWith("chat.")) {
|
|
1188
|
+
const payload = evt.payload ?? {};
|
|
1189
|
+
if (evt.event === "chat.content") {
|
|
1190
|
+
events.push({ type: "content", text: payload.text ?? "" });
|
|
1191
|
+
}
|
|
1192
|
+
else if (evt.event === "chat.thinking") {
|
|
1193
|
+
events.push({ type: "thinking", text: payload.text ?? "" });
|
|
1194
|
+
}
|
|
1195
|
+
else if (evt.event === "chat.tool_call") {
|
|
1196
|
+
events.push({ type: "tool_call", data: payload });
|
|
1197
|
+
}
|
|
1198
|
+
else if (evt.event === "chat.tool_result") {
|
|
1199
|
+
events.push({ type: "tool_result", data: payload });
|
|
1200
|
+
}
|
|
1201
|
+
else if (evt.event === "chat.done") {
|
|
1202
|
+
events.push({ type: "done" });
|
|
1203
|
+
done = true;
|
|
1204
|
+
}
|
|
1205
|
+
else if (evt.event === "chat.error") {
|
|
1206
|
+
events.push({ type: "error", text: payload.message ?? "Unknown error" });
|
|
1207
|
+
done = true;
|
|
1208
|
+
}
|
|
1209
|
+
resolveWait?.();
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
this.eventHandlers.add(handler);
|
|
1213
|
+
// Also listen for the RPC response
|
|
1214
|
+
const timer = setTimeout(() => {
|
|
1215
|
+
this.pending.delete(id);
|
|
1216
|
+
error = new Error("Chat timeout");
|
|
1217
|
+
done = true;
|
|
1218
|
+
resolveWait?.();
|
|
1219
|
+
}, CHAT_TIMEOUT);
|
|
1220
|
+
this.pending.set(id, {
|
|
1221
|
+
resolve: () => {
|
|
1222
|
+
clearTimeout(timer);
|
|
1223
|
+
done = true;
|
|
1224
|
+
resolveWait?.();
|
|
1225
|
+
},
|
|
1226
|
+
reject: (rejection) => {
|
|
1227
|
+
clearTimeout(timer);
|
|
1228
|
+
error = rejection instanceof Error ? rejection : new Error(String(rejection));
|
|
1229
|
+
done = true;
|
|
1230
|
+
resolveWait?.();
|
|
1231
|
+
},
|
|
1232
|
+
timer,
|
|
1233
|
+
});
|
|
1234
|
+
this.ws.send(JSON.stringify(req));
|
|
1235
|
+
try {
|
|
1236
|
+
while (!done || events.length > 0) {
|
|
1237
|
+
if (events.length > 0) {
|
|
1238
|
+
yield events.shift();
|
|
1239
|
+
}
|
|
1240
|
+
else if (!done) {
|
|
1241
|
+
await new Promise((resolve) => {
|
|
1242
|
+
resolveWait = resolve;
|
|
1243
|
+
});
|
|
1244
|
+
resolveWait = null;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
if (error)
|
|
1248
|
+
throw error;
|
|
1249
|
+
}
|
|
1250
|
+
finally {
|
|
1251
|
+
clearTimeout(timer);
|
|
1252
|
+
this.eventHandlers.delete(handler);
|
|
1253
|
+
this.pending.delete(id);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
// ---------------------------------------------------------------------------
|
|
1257
|
+
// Files (agent workspace files)
|
|
1258
|
+
// ---------------------------------------------------------------------------
|
|
1259
|
+
async filesList(agentId = "main") {
|
|
1260
|
+
const res = await this.rpc("agents.files.list", { agentId });
|
|
1261
|
+
return res?.files ?? [];
|
|
1262
|
+
}
|
|
1263
|
+
async fileGet(agentId, name) {
|
|
1264
|
+
const res = await this.rpc("agents.files.get", { agentId, name });
|
|
1265
|
+
return res?.content ?? "";
|
|
1266
|
+
}
|
|
1267
|
+
async fileSet(agentId, name, content) {
|
|
1268
|
+
await this.rpc("agents.files.set", { agentId, name, content });
|
|
1269
|
+
}
|
|
1270
|
+
// ---------------------------------------------------------------------------
|
|
1271
|
+
// Agents
|
|
1272
|
+
// ---------------------------------------------------------------------------
|
|
1273
|
+
async agentsList() {
|
|
1274
|
+
const res = await this.rpc("agents.list");
|
|
1275
|
+
const agents = res?.agents ?? res ?? [];
|
|
1276
|
+
return Array.isArray(agents)
|
|
1277
|
+
? agents.map((agent) => ({ ...agent, id: agent?.agentId ?? agent?.id }))
|
|
1278
|
+
: [];
|
|
1279
|
+
}
|
|
1280
|
+
// ---------------------------------------------------------------------------
|
|
1281
|
+
// Cron
|
|
1282
|
+
// ---------------------------------------------------------------------------
|
|
1283
|
+
async cronList() {
|
|
1284
|
+
const res = await this.rpc("cron.list");
|
|
1285
|
+
return res?.jobs ?? res ?? [];
|
|
1286
|
+
}
|
|
1287
|
+
async cronAdd(job) {
|
|
1288
|
+
return this.rpc("cron.add", { job });
|
|
1289
|
+
}
|
|
1290
|
+
async cronRemove(jobId) {
|
|
1291
|
+
await this.rpc("cron.remove", { jobId });
|
|
1292
|
+
}
|
|
1293
|
+
async execApprove(execId) {
|
|
1294
|
+
await this.rpc("exec.approve", { execId });
|
|
1295
|
+
}
|
|
1296
|
+
async execDeny(execId) {
|
|
1297
|
+
await this.rpc("exec.deny", { execId });
|
|
1298
|
+
}
|
|
1299
|
+
// ---------------------------------------------------------------------------
|
|
1300
|
+
// Status
|
|
1301
|
+
// ---------------------------------------------------------------------------
|
|
1302
|
+
async status() {
|
|
1303
|
+
return this.rpc("status");
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
//# sourceMappingURL=gateway.js.map
|