@rine-network/openclaw 0.4.0 → 0.6.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.
@@ -2,15 +2,25 @@ import type { OpenClawConfig, PluginLogger, PluginRuntime } from "openclaw/plugi
2
2
  import { type DispatchFn } from "./dispatch.js";
3
3
  import type { RineClient } from "./rine-client.js";
4
4
  import type { RineInbound } from "./types.js";
5
+ /** Where an agent's turn is delivered: to the whole group, or back to the sender. */
6
+ type AnswerRoute = {
7
+ kind: "group" | "direct";
8
+ handle: string;
9
+ };
5
10
  /**
6
11
  * The inline pointer body — ciphertext stays out of the transcript; agent calls `rine_read`.
7
12
  *
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`. The auto-routing path is
11
- * `rine_reply message_id=<id>`, which derives the recipient from the original sender server-side.
13
+ * The body names only tools this plugin exposes. It used to point at `rine_reply`, which is
14
+ * an INTERNAL tool the host never registers, so every inbound spent a turn on tool calls the
15
+ * runtime answered with `Tool rine_reply not found`. The plugin routes the turn's text out on
16
+ * its own, so the correct instruction is to answer and call nothing.
17
+ *
18
+ * The body also states the audience. On a group post the old text named the poster twice and
19
+ * the group never, i.e. it instructed the model to answer a group by privately DM'ing whoever
20
+ * spoke last. `route` is resolved once by the dispatcher and passed in, so what the model is
21
+ * told matches what `deliver` actually does — absent when the turn is not delivered at all.
12
22
  */
13
- export declare function pointerText(msg: RineInbound): string;
23
+ export declare function pointerText(msg: RineInbound, route?: AnswerRoute): string;
14
24
  /**
15
25
  * Production dispatch fn: wakes an agent turn for one inbound rine message via the
16
26
  * canonical `dispatchInboundDirectDmWithRuntime` helper. `api.config` (cfg) and
@@ -26,3 +36,4 @@ export declare function makeRuntimeDispatcher(params: {
26
36
  agentId: string;
27
37
  logger: PluginLogger;
28
38
  }): DispatchFn;
39
+ export {};
@@ -5,21 +5,11 @@ import type { TransportContext } from "./transports/context.js";
5
5
  import type { RineConfig } from "./types.js";
6
6
  /** Run the chosen transport. Exposed for testing with a stubbed transport map. */
7
7
  export declare function runTransport(tc: TransportContext): Promise<void>;
8
- /**
9
- * Resolve the rine agent id this install is bound to. `channels.rine.agentId` (a UUID,
10
- * handle, or bare name) wins; otherwise the org's sole agent is auto-selected — the same
11
- * resolution the CLI uses (`fetchAgents` + `resolveAgent`).
12
- *
13
- * The OAuth `client_id` is NOT an agent id: credentials.json stores `client_id`/`client_secret`,
14
- * and binding the transport to it requests `/agents/{client_id}/stream|messages`, which 404s on
15
- * every transport. Returns undefined (the service idles) on a network error or an ambiguous
16
- * multi-agent org with no `agentId` set — both surface an actionable log; the gateway
17
- * health-monitor restarts the idle service, re-resolving once connectivity/config is fixed.
18
- */
19
8
  export declare function resolveBoundAgentId(client: RineClient, config: RineConfig, logger: PluginLogger): Promise<string | undefined>;
20
9
  /**
21
10
  * The single background notify service. `start(ctx)` resolves creds, owns the
22
- * AbortController (the service ctx has NO abort signal — see SDK_CONTRACT.md), then
11
+ * AbortController OpenClaw hands a service context that carries no abort signal, so
12
+ * cancellation is ours to construct and ours to fire — then
23
13
  * hands off to `startNotifyLoop` which resolves the bound agent id and runs the transport.
24
14
  * `stop` aborts it. The long-lived loop lives here, not in a channel `gateway.startAccount`.
25
15
  */
@@ -0,0 +1,52 @@
1
+ import type { ToolDef } from "@rine-network/mcp/tools";
2
+ import type { EXPOSED_TOOLS } from "./tools.js";
3
+ type ExposedTool = (typeof EXPOSED_TOOLS)[number];
4
+ /**
5
+ * The exposed tools that take an acting agent.
6
+ *
7
+ * A set rather than a per-tool spelling: rine-mcp spells the concept `agent` on every one
8
+ * of them, sends included, so the map's values had collapsed to one word and the only
9
+ * thing left to record is membership. The retired spellings (`as`, and `from` on the two
10
+ * sends) are still *read* by rine-mcp, but nothing here writes them — a wrapper that
11
+ * defaulted a retired word would put it back in front of the model.
12
+ *
13
+ * A tool absent from this set takes no acting agent at all. Where such a tool has an
14
+ * `agent`-shaped argument it means something else: `rine_inspect`'s `agent` is the
15
+ * agent being looked up, `rine_onboard`'s `agent_name` names the agent being created,
16
+ * and `rine_pay`/`rine_fulfill` derive the payee from the quote's own addressee.
17
+ *
18
+ * `rine_group_invite` is the tool the rename moved. Its `agent` used to be the
19
+ * **invitee** and is now the actor, the invitee having taken its own `invitee` /
20
+ * `invitees` slots. No presence check can tell those two readings apart — the property
21
+ * exists either way — so the pair is pinned against the shipped schema by test, and
22
+ * what makes that test meaningful is `invitee` existing at all: the invitee has
23
+ * nowhere else to live. Swapping them would invite the installation's own agent and
24
+ * report success.
25
+ */
26
+ export declare const ACTOR_TOOLS: ReadonlySet<ExposedTool>;
27
+ /**
28
+ * Bind the acting agent this install is configured for (`channels.rine.agentId`) as the
29
+ * default of the tool's own acting-agent argument. Without it the plugin emits
30
+ * `X-Rine-Agent` on no tool call at all, so a host running several agents acts as
31
+ * whichever one the org's single-agent shortcut picks — or fails to resolve one.
32
+ *
33
+ * The operator's string travels **unresolved**: rine-mcp accepts a name, handle or UUID
34
+ * and resolves it per call. This is the tool-facing half of `channels.rine.agentId`, and
35
+ * it is not the same object as the notify service's binding — `resolveBoundAgentId`
36
+ * turns the same setting into one UUID, up front, for the inbox the transports read.
37
+ *
38
+ * A model-supplied value still wins, so the operator's binding is a default and not a
39
+ * cap — but an empty string names nobody, and letting one through would discard the
40
+ * binding and hand the choice straight back to the server. 🔴 "Did the model name one?"
41
+ * is rine-mcp's question to answer, not this file's: it reads retired spellings this
42
+ * plugin never writes, and defaulting `agent` beside a model-supplied `from` would turn
43
+ * one caller's choice into a two-values refusal. Configuration is a lower rung than the
44
+ * call, and a lower rung loses silently.
45
+ *
46
+ * Mirrors {@link withAutoPayDefault}: one argument re-defaulted, nothing else touched. A
47
+ * tool with no acting-agent argument is returned unchanged, and a tool whose argument has
48
+ * disappeared upstream throws at load rather than silently dropping the actor — the
49
+ * same version-skew posture as `requireTool`.
50
+ */
51
+ export declare function withActingAgent(def: ToolDef, agentId: string | undefined): ToolDef;
52
+ export {};
@@ -2,8 +2,44 @@ 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", "rine_pay", "rine_fulfill"];
6
- /** Mutating tools gated behind the allowlist (manifest `toolMetadata.optional`). */
5
+ export declare const EXPOSED_TOOLS: readonly ["rine_whoami", "rine_discover", "rine_inspect", "rine_discover_groups", "rine_groups", "rine_group_create", "rine_group_join", "rine_group_roster", "rine_group_inspect", "rine_group_invite", "rine_group_invites", "rine_group_requests", "rine_group_vote", "rine_group_leave", "rine_group_remove", "rine_group_sync", "rine_group_reclaim", "rine_send", "rine_send_and_wait", "rine_read", "rine_inbox", "rine_thread", "rine_onboard", "rine_pay", "rine_fulfill"];
6
+ /**
7
+ * Tools with an irreversible external effect ON A THIRD PARTY, gated behind the operator's
8
+ * tool allowlist. The manifest's `toolMetadata.optional` carries the same set — OpenClaw ORs
9
+ * the two, so both must agree. `rine_send` and `rine_send_and_wait` speak to a third party
10
+ * over an encrypted channel — the second one is a send like any other, and the reply it waits
11
+ * for does not make the message it already delivered take-backable;
12
+ * `rine_pay` moves funds and `rine_fulfill` settles on-chain.
13
+ *
14
+ * `rine_group_invite`, `rine_group_vote`, `rine_group_remove` and `rine_group_reclaim` join
15
+ * them for the same reason. An invite acts on the group's roster on membership alone: the
16
+ * server authorises it with read access rather than admin on every policy but `closed`. On
17
+ * a closed group it hands the named agent a seat it can redeem straight away, together with
18
+ * a ratchet-tree leaf and Welcome minted inside the same call; on a majority or unanimity
19
+ * group it files a join request in the group's name and spends this host's own approval on
20
+ * it, and the electorate decides. An approve that crosses a group's threshold hands a
21
+ * stranger the group's keys and cannot be taken back. A remove evicts somebody else — on an
22
+ * MLS group it posts a Remove commit that takes their ratchet-tree leaf and that every
23
+ * member downloads at O(N). Reclamation posts one Remove commit per orphaned leaf, and an
24
+ * agent whose invitation lapsed and who is re-invited between the tree read and the commit
25
+ * loses the fresh leaf with them.
26
+ *
27
+ * Note the asymmetry with the ungated `rine_group_sync`, whose expensive rung also costs
28
+ * the group O(N): the criterion is an irreversible effect on a THIRD PARTY, not cost.
29
+ *
30
+ * Every other group verb is offered under the default empty allowlist, on the same reasoning
31
+ * that keeps `rine_onboard` out of this set: gating a tool the plugin's core function depends
32
+ * on leaves a fresh agent unable to work. An agent that cannot create, discover, join or read
33
+ * a group is not a group participant at all, and an operator would have to edit the allowlist
34
+ * before the plugin did anything with groups. Note the asymmetry with `rine_group_leave`,
35
+ * which is irreversible but acts only on THIS host: gating it would leave an agent unable to
36
+ * get out of a group somebody else put it in.
37
+ *
38
+ * `rine_onboard` is deliberately absent: registering the org is the first thing an
39
+ * uncredentialed install does, and on an install that already holds credentials the tool
40
+ * returns the existing org and writes nothing. Gating it leaves a fresh agent unable to
41
+ * register itself.
42
+ */
7
43
  export declare const OPTIONAL_TOOLS: Set<string>;
8
44
  /**
9
45
  * Strip raw ciphertext from any tool result before it reaches a transcript.
@@ -27,7 +63,7 @@ export declare function assertInternalTools(all?: ToolDef[]): void;
27
63
  /**
28
64
  * Adapt one mcp ToolDef into an OpenClaw AnyAgentTool with ciphertext stripping.
29
65
  * `parameters` is rine-mcp's JSON-schema `inputSchema`; `registerTool` stores it
30
- * verbatim (no TypeBox validation see SDK_CONTRACT.md), so the whole tool object
66
+ * verbatim and validates none of it against TypeBox, so the whole tool object
31
67
  * is cast to `AnyAgentTool` at one documented seam. (typebox is a transitive dep of
32
68
  * openclaw, not directly importable, so we never reference its `TSchema` type.)
33
69
  *
@@ -49,12 +85,19 @@ export declare function withAutoPayDefault(def: ToolDef, autoPay: boolean): Tool
49
85
  /** Options for {@link registerRineTools}. `tools` is a test seam (defaults to mcpTools). */
50
86
  export interface RegisterToolsOptions {
51
87
  autoPay?: boolean;
88
+ /** The agent this install acts as (`channels.rine.agentId`), named on every tool call that takes one. */
89
+ agentId?: string;
52
90
  tools?: ToolDef[];
53
91
  }
54
92
  /**
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.
93
+ * Register the exposed rine tools on the plugin api. The {@link OPTIONAL_TOOLS} register
94
+ * `{ optional: true }`, so OpenClaw offers them only when the agent's tool allowlist names
95
+ * them; every other tool `rine_onboard` included is offered under the default empty
96
+ * allowlist. Internal deps (`rine_reply`) are validated here too so version skew fails at
97
+ * load. With `opts.autoPay`, `rine_pay` is threshold-gated by default; with `opts.agentId`,
98
+ * every tool that declares an acting-agent argument defaults it to that agent (see
99
+ * {@link withActingAgent}). What each tool then does with the argument is rine-mcp's:
100
+ * nearly all of them send it as `X-Rine-Agent`, while `rine_read` reads it only to pick
101
+ * the identity it decrypts as and sends no header of its own.
59
102
  */
60
103
  export declare function registerRineTools(api: OpenClawPluginApi, ctx: ToolContext, opts?: RegisterToolsOptions): void;
@@ -52,6 +52,14 @@ export interface RinePaymentsConfig {
52
52
  export interface RineConfig {
53
53
  transport: "expose" | "sse" | "poll";
54
54
  configDir?: string;
55
+ /**
56
+ * The agent this install acts as, exactly as the operator wrote it — a bare name, a
57
+ * handle or a UUID, never resolved here. Its two consumers are not the same notion:
58
+ * `resolveBoundAgentId` turns it into the UUID the notify service binds its inbox to,
59
+ * while `withActingAgent` threads the raw string onto every tool call as that call's
60
+ * acting-agent default and lets rine-mcp resolve it per call. Unset means "the org's
61
+ * only agent" on both paths, and nothing at all on an org that has several.
62
+ */
55
63
  agentId?: string;
56
64
  baseUrl?: string;
57
65
  pollIntervalMs: number;
@@ -1,5 +1,4 @@
1
- import { r as normalizeRineEvent } from "./inbound-DEe69mNL.js";
2
- import { n as sleep, t as backoff } from "./backoff-BMNABavv.js";
1
+ import { a as normalizeRineEvent, n as sleep, t as backoff } from "./backoff-a3Dly8X4.js";
3
2
  import { n as saveCursor, t as loadCursor } from "./cursor-DPl6-dKW.js";
4
3
  //#region src/transports/sse.ts
5
4
  const MAX_ATTEMPTS_BEFORE_FALLBACK = 5;
@@ -57,7 +56,7 @@ async function runSseTransport(tc) {
57
56
  logger.warn(`rine: SSE stream error (attempt ${attempt}): ${err instanceof Error ? err.message : String(err)}`);
58
57
  if (attempt >= MAX_ATTEMPTS_BEFORE_FALLBACK) {
59
58
  logger.warn("rine: SSE failed repeatedly — falling back to POLL transport");
60
- const { runPollTransport } = await import("./poll-DKsUxQpG.js");
59
+ const { runPollTransport } = await import("./poll-BWvVevTN.js");
61
60
  await runPollTransport(tc);
62
61
  return;
63
62
  }
@@ -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.3.0",
6
+ "version": "0.6.0",
7
7
  "channels": ["rine"],
8
8
  "skills": ["skills/rine"],
9
9
  "activation": { "onStartup": true },
@@ -11,7 +11,23 @@
11
11
  "tools": [
12
12
  "rine_whoami",
13
13
  "rine_discover",
14
+ "rine_inspect",
15
+ "rine_discover_groups",
16
+ "rine_groups",
17
+ "rine_group_create",
18
+ "rine_group_join",
19
+ "rine_group_roster",
20
+ "rine_group_inspect",
21
+ "rine_group_invite",
22
+ "rine_group_invites",
23
+ "rine_group_requests",
24
+ "rine_group_vote",
25
+ "rine_group_leave",
26
+ "rine_group_remove",
27
+ "rine_group_sync",
28
+ "rine_group_reclaim",
14
29
  "rine_send",
30
+ "rine_send_and_wait",
15
31
  "rine_read",
16
32
  "rine_inbox",
17
33
  "rine_thread",
@@ -22,9 +38,13 @@
22
38
  },
23
39
  "toolMetadata": {
24
40
  "rine_send": { "optional": true },
25
- "rine_onboard": { "optional": true },
41
+ "rine_send_and_wait": { "optional": true },
26
42
  "rine_pay": { "optional": true },
27
- "rine_fulfill": { "optional": true }
43
+ "rine_fulfill": { "optional": true },
44
+ "rine_group_invite": { "optional": true },
45
+ "rine_group_vote": { "optional": true },
46
+ "rine_group_remove": { "optional": true },
47
+ "rine_group_reclaim": { "optional": true }
28
48
  },
29
49
  "uiHints": {
30
50
  "exposeBaseUrl": {
@@ -55,7 +75,7 @@
55
75
  },
56
76
  "agentId": {
57
77
  "type": "string",
58
- "description": "rine agent id to bind (default: credentialed agent)."
78
+ "description": "The rine agent this install acts as whose inbox the notify service reads, and the acting agent defaulted onto every tool call that takes one. Name, handle, or UUID. Unset, the RINE_AGENT environment variable is used, then the org's only agent; an org with more than one and neither set idles the notify service."
59
79
  },
60
80
  "baseUrl": {
61
81
  "type": "string",
@@ -64,7 +84,7 @@
64
84
  "pollIntervalMs": {
65
85
  "type": "number",
66
86
  "default": 60000,
67
- "description": "POLL only: interval between /poll checks."
87
+ "description": "Interval between /poll checks on the POLL transport, and how often an install without credentials re-checks for them on any transport."
68
88
  },
69
89
  "reconnectBaseMs": {
70
90
  "type": "number",
@@ -135,7 +155,7 @@
135
155
  },
136
156
  "agentId": {
137
157
  "type": "string",
138
- "description": "rine agent id to bind (default: credentialed agent)."
158
+ "description": "The rine agent this install acts as whose inbox the notify service reads, and the acting agent defaulted onto every tool call that takes one. Name, handle, or UUID. Unset, the RINE_AGENT environment variable is used, then the org's only agent; an org with more than one and neither set idles the notify service."
139
159
  },
140
160
  "baseUrl": {
141
161
  "type": "string",
@@ -144,7 +164,7 @@
144
164
  "pollIntervalMs": {
145
165
  "type": "number",
146
166
  "default": 60000,
147
- "description": "POLL only: interval between /poll checks."
167
+ "description": "Interval between /poll checks on the POLL transport, and how often an install without credentials re-checks for them on any transport."
148
168
  },
149
169
  "reconnectBaseMs": {
150
170
  "type": "number",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rine-network/openclaw",
3
- "version": "0.4.0",
3
+ "version": "0.6.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",
@@ -60,12 +60,11 @@
60
60
  "build": "tsdown && tsc --emitDeclarationOnly -p tsconfig.build.json",
61
61
  "typecheck": "tsc --noEmit",
62
62
  "test": "vitest run",
63
- "sync-skill": "node scripts/sync-skill.mjs",
64
63
  "prepublishOnly": "node scripts/check-no-file-deps.mjs"
65
64
  },
66
65
  "dependencies": {
67
- "@rine-network/core": "^0.12.0",
68
- "@rine-network/mcp": "^0.9.0"
66
+ "@rine-network/core": "^0.14.0",
67
+ "@rine-network/mcp": "^0.11.0"
69
68
  },
70
69
  "peerDependencies": {
71
70
  "openclaw": ">=2026.6.1"
@@ -80,7 +79,7 @@
80
79
  "homepage": "https://rine.network",
81
80
  "repository": {
82
81
  "type": "git",
83
- "url": "https://codeberg.org/rine/openclaw"
82
+ "url": "https://codeberg.org/rine/rine-openclaw"
84
83
  },
85
84
  "publishConfig": {
86
85
  "access": "public"