@relaymessenger/openclaw-plugin 0.3.0 → 0.3.3

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.
@@ -27,6 +27,7 @@ import { buildRelayInboundFacts } from "./inbound.js";
27
27
  import { createRelayAccountLifecycleRegistry } from "./lifecycle.js";
28
28
  import { deriveRelayIdempotencyKey, RELAY_TEXT_CHUNK_LIMIT, reconcileRelayUnknownSend, sendRelayText, } from "./outbound.js";
29
29
  import { runRelayPollLoop } from "./poll-loop.js";
30
+ import { markRespondingBeforeAttempt } from "./responding.js";
30
31
  import { getRelayRuntime } from "./runtime.js";
31
32
  import { relaySenderIsAllowed, resolveRelayAllowedSenderIds } from "./security.js";
32
33
  export const RELAY_CHANNEL_ID = "relay";
@@ -244,7 +245,13 @@ async function dispatchRelayInbound(params) {
244
245
  // Admission, runtime resolution, route/session lookup, envelope building,
245
246
  // and context finalization above are replay-safe. The durable attempt starts
246
247
  // immediately before OpenClaw can invoke the agent or its tools.
247
- await params.markAttempt();
248
+ await markRespondingBeforeAttempt({
249
+ client: params.client,
250
+ conversationId: facts.conversationId,
251
+ messageId: facts.messageId,
252
+ label: "OpenClaw",
253
+ markAttempt: params.markAttempt,
254
+ });
248
255
  await runtime.channel.inbound.dispatchReply({
249
256
  cfg: params.cfg,
250
257
  channel: RELAY_CHANNEL_ID,
@@ -434,11 +441,6 @@ async function startRelayAccount(ctx) {
434
441
  allowedSenderIds,
435
442
  markAttempt,
436
443
  });
437
- // Read watermark after the turn is handled: read implies delivered;
438
- // best effort — a failed receipt must not replay the event.
439
- await client
440
- .markRead({ conversationId: facts.conversationId, messageId: facts.messageId })
441
- .catch((error) => log(`[relay] markRead failed: ${String(error)}`));
442
444
  },
443
445
  });
444
446
  }
@@ -1,7 +1,6 @@
1
- // Thin Relay REST client for the OpenClaw channel plugin. Bespoke fetch until
2
- // the Relay SDK ships. Owns the abort-aware long poll, idempotent
3
- // sends, typing, and read watermarks. No SDK imports so unit tests run
4
- // without an OpenClaw runtime.
1
+ // Thin Relay REST client boundary for the standalone OpenClaw channel plugin.
2
+ // Owns the abort-aware long poll, idempotent sends, typing, responding, and
3
+ // read watermarks without loading an OpenClaw runtime in unit tests.
5
4
  import { isIP } from "node:net";
6
5
  export const DEFAULT_RELAY_BASE_URL = "https://api.relayapp.im";
7
6
  function isLoopbackHostname(hostname) {
@@ -207,6 +206,17 @@ export function createRelayClient(options) {
207
206
  signal: params.signal,
208
207
  });
209
208
  },
209
+ setResponding: async (params) => {
210
+ await request({
211
+ method: "POST",
212
+ path: `/v1/conversations/${encodeURIComponent(params.conversationId)}/responding`,
213
+ body: {
214
+ message_id: params.messageId,
215
+ ...(params.label ? { label: params.label } : {}),
216
+ },
217
+ signal: params.signal,
218
+ });
219
+ },
210
220
  markRead: async (params) => {
211
221
  await request({
212
222
  method: "POST",
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The receipt must commit before OpenClaw can run an agent or tool. A rejected
3
+ * receipt leaves the durable attempt marker untouched, so the poll loop can
4
+ * replay the event safely instead of hiding the failure after execution.
5
+ */
6
+ export async function markRespondingBeforeAttempt(params) {
7
+ await params.client.setResponding({
8
+ conversationId: params.conversationId,
9
+ messageId: params.messageId,
10
+ label: params.label,
11
+ });
12
+ await params.markAttempt();
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relaymessenger/openclaw-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "description": "Relay channel plugin for OpenClaw. Text your OpenClaw like a friend.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
package/src/channel.ts CHANGED
@@ -50,6 +50,7 @@ import {
50
50
  sendRelayText,
51
51
  } from "./outbound.js";
52
52
  import { runRelayPollLoop } from "./poll-loop.js";
53
+ import { markRespondingBeforeAttempt } from "./responding.js";
53
54
  import { getRelayRuntime } from "./runtime.js";
54
55
  import { relaySenderIsAllowed, resolveRelayAllowedSenderIds } from "./security.js";
55
56
  import type { RelayCoreConfig, ResolvedRelayAccount } from "./types.js";
@@ -286,7 +287,13 @@ async function dispatchRelayInbound(params: {
286
287
  // Admission, runtime resolution, route/session lookup, envelope building,
287
288
  // and context finalization above are replay-safe. The durable attempt starts
288
289
  // immediately before OpenClaw can invoke the agent or its tools.
289
- await params.markAttempt();
290
+ await markRespondingBeforeAttempt({
291
+ client: params.client,
292
+ conversationId: facts.conversationId,
293
+ messageId: facts.messageId,
294
+ label: "OpenClaw",
295
+ markAttempt: params.markAttempt,
296
+ });
290
297
  await runtime.channel.inbound.dispatchReply({
291
298
  cfg: params.cfg,
292
299
  channel: RELAY_CHANNEL_ID,
@@ -492,11 +499,6 @@ async function startRelayAccount(ctx: ChannelGatewayContext<ResolvedRelayAccount
492
499
  allowedSenderIds,
493
500
  markAttempt,
494
501
  });
495
- // Read watermark after the turn is handled: read implies delivered;
496
- // best effort — a failed receipt must not replay the event.
497
- await client
498
- .markRead({ conversationId: facts.conversationId, messageId: facts.messageId })
499
- .catch((error) => log(`[relay] markRead failed: ${String(error)}`));
500
502
  },
501
503
  });
502
504
  } catch (error) {
package/src/client.ts CHANGED
@@ -1,7 +1,6 @@
1
- // Thin Relay REST client for the OpenClaw channel plugin. Bespoke fetch until
2
- // the Relay SDK ships. Owns the abort-aware long poll, idempotent
3
- // sends, typing, and read watermarks. No SDK imports so unit tests run
4
- // without an OpenClaw runtime.
1
+ // Thin Relay REST client boundary for the standalone OpenClaw channel plugin.
2
+ // Owns the abort-aware long poll, idempotent sends, typing, responding, and
3
+ // read watermarks without loading an OpenClaw runtime in unit tests.
5
4
  import { isIP } from "node:net";
6
5
  import type {
7
6
  RelayAgentProfile,
@@ -146,6 +145,12 @@ export type RelayClient = {
146
145
  label?: string;
147
146
  signal?: AbortSignal;
148
147
  }) => Promise<void>;
148
+ setResponding: (params: {
149
+ conversationId: string;
150
+ messageId: string;
151
+ label?: string;
152
+ signal?: AbortSignal;
153
+ }) => Promise<void>;
149
154
  markRead: (params: {
150
155
  conversationId: string;
151
156
  messageId: string;
@@ -301,6 +306,18 @@ export function createRelayClient(options: RelayClientOptions): RelayClient {
301
306
  });
302
307
  },
303
308
 
309
+ setResponding: async (params) => {
310
+ await request({
311
+ method: "POST",
312
+ path: `/v1/conversations/${encodeURIComponent(params.conversationId)}/responding`,
313
+ body: {
314
+ message_id: params.messageId,
315
+ ...(params.label ? { label: params.label } : {}),
316
+ },
317
+ signal: params.signal,
318
+ });
319
+ },
320
+
304
321
  markRead: async (params) => {
305
322
  await request({
306
323
  method: "POST",
@@ -0,0 +1,21 @@
1
+ import type { RelayClient } from "./client.js";
2
+
3
+ /**
4
+ * The receipt must commit before OpenClaw can run an agent or tool. A rejected
5
+ * receipt leaves the durable attempt marker untouched, so the poll loop can
6
+ * replay the event safely instead of hiding the failure after execution.
7
+ */
8
+ export async function markRespondingBeforeAttempt(params: {
9
+ client: RelayClient;
10
+ conversationId: string;
11
+ messageId: string;
12
+ label: string;
13
+ markAttempt: () => Promise<void>;
14
+ }): Promise<void> {
15
+ await params.client.setResponding({
16
+ conversationId: params.conversationId,
17
+ messageId: params.messageId,
18
+ label: params.label,
19
+ });
20
+ await params.markAttempt();
21
+ }