@relaymessenger/openclaw-plugin 0.4.7-staging.21 → 0.4.7-staging.23

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
@@ -26,11 +26,13 @@ checks any number of options and submits them once; checking sends nothing, and
26
26
  a person answers a given selection once. iOS may draw a checkmark in place of
27
27
  each bullet and repeat the prompt's title, as presentation only.
28
28
 
29
- ## Invoice
29
+ ## Payment
30
30
 
31
- A verified agent ends the final answer with an `invoice` JSON fence holding a
32
- Stripe checkout link; the words go first and the invoice follows as its own
33
- Message.
31
+ The agent ends the final answer with a `payment` JSON fence holding the
32
+ payment request's fields (`description`, `category`, and `amount` with
33
+ `currency`, or `mode: "subscription"` with `price_id`). The plugin creates the
34
+ request with its own Relay token, on the card's own idempotency key; the words
35
+ go first and the payment card follows as its own Message.
34
36
 
35
37
  ## Install
36
38
 
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "relayServer": {
3
3
  "repository": "RelayMessenger/Relay-Server",
4
- "commit": "3b7425e5bafcf25cdc8ceff009715e4f06877d18",
4
+ "commit": "51bc3ecd9b203a3fc75fe0ab7a105b6751080678",
5
5
  "openapiPath": "contracts/developer/openapi.yaml",
6
- "sha256": "3fb4873a3c09b7dade09012ecfc57acfef8cfb983b6b35fff9d320cf62bb3b00"
6
+ "sha256": "7b41c21bebd99d28d103da1c3fe380642542e5b6243bb4319e501d7609d8ab0f"
7
7
  },
8
8
  "relaySdk": {
9
9
  "package": "@relaymessenger/sdk",
10
- "version": "0.3.6-staging.22",
11
- "integrity": "sha512-PnMaOgFgLcYD8xTspki0mGpySfVnkxC/EjnyTiNYioWuy+hJjtYzE3xwjJEnDYyJ6HlucDzZqmFCE07PgILCGw==",
12
- "operationsSha256": "5d9cc3d0eed738d85deb3842b6efc0db5fe720299aa53e959f87e83c8585be36",
13
- "workspaceOpenapiSha256": "3fb4873a3c09b7dade09012ecfc57acfef8cfb983b6b35fff9d320cf62bb3b00",
10
+ "version": "0.3.6-staging.25",
11
+ "integrity": "sha512-OHmpqxGHc5DRjIaNc+5v5m7zuf7+y+5032Ca6lCvNajUUtkLeYp4TGMxzGTzCUg3Kmv62Kj7HE2wpQn1hfZxuQ==",
12
+ "operationsSha256": "2480be91815b2e9e35305026b0a6ef61ddf2bb6db1b349d2a6da3eb875f4707f",
13
+ "workspaceOpenapiSha256": "7b41c21bebd99d28d103da1c3fe380642542e5b6243bb4319e501d7609d8ab0f",
14
14
  "usedOperations": [
15
15
  {
16
16
  "method": "GET",
@@ -1,4 +1,4 @@
1
- import { BUTTONS_GUIDANCE, BUTTONS_BLOCK_INSTRUCTION, INVOICE_BLOCK_INSTRUCTION, INVOICE_GUIDANCE, LINK_LINE_INSTRUCTION, SELECTION_GUIDANCE, SELECTION_BLOCK_INSTRUCTION, selectionReplyContext } from "@relaymessenger/sdk";
1
+ import { BUTTONS_GUIDANCE, BUTTONS_BLOCK_INSTRUCTION, PAYMENT_BLOCK_INSTRUCTION, PAYMENT_GUIDANCE, LINK_LINE_INSTRUCTION, SELECTION_GUIDANCE, SELECTION_BLOCK_INSTRUCTION, selectionReplyContext } from "@relaymessenger/sdk";
2
2
  import { buildChannelInboundEventContext, resolveChannelInboundRouteEnvelope, } from "openclaw/plugin-sdk/channel-inbound";
3
3
  import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
4
4
  import { bindIngressLifecycleToReplyOptions } from "openclaw/plugin-sdk/channel-outbound";
@@ -179,7 +179,7 @@ export async function dispatchRelayEvent(params) {
179
179
  inboundEventKind: "user_request",
180
180
  body,
181
181
  bodyForAgent: [facts.text, selectionReplyContext(facts.selection, facts.richMessage),
182
- `${BUTTONS_BLOCK_INSTRUCTION} ${LINK_LINE_INSTRUCTION} ${BUTTONS_GUIDANCE} ${SELECTION_BLOCK_INSTRUCTION} ${SELECTION_GUIDANCE} ${INVOICE_BLOCK_INSTRUCTION} ${INVOICE_GUIDANCE}`,
182
+ `${BUTTONS_BLOCK_INSTRUCTION} ${LINK_LINE_INSTRUCTION} ${BUTTONS_GUIDANCE} ${SELECTION_BLOCK_INSTRUCTION} ${SELECTION_GUIDANCE} ${PAYMENT_BLOCK_INSTRUCTION} ${PAYMENT_GUIDANCE}`,
183
183
  ].filter(Boolean).join("\n\n"),
184
184
  rawBody: facts.text,
185
185
  commandBody: facts.text,
@@ -1,5 +1,5 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { answerMessages, indexedIdempotencyKey, Relay, RelayAPIError, } from "@relaymessenger/sdk";
2
+ import { answerMessages, createPaymentPart, indexedIdempotencyKey, Relay, RelayAPIError, } from "@relaymessenger/sdk";
3
3
  export const RELAY_TEXT_CHUNK_LIMIT = 10_000;
4
4
  const IDEMPOTENCY_KEY_MAX_LENGTH = 255;
5
5
  export function createRelaySdkClient(account) {
@@ -27,11 +27,25 @@ export function deriveRelayIdempotencyKey(params) {
27
27
  */
28
28
  export async function sendRelayText(params) {
29
29
  await params.onPlatformSendDispatch?.();
30
- const { messages, error } = answerMessages(params.text);
30
+ const { messages, payment, error } = answerMessages(params.text);
31
31
  if (error)
32
32
  params.onButtonsError?.(error);
33
- if (messages.length === 0)
33
+ if (messages.length === 0 && !payment)
34
34
  messages.push([{ type: "text", value: params.text }]);
35
+ if (payment) {
36
+ // Created with the card's own key, so a retry of this delivery returns
37
+ // the same request. A refusal after words went out is reported and the
38
+ // words stand; with nothing sent yet, it is the delivery's own error.
39
+ const key = indexedIdempotencyKey(params.idempotencyKey, messages.length);
40
+ try {
41
+ messages.push([await createPaymentPart(params.relay, payment, key, params.signal ? { signal: params.signal } : undefined)]);
42
+ }
43
+ catch (refusal) {
44
+ if (!(refusal instanceof RelayAPIError) || refusal.retryable || messages.length === 0)
45
+ throw refusal;
46
+ params.onButtonsError?.(`the payment was not sent: ${refusal.message}`);
47
+ }
48
+ }
35
49
  let first;
36
50
  for (const [index, parts] of messages.entries()) {
37
51
  const response = await params.relay.chats.messages.send(params.chatId, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relaymessenger/openclaw-plugin",
3
- "version": "0.4.7-staging.21",
3
+ "version": "0.4.7-staging.23",
4
4
  "description": "Native Relay channel plugin for OpenClaw",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,7 +37,7 @@
37
37
  "prepack": "npm run build"
38
38
  },
39
39
  "dependencies": {
40
- "@relaymessenger/sdk": "0.3.6-staging.22"
40
+ "@relaymessenger/sdk": "0.3.6-staging.25"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^26.6.2",
package/src/dispatch.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BUTTONS_GUIDANCE, BUTTONS_BLOCK_INSTRUCTION, INVOICE_BLOCK_INSTRUCTION, INVOICE_GUIDANCE, LINK_LINE_INSTRUCTION, SELECTION_GUIDANCE, SELECTION_BLOCK_INSTRUCTION, selectionReplyContext } from "@relaymessenger/sdk";
1
+ import { BUTTONS_GUIDANCE, BUTTONS_BLOCK_INSTRUCTION, PAYMENT_BLOCK_INSTRUCTION, PAYMENT_GUIDANCE, LINK_LINE_INSTRUCTION, SELECTION_GUIDANCE, SELECTION_BLOCK_INSTRUCTION, selectionReplyContext } from "@relaymessenger/sdk";
2
2
  import type {
3
3
  Message,
4
4
  Relay,
@@ -245,7 +245,7 @@ export async function dispatchRelayEvent(params: {
245
245
  inboundEventKind: "user_request",
246
246
  body,
247
247
  bodyForAgent: [facts.text, selectionReplyContext(facts.selection, facts.richMessage),
248
- `${BUTTONS_BLOCK_INSTRUCTION} ${LINK_LINE_INSTRUCTION} ${BUTTONS_GUIDANCE} ${SELECTION_BLOCK_INSTRUCTION} ${SELECTION_GUIDANCE} ${INVOICE_BLOCK_INSTRUCTION} ${INVOICE_GUIDANCE}`,
248
+ `${BUTTONS_BLOCK_INSTRUCTION} ${LINK_LINE_INSTRUCTION} ${BUTTONS_GUIDANCE} ${SELECTION_BLOCK_INSTRUCTION} ${SELECTION_GUIDANCE} ${PAYMENT_BLOCK_INSTRUCTION} ${PAYMENT_GUIDANCE}`,
249
249
  ].filter(Boolean).join("\n\n"),
250
250
  rawBody: facts.text,
251
251
  commandBody: facts.text,
package/src/outbound.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import {
3
3
  answerMessages,
4
+ createPaymentPart,
4
5
  indexedIdempotencyKey,
5
6
  Relay,
6
7
  RelayAPIError,
@@ -43,7 +44,7 @@ export function deriveRelayIdempotencyKey(params: {
43
44
  * Message; the response is the first Message's, the one the reply anchors to.
44
45
  */
45
46
  export async function sendRelayText(params: {
46
- relay: Pick<Relay, "chats">;
47
+ relay: Pick<Relay, "chats" | "paymentRequests">;
47
48
  chatId: string;
48
49
  text: string;
49
50
  replyToId?: string | null | undefined;
@@ -53,9 +54,21 @@ export async function sendRelayText(params: {
53
54
  onButtonsError?: (error: string) => void;
54
55
  }): Promise<MessageSendResponse> {
55
56
  await params.onPlatformSendDispatch?.();
56
- const { messages, error } = answerMessages(params.text);
57
+ const { messages, payment, error } = answerMessages(params.text);
57
58
  if (error) params.onButtonsError?.(error);
58
- if (messages.length === 0) messages.push([{ type: "text", value: params.text }]);
59
+ if (messages.length === 0 && !payment) messages.push([{ type: "text", value: params.text }]);
60
+ if (payment) {
61
+ // Created with the card's own key, so a retry of this delivery returns
62
+ // the same request. A refusal after words went out is reported and the
63
+ // words stand; with nothing sent yet, it is the delivery's own error.
64
+ const key = indexedIdempotencyKey(params.idempotencyKey, messages.length);
65
+ try {
66
+ messages.push([await createPaymentPart(params.relay, payment, key, params.signal ? { signal: params.signal } : undefined)]);
67
+ } catch (refusal) {
68
+ if (!(refusal instanceof RelayAPIError) || refusal.retryable || messages.length === 0) throw refusal;
69
+ params.onButtonsError?.(`the payment was not sent: ${refusal.message}`);
70
+ }
71
+ }
59
72
  let first: MessageSendResponse | undefined;
60
73
  for (const [index, parts] of messages.entries()) {
61
74
  const response = await params.relay.chats.messages.send(