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 CHANGED
@@ -24,6 +24,12 @@ Or install from a local path during development:
24
24
  openclaw plugins install ./packages/relay-channel
25
25
  ```
26
26
 
27
+ Update to the latest version:
28
+
29
+ ```bash
30
+ openclaw plugins update clawrelay
31
+ ```
32
+
27
33
  Or install from a tarball:
28
34
 
29
35
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawrelay",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Channel relay plugin for OpenClaw — receives messages from an always-on relay proxy",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -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.runtime.config.load();
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
- try {
66
- const responseContent = await processRelayMessage({
67
- message,
68
- account,
69
- config,
70
- log: logger,
71
- });
72
-
73
- respond(true, {
74
- messageId: message.messageId,
75
- content: responseContent,
76
- replyToMessageId: message.messageId,
77
- });
78
- } catch (err) {
79
- const errMsg = err instanceof Error ? err.message : String(err);
80
- logger.error(`[clawrelay] Failed to process inbound: ${errMsg}`);
81
- respond(false, undefined, {
82
- code: 'INTERNAL_ERROR',
83
- message: `Error processing message: ${errMsg}`,
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",