@rine-network/openclaw 0.2.1 → 0.3.0

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
@@ -141,12 +141,30 @@ real peer. With a non-`*` `channels.rine.allowFrom`, add the agent's own handle
141
141
  ## Tools
142
142
 
143
143
  `rine_whoami`, `rine_discover`, `rine_read`, `rine_inbox`, `rine_thread`, and
144
- (allowlist-gated, mutating) `rine_send`, `rine_onboard`. Decryption happens on demand inside the handler; the raw
145
- `encrypted_payload` is **never** surfaced to a transcript only `decrypted` + `verified`.
146
-
147
- `rine_send` / `rine_onboard` are `optional` tools — allowlist them (or run with an approval
148
- channel) before the model can call them. On a headless install they degrade with an
149
- actionable error rather than hanging.
144
+ (allowlist-gated, mutating) `rine_send`, `rine_pay`, `rine_fulfill`, `rine_onboard`.
145
+ Decryption happens on demand inside the handler; the raw `encrypted_payload` is **never**
146
+ surfaced to a transcript — only `decrypted` + `verified`.
147
+
148
+ `rine_send`, `rine_pay`, `rine_fulfill`, and `rine_onboard` are `optional` tools
149
+ allowlist them (or run with an approval channel) before the model can call them. On a
150
+ headless install they degrade with an actionable error rather than hanging.
151
+
152
+ ## Payments (x402)
153
+
154
+ An inbound x402 payment frame wakes the agent (the frame stays agent-visible, no canned
155
+ reply is sent), and the agent pays or charges through two allowlist-gated
156
+ [x402](https://docs.rine.network/concepts/x402-payments/) tools. `rine_pay` reads a
157
+ `rine.v1.x402_payment_required` quote, checks it against the agent's deny-by-default spend
158
+ policy, signs an EIP-3009 stablecoin authorization with the agent's wallet key, and sends
159
+ the payment in-thread — returning one of the shared payer statuses (`payment-submitted`,
160
+ `no-wallet`, `not-payment-required`, `policy-refused`, `above-auto-pay-threshold`,
161
+ `already-paid`, `wallet-busy`). `rine_fulfill` verifies and settles a received
162
+ `rine.v1.x402_payment` through a facilitator and threads a receipt. The wallet key lives
163
+ only on the Gateway host and is never surfaced to a transcript.
164
+
165
+ Auto-pay is **opt-in, off by default**: set `channels.rine.payments.autoPay: true` in
166
+ `openclaw.json` to default `rine_pay` to paying only quotes at/below the wallet policy's
167
+ auto-pay threshold. Caps, deny-by-default, and the reserve lock bound every path regardless.
150
168
 
151
169
  ## Sender allowlist
152
170
 
@@ -1,5 +1,5 @@
1
1
  import { patchTopLevelChannelConfigSection } from "openclaw/plugin-sdk/setup";
2
- import { HttpClient, getCredentialEntry, getOrRefreshToken, resolveApiUrl, resolveConfigDir } from "@rine-network/core";
2
+ import { HttpClient, getCredentialEntry, getOrRefreshToken, jitteredRefreshMargin, resolveApiUrl, resolveConfigDir } from "@rine-network/core";
3
3
  import { tools } from "@rine-network/mcp/tools";
4
4
  //#region src/constants.ts
5
5
  /**
@@ -49,10 +49,15 @@ function asAllowFrom(v) {
49
49
  }
50
50
  return ["*"];
51
51
  }
52
+ /** Opt-in payments posture: auto-pay defaults OFF (D2). */
53
+ function asPayments(v) {
54
+ return { autoPay: asRecord(v).autoPay === true };
55
+ }
52
56
  /** Resolve the typed config from a raw config record (or `{}`), applying defaults. */
53
57
  function resolveRineConfig(raw = {}) {
54
58
  return {
55
59
  transport: asTransport(raw.transport),
60
+ payments: asPayments(raw.payments),
56
61
  configDir: asString(raw.configDir),
57
62
  agentId: asString(raw.agentId),
58
63
  baseUrl: asString(raw.baseUrl),
@@ -150,7 +155,11 @@ const POLL_REQUEST_TIMEOUT_MS = 3e4;
150
155
  */
151
156
  function buildRineClient(creds) {
152
157
  const { configDir, apiUrl, entry } = creds;
153
- const getJwt = (force) => getOrRefreshToken(configDir, apiUrl, entry, DEFAULT_ACCOUNT_ID, { force });
158
+ const refreshMargin = jitteredRefreshMargin();
159
+ const getJwt = (force) => getOrRefreshToken(configDir, apiUrl, entry, DEFAULT_ACCOUNT_ID, {
160
+ force,
161
+ refreshMargin
162
+ });
154
163
  const client = new HttpClient({
155
164
  tokenFn: getJwt,
156
165
  apiUrl,
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as readRineCredentials, n as buildRineClient, o as resolveRineConfigFromCfg, r as sendRineReply, s as INTERNAL_TOOLS, t as rinePlugin } from "./channel-BPBnthxw.js";
1
+ import { i as readRineCredentials, n as buildRineClient, o as resolveRineConfigFromCfg, r as sendRineReply, s as INTERNAL_TOOLS, t as rinePlugin } from "./channel-gwGzeOb6.js";
2
2
  import { i as normalizeStandardWebhook, n as normalizeA2A, t as isAllowed } from "./inbound-DEe69mNL.js";
3
3
  import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
4
4
  import { fetchAgents, resolveAgent } from "@rine-network/core";
@@ -80,40 +80,26 @@ var configSchema = {
80
80
  "default": false,
81
81
  "description": "Whether OpenClaw's channel-health-monitor may restart this channel. Recommended false for rine — nothing to monitor."
82
82
  } }
83
+ },
84
+ "payments": {
85
+ "type": "object",
86
+ "additionalProperties": false,
87
+ "description": "x402 payment posture. Opt-in auto-pay is OFF by default: rine_pay stays an explicit, caps-bounded tool call. The spend caps, autoPayThreshold, deny-by-default policy and journal live in the wallet spend policy (rine wallet policy), not here.",
88
+ "properties": { "autoPay": {
89
+ "type": "boolean",
90
+ "default": false,
91
+ "description": "When true, rine_pay defaults to threshold-gated auto-pay — it pays a quote only at/below the wallet policy's autoPayThreshold, else returns above-auto-pay-threshold. Default false: each rine_pay call is the explicit authorization (still bounded by the spend caps)."
92
+ } }
83
93
  }
84
94
  }
85
95
  };
86
96
  //#endregion
87
97
  //#region src/dispatch.ts
88
- /**
89
- * The Funnel relay self-sends this type (`from == to`); it is a one-way event the agent ACTS
90
- * on and must never auto-reply to (the reply is a self-send the server rejects). Compared as a
91
- * literal so the guard never depends on the installed SDK exporting a named constant — mirrors
92
- * the rine-hermes `_FUNNEL_WEBHOOK_TYPE` precedent.
93
- */
94
- const WEBHOOK_TYPE = "rine.v1.webhook";
95
- /**
96
- * A one-way inbound: a Funnel webhook, or any self-addressed message (`from == to`). The agent
97
- * wakes to act via tools, but an auto-reply would target itself and be rejected (REQ-FSH-02).
98
- */
99
- function isOneWayEvent(msg) {
100
- return msg.type === WEBHOOK_TYPE || msg.selfSend === true;
101
- }
98
+ /** Extract a human message from an unknown throwable (shared with `runtime-dispatch.ts`). */
102
99
  function msgOf(err) {
103
100
  return err instanceof Error ? err.message : String(err);
104
101
  }
105
102
  /**
106
- * The inline pointer body — ciphertext stays out of the transcript; agent calls `rine_read`.
107
- *
108
- * The body also binds the reply target to the SENDER. Without this, a model that replies via the
109
- * optional `rine_send` tool can copy the message's own `to_agent_id` (= this agent's id, surfaced
110
- * by `rine_read`) and self-target, producing `Unknown target` (TODO-4). The auto-routing path is
111
- * `rine_reply message_id=<id>`, which derives the recipient from the original sender server-side.
112
- */
113
- function pointerText(msg) {
114
- return `[rine ${msg.type} from ${msg.fromHandle}] message ${msg.id} (read with rine_read; reply with rine_reply message_id=${msg.id} — it auto-routes to ${msg.fromHandle}. If you use rine_send instead, set to=${msg.fromHandle}, never your own agent id.)`;
115
- }
116
- /**
117
103
  * Build the shared `onMessage` path all transports converge on.
118
104
  *
119
105
  * Invariants (acceptance §6):
@@ -161,6 +147,48 @@ function makeOnMessage(deps) {
161
147
  return true;
162
148
  };
163
149
  }
150
+ //#endregion
151
+ //#region src/runtime-dispatch.ts
152
+ /**
153
+ * The Funnel relay self-sends this type (`from == to`); it is a one-way event the agent ACTS
154
+ * on and must never auto-reply to (the reply is a self-send the server rejects). Compared as a
155
+ * literal so the guard never depends on the installed SDK exporting a named constant — mirrors
156
+ * the rine-hermes `_FUNNEL_WEBHOOK_TYPE` precedent.
157
+ */
158
+ const WEBHOOK_TYPE = "rine.v1.webhook";
159
+ /**
160
+ * A one-way inbound: a Funnel webhook, or any self-addressed message (`from == to`). The agent
161
+ * wakes to act via tools, but an auto-reply would target itself and be rejected (REQ-FSH-02).
162
+ */
163
+ function isOneWayEvent(msg) {
164
+ return msg.type === WEBHOOK_TYPE || msg.selfSend === true;
165
+ }
166
+ /**
167
+ * The three x402 payment frames: the 402 quote, the signed authorization, and the settlement
168
+ * receipt. Like a one-way event the agent still wakes to ACT (pay via the `rine_pay` tool, read
169
+ * the receipt), but a canned TEXT auto-reply would be injected into the payment thread and
170
+ * corrupt the x402 handshake. Compared as literals so the guard never depends on the installed
171
+ * core SDK exporting `X402_MESSAGE_TYPE` — mirrors the WEBHOOK_TYPE precedent above.
172
+ */
173
+ const X402_FRAME_TYPES = new Set([
174
+ "rine.v1.x402_payment_required",
175
+ "rine.v1.x402_payment",
176
+ "rine.v1.x402_receipt"
177
+ ]);
178
+ function isX402Frame(msg) {
179
+ return X402_FRAME_TYPES.has(msg.type);
180
+ }
181
+ /**
182
+ * The inline pointer body — ciphertext stays out of the transcript; agent calls `rine_read`.
183
+ *
184
+ * The body also binds the reply target to the SENDER. Without this, a model that replies via the
185
+ * optional `rine_send` tool can copy the message's own `to_agent_id` (= this agent's id, surfaced
186
+ * by `rine_read`) and self-target, producing `Unknown target` (TODO-4). The auto-routing path is
187
+ * `rine_reply message_id=<id>`, which derives the recipient from the original sender server-side.
188
+ */
189
+ function pointerText(msg) {
190
+ return `[rine ${msg.type} from ${msg.fromHandle}] message ${msg.id} (read with rine_read; reply with rine_reply message_id=${msg.id} — it auto-routes to ${msg.fromHandle}. If you use rine_send instead, set to=${msg.fromHandle}, never your own agent id.)`;
191
+ }
164
192
  /**
165
193
  * Production dispatch fn: wakes an agent turn for one inbound rine message via the
166
194
  * canonical `dispatchInboundDirectDmWithRuntime` helper. `api.config` (cfg) and
@@ -203,6 +231,10 @@ function makeRuntimeDispatcher(params) {
203
231
  logger.debug?.(`rine: no auto-reply for one-way ${msg.type} ${msg.id} (act via tools; reply would target self)`);
204
232
  return;
205
233
  }
234
+ if (isX402Frame(msg)) {
235
+ logger.debug?.(`rine: no auto-reply for x402 frame ${msg.type} ${msg.id} (act via the rine_pay tool; a text reply would corrupt the payment handshake)`);
236
+ return;
237
+ }
206
238
  if (opts?.replayed) {
207
239
  logger.debug?.(`rine: no auto-reply for catch-up/replayed ${msg.id} (live-phase frames only)`);
208
240
  return;
@@ -482,10 +514,25 @@ const EXPOSED_TOOLS = [
482
514
  "rine_read",
483
515
  "rine_inbox",
484
516
  "rine_thread",
485
- "rine_onboard"
517
+ "rine_onboard",
518
+ "rine_pay",
519
+ "rine_fulfill"
486
520
  ];
487
521
  /** Mutating tools gated behind the allowlist (manifest `toolMetadata.optional`). */
488
- const OPTIONAL_TOOLS = new Set(["rine_send", "rine_onboard"]);
522
+ const OPTIONAL_TOOLS = new Set([
523
+ "rine_send",
524
+ "rine_onboard",
525
+ "rine_pay",
526
+ "rine_fulfill"
527
+ ]);
528
+ /**
529
+ * Payee tools exposed only when the installed mcp actually provides them. `rine_fulfill`
530
+ * ships in the mcp minor that lands the payee path; every PUBLISHED openclaw pins an mcp
531
+ * `^`-range that includes it, so real installs always expose it. This lenience only covers
532
+ * the version window where a locally-linked mcp predates the payee tool — it never masks a
533
+ * skew of a tool openclaw's core function depends on (those stay strict via `requireTool`).
534
+ */
535
+ const LENIENT_TOOLS = new Set(["rine_fulfill"]);
489
536
  const CIPHERTEXT_KEYS = ["encrypted_payload", "self_encrypted_payload"];
490
537
  /**
491
538
  * Strip raw ciphertext from any tool result before it reaches a transcript.
@@ -511,10 +558,19 @@ function requireTool(byName, name, all) {
511
558
  if (!def) throw new Error(`rine: expected mcp tool "${name}" not found in @rine-network/mcp/tools (got: ${all.map((t) => t.name).join(", ")}) — version skew`);
512
559
  return def;
513
560
  }
514
- /** Filter mcp tools to the exposed set; fail loud on a missing name (version skew). */
561
+ /**
562
+ * Filter mcp tools to the exposed set; fail loud on a missing name (version skew). A
563
+ * {@link LENIENT_TOOLS} entry absent from the installed mcp is skipped (not thrown) so a
564
+ * locally-linked mcp that predates the payee tool still loads — everything else is strict.
565
+ */
515
566
  function selectExposedTools(all = tools) {
516
567
  const byName = new Map(all.map((t) => [t.name, t]));
517
- return EXPOSED_TOOLS.map((name) => requireTool(byName, name, all));
568
+ const out = [];
569
+ for (const name of EXPOSED_TOOLS) {
570
+ if (LENIENT_TOOLS.has(name) && !byName.has(name)) continue;
571
+ out.push(requireTool(byName, name, all));
572
+ }
573
+ return out;
518
574
  }
519
575
  /**
520
576
  * Validate the undeclared internal tools the plugin relies on (e.g. `rine_reply`,
@@ -555,18 +611,39 @@ function toOpenClawTool(def, ctx) {
555
611
  };
556
612
  }
557
613
  /**
558
- * Register the exposed rine tools on the plugin api. `rine_send`/`rine_onboard`
559
- * are registered `{ optional: true }` (allowlist gating per manifest toolMetadata).
560
- * Internal deps (`rine_reply`) are validated here too so version skew fails at load.
614
+ * Opt-in auto-pay (D2). When `channels.rine.payments.autoPay` is true, the exposed
615
+ * `rine_pay` tool defaults its `auto_pay` argument to `true` so a call that omits the
616
+ * argument pays only at/below the wallet policy's `autoPayThreshold` (and returns
617
+ * `above-auto-pay-threshold` otherwise). The model may still pass `auto_pay: false`
618
+ * explicitly to override. Default OFF leaves the shipped explicit-authorization semantics
619
+ * untouched. The wrapper only re-defaults one argument — the caps, deny-by-default policy,
620
+ * journal and reserve-lock all stay in rine-core, unchanged.
621
+ */
622
+ function withAutoPayDefault(def, autoPay) {
623
+ if (!autoPay || def.name !== "rine_pay") return def;
624
+ return {
625
+ ...def,
626
+ handler: (ctx, params) => def.handler(ctx, {
627
+ auto_pay: true,
628
+ ...params ?? {}
629
+ })
630
+ };
631
+ }
632
+ /**
633
+ * Register the exposed rine tools on the plugin api. `rine_send`/`rine_onboard` and the
634
+ * fund-moving `rine_pay`/`rine_fulfill` are registered `{ optional: true }` (allowlist
635
+ * gating per manifest toolMetadata). Internal deps (`rine_reply`) are validated here too so
636
+ * version skew fails at load. With `opts.autoPay`, `rine_pay` is threshold-gated by default.
561
637
  */
562
- function registerRineTools(api, ctx) {
563
- assertInternalTools();
564
- for (const def of selectExposedTools()) {
565
- const opts = OPTIONAL_TOOLS.has(def.name) ? {
638
+ function registerRineTools(api, ctx, opts = {}) {
639
+ assertInternalTools(opts.tools);
640
+ for (const def of selectExposedTools(opts.tools)) {
641
+ const tuned = withAutoPayDefault(def, opts.autoPay === true);
642
+ const regOpts = OPTIONAL_TOOLS.has(def.name) ? {
566
643
  name: def.name,
567
644
  optional: true
568
645
  } : { name: def.name };
569
- api.registerTool(toOpenClawTool(def, ctx), opts);
646
+ api.registerTool(toOpenClawTool(tuned, ctx), regOpts);
570
647
  }
571
648
  }
572
649
  //#endregion
@@ -587,7 +664,8 @@ var rine_openclaw_default = defineChannelPluginEntry({
587
664
  uiHints
588
665
  },
589
666
  registerFull(api) {
590
- registerRineTools(api, buildRineClient(readRineCredentials(resolveRineConfigFromCfg(api.config))).toolContext);
667
+ const config = resolveRineConfigFromCfg(api.config);
668
+ registerRineTools(api, buildRineClient(readRineCredentials(config)).toolContext, { autoPay: config.payments.autoPay });
591
669
  registerExposeRoute(api);
592
670
  api.registerService(makeRineService(api));
593
671
  }
package/dist/setup.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as resolveRineConfig, i as readRineCredentials, t as rinePlugin } from "./channel-BPBnthxw.js";
1
+ import { a as resolveRineConfig, i as readRineCredentials, t as rinePlugin } from "./channel-gwGzeOb6.js";
2
2
  import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
3
3
  //#region setup.ts
4
4
  /** Auto-detect existing rine creds for the setup flow. Pure read; no writes. */
@@ -1,11 +1,11 @@
1
- import type { OpenClawConfig, PluginLogger, PluginRuntime } from "openclaw/plugin-sdk";
1
+ import type { PluginLogger } from "openclaw/plugin-sdk";
2
2
  import type { RineClient } from "./rine-client.js";
3
3
  import type { RineConfig, RineInbound } from "./types.js";
4
4
  /**
5
5
  * The agent-turn dispatch fn — injected so the dedupe/ordering logic is unit-testable
6
- * without the SDK. In production it wraps `dispatchInboundDirectDmWithRuntime`
7
- * (`openclaw/plugin-sdk/channel-inbound`), the canonical inbound→agent-turn helper that
8
- * routes, finalizes the PascalCase ctx, records the session, and dispatches.
6
+ * without the SDK. In production it wraps `dispatchInboundDirectDmWithRuntime` via
7
+ * `makeRuntimeDispatcher` (`runtime-dispatch.ts`), the canonical inbound→agent-turn
8
+ * helper that routes, finalizes the PascalCase ctx, records the session, and dispatches.
9
9
  */
10
10
  export type DispatchFn = (msg: RineInbound, signal: AbortSignal, opts?: DispatchOpts) => Promise<void>;
11
11
  /**
@@ -36,15 +36,8 @@ export interface OnMessageDeps {
36
36
  * a transient error that must be retried, so the cursor must stay behind it.
37
37
  */
38
38
  export type OnMessage = (msg: RineInbound, opts?: DispatchOpts) => Promise<boolean>;
39
- /**
40
- * The inline pointer body — ciphertext stays out of the transcript; agent calls `rine_read`.
41
- *
42
- * The body also binds the reply target to the SENDER. Without this, a model that replies via the
43
- * optional `rine_send` tool can copy the message's own `to_agent_id` (= this agent's id, surfaced
44
- * by `rine_read`) and self-target, producing `Unknown target` (TODO-4). The auto-routing path is
45
- * `rine_reply message_id=<id>`, which derives the recipient from the original sender server-side.
46
- */
47
- export declare function pointerText(msg: RineInbound): string;
39
+ /** Extract a human message from an unknown throwable (shared with `runtime-dispatch.ts`). */
40
+ export declare function msgOf(err: unknown): string;
48
41
  /**
49
42
  * Build the shared `onMessage` path all transports converge on.
50
43
  *
@@ -56,18 +49,3 @@ export declare function pointerText(msg: RineInbound): string;
56
49
  * delivered (recoverable after the operator fixes allowFrom + restarts the gateway).
57
50
  */
58
51
  export declare function makeOnMessage(deps: OnMessageDeps): OnMessage;
59
- /**
60
- * Production dispatch fn: wakes an agent turn for one inbound rine message via the
61
- * canonical `dispatchInboundDirectDmWithRuntime` helper. `api.config` (cfg) and
62
- * `api.runtime` (the `DirectDmRuntime` facade) are threaded in from the service.
63
- *
64
- * Keeps the pointer-body design: `pointerText(msg)` carries only a "read with rine_read"
65
- * pointer, so ciphertext never enters the transcript.
66
- */
67
- export declare function makeRuntimeDispatcher(params: {
68
- cfg: OpenClawConfig;
69
- runtime: PluginRuntime;
70
- client: RineClient;
71
- agentId: string;
72
- logger: PluginLogger;
73
- }): DispatchFn;
@@ -0,0 +1,28 @@
1
+ import type { OpenClawConfig, PluginLogger, PluginRuntime } from "openclaw/plugin-sdk";
2
+ import { type DispatchFn } from "./dispatch.js";
3
+ import type { RineClient } from "./rine-client.js";
4
+ import type { RineInbound } from "./types.js";
5
+ /**
6
+ * The inline pointer body — ciphertext stays out of the transcript; agent calls `rine_read`.
7
+ *
8
+ * The body also binds the reply target to the SENDER. Without this, a model that replies via the
9
+ * optional `rine_send` tool can copy the message's own `to_agent_id` (= this agent's id, surfaced
10
+ * by `rine_read`) and self-target, producing `Unknown target` (TODO-4). The auto-routing path is
11
+ * `rine_reply message_id=<id>`, which derives the recipient from the original sender server-side.
12
+ */
13
+ export declare function pointerText(msg: RineInbound): string;
14
+ /**
15
+ * Production dispatch fn: wakes an agent turn for one inbound rine message via the
16
+ * canonical `dispatchInboundDirectDmWithRuntime` helper. `api.config` (cfg) and
17
+ * `api.runtime` (the `DirectDmRuntime` facade) are threaded in from the service.
18
+ *
19
+ * Keeps the pointer-body design: `pointerText(msg)` carries only a "read with rine_read"
20
+ * pointer, so ciphertext never enters the transcript.
21
+ */
22
+ export declare function makeRuntimeDispatcher(params: {
23
+ cfg: OpenClawConfig;
24
+ runtime: PluginRuntime;
25
+ client: RineClient;
26
+ agentId: string;
27
+ logger: PluginLogger;
28
+ }): DispatchFn;
@@ -2,7 +2,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import type { AnyAgentTool } from "openclaw/plugin-sdk";
3
3
  import type { ToolContext, ToolDef } from "@rine-network/mcp/tools";
4
4
  /** Tools exposed by the plugin (filtered from the mcp tool array). */
5
- export declare const EXPOSED_TOOLS: readonly ["rine_whoami", "rine_discover", "rine_send", "rine_read", "rine_inbox", "rine_thread", "rine_onboard"];
5
+ export declare const EXPOSED_TOOLS: readonly ["rine_whoami", "rine_discover", "rine_send", "rine_read", "rine_inbox", "rine_thread", "rine_onboard", "rine_pay", "rine_fulfill"];
6
6
  /** Mutating tools gated behind the allowlist (manifest `toolMetadata.optional`). */
7
7
  export declare const OPTIONAL_TOOLS: Set<string>;
8
8
  /**
@@ -12,7 +12,11 @@ export declare const OPTIONAL_TOOLS: Set<string>;
12
12
  * `decrypted`/`verified` fields the mcp handlers add.
13
13
  */
14
14
  export declare function stripCiphertext(value: unknown): unknown;
15
- /** Filter mcp tools to the exposed set; fail loud on a missing name (version skew). */
15
+ /**
16
+ * Filter mcp tools to the exposed set; fail loud on a missing name (version skew). A
17
+ * {@link LENIENT_TOOLS} entry absent from the installed mcp is skipped (not thrown) so a
18
+ * locally-linked mcp that predates the payee tool still loads — everything else is strict.
19
+ */
16
20
  export declare function selectExposedTools(all?: ToolDef[]): ToolDef[];
17
21
  /**
18
22
  * Validate the undeclared internal tools the plugin relies on (e.g. `rine_reply`,
@@ -33,8 +37,24 @@ export declare function assertInternalTools(all?: ToolDef[]): void;
33
37
  */
34
38
  export declare function toOpenClawTool(def: ToolDef, ctx: ToolContext): AnyAgentTool;
35
39
  /**
36
- * Register the exposed rine tools on the plugin api. `rine_send`/`rine_onboard`
37
- * are registered `{ optional: true }` (allowlist gating per manifest toolMetadata).
38
- * Internal deps (`rine_reply`) are validated here too so version skew fails at load.
40
+ * Opt-in auto-pay (D2). When `channels.rine.payments.autoPay` is true, the exposed
41
+ * `rine_pay` tool defaults its `auto_pay` argument to `true` so a call that omits the
42
+ * argument pays only at/below the wallet policy's `autoPayThreshold` (and returns
43
+ * `above-auto-pay-threshold` otherwise). The model may still pass `auto_pay: false`
44
+ * explicitly to override. Default OFF leaves the shipped explicit-authorization semantics
45
+ * untouched. The wrapper only re-defaults one argument — the caps, deny-by-default policy,
46
+ * journal and reserve-lock all stay in rine-core, unchanged.
47
+ */
48
+ export declare function withAutoPayDefault(def: ToolDef, autoPay: boolean): ToolDef;
49
+ /** Options for {@link registerRineTools}. `tools` is a test seam (defaults to mcpTools). */
50
+ export interface RegisterToolsOptions {
51
+ autoPay?: boolean;
52
+ tools?: ToolDef[];
53
+ }
54
+ /**
55
+ * Register the exposed rine tools on the plugin api. `rine_send`/`rine_onboard` and the
56
+ * fund-moving `rine_pay`/`rine_fulfill` are registered `{ optional: true }` (allowlist
57
+ * gating per manifest toolMetadata). Internal deps (`rine_reply`) are validated here too so
58
+ * version skew fails at load. With `opts.autoPay`, `rine_pay` is threshold-gated by default.
39
59
  */
40
- export declare function registerRineTools(api: OpenClawPluginApi, ctx: ToolContext): void;
60
+ export declare function registerRineTools(api: OpenClawPluginApi, ctx: ToolContext, opts?: RegisterToolsOptions): void;
@@ -35,6 +35,19 @@ export interface RineInbound {
35
35
  /** Group id (uuid) for group decrypt. */
36
36
  groupId?: string;
37
37
  }
38
+ /**
39
+ * x402 payment posture (D2). Opt-in, default OFF: with `autoPay` false the exposed
40
+ * `rine_pay` tool keeps its shipped explicit-authorization semantics (each call is the
41
+ * authorization, bounded by the wallet spend caps). With `autoPay` true the tool defaults
42
+ * to threshold-gated auto-pay — it pays a quote only at/below the wallet policy's
43
+ * `autoPayThreshold` and returns `above-auto-pay-threshold` otherwise. The threshold, caps,
44
+ * deny-by-default policy, journal and reserve-lock all live in the wallet spend policy
45
+ * (`rine wallet policy`, enforced in rine-core); this flag only flips the tool-call default,
46
+ * it never widens a cap and never touches dispatch.
47
+ */
48
+ export interface RinePaymentsConfig {
49
+ autoPay: boolean;
50
+ }
38
51
  /** Resolved plugin config (after defaults). Mirrors `openclaw.plugin.json` configSchema. */
39
52
  export interface RineConfig {
40
53
  transport: "expose" | "sse" | "poll";
@@ -47,6 +60,7 @@ export interface RineConfig {
47
60
  exposeBaseUrl?: string;
48
61
  a2aAcceptCleartext: boolean;
49
62
  allowFrom: string[];
63
+ payments: RinePaymentsConfig;
50
64
  }
51
65
  /** Disposition for a sender vs the allowlist. */
52
66
  export type AllowDecision = "allowed" | "quarantined";
@@ -3,7 +3,7 @@
3
3
  "kind": "channel",
4
4
  "name": "rine",
5
5
  "description": "Agent-to-agent E2EE messaging over the rine network (A2A relay / SSE / poll).",
6
- "version": "0.2.1",
6
+ "version": "0.3.0",
7
7
  "channels": ["rine"],
8
8
  "skills": ["skills/rine"],
9
9
  "activation": { "onStartup": true },
@@ -15,12 +15,16 @@
15
15
  "rine_read",
16
16
  "rine_inbox",
17
17
  "rine_thread",
18
- "rine_onboard"
18
+ "rine_onboard",
19
+ "rine_pay",
20
+ "rine_fulfill"
19
21
  ]
20
22
  },
21
23
  "toolMetadata": {
22
24
  "rine_send": { "optional": true },
23
- "rine_onboard": { "optional": true }
25
+ "rine_onboard": { "optional": true },
26
+ "rine_pay": { "optional": true },
27
+ "rine_fulfill": { "optional": true }
24
28
  },
25
29
  "uiHints": {
26
30
  "exposeBaseUrl": {
@@ -98,6 +102,18 @@
98
102
  "description": "Whether OpenClaw's channel-health-monitor may restart this channel. Recommended false for rine — nothing to monitor."
99
103
  }
100
104
  }
105
+ },
106
+ "payments": {
107
+ "type": "object",
108
+ "additionalProperties": false,
109
+ "description": "x402 payment posture. Opt-in auto-pay is OFF by default: rine_pay stays an explicit, caps-bounded tool call. The spend caps, autoPayThreshold, deny-by-default policy and journal live in the wallet spend policy (rine wallet policy), not here.",
110
+ "properties": {
111
+ "autoPay": {
112
+ "type": "boolean",
113
+ "default": false,
114
+ "description": "When true, rine_pay defaults to threshold-gated auto-pay — it pays a quote only at/below the wallet policy's autoPayThreshold, else returns above-auto-pay-threshold. Default false: each rine_pay call is the explicit authorization (still bounded by the spend caps)."
115
+ }
116
+ }
101
117
  }
102
118
  }
103
119
  }
@@ -166,6 +182,18 @@
166
182
  "description": "Whether OpenClaw's channel-health-monitor may restart this channel. Recommended false for rine — nothing to monitor."
167
183
  }
168
184
  }
185
+ },
186
+ "payments": {
187
+ "type": "object",
188
+ "additionalProperties": false,
189
+ "description": "x402 payment posture. Opt-in auto-pay is OFF by default: rine_pay stays an explicit, caps-bounded tool call. The spend caps, autoPayThreshold, deny-by-default policy and journal live in the wallet spend policy (rine wallet policy), not here.",
190
+ "properties": {
191
+ "autoPay": {
192
+ "type": "boolean",
193
+ "default": false,
194
+ "description": "When true, rine_pay defaults to threshold-gated auto-pay — it pays a quote only at/below the wallet policy's autoPayThreshold, else returns above-auto-pay-threshold. Default false: each rine_pay call is the explicit authorization (still bounded by the spend caps)."
195
+ }
196
+ }
169
197
  }
170
198
  }
171
199
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rine-network/openclaw",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Official OpenClaw plugin for rine.network \u2014 agent-to-agent E2EE messaging as a native channel, with A2A-relay / SSE / poll transports, tools, and the bundled rine skill.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -64,8 +64,8 @@
64
64
  "prepublishOnly": "node scripts/check-no-file-deps.mjs"
65
65
  },
66
66
  "dependencies": {
67
- "@rine-network/core": "^0.8.1",
68
- "@rine-network/mcp": "^0.6.0"
67
+ "@rine-network/core": "^0.11.0",
68
+ "@rine-network/mcp": "^0.8.0"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "openclaw": ">=2026.6.1"