@rethinkingstudio/clawpilot 1.1.15-beta.0 → 1.1.15-beta.2
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/commands/pair.js +1 -1
- package/dist/commands/pair.js.map +1 -1
- package/dist/config/config.d.ts +2 -2
- package/dist/config/config.js +48 -12
- package/dist/config/config.js.map +1 -1
- package/dist/generated/build-config.d.ts +1 -0
- package/dist/generated/build-config.js +2 -0
- package/dist/generated/build-config.js.map +1 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/media/assistant-attachments.d.ts +35 -0
- package/dist/media/assistant-attachments.js +144 -0
- package/dist/media/assistant-attachments.js.map +1 -0
- package/dist/media/oss-uploader.d.ts +22 -0
- package/dist/media/oss-uploader.js +180 -0
- package/dist/media/oss-uploader.js.map +1 -0
- package/dist/platform/service-manager.js +10 -7
- package/dist/platform/service-manager.js.map +1 -1
- package/dist/relay/relay-manager.js +18 -4
- package/dist/relay/relay-manager.js.map +1 -1
- package/package.json +6 -2
- package/scripts/verify-build-config.mjs +35 -0
- package/scripts/write-build-config.mjs +17 -0
- package/src/commands/pair.ts +1 -2
- package/src/config/config.ts +70 -13
- package/src/generated/build-config.ts +1 -0
- package/src/index.ts +2 -1
- package/src/media/assistant-attachments.ts +205 -0
- package/src/media/oss-uploader.ts +280 -0
- package/src/platform/service-manager.ts +10 -6
- package/src/relay/relay-manager.ts +27 -5
- package/dist/relay/session-proxy.d.ts +0 -18
- package/dist/relay/session-proxy.js +0 -75
- package/dist/relay/session-proxy.js.map +0 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { createHmac } from "crypto";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Types
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
export interface StsCredentials {
|
|
9
|
+
bucket: string;
|
|
10
|
+
region: string;
|
|
11
|
+
endpoint: string;
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
dirPrefix: string;
|
|
14
|
+
expiresAt: number;
|
|
15
|
+
credentials: {
|
|
16
|
+
accessKeyId: string;
|
|
17
|
+
accessKeySecret: string;
|
|
18
|
+
securityToken: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UploadResult {
|
|
23
|
+
url: string; // public CDN URL
|
|
24
|
+
thumbnailUrl?: string; // OSS snapshot URL (video only)
|
|
25
|
+
mimeType: string;
|
|
26
|
+
size: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// OSS HMAC-SHA1 request signing (with STS security token)
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
function ossSign(
|
|
34
|
+
method: string,
|
|
35
|
+
contentMd5: string,
|
|
36
|
+
contentType: string,
|
|
37
|
+
date: string,
|
|
38
|
+
canonicalizedHeaders: string,
|
|
39
|
+
canonicalizedResource: string,
|
|
40
|
+
secretKey: string
|
|
41
|
+
): string {
|
|
42
|
+
const stringToSign = [
|
|
43
|
+
method,
|
|
44
|
+
contentMd5,
|
|
45
|
+
contentType,
|
|
46
|
+
date,
|
|
47
|
+
canonicalizedHeaders + canonicalizedResource,
|
|
48
|
+
].join("\n");
|
|
49
|
+
return createHmac("sha1", secretKey).update(stringToSign).digest("base64");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Simple PutObject upload (images / small files)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
export async function putObject(
|
|
57
|
+
sts: StsCredentials,
|
|
58
|
+
data: Buffer,
|
|
59
|
+
ext: string,
|
|
60
|
+
mimeType: string
|
|
61
|
+
): Promise<UploadResult> {
|
|
62
|
+
const { bucket, endpoint, baseUrl, dirPrefix, credentials } = sts;
|
|
63
|
+
const objectKey = `${dirPrefix}/${randomUUID()}${ext}`;
|
|
64
|
+
const date = new Date().toUTCString();
|
|
65
|
+
const canonicalizedResource = `/${bucket}/${objectKey}`;
|
|
66
|
+
const canonicalizedHeaders = `x-oss-security-token:${credentials.securityToken}\n`;
|
|
67
|
+
|
|
68
|
+
const sig = ossSign(
|
|
69
|
+
"PUT",
|
|
70
|
+
"",
|
|
71
|
+
mimeType,
|
|
72
|
+
date,
|
|
73
|
+
canonicalizedHeaders,
|
|
74
|
+
canonicalizedResource,
|
|
75
|
+
credentials.accessKeySecret
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const url = `${endpoint.replace(/\/$/, "")}/${objectKey}`;
|
|
79
|
+
const res = await fetch(url, {
|
|
80
|
+
method: "PUT",
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": mimeType,
|
|
83
|
+
"Date": date,
|
|
84
|
+
"x-oss-security-token": credentials.securityToken,
|
|
85
|
+
"Authorization": `OSS ${credentials.accessKeyId}:${sig}`,
|
|
86
|
+
},
|
|
87
|
+
body: data as unknown as BodyInit,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!res.ok) {
|
|
91
|
+
const text = await res.text().catch(() => "");
|
|
92
|
+
throw new Error(`OSS PutObject failed: ${res.status} ${text}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const publicUrl = `${baseUrl.replace(/\/$/, "")}/${objectKey}`;
|
|
96
|
+
return { url: publicUrl, mimeType, size: data.length };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Multipart upload (videos > 10 MB)
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
const PART_SIZE = 5 * 1024 * 1024; // 5 MB per part
|
|
104
|
+
|
|
105
|
+
export async function multipartUpload(
|
|
106
|
+
sts: StsCredentials,
|
|
107
|
+
data: Buffer,
|
|
108
|
+
ext: string,
|
|
109
|
+
mimeType: string
|
|
110
|
+
): Promise<UploadResult> {
|
|
111
|
+
const { bucket, endpoint, baseUrl, dirPrefix, credentials } = sts;
|
|
112
|
+
const objectKey = `${dirPrefix}/${randomUUID()}${ext}`;
|
|
113
|
+
const baseOssUrl = `${endpoint.replace(/\/$/, "")}/${objectKey}`;
|
|
114
|
+
|
|
115
|
+
function makeHeaders(
|
|
116
|
+
method: string,
|
|
117
|
+
contentType: string,
|
|
118
|
+
extraHeaders: Record<string, string>,
|
|
119
|
+
resource: string
|
|
120
|
+
): Headers {
|
|
121
|
+
const date = new Date().toUTCString();
|
|
122
|
+
const canonicalizedHeaders = [
|
|
123
|
+
"x-oss-security-token:" + credentials.securityToken,
|
|
124
|
+
...Object.entries(extraHeaders)
|
|
125
|
+
.filter(([k]) => k.startsWith("x-oss-"))
|
|
126
|
+
.sort()
|
|
127
|
+
.map(([k, v]) => `${k}:${v}`),
|
|
128
|
+
]
|
|
129
|
+
.sort()
|
|
130
|
+
.join("\n") + "\n";
|
|
131
|
+
|
|
132
|
+
const sig = ossSign(
|
|
133
|
+
method,
|
|
134
|
+
"",
|
|
135
|
+
contentType,
|
|
136
|
+
date,
|
|
137
|
+
canonicalizedHeaders,
|
|
138
|
+
`/${bucket}/${objectKey}`,
|
|
139
|
+
credentials.accessKeySecret
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const headers = new Headers({
|
|
143
|
+
"Date": date,
|
|
144
|
+
"x-oss-security-token": credentials.securityToken,
|
|
145
|
+
"Authorization": `OSS ${credentials.accessKeyId}:${sig}`,
|
|
146
|
+
...extraHeaders,
|
|
147
|
+
});
|
|
148
|
+
if (contentType) headers.set("Content-Type", contentType);
|
|
149
|
+
return headers;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 1. Initiate
|
|
153
|
+
const initRes = await fetch(`${baseOssUrl}?uploads`, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: makeHeaders("POST", mimeType, {}, `/${bucket}/${objectKey}`),
|
|
156
|
+
});
|
|
157
|
+
if (!initRes.ok) throw new Error(`OSS InitiateMultipartUpload failed: ${initRes.status}`);
|
|
158
|
+
const initText = await initRes.text();
|
|
159
|
+
const uploadIdMatch = initText.match(/<UploadId>([^<]+)<\/UploadId>/);
|
|
160
|
+
if (!uploadIdMatch) throw new Error("OSS: could not parse UploadId");
|
|
161
|
+
const uploadId = uploadIdMatch[1];
|
|
162
|
+
|
|
163
|
+
// 2. Upload parts
|
|
164
|
+
const parts: Array<{ partNumber: number; etag: string }> = [];
|
|
165
|
+
let partNumber = 1;
|
|
166
|
+
let offset = 0;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
while (offset < data.length) {
|
|
170
|
+
const chunk = data.slice(offset, offset + PART_SIZE);
|
|
171
|
+
const partDate = new Date().toUTCString();
|
|
172
|
+
const canonicalizedHeaders = `x-oss-security-token:${credentials.securityToken}\n`;
|
|
173
|
+
const sig = ossSign(
|
|
174
|
+
"PUT", "", "application/octet-stream", partDate,
|
|
175
|
+
canonicalizedHeaders, `/${bucket}/${objectKey}`,
|
|
176
|
+
credentials.accessKeySecret
|
|
177
|
+
);
|
|
178
|
+
const partRes = await fetch(
|
|
179
|
+
`${baseOssUrl}?partNumber=${partNumber}&uploadId=${uploadId}`,
|
|
180
|
+
{
|
|
181
|
+
method: "PUT",
|
|
182
|
+
headers: {
|
|
183
|
+
"Content-Type": "application/octet-stream",
|
|
184
|
+
"Date": partDate,
|
|
185
|
+
"x-oss-security-token": credentials.securityToken,
|
|
186
|
+
"Authorization": `OSS ${credentials.accessKeyId}:${sig}`,
|
|
187
|
+
},
|
|
188
|
+
body: chunk as unknown as BodyInit,
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
if (!partRes.ok) throw new Error(`OSS UploadPart ${partNumber} failed: ${partRes.status}`);
|
|
192
|
+
const etag = partRes.headers.get("etag") ?? "";
|
|
193
|
+
parts.push({ partNumber, etag });
|
|
194
|
+
partNumber++;
|
|
195
|
+
offset += PART_SIZE;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 3. Complete
|
|
199
|
+
const completeBody = [
|
|
200
|
+
"<CompleteMultipartUpload>",
|
|
201
|
+
...parts.map(
|
|
202
|
+
(p) => `<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${p.etag}</ETag></Part>`
|
|
203
|
+
),
|
|
204
|
+
"</CompleteMultipartUpload>",
|
|
205
|
+
].join("");
|
|
206
|
+
|
|
207
|
+
const completeDate = new Date().toUTCString();
|
|
208
|
+
const cHeaders = `x-oss-security-token:${credentials.securityToken}\n`;
|
|
209
|
+
const cSig = ossSign(
|
|
210
|
+
"POST", "", "application/xml", completeDate,
|
|
211
|
+
cHeaders, `/${bucket}/${objectKey}`, credentials.accessKeySecret
|
|
212
|
+
);
|
|
213
|
+
const completeRes = await fetch(`${baseOssUrl}?uploadId=${uploadId}`, {
|
|
214
|
+
method: "POST",
|
|
215
|
+
headers: {
|
|
216
|
+
"Content-Type": "application/xml",
|
|
217
|
+
"Date": completeDate,
|
|
218
|
+
"x-oss-security-token": credentials.securityToken,
|
|
219
|
+
"Authorization": `OSS ${credentials.accessKeyId}:${cSig}`,
|
|
220
|
+
},
|
|
221
|
+
body: completeBody,
|
|
222
|
+
});
|
|
223
|
+
if (!completeRes.ok) throw new Error(`OSS CompleteMultipartUpload failed: ${completeRes.status}`);
|
|
224
|
+
|
|
225
|
+
} catch (err) {
|
|
226
|
+
// Best-effort abort to avoid billing for incomplete upload
|
|
227
|
+
try {
|
|
228
|
+
const abortDate = new Date().toUTCString();
|
|
229
|
+
const aHeaders = `x-oss-security-token:${credentials.securityToken}\n`;
|
|
230
|
+
const aSig = ossSign(
|
|
231
|
+
"DELETE", "", "", abortDate,
|
|
232
|
+
aHeaders, `/${bucket}/${objectKey}`, credentials.accessKeySecret
|
|
233
|
+
);
|
|
234
|
+
await fetch(`${baseOssUrl}?uploadId=${uploadId}`, {
|
|
235
|
+
method: "DELETE",
|
|
236
|
+
headers: {
|
|
237
|
+
"Date": abortDate,
|
|
238
|
+
"x-oss-security-token": credentials.securityToken,
|
|
239
|
+
"Authorization": `OSS ${credentials.accessKeyId}:${aSig}`,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
} catch { /* ignore abort errors */ }
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const publicUrl = `${baseUrl.replace(/\/$/, "")}/${objectKey}`;
|
|
247
|
+
// OSS video snapshot thumbnail URL
|
|
248
|
+
const thumbnailUrl = `${publicUrl}?x-oss-process=video/snapshot,t_0,f_jpg,w_0,h_0,m_fast`;
|
|
249
|
+
return { url: publicUrl, thumbnailUrl, mimeType, size: data.length };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// Convenience: auto-select PutObject vs multipart based on size
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
const MULTIPART_THRESHOLD = 10 * 1024 * 1024; // 10 MB
|
|
257
|
+
|
|
258
|
+
export async function uploadMedia(
|
|
259
|
+
sts: StsCredentials,
|
|
260
|
+
data: Buffer,
|
|
261
|
+
mimeType: string
|
|
262
|
+
): Promise<UploadResult> {
|
|
263
|
+
const ext = mimeTypeToExt(mimeType);
|
|
264
|
+
if (data.length >= MULTIPART_THRESHOLD) {
|
|
265
|
+
return multipartUpload(sts, data, ext, mimeType);
|
|
266
|
+
}
|
|
267
|
+
return putObject(sts, data, ext, mimeType);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function mimeTypeToExt(mimeType: string): string {
|
|
271
|
+
const map: Record<string, string> = {
|
|
272
|
+
"image/jpeg": ".jpg",
|
|
273
|
+
"image/png": ".png",
|
|
274
|
+
"image/gif": ".gif",
|
|
275
|
+
"image/webp": ".webp",
|
|
276
|
+
"video/mp4": ".mp4",
|
|
277
|
+
"video/quicktime": ".mov",
|
|
278
|
+
};
|
|
279
|
+
return map[mimeType] ?? ".bin";
|
|
280
|
+
}
|
|
@@ -225,19 +225,23 @@ function installLinuxServiceNohup(): boolean {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
function installLinuxService(): boolean {
|
|
228
|
+
try {
|
|
229
|
+
if (installLinuxServiceNohup()) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
// Fall back to systemd below.
|
|
234
|
+
}
|
|
235
|
+
|
|
228
236
|
if (canUseSystemdUser()) {
|
|
229
237
|
try {
|
|
230
238
|
return installLinuxServiceSystemd();
|
|
231
239
|
} catch {
|
|
232
|
-
//
|
|
240
|
+
// Give up below.
|
|
233
241
|
}
|
|
234
242
|
}
|
|
235
243
|
|
|
236
|
-
|
|
237
|
-
return installLinuxServiceNohup();
|
|
238
|
-
} catch {
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
244
|
+
return false;
|
|
241
245
|
}
|
|
242
246
|
|
|
243
247
|
function installWindowsService(): boolean {
|
|
@@ -2,6 +2,8 @@ import { WebSocket } from "ws";
|
|
|
2
2
|
import { OpenClawGatewayClient } from "./gateway-client.js";
|
|
3
3
|
import { handleLocalCommand } from "../commands/local-handlers.js";
|
|
4
4
|
import { handleProviderCommand } from "../commands/provider-handlers.js";
|
|
5
|
+
import { getServicePlatform } from "../platform/service-manager.js";
|
|
6
|
+
import { uploadAssistantAttachments } from "../media/assistant-attachments.js";
|
|
5
7
|
import { homedir } from "os";
|
|
6
8
|
import { join } from "path";
|
|
7
9
|
import { mkdir, writeFile } from "fs/promises";
|
|
@@ -19,6 +21,7 @@ const OUTBOUND_DIR = join(homedir(), ".openclaw", "media", "outbound");
|
|
|
19
21
|
|
|
20
22
|
/** Messages the relay client sends to the relay server. */
|
|
21
23
|
type ToServer =
|
|
24
|
+
| { type: "relay_hello"; platform: "macos" | "linux" | "windows" | "unsupported" }
|
|
22
25
|
| { type: "gateway_connected" }
|
|
23
26
|
| { type: "gateway_disconnected"; reason: string }
|
|
24
27
|
| { type: "event"; event: string; payload: unknown }
|
|
@@ -82,6 +85,7 @@ export async function runRelayManager(opts: RelayManagerOptions): Promise<boolea
|
|
|
82
85
|
|
|
83
86
|
relayWs.on("open", () => {
|
|
84
87
|
console.log(`Connected to relay server (gatewayId=${opts.gatewayId})`);
|
|
88
|
+
send({ type: "relay_hello", platform: getServicePlatform() });
|
|
85
89
|
opts.onConnected?.();
|
|
86
90
|
|
|
87
91
|
// Start the persistent gateway connection as soon as we're connected
|
|
@@ -102,15 +106,15 @@ export async function runRelayManager(opts: RelayManagerOptions): Promise<boolea
|
|
|
102
106
|
},
|
|
103
107
|
|
|
104
108
|
onEvent: (event, payload) => {
|
|
105
|
-
// On chat final, fetch history to get actual content
|
|
106
|
-
// no longer includes message content in
|
|
107
|
-
// This mirrors what the macOS 2026.3.2 client does.
|
|
109
|
+
// On chat final, fetch history to get actual content + extract media attachments.
|
|
110
|
+
// OpenClaw 2026.3.2+ no longer includes message content in chat final payload.
|
|
108
111
|
if (event === "chat") {
|
|
109
112
|
const p = payload as { state?: string; sessionKey?: string; runId?: string; message?: unknown };
|
|
110
113
|
if (p?.state === "final" && p?.sessionKey) {
|
|
111
114
|
const sessionKey = p.sessionKey;
|
|
112
115
|
const runId = p.runId;
|
|
113
|
-
type
|
|
116
|
+
type ContentBlock = { type: string; text?: string; source?: { type?: string; media_type?: string; data?: string; path?: string } };
|
|
117
|
+
type HistoryResponse = { messages?: Array<{ role: string; content?: ContentBlock[] }> };
|
|
114
118
|
const fetchHistory = () =>
|
|
115
119
|
gatewayClient!.request<HistoryResponse>("chat.history", { sessionKey, limit: 10 });
|
|
116
120
|
const extractText = (h: HistoryResponse | undefined) => {
|
|
@@ -126,10 +130,28 @@ export async function runRelayManager(opts: RelayManagerOptions): Promise<boolea
|
|
|
126
130
|
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
127
131
|
const retryHistory = await fetchHistory();
|
|
128
132
|
text = extractText(retryHistory);
|
|
133
|
+
history = retryHistory;
|
|
129
134
|
}
|
|
130
135
|
if (text) {
|
|
131
136
|
(p as Record<string, unknown>).message = { content: [{ type: "text", text }] };
|
|
132
137
|
}
|
|
138
|
+
|
|
139
|
+
// Upload any media blocks found in the history
|
|
140
|
+
try {
|
|
141
|
+
const attachments = await uploadAssistantAttachments(
|
|
142
|
+
history ?? {},
|
|
143
|
+
opts.relayServerUrl,
|
|
144
|
+
opts.gatewayId,
|
|
145
|
+
opts.relaySecret
|
|
146
|
+
);
|
|
147
|
+
if (attachments.length > 0) {
|
|
148
|
+
(p as Record<string, unknown>).attachments = attachments;
|
|
149
|
+
console.log(`[relay] chat final: injected ${attachments.length} attachment(s) runId=${runId}`);
|
|
150
|
+
}
|
|
151
|
+
} catch (mediaErr) {
|
|
152
|
+
console.error(`[relay] media upload error (non-fatal): ${mediaErr}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
133
155
|
console.log(`[relay] chat final (history fetched): runId=${runId} textLength=${text?.length ?? 0}`);
|
|
134
156
|
send({ type: "event", event, payload });
|
|
135
157
|
})
|
|
@@ -137,7 +159,7 @@ export async function runRelayManager(opts: RelayManagerOptions): Promise<boolea
|
|
|
137
159
|
console.error(`[relay] chat.history fetch failed: ${err}`);
|
|
138
160
|
send({ type: "event", event, payload });
|
|
139
161
|
});
|
|
140
|
-
return; // will send after history fetch
|
|
162
|
+
return; // will send after history fetch + media upload
|
|
141
163
|
}
|
|
142
164
|
}
|
|
143
165
|
send({ type: "event", event, payload });
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { WebSocket } from "ws";
|
|
2
|
-
/**
|
|
3
|
-
* Proxies WebSocket frames between the relay server (via relayWs) and the
|
|
4
|
-
* local OpenClaw Gateway (via a new direct WebSocket connection).
|
|
5
|
-
*
|
|
6
|
-
* All frames are forwarded as raw bytes — no parsing of OpenClaw protocol.
|
|
7
|
-
*/
|
|
8
|
-
export declare class SessionProxy {
|
|
9
|
-
private readonly sessionId;
|
|
10
|
-
private readonly relayWs;
|
|
11
|
-
private readonly gatewayUrl;
|
|
12
|
-
private gwWs;
|
|
13
|
-
private closed;
|
|
14
|
-
constructor(sessionId: string, relayWs: WebSocket, gatewayUrl: string);
|
|
15
|
-
start(): Promise<void>;
|
|
16
|
-
forwardToGateway(base64Data: string): void;
|
|
17
|
-
close(): void;
|
|
18
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { WebSocket } from "ws";
|
|
2
|
-
/**
|
|
3
|
-
* Proxies WebSocket frames between the relay server (via relayWs) and the
|
|
4
|
-
* local OpenClaw Gateway (via a new direct WebSocket connection).
|
|
5
|
-
*
|
|
6
|
-
* All frames are forwarded as raw bytes — no parsing of OpenClaw protocol.
|
|
7
|
-
*/
|
|
8
|
-
export class SessionProxy {
|
|
9
|
-
sessionId;
|
|
10
|
-
relayWs;
|
|
11
|
-
gatewayUrl;
|
|
12
|
-
gwWs = null;
|
|
13
|
-
closed = false;
|
|
14
|
-
constructor(sessionId, relayWs, gatewayUrl) {
|
|
15
|
-
this.sessionId = sessionId;
|
|
16
|
-
this.relayWs = relayWs;
|
|
17
|
-
this.gatewayUrl = gatewayUrl;
|
|
18
|
-
}
|
|
19
|
-
async start() {
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
const gw = new WebSocket(this.gatewayUrl);
|
|
22
|
-
this.gwWs = gw;
|
|
23
|
-
const timeout = setTimeout(() => {
|
|
24
|
-
gw.terminate();
|
|
25
|
-
reject(new Error(`Timeout connecting to gateway at ${this.gatewayUrl}`));
|
|
26
|
-
}, 10_000);
|
|
27
|
-
gw.on("open", () => {
|
|
28
|
-
clearTimeout(timeout);
|
|
29
|
-
resolve();
|
|
30
|
-
});
|
|
31
|
-
gw.on("error", (err) => {
|
|
32
|
-
clearTimeout(timeout);
|
|
33
|
-
if (!this.closed)
|
|
34
|
-
reject(err);
|
|
35
|
-
});
|
|
36
|
-
// Gateway → relay server
|
|
37
|
-
gw.on("message", (raw) => {
|
|
38
|
-
if (this.relayWs.readyState !== WebSocket.OPEN)
|
|
39
|
-
return;
|
|
40
|
-
const data = raw instanceof Buffer ? raw : Buffer.from(raw);
|
|
41
|
-
const msg = {
|
|
42
|
-
ctrl: "DATA",
|
|
43
|
-
sessionId: this.sessionId,
|
|
44
|
-
data: data.toString("base64"),
|
|
45
|
-
};
|
|
46
|
-
this.relayWs.send(JSON.stringify(msg));
|
|
47
|
-
});
|
|
48
|
-
gw.on("close", () => {
|
|
49
|
-
if (!this.closed) {
|
|
50
|
-
this.closed = true;
|
|
51
|
-
// Notify relay server that session is done
|
|
52
|
-
if (this.relayWs.readyState === WebSocket.OPEN) {
|
|
53
|
-
const msg = {
|
|
54
|
-
ctrl: "SESSION_CLOSE",
|
|
55
|
-
sessionId: this.sessionId,
|
|
56
|
-
};
|
|
57
|
-
this.relayWs.send(JSON.stringify(msg));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
// Called when DATA arrives from the relay server for this session
|
|
64
|
-
forwardToGateway(base64Data) {
|
|
65
|
-
if (!this.gwWs || this.gwWs.readyState !== WebSocket.OPEN)
|
|
66
|
-
return;
|
|
67
|
-
const buf = Buffer.from(base64Data, "base64");
|
|
68
|
-
this.gwWs.send(buf);
|
|
69
|
-
}
|
|
70
|
-
close() {
|
|
71
|
-
this.closed = true;
|
|
72
|
-
this.gwWs?.close();
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
//# sourceMappingURL=session-proxy.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-proxy.js","sourceRoot":"","sources":["../../src/relay/session-proxy.ts"],"names":[],"mappings":"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"}
|