clawrelay 0.3.2 → 0.3.4
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 +6 -0
- package/package.json +1 -1
- package/src/gateway-handler.ts +68 -23
- package/src/onboarding.ts +98 -0
package/README.md
CHANGED
package/package.json
CHANGED
package/src/gateway-handler.ts
CHANGED
|
@@ -23,7 +23,7 @@ export function createRelayInboundHandler(api: any) {
|
|
|
23
23
|
respond: (ok: boolean, payload?: unknown, error?: unknown) => void;
|
|
24
24
|
context: any;
|
|
25
25
|
}) => {
|
|
26
|
-
const { params, respond } = opts;
|
|
26
|
+
const { params, client, respond } = opts;
|
|
27
27
|
|
|
28
28
|
const message = params.message as RelayInboundMessage | undefined;
|
|
29
29
|
if (!message || !message.messageId || !message.content) {
|
|
@@ -36,7 +36,7 @@ export function createRelayInboundHandler(api: any) {
|
|
|
36
36
|
|
|
37
37
|
// Resolve the account — use accountId from params or fall back to default
|
|
38
38
|
const accountId = (params.accountId as string) ?? 'default';
|
|
39
|
-
const config = api.
|
|
39
|
+
const config = api.config;
|
|
40
40
|
const accounts = config.channels?.relay?.accounts ?? {};
|
|
41
41
|
const accountData = accounts[accountId] ?? config.channels?.relay;
|
|
42
42
|
|
|
@@ -62,26 +62,69 @@ export function createRelayInboundHandler(api: any) {
|
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
65
|
+
const streaming = params.streaming === true;
|
|
66
|
+
|
|
67
|
+
if (streaming) {
|
|
68
|
+
// Streaming mode: ack immediately, then send deltas via events
|
|
69
|
+
respond(true, { messageId: message.messageId, streaming: true });
|
|
70
|
+
|
|
71
|
+
const streamCallback = (text: string) => {
|
|
72
|
+
try {
|
|
73
|
+
client.sendEvent('relay.stream.delta', {
|
|
74
|
+
messageId: message.messageId,
|
|
75
|
+
text,
|
|
76
|
+
kind: 'deliver',
|
|
77
|
+
});
|
|
78
|
+
} catch (err) {
|
|
79
|
+
logger.debug(`[clawrelay] Failed to send stream delta: ${err}`);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const responseContent = await processRelayMessage({
|
|
85
|
+
message,
|
|
86
|
+
account,
|
|
87
|
+
config,
|
|
88
|
+
log: logger,
|
|
89
|
+
streamCallback,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
client.sendEvent('relay.stream.done', {
|
|
93
|
+
messageId: message.messageId,
|
|
94
|
+
text: responseContent,
|
|
95
|
+
});
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
logger.error(`[clawrelay] Failed to process inbound (streaming): ${errMsg}`);
|
|
99
|
+
client.sendEvent('relay.stream.done', {
|
|
100
|
+
messageId: message.messageId,
|
|
101
|
+
text: '',
|
|
102
|
+
error: `Error processing message: ${errMsg}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// Non-streaming: buffered response as before
|
|
107
|
+
try {
|
|
108
|
+
const responseContent = await processRelayMessage({
|
|
109
|
+
message,
|
|
110
|
+
account,
|
|
111
|
+
config,
|
|
112
|
+
log: logger,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
respond(true, {
|
|
116
|
+
messageId: message.messageId,
|
|
117
|
+
content: responseContent,
|
|
118
|
+
replyToMessageId: message.messageId,
|
|
119
|
+
});
|
|
120
|
+
} catch (err) {
|
|
121
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
122
|
+
logger.error(`[clawrelay] Failed to process inbound: ${errMsg}`);
|
|
123
|
+
respond(false, undefined, {
|
|
124
|
+
code: 'INTERNAL_ERROR',
|
|
125
|
+
message: `Error processing message: ${errMsg}`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
85
128
|
}
|
|
86
129
|
};
|
|
87
130
|
}
|
|
@@ -108,8 +151,9 @@ async function processRelayMessage(params: {
|
|
|
108
151
|
account: RelayAccount;
|
|
109
152
|
config: any;
|
|
110
153
|
log: any;
|
|
154
|
+
streamCallback?: (text: string) => void;
|
|
111
155
|
}): Promise<string> {
|
|
112
|
-
const { message, account, config, log } = params;
|
|
156
|
+
const { message, account, config, log, streamCallback } = params;
|
|
113
157
|
|
|
114
158
|
const core = getRelayRuntime();
|
|
115
159
|
const peerId = resolvePeerId(message);
|
|
@@ -190,6 +234,7 @@ async function processRelayMessage(params: {
|
|
|
190
234
|
const text = payload.text ?? '';
|
|
191
235
|
if (text.trim()) {
|
|
192
236
|
parts.push(text);
|
|
237
|
+
streamCallback?.(text);
|
|
193
238
|
}
|
|
194
239
|
},
|
|
195
240
|
onError: (err: unknown, info: { kind: string }) => {
|
package/src/onboarding.ts
CHANGED
|
@@ -9,9 +9,104 @@ import {
|
|
|
9
9
|
promptAccountId,
|
|
10
10
|
} from "openclaw/plugin-sdk";
|
|
11
11
|
import crypto from "node:crypto";
|
|
12
|
+
import { execSync } from "node:child_process";
|
|
13
|
+
import path from "node:path";
|
|
12
14
|
|
|
13
15
|
const channel = "relay" as const;
|
|
14
16
|
|
|
17
|
+
const SPRITE_ENV_BIN = "/.sprite/bin/sprite-env";
|
|
18
|
+
const DEFAULT_GATEWAY_PORT = 18789;
|
|
19
|
+
const GATEWAY_SERVICE_NAME = "openclaw-gateway";
|
|
20
|
+
|
|
21
|
+
function isOnSprite(): boolean {
|
|
22
|
+
try {
|
|
23
|
+
execSync(`test -x ${SPRITE_ENV_BIN}`, { stdio: "ignore" });
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getExistingGatewayService(): Record<string, unknown> | undefined {
|
|
31
|
+
try {
|
|
32
|
+
const out = execSync(`${SPRITE_ENV_BIN} services list`, {
|
|
33
|
+
encoding: "utf-8",
|
|
34
|
+
timeout: 5000,
|
|
35
|
+
}).trim();
|
|
36
|
+
const services: Record<string, unknown>[] = JSON.parse(out);
|
|
37
|
+
return services.find(
|
|
38
|
+
(s) => (s as any).name === GATEWAY_SERVICE_NAME,
|
|
39
|
+
);
|
|
40
|
+
} catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function findOpenclawBinary(): string | undefined {
|
|
46
|
+
const binDir = path.dirname(process.execPath);
|
|
47
|
+
const candidate = path.join(binDir, "openclaw");
|
|
48
|
+
try {
|
|
49
|
+
execSync(`test -x "${candidate}"`, { stdio: "ignore" });
|
|
50
|
+
return candidate;
|
|
51
|
+
} catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function ensureSpriteGatewayService(
|
|
57
|
+
cfg: OpenClawConfig,
|
|
58
|
+
prompter: WizardPrompter,
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
if (!isOnSprite()) return;
|
|
61
|
+
|
|
62
|
+
const existing = getExistingGatewayService();
|
|
63
|
+
if (existing) {
|
|
64
|
+
await prompter.note(
|
|
65
|
+
`Sprite gateway service already exists (${GATEWAY_SERVICE_NAME}).`,
|
|
66
|
+
"Sprite",
|
|
67
|
+
);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const openclawBin = findOpenclawBinary();
|
|
72
|
+
if (!openclawBin) {
|
|
73
|
+
await prompter.note(
|
|
74
|
+
"Could not locate the openclaw binary to create the gateway service. You may need to create it manually:\n" +
|
|
75
|
+
` sprite-env services create ${GATEWAY_SERVICE_NAME} --cmd openclaw --args "gateway,--port,${DEFAULT_GATEWAY_PORT}" --http-port ${DEFAULT_GATEWAY_PORT}`,
|
|
76
|
+
"Sprite",
|
|
77
|
+
);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const gwCfg = (cfg as any).gateway;
|
|
82
|
+
const port = gwCfg?.port ?? DEFAULT_GATEWAY_PORT;
|
|
83
|
+
|
|
84
|
+
const shouldCreate = await prompter.confirm({
|
|
85
|
+
message: `Create sprite gateway service (${GATEWAY_SERVICE_NAME} on port ${port})?`,
|
|
86
|
+
initialValue: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (!shouldCreate) return;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
execSync(
|
|
93
|
+
`${SPRITE_ENV_BIN} services create ${GATEWAY_SERVICE_NAME} --cmd "${openclawBin}" --args "gateway,--port,${port}" --http-port ${port}`,
|
|
94
|
+
{ encoding: "utf-8", timeout: 10000 },
|
|
95
|
+
);
|
|
96
|
+
await prompter.note(
|
|
97
|
+
`Gateway service created: ${GATEWAY_SERVICE_NAME} → ${openclawBin} gateway --port ${port}`,
|
|
98
|
+
"Sprite",
|
|
99
|
+
);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
102
|
+
await prompter.note(
|
|
103
|
+
`Failed to create gateway service: ${msg}\n\nYou can create it manually:\n` +
|
|
104
|
+
` sprite-env services create ${GATEWAY_SERVICE_NAME} --cmd "${openclawBin}" --args "gateway,--port,${port}" --http-port ${port}`,
|
|
105
|
+
"Sprite",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
15
110
|
function generateAuthToken(): string {
|
|
16
111
|
const bytes = crypto.randomBytes(24);
|
|
17
112
|
return `crly_${bytes.toString("base64url")}`;
|
|
@@ -168,6 +263,9 @@ export const relayOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
168
263
|
authToken,
|
|
169
264
|
});
|
|
170
265
|
|
|
266
|
+
// --- Sprite gateway service ---
|
|
267
|
+
await ensureSpriteGatewayService(next, prompter);
|
|
268
|
+
|
|
171
269
|
await prompter.note(
|
|
172
270
|
`Relay channel configured (token: ${authToken.slice(0, 12)}...). Select Finished below, then deploy the relay service.`,
|
|
173
271
|
"Relay",
|