@nhtio/rocket-chat-openclaw-integration 0.1.0-master-7f84cdcb
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/LICENSE.md +9 -0
- package/README.md +212 -0
- package/index.cjs +2 -0
- package/index.cjs.map +1 -0
- package/index.d.mts +751 -0
- package/index.mjs +630 -0
- package/index.mjs.map +1 -0
- package/openclaw.plugin.json +51 -0
- package/package.json +42 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 New Horizon Technology LTD (nht.io)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Rocket.Chat OpenClaw Integration (Spec)
|
|
2
|
+
|
|
3
|
+
This repository specifies and implements an integration between:
|
|
4
|
+
|
|
5
|
+
- Rocket.Chat (self-hosted, via Realtime API WebSocket/DDP)
|
|
6
|
+
- OpenClaw (via a first-class "channel" plugin integration, comparable to Slack/Telegram/Discord)
|
|
7
|
+
|
|
8
|
+
Primary docs referenced by this spec:
|
|
9
|
+
|
|
10
|
+
- Rocket.Chat Realtime API (deprecated notice applies; we accept risk for v1): <https://developer.rocket.chat/apidocs/realtimeapi>
|
|
11
|
+
- OpenClaw channels:
|
|
12
|
+
- Slack: <https://docs.openclaw.ai/channels/slack>
|
|
13
|
+
- Telegram: <https://docs.openclaw.ai/channels/telegram>
|
|
14
|
+
- Discord: <https://docs.openclaw.ai/channels/discord>
|
|
15
|
+
- OpenClaw plugin development: <https://docs.openclaw.ai/plugins/building-plugins>
|
|
16
|
+
- OpenClaw channel plugins (how they work): <https://docs.openclaw.ai/plugins/sdk-channel-plugins#how-channel-plugins-work>
|
|
17
|
+
|
|
18
|
+
## Goals
|
|
19
|
+
|
|
20
|
+
- Provide a Rocket.Chat "channel" integration for OpenClaw that matches existing channel behavior (send/receive, threading/replies where applicable, identity mapping, attachments, reactions, etc.).
|
|
21
|
+
- Implement as an OpenClaw channel plugin that connects directly to Rocket.Chat.
|
|
22
|
+
- Keep OpenClaw-side changes minimal; introduce additional OpenClaw plugin capabilities only if required to achieve parity with existing channel integrations.
|
|
23
|
+
|
|
24
|
+
## Non-goals (initial)
|
|
25
|
+
|
|
26
|
+
- Re-implement Rocket.Chat's full permission/admin surface; only what is required for install, auth, and message/event delivery.
|
|
27
|
+
- Multi-workspace support (assume one Rocket.Chat workspace per OpenClaw gateway for v1).
|
|
28
|
+
- Backfilling historical messages (focus on real-time sync first).
|
|
29
|
+
|
|
30
|
+
## High-level Architecture
|
|
31
|
+
|
|
32
|
+
We expect a single OpenClaw channel plugin that connects directly to Rocket.Chat.
|
|
33
|
+
|
|
34
|
+
- Transport: Rocket.Chat Realtime API (WebSocket/DDP) (deprecated per Rocket.Chat docs; accept risk for v1)
|
|
35
|
+
- Target Rocket.Chat version (v1): 8.2.0 (self-hosted; upgrades are controlled)
|
|
36
|
+
- Bot identity: one Rocket.Chat user per OpenClaw agent, each with the `bot` role (admin-provisioned)
|
|
37
|
+
- Auth: Personal Access Token (PAT) per bot user (manual provisioning)
|
|
38
|
+
|
|
39
|
+
A Rocket.Chat Apps-Engine app is explicitly out of scope for v1 (keeps deployment and ops simpler). If Rocket.Chat later removes or breaks Realtime API subscription streams we rely on, we will revisit an Apps-Engine-based design.
|
|
40
|
+
|
|
41
|
+
## OpenClaw Channel Plugin Expectations (Draft)
|
|
42
|
+
|
|
43
|
+
Per the OpenClaw channel plugin documentation, OpenClaw core owns the shared
|
|
44
|
+
`message` tool and core prompt wiring. The channel plugin typically owns:
|
|
45
|
+
|
|
46
|
+
- Config (account resolution + setup)
|
|
47
|
+
- Security (DM policy + allowlists)
|
|
48
|
+
- Pairing (DM approval flow)
|
|
49
|
+
- Session grammar (mapping provider conversation ids to base chats + threads)
|
|
50
|
+
- Outbound delivery (send text/media/polls)
|
|
51
|
+
- Threading semantics (reply behavior)
|
|
52
|
+
|
|
53
|
+
Canonical session-id parsing hook:
|
|
54
|
+
|
|
55
|
+
- `messaging.resolveSessionConversation(...)` (and optionally a bootstrap-safe
|
|
56
|
+
`session-key-api.ts` export for bundled plugins)
|
|
57
|
+
|
|
58
|
+
Reference: <https://docs.openclaw.ai/plugins/sdk-channel-plugins#how-channel-plugins-work>
|
|
59
|
+
|
|
60
|
+
## Interfaces & Data Flow (Draft)
|
|
61
|
+
|
|
62
|
+
- Inbound (Rocket.Chat -> OpenClaw)
|
|
63
|
+
- OpenClaw channel plugin maintains a Realtime API WebSocket connection per configured bot account.
|
|
64
|
+
- The plugin subscribes to message streams for allowlisted rooms and forwards eligible events into OpenClaw sessions.
|
|
65
|
+
- Outbound (OpenClaw -> Rocket.Chat)
|
|
66
|
+
- OpenClaw issues channel actions (send text/media/reactions/etc) which the channel plugin executes via Rocket.Chat APIs.
|
|
67
|
+
|
|
68
|
+
## Authentication & Trust (v1)
|
|
69
|
+
|
|
70
|
+
Trust boundary is Rocket.Chat <-> OpenClaw. There is no intermediate Apps-Engine app.
|
|
71
|
+
|
|
72
|
+
- Bot auth mechanism: Rocket.Chat Personal Access Tokens (PATs) + `userId` (manual admin provisioning).
|
|
73
|
+
- PAT docs: <https://docs.rocket.chat/docs/manage-personal-access-tokens>
|
|
74
|
+
- Transport security:
|
|
75
|
+
- The plugin MUST use `wss://` for Realtime API connections and `https://` for any REST calls.
|
|
76
|
+
- TLS certificate validation MUST be enabled (no insecure / skip-verify) unless an explicit
|
|
77
|
+
`dangerouslyAllowInsecure: true`-style config flag is set for local dev.
|
|
78
|
+
- Secret storage: PATs are stored only in OpenClaw config (not in repo) and treated as credentials equivalent to a password.
|
|
79
|
+
- Rotation / revocation: rotating or deleting a PAT in Rocket.Chat must revoke OpenClaw access for that bot.
|
|
80
|
+
Enforcement requirements for long-lived connections:
|
|
81
|
+
- On any auth error/unauthorized response on the WebSocket (or any REST call), the plugin MUST immediately close the WebSocket and stop processing events for that account.
|
|
82
|
+
- The plugin MUST periodically re-validate credentials (REST check) on a fixed cadence to bound worst-case revocation delay.
|
|
83
|
+
Default target: <= 5 minutes.
|
|
84
|
+
- Blast radius: one bot account per OpenClaw agent id; compromising one PAT should not compromise other agents.
|
|
85
|
+
- Session-level authorization: inbound events must still pass OpenClaw allowlist + policy checks (DM policy, group policy, mention gating, per-room/per-user allowlists) before reaching the model.
|
|
86
|
+
|
|
87
|
+
## Message Semantics (Draft)
|
|
88
|
+
|
|
89
|
+
For v1, feature scope should match the Slack channel integration behavior and
|
|
90
|
+
capabilities as documented here:
|
|
91
|
+
|
|
92
|
+
- Slack channel docs: <https://docs.openclaw.ai/channels/slack>
|
|
93
|
+
|
|
94
|
+
At minimum, Slack documents inbound event subscriptions for:
|
|
95
|
+
|
|
96
|
+
- message events across channel types (`message.channels`, `message.groups`,
|
|
97
|
+
`message.im`, `message.mpim`)
|
|
98
|
+
- reactions (`reaction_added`, `reaction_removed`)
|
|
99
|
+
|
|
100
|
+
We should target comparable Rocket.Chat capabilities over the Realtime API.
|
|
101
|
+
|
|
102
|
+
Chat type semantics (Slack-aligned):
|
|
103
|
+
|
|
104
|
+
- DMs: 1:1 conversations (no mention requirement by default; gated by `dmPolicy`)
|
|
105
|
+
- Group chats: any multi-user room (public channels + private groups)
|
|
106
|
+
- mention-gated by default
|
|
107
|
+
- gated by group policy + allowlists (Slack-style defaults)
|
|
108
|
+
|
|
109
|
+
Draft mapping:
|
|
110
|
+
|
|
111
|
+
- Identity mapping: Rocket.Chat users <-> OpenClaw authors
|
|
112
|
+
- Rooms/channels: Rocket.Chat rooms <-> OpenClaw channel targets
|
|
113
|
+
- Threads/replies: follow Slack-style reply controls (reply-to mode + thread session keys)
|
|
114
|
+
- Attachments: deferred until explicitly specified (v1 supports plain text only)
|
|
115
|
+
- Edits/deletes: parity with Slack channel behavior
|
|
116
|
+
- Reactions: v1 supports reaction add/remove (Slack parity)
|
|
117
|
+
|
|
118
|
+
## Deliverables
|
|
119
|
+
|
|
120
|
+
- An OpenClaw channel plugin for Rocket.Chat.
|
|
121
|
+
- Minimal installation/configuration steps.
|
|
122
|
+
|
|
123
|
+
Provisioning assumptions (v1):
|
|
124
|
+
|
|
125
|
+
- One Rocket.Chat bot user per OpenClaw agent id.
|
|
126
|
+
- Admin provides per-bot credentials/config into OpenClaw:
|
|
127
|
+
- `serverUrl`
|
|
128
|
+
- `userId`
|
|
129
|
+
- `username` (for mention gating)
|
|
130
|
+
- `pat` (personal access token)
|
|
131
|
+
- Room allowlists accept room ids and/or room names; names are resolved to ids at startup when possible (Slack-style).
|
|
132
|
+
|
|
133
|
+
## Compatibility Targets (Slack-parity)
|
|
134
|
+
|
|
135
|
+
Slack parity is the reference for how features *should* behave when implemented.
|
|
136
|
+
For v1, this spec is explicit about what is in-scope vs deferred.
|
|
137
|
+
|
|
138
|
+
- Reference behavior: <https://docs.openclaw.ai/channels/slack>
|
|
139
|
+
|
|
140
|
+
V1 required support (MUST):
|
|
141
|
+
|
|
142
|
+
- Inbound: new messages (DMs + group chats), mention-gating in group chats
|
|
143
|
+
- Outbound: send text, reply in thread when inbound is threaded
|
|
144
|
+
- Reactions: reaction add/remove
|
|
145
|
+
- Commands: minimal prefix commands in-message (see below)
|
|
146
|
+
|
|
147
|
+
Deferred until explicitly specified (NOT v1):
|
|
148
|
+
|
|
149
|
+
- Edits
|
|
150
|
+
- Deletes
|
|
151
|
+
- Attachments (beyond plain text)
|
|
152
|
+
|
|
153
|
+
When we add any deferred item, it must match Slack semantics unless Rocket.Chat cannot represent it.
|
|
154
|
+
|
|
155
|
+
## Commands (v1)
|
|
156
|
+
|
|
157
|
+
We intentionally do not use Rocket.Chat slash commands.
|
|
158
|
+
|
|
159
|
+
- Syntax: prefix `!` at the start of the message (after trim)
|
|
160
|
+
- Examples: `!status`, `!model g5.2`, `!stop`
|
|
161
|
+
- Parsing precedence:
|
|
162
|
+
- If the trimmed message starts with `!`, treat it as a command; do not send the command text to the model.
|
|
163
|
+
- Otherwise treat as normal chat content.
|
|
164
|
+
|
|
165
|
+
Authorization model:
|
|
166
|
+
|
|
167
|
+
- Baseline gating (all commands): the sender must already be authorized for this room/DM by the normal channel policy
|
|
168
|
+
(room allowlist + group policy + user allowlists / dmPolicy).
|
|
169
|
+
- Risky commands are *more* restricted:
|
|
170
|
+
- `!status`: allowed for any authorized sender.
|
|
171
|
+
- `!model` and `!stop`: allowed only for command operators.
|
|
172
|
+
|
|
173
|
+
Command operators (v1):
|
|
174
|
+
|
|
175
|
+
- Explicit allowlist per Rocket.Chat account (Slack-style).
|
|
176
|
+
- If `commandAllowFrom` is unset/empty, default is deny for `!model` + `!stop` in group chats.
|
|
177
|
+
|
|
178
|
+
Reply target:
|
|
179
|
+
|
|
180
|
+
- Reply publicly in the same room/thread (no DM fallback).
|
|
181
|
+
|
|
182
|
+
Semantics:
|
|
183
|
+
|
|
184
|
+
- `!status` -> OpenClaw `/status`
|
|
185
|
+
- `!model <name>` -> OpenClaw `/model <name>` (accept aliases + `provider/model`)
|
|
186
|
+
- `!stop` -> OpenClaw `/stop`
|
|
187
|
+
- Reference: <https://docs.openclaw.ai/tools/slash-commands>
|
|
188
|
+
|
|
189
|
+
## Milestones (Draft)
|
|
190
|
+
|
|
191
|
+
- Event ingestion: new message events from allowlisted Rocket.Chat rooms arrive in OpenClaw
|
|
192
|
+
- Routing: inbound messages route to the OpenClaw agent represented by the bot account that was mentioned/DM'd
|
|
193
|
+
- Outbound send: OpenClaw can post messages into Rocket.Chat (thread replies supported)
|
|
194
|
+
- Parity pass (post-v1): edits, deletes, attachments
|
|
195
|
+
- Operational hardening: retries, idempotency, observability
|
|
196
|
+
|
|
197
|
+
## Open Questions
|
|
198
|
+
|
|
199
|
+
These block finalizing the interface and auth model:
|
|
200
|
+
|
|
201
|
+
1. Rocket.Chat Realtime API scope: which subscription streams cover all Slack-parity features (new messages, reactions, edits, deletes, pins/renames/membership if applicable)?
|
|
202
|
+
2. Rocket.Chat thread model: what is the exact mapping between Rocket.Chat thread/reply metadata and OpenClaw's `reply_to` + optional `:thread:` session keys (Slack-style semantics)? Default: reply in thread when inbound is threaded.
|
|
203
|
+
3. Rocket.Chat mention semantics: confirm how mention metadata is exposed on Realtime API payloads (preferred) vs requiring text scanning for `@<username>`.
|
|
204
|
+
4. Slack-default access control mapping: mirror Slack's `dmPolicy`, `groupPolicy`, allowlists, and mention gating defaults in Rocket.Chat.
|
|
205
|
+
- Slack docs: <https://docs.openclaw.ai/channels/slack>
|
|
206
|
+
5. Realtime API deprecation: Rocket.Chat docs mark DDP methods as deprecated/unreliable and recommend REST for new development; v1 accepts this risk because Rocket.Chat upgrades are controlled (target version 8.2.0).
|
|
207
|
+
- Realtime API doc: <https://developer.rocket.chat/apidocs/realtimeapi>
|
|
208
|
+
|
|
209
|
+
## Development Notes
|
|
210
|
+
|
|
211
|
+
- Keep top-level spec in this `README.md`.
|
|
212
|
+
- If/when the spec becomes too large, create a `specs/` directory with focused documents (auth, message model, retry/idempotency, etc.).
|
package/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});let e=require(`openclaw/plugin-sdk/core`);var t=`pairing`,n=`allowlist`,r=!0;function i(e){return{...e,dmPolicy:e?.dmPolicy??`pairing`,groupPolicy:e?.groupPolicy??`allowlist`}}function a(e){let t=e.trim();if(!t)throw Error(`rocketchat: accountId is required`);return t}function o(e,t){let n=e.trim();if(!n)throw Error(`rocketchat: ${t} is required`);if(!n.startsWith(`https://`))throw Error(`rocketchat: ${t} must start with https:// (got ${n})`);return n.replace(/\/$/,``)}function s(e){return(e??[]).map(e=>e.trim()).filter(e=>e.length>0)}function c(e,t){return{...e??{},...t??{}}}function l(e){let t=a(e.accountId),n=e.cfg.channels?.rocketchat,r=n?.enabled!==!1,i=n?.accounts?.[t],l=i?.enabled!==!1,u=r&&l,d=i?.serverUrl??n?.serverUrl,f=i?.userId,p=i?.username,m=i?.pat;if(!u)return{accountId:t,enabled:!1,serverUrl:d?.trim()||``,userId:f?.trim()||``,username:p?.trim()||``,pat:m?.trim()||``,dmPolicy:i?.dmPolicy??n?.dmPolicy??`pairing`,groupPolicy:i?.groupPolicy??n?.groupPolicy??`allowlist`,allowFrom:s(i?.allowFrom??n?.allowFrom),commandAllowFrom:s(i?.commandAllowFrom??n?.commandAllowFrom),rooms:c(n?.rooms,i?.rooms),defaultRequireMention:!0};let h=o(d??``,`serverUrl`),g=(f??``).trim(),_=(p??``).trim(),v=(m??``).trim();if(!g)throw Error(`rocketchat: userId is required for ${t}`);if(!_)throw Error(`rocketchat: username is required for ${t}`);if(!v)throw Error(`rocketchat: pat is required for ${t}`);return{accountId:t,enabled:!0,serverUrl:h,userId:g,username:_,pat:v,dmPolicy:i?.dmPolicy??n?.dmPolicy??`pairing`,groupPolicy:i?.groupPolicy??n?.groupPolicy??`allowlist`,allowFrom:s(i?.allowFrom??n?.allowFrom),commandAllowFrom:s(i?.commandAllowFrom??n?.commandAllowFrom),rooms:c(n?.rooms,i?.rooms),defaultRequireMention:!0}}function u(e){return e.trim().toLowerCase()}function d(e){let t=[];return e.id&&t.push(u(e.id)),e.username&&t.push(u(e.username)),t}function f(e){let t=e.allowFrom.map(u);if(t.includes(`*`))return{allowed:!0};let n=d(e.sender);return n.length===0?{allowed:!1,reason:`missing-sender-id`}:n.some(e=>t.includes(e))?{allowed:!0}:{allowed:!1,reason:`not-allowlisted`}}function p(e){let t=e.text,n=e.botUsername.trim().replace(/^@/,``);return n?t.includes(`@${n}`):!1}function m(e){if(!e.account.enabled)return{allowed:!1,reason:`account-disabled`};let t=f({allowFrom:e.account.allowFrom,sender:e.sender});if(e.chatType===`direct`)return e.account.dmPolicy===`disabled`?{allowed:!1,reason:`dm-disabled`}:e.account.dmPolicy===`open`||e.account.dmPolicy===`allowlist`?t.allowed?{allowed:!0}:{allowed:!1,reason:t.reason}:t.allowed?{allowed:!0}:{allowed:!1,reason:t.reason??`pairing-required`};if(e.account.groupPolicy===`disabled`)return{allowed:!1,reason:`group-disabled`};let n=e.account.rooms[e.roomId];if((n?.requireMention??e.account.defaultRequireMention)&&!p({text:e.text,botUsername:e.account.username}))return{allowed:!1,reason:`missing-mention`};if(e.account.groupPolicy===`allowlist`&&!n)return{allowed:!1,reason:`room-not-allowlisted`};if(!t.allowed)return{allowed:!1,reason:t.reason};let r=n?.users??[];if(r.length>0){let t=f({allowFrom:r,sender:e.sender});if(!t.allowed)return{allowed:!1,reason:t.reason??`room-user-not-allowlisted`}}return{allowed:!0}}function h(e){if(!e.account.enabled)return{allowed:!1,reason:`account-disabled`};let t=f({allowFrom:e.account.allowFrom,sender:e.sender});if(!t.allowed)return{allowed:!1,reason:t.reason};if(e.command===`status`)return{allowed:!0};let n=e.account.commandAllowFrom;if(e.chatType===`group`&&n.length===0)return{allowed:!1,reason:`operator-allowlist-empty`};let r=f({allowFrom:n,sender:e.sender});return r.allowed?{allowed:!0}:{allowed:!1,reason:r.reason??`not-operator`}}var g=class{messageHandlers=[];closeHandlers=[];sent=[];closed=!1;onMessage(e){this.messageHandlers.push(e)}onClose(e){this.closeHandlers.push(e)}send(e){this.sent.push(e)}close(e){this.closed=!0;for(let t of this.closeHandlers)t(e)}inject(e){for(let t of this.messageHandlers)t(e)}};function _(){return{msg:`connect`,version:`1`,support:[`1`]}}function v(e){return e.transport.onMessage(t=>{let n=t;n&&n.msg===`ping`&&e.transport.send({msg:`pong`})}),{sendConnect:()=>{e.transport.send(_())}}}function y(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.msg===`result`&&typeof t.id==`string`}var b=class{nextId=1;pending=new Map;constructor(e){this.transport=e,this.transport.onMessage(e=>{if(!y(e))return;let t=this.pending.get(e.id);if(t){if(this.pending.delete(e.id),e.error!==void 0){t.reject(e.error);return}t.resolve(e.result)}}),this.transport.onClose(e=>{let t=Error(`DDP transport closed${e?.code?` (code=${e.code})`:``}${e?.reason?` reason=${e.reason}`:``}`);for(let[,e]of this.pending)e.reject(t);this.pending.clear()})}call(e,t){let n=String(this.nextId++),r={msg:`method`,method:e,id:n};t&&(r.params=t);let i=new Promise((e,t)=>{this.pending.set(n,{resolve:e,reject:t})});return this.transport.send(r),i}};async function x(e){return await e.ddp.call(`login`,[{resume:e.resumeToken}])}function S(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.msg===`ready`&&Array.isArray(t.subs)}var C=class{nextId=1;constructor(e){this.transport=e}subscribe(e,t){let n=String(this.nextId++),r={msg:`sub`,id:n,name:e,params:t},i=new Promise(e=>{this.transport.onMessage(t=>{S(t)&&t.subs.includes(n)&&e({subId:n})})});return this.transport.send(r),i}subscribeRoomMessages(e){return this.subscribe(`stream-room-messages`,[e,!1])}},w=class{ddp;subs;protocol;constructor(e){this.ddp=new b(e),this.subs=new C(e),this.protocol=v({transport:e})}connect(){this.protocol.sendConnect()}async loginWithResume(e){await x({ddp:this.ddp,resumeToken:e})}async subscribeRoomMessages(e){for(let t of e)await this.subs.subscribeRoomMessages(t)}async start(e){this.connect(),await this.loginWithResume(e.resumeToken),await this.subscribeRoomMessages(e.roomIds)}};function T(e){return!!e&&typeof e==`object`}function E(e){return{id:(e?._id??``).trim(),username:e?.username?.trim()||void 0}}function D(e){let t=(e.rid??``).trim(),n=(e._id??``).trim(),r=(e.msg??``).toString(),i=E(e.u),a=e.tmid?.trim()||void 0;return!t||!n||!i.id?null:{roomId:t,messageId:n,text:r,sender:i,threadId:a}}function O(e){if(!T(e))return null;if(`rid`in e||`_id`in e&&`u`in e)return D(e);let t=e;if(t.msg!==`changed`||t.collection&&t.collection!==`stream-room-messages`&&t.collection!==`stream-room-messages-realtime`)return null;let n=t.fields?.args,r=Array.isArray(n)?n[0]:void 0;return T(r)?D(r):null}function k(e){return e.roomTypeById?.[e.roomId]??`group`}function A(e){let t=e.roomId.trim(),n=e.threadId?.trim()||void 0;return n?{baseConversationId:t,threadId:n}:{baseConversationId:t}}function j(e){let t=O(e.rawEvent);if(!t)return{ok:!1,reason:`unrecognized-message-shape`};if(t.sender.id===e.account.userId)return{ok:!1,reason:`self-message`};let n=k({roomId:t.roomId,roomTypeById:e.roomTypeById}),r=m({account:e.account,chatType:n,roomId:t.roomId,sender:t.sender,text:t.text});return r.allowed?{ok:!0,value:{roomId:t.roomId,messageId:t.messageId,text:t.text,sender:t.sender,chatType:n,threadId:t.threadId}}:{ok:!1,reason:r.reason??`blocked`}}function M(e){let t=e.trim().replace(/\/$/,``);if(!t.startsWith(`https://`))throw Error(`rocketchat: serverUrl must start with https:// (got ${t})`);return t}function N(e){let t=`${M(e.serverUrl)}/api/v1/chat.postMessage`,n={rid:e.roomId,msg:e.text};return e.threadId&&(n.tmid=e.threadId),{url:t,method:`POST`,headers:{"Content-Type":`application/json`,"X-User-Id":e.userId,"X-Auth-Token":e.authToken},body:JSON.stringify(n)}}async function P(e){let t=N(e),n=await(e.fetcher??fetch)(t.url,{method:t.method,headers:t.headers,body:t.body}),r=await n.text();if(!n.ok)throw Error(`rocketchat: chat.postMessage failed (${n.status}): ${r}`);try{return JSON.parse(r)}catch{return r}}function F(e){let t=e.trim().replace(/\/$/,``);if(!t.startsWith(`https://`))throw Error(`rocketchat: serverUrl must start with https:// (got ${t})`);return t}function I(e){return{url:`${F(e.serverUrl)}/api/v1/chat.react`,method:`POST`,headers:{"Content-Type":`application/json`,"X-User-Id":e.userId,"X-Auth-Token":e.authToken},body:JSON.stringify({messageId:e.messageId,emoji:e.emoji,shouldReact:e.shouldReact})}}async function L(e){let t=I(e),n=await(e.fetcher??fetch)(t.url,{method:t.method,headers:t.headers,body:t.body}),r=await n.text();if(!n.ok)throw Error(`rocketchat: chat.react failed (${n.status}): ${r}`);try{return JSON.parse(r)}catch{return r}}function R(e){let t=e.trim().replace(/\/$/,``);if(!t.startsWith(`https://`))throw Error(`rocketchat: serverUrl must start with https:// (got ${t})`);return t}function z(e){return{url:`${R(e.serverUrl)}/api/v1/me`,method:`GET`,headers:{"X-User-Id":e.userId,"X-Auth-Token":e.authToken}}}async function B(e){let t=z(e),n=await(e.fetcher??fetch)(t.url,{method:t.method,headers:t.headers}),r=await n.text();if(!n.ok)throw Error(`rocketchat: /api/v1/me failed (${n.status}): ${r}`);try{return JSON.parse(r)}catch{return r}}function V(e){return e.trim().split(/\s+/).map(e=>e.trim()).filter(e=>e.length>0)}function H(e){let t=e.trim();if(!t.startsWith(`!`))return{kind:`not-a-command`};let n=V(t.slice(1)),r=(n[0]??``).toLowerCase();if(r===`status`)return{kind:`command`,name:`status`,raw:t};if(r===`stop`)return{kind:`command`,name:`stop`,raw:t};if(r===`model`){let e=n.slice(1).join(` `).trim();return e?{kind:`command`,name:`model`,raw:t,model:e}:{kind:`not-a-command`}}return{kind:`not-a-command`}}function U(e){if(e.parsed.kind!==`command`)return{ok:!1,reason:`not-a-command`};let t=h({account:e.account,chatType:e.chatType,sender:e.sender,command:e.parsed.name});return t.allowed?e.parsed.name===`status`?{ok:!0,kind:`openclaw-command`,commandText:`/status`}:e.parsed.name===`stop`?{ok:!0,kind:`openclaw-command`,commandText:`/stop`}:{ok:!0,kind:`openclaw-command`,commandText:`/model ${e.parsed.model}`}:{ok:!1,reason:t.reason??`blocked`}}function W(e){e.transport.close({reason:`unauthorized: ${e.reason}`})}function G(e){let t=!1,n=setInterval(async()=>{if(!t)try{let t=await e.validateOnce();t.ok||W({transport:e.transport,reason:t.reason})}catch(t){let n=t instanceof Error?t.message:String(t);W({transport:e.transport,reason:n})}},e.options.intervalMs);return{stop:()=>{t=!0,clearInterval(n)}}}async function K(e){let t=e.createTransport({account:e.account}),n=new w(t);await n.start({resumeToken:e.account.pat,roomIds:e.roomIds});let r=G({transport:t,options:{intervalMs:e.revalidateIntervalMs??300*1e3},validateOnce:async()=>{try{return await B({serverUrl:e.account.serverUrl,userId:e.account.userId,authToken:e.account.pat,fetcher:e.fetcher}),{ok:!0}}catch(e){return{ok:!1,reason:e instanceof Error?e.message:String(e)}}}});return{client:n,stop:()=>{r.stop(),t.close({reason:`stopped`})}}}var q=`0.1.0-master-7f84cdcb`,J=(0,e.defineChannelPluginEntry)({id:`rocketchat`,name:`Rocket.Chat`,description:`Rocket.Chat channel plugin`,plugin:null});exports.DEFAULT_DM_POLICY=t,exports.DEFAULT_GROUP_POLICY=n,exports.DEFAULT_REQUIRE_MENTION=r,exports.DdpMethodClient=b,exports.DdpSubscriptionClient=C,exports.FakeDdpTransport=g,exports.RocketChatClient=w,exports.authorizeCommand=h,exports.authorizeInboundMessage=m,exports.buildMeRequest=z,exports.buildPostMessageRequest=N,exports.buildReactRequest=I,exports.classifyChatType=k,exports.computeSessionConversation=A,exports.ddpLoginWithResume=x,exports.decodeRoomMessageEvent=O,exports.default=J,exports.dispatchPrefixCommand=U,exports.getMe=B,exports.installDdpProtocol=v,exports.makeConnectFrame=_,exports.parsePrefixCommand=H,exports.postMessage=P,exports.processInboundRoomMessage=j,exports.reactToMessage=L,exports.resolveChannelDefaults=i,exports.resolveRocketChatAccount=l,exports.revokeOnUnauthorized=W,exports.startCredentialRevalidation=G,exports.startRocketChatAccount=K,exports.version=q;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
package/index.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/rocketchat/config/defaults.ts","../src/rocketchat/config/resolve_account.ts","../src/rocketchat/policy/allowlist.ts","../src/rocketchat/policy/mentions.ts","../src/rocketchat/policy/policy.ts","../src/rocketchat/ddp/fake_transport.ts","../src/rocketchat/ddp/protocol.ts","../src/rocketchat/ddp/methods.ts","../src/rocketchat/ddp/auth.ts","../src/rocketchat/ddp/subscriptions.ts","../src/rocketchat/client.ts","../src/rocketchat/inbound/decode_message.ts","../src/rocketchat/inbound/classify_chat_type.ts","../src/rocketchat/inbound/session_keys.ts","../src/rocketchat/inbound/process_inbound.ts","../src/rocketchat/rest/post_message.ts","../src/rocketchat/rest/react.ts","../src/rocketchat/rest/me.ts","../src/rocketchat/commands/parse.ts","../src/rocketchat/commands/dispatch.ts","../src/rocketchat/security/revocation.ts","../src/rocketchat/security/revalidate.ts","../src/openclaw/runtime/account_runner.ts","../src/index.ts"],"sourcesContent":["import type { RocketChatChannelConfig, RocketChatDmPolicy, RocketChatGroupPolicy } from './types'\n\n/**\n * Default DM policy for Rocket.Chat.\n *\n * v1 default is `pairing` (Slack-aligned).\n */\nexport const DEFAULT_DM_POLICY: RocketChatDmPolicy = 'pairing'\n\n/**\n * Default group policy for Rocket.Chat.\n *\n * v1 default is `allowlist` (fail closed).\n */\nexport const DEFAULT_GROUP_POLICY: RocketChatGroupPolicy = 'allowlist'\n\n/**\n * Default setting for mention gating in group chats.\n */\nexport const DEFAULT_REQUIRE_MENTION = true\n\n/**\n * Compute the effective channel config.\n *\n * This applies defaults, but does not validate credentials.\n */\nexport function resolveChannelDefaults(\n cfg: RocketChatChannelConfig | undefined\n): Required<Pick<RocketChatChannelConfig, 'dmPolicy' | 'groupPolicy'>> & RocketChatChannelConfig {\n return {\n ...cfg,\n dmPolicy: cfg?.dmPolicy ?? DEFAULT_DM_POLICY,\n groupPolicy: cfg?.groupPolicy ?? DEFAULT_GROUP_POLICY,\n }\n}\n","import { DEFAULT_DM_POLICY, DEFAULT_GROUP_POLICY, DEFAULT_REQUIRE_MENTION } from './defaults'\nimport type {\n RocketChatAccountConfig,\n RocketChatChannelConfig,\n RocketChatOpenClawConfig,\n RocketChatRoomConfig,\n} from './types'\n\n/**\n * Resolved account config for a specific OpenClaw agent id.\n */\nexport interface ResolvedRocketChatAccount {\n /** OpenClaw agent id / account id key. */\n accountId: string\n\n /** Whether this account is enabled after all merges. */\n enabled: boolean\n\n /** Rocket.Chat server base URL (https://...). */\n serverUrl: string\n\n /** Rocket.Chat bot user id. */\n userId: string\n\n /** Rocket.Chat bot username (for mention gating). */\n username: string\n\n /** Rocket.Chat personal access token. */\n pat: string\n\n /** Effective DM policy. */\n dmPolicy: NonNullable<RocketChatChannelConfig['dmPolicy']>\n\n /** Effective group policy. */\n groupPolicy: NonNullable<RocketChatChannelConfig['groupPolicy']>\n\n /** Effective sender allowlist. */\n allowFrom: string[]\n\n /** Effective operator allowlist for risky commands. */\n commandAllowFrom: string[]\n\n /** Effective per-room config. */\n rooms: Record<string, RocketChatRoomConfig>\n\n /** Default mention gating for group chats when room override is not present. */\n defaultRequireMention: boolean\n}\n\nfunction normalizeAccountId(raw: string): string {\n const trimmed = raw.trim()\n if (!trimmed) {\n throw new Error('rocketchat: accountId is required')\n }\n return trimmed\n}\n\nfunction normalizeHttpsUrl(raw: string, pathLabel: string): string {\n const trimmed = raw.trim()\n if (!trimmed) {\n throw new Error(`rocketchat: ${pathLabel} is required`)\n }\n if (!trimmed.startsWith('https://')) {\n throw new Error(`rocketchat: ${pathLabel} must start with https:// (got ${trimmed})`)\n }\n return trimmed.replace(/\\/$/, '')\n}\n\nfunction normalizeAllowlist(raw?: string[]): string[] {\n return (raw ?? []).map((v) => v.trim()).filter((v) => v.length > 0)\n}\n\nfunction mergeRooms(\n base: Record<string, RocketChatRoomConfig> | undefined,\n override: Record<string, RocketChatRoomConfig> | undefined\n): Record<string, RocketChatRoomConfig> {\n return {\n ...(base ?? {}),\n ...(override ?? {}),\n }\n}\n\n/**\n * Resolve the effective Rocket.Chat account configuration for a given agent id.\n *\n * Merging rules:\n * - Top-level `channels.rocketchat` is the base.\n * - `channels.rocketchat.accounts[accountId]` overrides base.\n *\n * Validation rules:\n * - If the merged account is enabled, it must have: serverUrl, userId, username, pat.\n */\nexport function resolveRocketChatAccount(params: {\n cfg: RocketChatOpenClawConfig\n accountId: string\n}): ResolvedRocketChatAccount {\n const accountId = normalizeAccountId(params.accountId)\n const channel: RocketChatChannelConfig | undefined = params.cfg.channels?.rocketchat\n\n const baseEnabled = channel?.enabled !== false\n const override: RocketChatAccountConfig | undefined = channel?.accounts?.[accountId]\n const accountEnabled = override?.enabled !== false\n const enabled = baseEnabled && accountEnabled\n\n const serverUrlRaw = override?.serverUrl ?? channel?.serverUrl\n const userIdRaw = override?.userId\n const usernameRaw = override?.username\n const patRaw = override?.pat\n\n if (!enabled) {\n // For disabled accounts we still return a fully-typed object, but with empty\n // credential fields. Callers should branch on enabled.\n return {\n accountId,\n enabled: false,\n serverUrl: serverUrlRaw?.trim() || '',\n userId: userIdRaw?.trim() || '',\n username: usernameRaw?.trim() || '',\n pat: patRaw?.trim() || '',\n dmPolicy: override?.dmPolicy ?? channel?.dmPolicy ?? DEFAULT_DM_POLICY,\n groupPolicy: override?.groupPolicy ?? channel?.groupPolicy ?? DEFAULT_GROUP_POLICY,\n allowFrom: normalizeAllowlist(override?.allowFrom ?? channel?.allowFrom),\n commandAllowFrom: normalizeAllowlist(override?.commandAllowFrom ?? channel?.commandAllowFrom),\n rooms: mergeRooms(channel?.rooms, override?.rooms),\n defaultRequireMention: DEFAULT_REQUIRE_MENTION,\n }\n }\n\n const serverUrl = normalizeHttpsUrl(serverUrlRaw ?? '', 'serverUrl')\n const userId = (userIdRaw ?? '').trim()\n const username = (usernameRaw ?? '').trim()\n const pat = (patRaw ?? '').trim()\n\n if (!userId) throw new Error(`rocketchat: userId is required for ${accountId}`)\n if (!username) throw new Error(`rocketchat: username is required for ${accountId}`)\n if (!pat) throw new Error(`rocketchat: pat is required for ${accountId}`)\n\n return {\n accountId,\n enabled: true,\n serverUrl,\n userId,\n username,\n pat,\n dmPolicy: override?.dmPolicy ?? channel?.dmPolicy ?? DEFAULT_DM_POLICY,\n groupPolicy: override?.groupPolicy ?? channel?.groupPolicy ?? DEFAULT_GROUP_POLICY,\n allowFrom: normalizeAllowlist(override?.allowFrom ?? channel?.allowFrom),\n commandAllowFrom: normalizeAllowlist(override?.commandAllowFrom ?? channel?.commandAllowFrom),\n rooms: mergeRooms(channel?.rooms, override?.rooms),\n defaultRequireMention: DEFAULT_REQUIRE_MENTION,\n }\n}\n","import type { RocketChatSender } from './chat_types'\n\n/**\n * Allowlist match result.\n */\nexport interface AllowlistMatch {\n /** True when the sender matches the allowlist. */\n allowed: boolean\n\n /** Optional reason for denial (for logging/debugging). */\n reason?: string\n}\n\nfunction normalizeEntry(raw: string): string {\n return raw.trim().toLowerCase()\n}\n\nfunction normalizeSender(sender: RocketChatSender): string[] {\n const entries: string[] = []\n if (sender.id) entries.push(normalizeEntry(sender.id))\n if (sender.username) entries.push(normalizeEntry(sender.username))\n return entries\n}\n\n/**\n * Match a sender against an allowlist.\n *\n * Supported allowlist formats (v1):\n * - `\"*\"` wildcard\n * - raw user id\n * - raw username (without @)\n */\nexport function matchAllowlist(params: {\n allowFrom: string[]\n sender: RocketChatSender\n}): AllowlistMatch {\n const allowFrom = params.allowFrom.map(normalizeEntry)\n if (allowFrom.includes('*')) {\n return { allowed: true }\n }\n\n const senderKeys = normalizeSender(params.sender)\n if (senderKeys.length === 0) {\n return { allowed: false, reason: 'missing-sender-id' }\n }\n\n const allowed = senderKeys.some((k) => allowFrom.includes(k))\n return allowed ? { allowed: true } : { allowed: false, reason: 'not-allowlisted' }\n}\n","/**\n * Check whether a message includes a mention of the bot.\n *\n * v1 decision: implement this using a simple substring check (no regex).\n */\nexport function messageMentionsBot(params: { text: string; botUsername: string }): boolean {\n const text = params.text\n const botUsername = params.botUsername.trim().replace(/^@/, '')\n if (!botUsername) return false\n return text.includes(`@${botUsername}`)\n}\n","import { matchAllowlist } from './allowlist'\nimport { messageMentionsBot } from './mentions'\nimport type { RocketChatChatType, RocketChatSender } from './chat_types'\nimport type { ResolvedRocketChatAccount } from '../config/resolve_account'\n\n/**\n * The result of applying policy checks to an inbound message.\n */\nexport interface PolicyDecision {\n allowed: boolean\n reason?: string\n}\n\n/**\n * Decide whether an inbound message is authorized to be processed.\n *\n * This is the single top-level policy function for v1.\n */\nexport function authorizeInboundMessage(params: {\n account: ResolvedRocketChatAccount\n chatType: RocketChatChatType\n roomId: string\n sender: RocketChatSender\n text: string\n}): PolicyDecision {\n if (!params.account.enabled) {\n return { allowed: false, reason: 'account-disabled' }\n }\n\n const baseAllowMatch = matchAllowlist({\n allowFrom: params.account.allowFrom,\n sender: params.sender,\n })\n\n if (params.chatType === 'direct') {\n if (params.account.dmPolicy === 'disabled') {\n return { allowed: false, reason: 'dm-disabled' }\n }\n\n if (params.account.dmPolicy === 'open') {\n // open still requires explicit wildcard or direct allowlist match.\n return baseAllowMatch.allowed\n ? { allowed: true }\n : { allowed: false, reason: baseAllowMatch.reason }\n }\n\n if (params.account.dmPolicy === 'allowlist') {\n return baseAllowMatch.allowed\n ? { allowed: true }\n : { allowed: false, reason: baseAllowMatch.reason }\n }\n\n // pairing (v1): treat as allowlist until pairing flow is implemented.\n return baseAllowMatch.allowed\n ? { allowed: true }\n : { allowed: false, reason: baseAllowMatch.reason ?? 'pairing-required' }\n }\n\n // group\n if (params.account.groupPolicy === 'disabled') {\n return { allowed: false, reason: 'group-disabled' }\n }\n\n const roomCfg = params.account.rooms[params.roomId]\n const requireMention = roomCfg?.requireMention ?? params.account.defaultRequireMention\n if (requireMention) {\n const mentioned = messageMentionsBot({\n text: params.text,\n botUsername: params.account.username,\n })\n if (!mentioned) {\n return { allowed: false, reason: 'missing-mention' }\n }\n }\n\n if (params.account.groupPolicy === 'allowlist') {\n // Room must be allowlisted under account.rooms. Using the presence of a room key\n // as the allowlist signal is consistent with Slack-style allowlist-by-id.\n if (!roomCfg) {\n return { allowed: false, reason: 'room-not-allowlisted' }\n }\n }\n\n if (!baseAllowMatch.allowed) {\n return { allowed: false, reason: baseAllowMatch.reason }\n }\n\n // Optional per-room user allowlist.\n const roomUsers = roomCfg?.users ?? []\n if (roomUsers.length > 0) {\n const roomMatch = matchAllowlist({ allowFrom: roomUsers, sender: params.sender })\n if (!roomMatch.allowed) {\n return { allowed: false, reason: roomMatch.reason ?? 'room-user-not-allowlisted' }\n }\n }\n\n return { allowed: true }\n}\n\n/**\n * Determine whether a prefix command is allowed.\n *\n * Risky commands (!model, !stop) require operator authorization.\n */\nexport function authorizeCommand(params: {\n account: ResolvedRocketChatAccount\n chatType: RocketChatChatType\n sender: RocketChatSender\n command: 'status' | 'model' | 'stop'\n}): PolicyDecision {\n if (!params.account.enabled) {\n return { allowed: false, reason: 'account-disabled' }\n }\n\n // Base sender must be allowlisted for all commands.\n const baseAllowMatch = matchAllowlist({\n allowFrom: params.account.allowFrom,\n sender: params.sender,\n })\n if (!baseAllowMatch.allowed) {\n return { allowed: false, reason: baseAllowMatch.reason }\n }\n\n if (params.command === 'status') {\n return { allowed: true }\n }\n\n // Operator allowlist.\n const operatorAllowFrom = params.account.commandAllowFrom\n\n // Default deny for risky commands in group chats when unset/empty.\n if (params.chatType === 'group' && operatorAllowFrom.length === 0) {\n return { allowed: false, reason: 'operator-allowlist-empty' }\n }\n\n const operatorMatch = matchAllowlist({\n allowFrom: operatorAllowFrom,\n sender: params.sender,\n })\n return operatorMatch.allowed\n ? { allowed: true }\n : { allowed: false, reason: operatorMatch.reason ?? 'not-operator' }\n}\n","import type { DdpTransport } from './transport'\n\n/**\n * A deterministic in-memory transport for unit testing DDP protocol logic.\n */\nexport class FakeDdpTransport implements DdpTransport {\n private messageHandlers: Array<(msg: unknown) => void> = []\n private closeHandlers: Array<(info?: { code?: number; reason?: string }) => void> = []\n\n /** Outbound frames sent by the client. */\n public readonly sent: unknown[] = []\n\n /** Whether close() has been called. */\n public closed = false\n\n onMessage(cb: (msg: unknown) => void): void {\n this.messageHandlers.push(cb)\n }\n\n onClose(cb: (info?: { code?: number; reason?: string }) => void): void {\n this.closeHandlers.push(cb)\n }\n\n send(msg: unknown): void {\n this.sent.push(msg)\n }\n\n close(info?: { code?: number; reason?: string }): void {\n this.closed = true\n for (const cb of this.closeHandlers) cb(info)\n }\n\n /**\n * Inject an inbound message as if it arrived from Rocket.Chat.\n */\n inject(msg: unknown): void {\n for (const cb of this.messageHandlers) cb(msg)\n }\n}\n","import type { DdpTransport } from './transport'\n\n/** DDP connect frame. */\nexport interface DdpConnectFrame {\n msg: 'connect'\n version: '1'\n support: ['1']\n}\n\n/** DDP ping frame. */\nexport interface DdpPingFrame {\n msg: 'ping'\n}\n\n/** DDP pong frame. */\nexport interface DdpPongFrame {\n msg: 'pong'\n}\n\n/**\n * Create the initial DDP connect frame.\n */\nexport function makeConnectFrame(): DdpConnectFrame {\n return { msg: 'connect', version: '1', support: ['1'] }\n}\n\n/**\n * Install baseline protocol handlers.\n *\n * Responsibilities:\n * - send initial connect\n * - respond to ping with pong\n */\nexport function installDdpProtocol(params: { transport: DdpTransport }): {\n sendConnect: () => void\n} {\n params.transport.onMessage((msg) => {\n const m = msg as Partial<DdpPingFrame> | null\n if (m && m.msg === 'ping') {\n const pong: DdpPongFrame = { msg: 'pong' }\n params.transport.send(pong)\n }\n })\n\n return {\n sendConnect: () => {\n params.transport.send(makeConnectFrame())\n },\n }\n}\n","import type { DdpTransport } from './transport'\n\nexport interface DdpMethodFrame {\n msg: 'method'\n method: string\n id: string\n params?: unknown[]\n}\n\nexport interface DdpResultFrame {\n msg: 'result'\n id: string\n result?: unknown\n error?: unknown\n}\n\nfunction isResultFrame(msg: unknown): msg is DdpResultFrame {\n if (!msg || typeof msg !== 'object') return false\n const m = msg as Record<string, unknown>\n return m.msg === 'result' && typeof m.id === 'string'\n}\n\n/**\n * Correlates DDP method calls with result frames.\n */\nexport class DdpMethodClient {\n private nextId = 1\n private pending = new Map<\n string,\n { resolve: (value: unknown) => void; reject: (err: unknown) => void }\n >()\n\n constructor(private readonly transport: DdpTransport) {\n this.transport.onMessage((msg) => {\n if (!isResultFrame(msg)) return\n const handler = this.pending.get(msg.id)\n if (!handler) return\n this.pending.delete(msg.id)\n\n if (msg.error !== undefined) {\n handler.reject(msg.error)\n return\n }\n handler.resolve(msg.result)\n })\n\n this.transport.onClose((info) => {\n // Fail all pending calls on close.\n const err = new Error(\n `DDP transport closed${info?.code ? ` (code=${info.code})` : ''}${\n info?.reason ? ` reason=${info.reason}` : ''\n }`\n )\n for (const [, handler] of this.pending) handler.reject(err)\n this.pending.clear()\n })\n }\n\n /**\n * Call a DDP method.\n */\n call(method: string, params?: unknown[]): Promise<unknown> {\n const id = String(this.nextId++)\n const frame: DdpMethodFrame = { msg: 'method', method, id }\n if (params) frame.params = params\n\n const promise = new Promise<unknown>((resolve, reject) => {\n this.pending.set(id, { resolve, reject })\n })\n\n this.transport.send(frame)\n return promise\n }\n}\n","import type { DdpMethodClient } from './methods'\n\n/**\n * Log in using a resume token.\n *\n * In v1 we treat the Rocket.Chat PAT as the resume token for DDP login.\n */\nexport async function ddpLoginWithResume(params: {\n ddp: DdpMethodClient\n resumeToken: string\n}): Promise<unknown> {\n return await params.ddp.call('login', [{ resume: params.resumeToken }])\n}\n","import type { DdpTransport } from './transport'\n\nexport interface DdpSubFrame {\n msg: 'sub'\n id: string\n name: string\n params: unknown[]\n}\n\nexport interface DdpReadyFrame {\n msg: 'ready'\n subs: string[]\n}\n\nfunction isReadyFrame(msg: unknown): msg is DdpReadyFrame {\n if (!msg || typeof msg !== 'object') return false\n const m = msg as Record<string, unknown>\n return m.msg === 'ready' && Array.isArray(m.subs)\n}\n\n/**\n * Minimal subscription client.\n */\nexport class DdpSubscriptionClient {\n private nextId = 1\n\n constructor(private readonly transport: DdpTransport) {}\n\n /**\n * Subscribe to a DDP stream.\n *\n * Resolves once the server acknowledges the subscription via a `ready` frame.\n */\n subscribe(name: string, params: unknown[]): Promise<{ subId: string }> {\n const subId = String(this.nextId++)\n const frame: DdpSubFrame = { msg: 'sub', id: subId, name, params }\n\n const promise = new Promise<{ subId: string }>((resolve) => {\n const handler = (msg: unknown) => {\n if (!isReadyFrame(msg)) return\n if (!msg.subs.includes(subId)) return\n resolve({ subId })\n }\n this.transport.onMessage(handler)\n })\n\n this.transport.send(frame)\n return promise\n }\n\n /**\n * Subscribe to the stream of room messages.\n */\n subscribeRoomMessages(roomId: string): Promise<{ subId: string }> {\n return this.subscribe('stream-room-messages', [roomId, false])\n }\n}\n","import { ddpLoginWithResume } from './ddp/auth'\nimport { DdpMethodClient } from './ddp/methods'\nimport { installDdpProtocol } from './ddp/protocol'\nimport { DdpSubscriptionClient } from './ddp/subscriptions'\nimport type { DdpTransport } from './ddp/transport'\n\n/**\n * Minimal Rocket.Chat client used by the OpenClaw channel plugin.\n *\n * v1 scope:\n * - DDP connect + ping/pong\n * - DDP login using resume token (PAT)\n * - subscribe to stream-room-messages\n *\n * This class is intentionally transport-agnostic so it can be unit-tested.\n */\nexport class RocketChatClient {\n private readonly ddp: DdpMethodClient\n private readonly subs: DdpSubscriptionClient\n private readonly protocol: ReturnType<typeof installDdpProtocol>\n\n constructor(transport: DdpTransport) {\n this.ddp = new DdpMethodClient(transport)\n this.subs = new DdpSubscriptionClient(transport)\n this.protocol = installDdpProtocol({ transport })\n }\n\n /**\n * Start the DDP session by sending the connect frame.\n */\n connect(): void {\n this.protocol.sendConnect()\n }\n\n /**\n * Authenticate the DDP session.\n */\n async loginWithResume(resumeToken: string): Promise<void> {\n await ddpLoginWithResume({ ddp: this.ddp, resumeToken })\n }\n\n /**\n * Subscribe to inbound room message streams for the provided rooms.\n */\n async subscribeRoomMessages(roomIds: string[]): Promise<void> {\n for (const roomId of roomIds) {\n await this.subs.subscribeRoomMessages(roomId)\n }\n }\n\n /**\n * Convenience helper for the common connect/login/subscribe sequence.\n */\n async start(params: { resumeToken: string; roomIds: string[] }): Promise<void> {\n this.connect()\n await this.loginWithResume(params.resumeToken)\n await this.subscribeRoomMessages(params.roomIds)\n }\n}\n","import type { InboundNormalizeResult } from './types'\nimport type { RocketChatSender } from '../policy/chat_types'\n\ntype RocketChatUser = {\n _id?: string\n username?: string\n}\n\ntype RocketChatMessage = {\n _id?: string\n rid?: string\n msg?: string\n u?: RocketChatUser\n tmid?: string\n}\n\ntype DdpChangedFrame = {\n msg?: string\n collection?: string\n fields?: {\n eventName?: string\n args?: unknown[]\n }\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return Boolean(v) && typeof v === 'object'\n}\n\nfunction decodeSender(u: RocketChatUser | undefined): RocketChatSender {\n return {\n id: (u?._id ?? '').trim(),\n username: u?.username?.trim() || undefined,\n }\n}\n\nfunction decodeMessage(m: RocketChatMessage): {\n roomId: string\n messageId: string\n text: string\n sender: RocketChatSender\n threadId?: string\n} | null {\n const roomId = (m.rid ?? '').trim()\n const messageId = (m._id ?? '').trim()\n const text = (m.msg ?? '').toString()\n const sender = decodeSender(m.u)\n const threadId = m.tmid?.trim() || undefined\n\n if (!roomId) return null\n if (!messageId) return null\n if (!sender.id) return null\n\n return { roomId, messageId, text, sender, threadId }\n}\n\n/**\n * Decode a `stream-room-messages` event payload into a normalized message shape.\n *\n * We support a couple of common payload shapes:\n * - direct message object\n * - DDP changed envelope with fields.args[0] = message\n */\nexport function decodeRoomMessageEvent(raw: unknown): {\n roomId: string\n messageId: string\n text: string\n sender: RocketChatSender\n threadId?: string\n} | null {\n if (!isObject(raw)) return null\n\n // Shape 1: raw is already a Rocket.Chat message document.\n //\n // Note: DDP envelopes also have a `msg` field (e.g. msg=\"changed\"), so we must not\n // treat the presence of `msg` alone as evidence this is a message document.\n if ('rid' in raw || ('_id' in raw && 'u' in raw)) {\n return decodeMessage(raw as RocketChatMessage)\n }\n\n // Shape 2: DDP changed frame.\n const changed = raw as DdpChangedFrame\n if (changed.msg !== 'changed') return null\n\n // Some Rocket.Chat deployments expose this subscription name under the same\n // `stream-room-messages` key used for subscribe(). For safety in early v1,\n // accept any changed frame that includes args[0] shaped like a message.\n //\n // We still prefer strict matching when collection is present and known.\n if (\n changed.collection &&\n changed.collection !== 'stream-room-messages' &&\n changed.collection !== 'stream-room-messages-realtime'\n ) {\n return null\n }\n\n const args = changed.fields?.args\n const first = Array.isArray(args) ? args[0] : undefined\n if (!isObject(first)) return null\n\n return decodeMessage(first as RocketChatMessage)\n}\n\n/**\n * Normalize and validate an inbound message event.\n */\nexport function normalizeDecodedMessage(raw: unknown): InboundNormalizeResult {\n const decoded = decodeRoomMessageEvent(raw)\n if (!decoded) return { ok: false, reason: 'unrecognized-message-shape' }\n if (!decoded.roomId) return { ok: false, reason: 'missing-room-id' }\n if (!decoded.messageId) return { ok: false, reason: 'missing-message-id' }\n if (!decoded.sender.id) return { ok: false, reason: 'missing-sender-id' }\n return { ok: true, value: { ...decoded, chatType: 'group' } as any }\n}\n","import type { RocketChatChatType } from '../policy/chat_types'\n\n/**\n * Determine chat type for a room.\n *\n * v1: this is config-driven. The caller can supply an explicit room type map.\n */\nexport function classifyChatType(params: {\n roomId: string\n roomTypeById?: Record<string, RocketChatChatType>\n}): RocketChatChatType {\n const hint = params.roomTypeById?.[params.roomId]\n return hint ?? 'group'\n}\n","/**\n * Rocket.Chat session key components.\n */\nexport interface RocketChatSessionConversation {\n /** Base conversation id (room). */\n baseConversationId: string\n\n /** Optional thread id (when replying in a thread). */\n threadId?: string\n}\n\n/**\n * Compute the canonical conversation ids for OpenClaw session grammar.\n */\nexport function computeSessionConversation(params: {\n roomId: string\n threadId?: string\n}): RocketChatSessionConversation {\n const baseConversationId = params.roomId.trim()\n const threadId = params.threadId?.trim() || undefined\n return threadId ? { baseConversationId, threadId } : { baseConversationId }\n}\n","import { classifyChatType } from './classify_chat_type'\nimport { decodeRoomMessageEvent } from './decode_message'\nimport { authorizeInboundMessage } from '../policy/policy'\nimport type { ResolvedRocketChatAccount } from '../config/resolve_account'\nimport type { InboundNormalizeResult, RocketChatInboundMessage } from './types'\n\n/**\n * Normalize, classify, and authorize an inbound Rocket.Chat message event.\n */\nexport function processInboundRoomMessage(params: {\n account: ResolvedRocketChatAccount\n rawEvent: unknown\n roomTypeById?: Record<string, 'direct' | 'group'>\n}): InboundNormalizeResult {\n const decoded = decodeRoomMessageEvent(params.rawEvent)\n if (!decoded) return { ok: false, reason: 'unrecognized-message-shape' }\n\n // Loop prevention: ignore self-authored events.\n if (decoded.sender.id === params.account.userId) {\n return { ok: false, reason: 'self-message' }\n }\n\n const chatType = classifyChatType({\n roomId: decoded.roomId,\n roomTypeById: params.roomTypeById,\n })\n\n const decision = authorizeInboundMessage({\n account: params.account,\n chatType,\n roomId: decoded.roomId,\n sender: decoded.sender,\n text: decoded.text,\n })\n\n if (!decision.allowed) {\n return { ok: false, reason: decision.reason ?? 'blocked' }\n }\n\n const value: RocketChatInboundMessage = {\n roomId: decoded.roomId,\n messageId: decoded.messageId,\n text: decoded.text,\n sender: decoded.sender,\n chatType,\n threadId: decoded.threadId,\n }\n\n return { ok: true, value }\n}\n","export interface RocketChatPostMessageParams {\n /** Rocket.Chat base server URL, for example https://chat.example.com */\n serverUrl: string\n\n /** Rocket.Chat user id for auth. */\n userId: string\n\n /** Rocket.Chat personal access token for auth. */\n authToken: string\n\n /** Room id to post into. */\n roomId: string\n\n /** Message text. */\n text: string\n\n /** Optional thread message id (Rocket.Chat tmid). */\n threadId?: string\n}\n\nexport interface RocketChatPostMessageRequest {\n url: string\n method: 'POST'\n headers: Record<string, string>\n body: string\n}\n\nfunction normalizeServerUrl(serverUrl: string): string {\n const trimmed = serverUrl.trim().replace(/\\/$/, '')\n if (!trimmed.startsWith('https://')) {\n throw new Error(`rocketchat: serverUrl must start with https:// (got ${trimmed})`)\n }\n return trimmed\n}\n\n/**\n * Build a REST request for Rocket.Chat `chat.postMessage`.\n *\n * Reference: https://developer.rocket.chat/reference/api/rest-api/endpoints/messaging/chat-endpoints/postmessage\n */\nexport function buildPostMessageRequest(\n params: RocketChatPostMessageParams\n): RocketChatPostMessageRequest {\n const serverUrl = normalizeServerUrl(params.serverUrl)\n const url = `${serverUrl}/api/v1/chat.postMessage`\n\n const payload: Record<string, unknown> = {\n rid: params.roomId,\n msg: params.text,\n }\n if (params.threadId) {\n payload.tmid = params.threadId\n }\n\n return {\n url,\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-User-Id': params.userId,\n 'X-Auth-Token': params.authToken,\n },\n body: JSON.stringify(payload),\n }\n}\n\n/**\n * Post a message using Rocket.Chat REST API.\n *\n * This function accepts an optional fetch implementation to keep unit tests deterministic.\n */\nexport async function postMessage(\n params: RocketChatPostMessageParams & {\n fetcher?: (input: string, init?: RequestInit) => Promise<Response>\n }\n): Promise<unknown> {\n const req = buildPostMessageRequest(params)\n const fetcher = params.fetcher ?? fetch\n\n const res = await fetcher(req.url, {\n method: req.method,\n headers: req.headers,\n body: req.body,\n })\n\n const text = await res.text()\n if (!res.ok) {\n throw new Error(`rocketchat: chat.postMessage failed (${res.status}): ${text}`)\n }\n\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n","export interface RocketChatReactParams {\n /** Rocket.Chat base server URL, for example https://chat.example.com */\n serverUrl: string\n\n /** Rocket.Chat user id for auth. */\n userId: string\n\n /** Rocket.Chat personal access token for auth. */\n authToken: string\n\n /** Message id to react to. */\n messageId: string\n\n /** Emoji name (without surrounding colons), e.g. \"smile\". */\n emoji: string\n\n /** Whether to add or remove the reaction. */\n shouldReact: boolean\n}\n\nexport interface RocketChatReactRequest {\n url: string\n method: 'POST'\n headers: Record<string, string>\n body: string\n}\n\nfunction normalizeServerUrl(serverUrl: string): string {\n const trimmed = serverUrl.trim().replace(/\\/$/, '')\n if (!trimmed.startsWith('https://')) {\n throw new Error(`rocketchat: serverUrl must start with https:// (got ${trimmed})`)\n }\n return trimmed\n}\n\n/**\n * Build a REST request for Rocket.Chat `chat.react`.\n *\n * Reference: https://developer.rocket.chat/apidocs/react-to-message\n */\nexport function buildReactRequest(params: RocketChatReactParams): RocketChatReactRequest {\n const serverUrl = normalizeServerUrl(params.serverUrl)\n const url = `${serverUrl}/api/v1/chat.react`\n\n return {\n url,\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-User-Id': params.userId,\n 'X-Auth-Token': params.authToken,\n },\n body: JSON.stringify({\n messageId: params.messageId,\n emoji: params.emoji,\n shouldReact: params.shouldReact,\n }),\n }\n}\n\n/**\n * Set or unset a reaction using Rocket.Chat REST API.\n */\nexport async function reactToMessage(\n params: RocketChatReactParams & {\n fetcher?: (input: string, init?: RequestInit) => Promise<Response>\n }\n): Promise<unknown> {\n const req = buildReactRequest(params)\n const fetcher = params.fetcher ?? fetch\n\n const res = await fetcher(req.url, {\n method: req.method,\n headers: req.headers,\n body: req.body,\n })\n\n const text = await res.text()\n if (!res.ok) {\n throw new Error(`rocketchat: chat.react failed (${res.status}): ${text}`)\n }\n\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n","export interface RocketChatMeParams {\n serverUrl: string\n userId: string\n authToken: string\n}\n\nexport interface RocketChatMeRequest {\n url: string\n method: 'GET'\n headers: Record<string, string>\n}\n\nfunction normalizeServerUrl(serverUrl: string): string {\n const trimmed = serverUrl.trim().replace(/\\/$/, '')\n if (!trimmed.startsWith('https://')) {\n throw new Error(`rocketchat: serverUrl must start with https:// (got ${trimmed})`)\n }\n return trimmed\n}\n\n/**\n * Build a REST request for Rocket.Chat `GET /api/v1/me`.\n */\nexport function buildMeRequest(params: RocketChatMeParams): RocketChatMeRequest {\n const serverUrl = normalizeServerUrl(params.serverUrl)\n return {\n url: `${serverUrl}/api/v1/me`,\n method: 'GET',\n headers: {\n 'X-User-Id': params.userId,\n 'X-Auth-Token': params.authToken,\n },\n }\n}\n\n/**\n * Validate current credentials by calling Rocket.Chat `GET /api/v1/me`.\n */\nexport async function getMe(\n params: RocketChatMeParams & {\n fetcher?: (input: string, init?: RequestInit) => Promise<Response>\n }\n): Promise<unknown> {\n const req = buildMeRequest(params)\n const fetcher = params.fetcher ?? fetch\n\n const res = await fetcher(req.url, {\n method: req.method,\n headers: req.headers,\n })\n\n const text = await res.text()\n if (!res.ok) {\n throw new Error(`rocketchat: /api/v1/me failed (${res.status}): ${text}`)\n }\n\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n","export type RocketChatPrefixCommandName = 'status' | 'model' | 'stop'\n\nexport type RocketChatPrefixCommand =\n | { kind: 'command'; name: 'status'; raw: string }\n | { kind: 'command'; name: 'stop'; raw: string }\n | { kind: 'command'; name: 'model'; raw: string; model: string }\n | { kind: 'not-a-command' }\n\nfunction tokenize(text: string): string[] {\n return text\n .trim()\n .split(/\\s+/)\n .map((t) => t.trim())\n .filter((t) => t.length > 0)\n}\n\n/**\n * Parse Rocket.Chat prefix commands.\n *\n * Rules:\n * - Only treat as command if the trimmed message begins with '!'.\n * - Commands supported: !status, !stop, !model <name>\n */\nexport function parsePrefixCommand(text: string): RocketChatPrefixCommand {\n const trimmed = text.trim()\n if (!trimmed.startsWith('!')) return { kind: 'not-a-command' }\n\n const tokens = tokenize(trimmed.slice(1))\n const name = (tokens[0] ?? '').toLowerCase()\n\n if (name === 'status') {\n return { kind: 'command', name: 'status', raw: trimmed }\n }\n\n if (name === 'stop') {\n return { kind: 'command', name: 'stop', raw: trimmed }\n }\n\n if (name === 'model') {\n const model = tokens.slice(1).join(' ').trim()\n if (!model) return { kind: 'not-a-command' }\n return { kind: 'command', name: 'model', raw: trimmed, model }\n }\n\n return { kind: 'not-a-command' }\n}\n","import { authorizeCommand } from '../policy/policy'\nimport type { RocketChatPrefixCommand } from './parse'\nimport type { ResolvedRocketChatAccount } from '../config/resolve_account'\nimport type { RocketChatChatType, RocketChatSender } from '../policy/chat_types'\n\n/**\n * Normalized command dispatch instruction.\n */\nexport type RocketChatCommandDispatch =\n | { ok: true; kind: 'openclaw-command'; commandText: string }\n | { ok: false; reason: string }\n\n/**\n * Map a parsed Rocket.Chat prefix command into an OpenClaw command message.\n */\nexport function dispatchPrefixCommand(params: {\n account: ResolvedRocketChatAccount\n chatType: RocketChatChatType\n sender: RocketChatSender\n parsed: RocketChatPrefixCommand\n}): RocketChatCommandDispatch {\n if (params.parsed.kind !== 'command') {\n return { ok: false, reason: 'not-a-command' }\n }\n\n const decision = authorizeCommand({\n account: params.account,\n chatType: params.chatType,\n sender: params.sender,\n command: params.parsed.name,\n })\n\n if (!decision.allowed) {\n return { ok: false, reason: decision.reason ?? 'blocked' }\n }\n\n if (params.parsed.name === 'status') {\n return { ok: true, kind: 'openclaw-command', commandText: '/status' }\n }\n\n if (params.parsed.name === 'stop') {\n return { ok: true, kind: 'openclaw-command', commandText: '/stop' }\n }\n\n return {\n ok: true,\n kind: 'openclaw-command',\n commandText: `/model ${params.parsed.model}`,\n }\n}\n","import type { DdpTransport } from '../ddp/transport'\n\n/**\n * Close the transport immediately when we determine the account is unauthorized.\n *\n * v1 spec requirement:\n * - On any auth error/unauthorized response on the WebSocket (or any REST call),\n * the plugin MUST immediately close the WebSocket and stop processing events\n * for that account.\n */\nexport function revokeOnUnauthorized(params: { transport: DdpTransport; reason: string }): void {\n params.transport.close({ reason: `unauthorized: ${params.reason}` })\n}\n","import { revokeOnUnauthorized } from './revocation'\nimport type { DdpTransport } from '../ddp/transport'\n\n/**\n * Options for periodic credential revalidation.\n */\nexport interface CredentialRevalidationOptions {\n /** Revalidation cadence in milliseconds. */\n intervalMs: number\n}\n\n/**\n * Start a periodic credential revalidation loop.\n *\n * This provides a bound on worst-case revocation latency for long-lived sessions.\n * v1 target: <= 5 minutes.\n */\nexport function startCredentialRevalidation(params: {\n transport: DdpTransport\n validateOnce: () => Promise<{ ok: true } | { ok: false; reason: string }>\n options: CredentialRevalidationOptions\n}): { stop: () => void } {\n let stopped = false\n const timer = setInterval(async () => {\n if (stopped) return\n\n try {\n const result = await params.validateOnce()\n if (!result.ok) {\n revokeOnUnauthorized({ transport: params.transport, reason: result.reason })\n }\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n revokeOnUnauthorized({ transport: params.transport, reason })\n }\n }, params.options.intervalMs)\n\n return {\n stop: () => {\n stopped = true\n clearInterval(timer)\n },\n }\n}\n","import { getMe } from '../../rocketchat/rest/me'\nimport { RocketChatClient } from '../../rocketchat/client'\nimport { startCredentialRevalidation } from '../../rocketchat/security/revalidate'\nimport type { DdpTransport } from '../../rocketchat/ddp/transport'\nimport type { ResolvedRocketChatAccount } from '../../rocketchat/config/resolve_account'\n\n/**\n * Factory for creating a transport per account.\n */\nexport type RocketChatTransportFactory = (params: {\n account: ResolvedRocketChatAccount\n}) => DdpTransport\n\n/**\n * Start (connect/login/subscribe) a Rocket.Chat client for a single account.\n *\n * v1 security requirements:\n * - periodic credential revalidation to bound revocation latency\n */\nexport async function startRocketChatAccount(params: {\n account: ResolvedRocketChatAccount\n roomIds: string[]\n createTransport: RocketChatTransportFactory\n\n /** Override credential revalidation cadence. Default is 5 minutes. */\n revalidateIntervalMs?: number\n\n /** Optional fetch injection for credential validation. */\n fetcher?: (input: string, init?: RequestInit) => Promise<Response>\n}): Promise<{ client: RocketChatClient; stop: () => void }> {\n const transport = params.createTransport({ account: params.account })\n const client = new RocketChatClient(transport)\n\n await client.start({ resumeToken: params.account.pat, roomIds: params.roomIds })\n\n const revalidate = startCredentialRevalidation({\n transport,\n options: { intervalMs: params.revalidateIntervalMs ?? 5 * 60 * 1000 },\n validateOnce: async () => {\n try {\n await getMe({\n serverUrl: params.account.serverUrl,\n userId: params.account.userId,\n authToken: params.account.pat,\n fetcher: params.fetcher,\n })\n return { ok: true } as const\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n return { ok: false, reason } as const\n }\n },\n })\n\n return {\n client,\n stop: () => {\n revalidate.stop()\n transport.close({ reason: 'stopped' })\n },\n }\n}\n","/**\n * @module @nhtio/rocket-chat-openclaw-integration\n */\n\nimport { defineChannelPluginEntry } from 'openclaw/plugin-sdk/core'\n\nexport type {\n RocketChatAccountConfig,\n RocketChatChannelConfig,\n RocketChatDmPolicy,\n RocketChatGroupPolicy,\n RocketChatOpenClawConfig,\n RocketChatRoomConfig,\n} from './rocketchat/config/types'\n\nexport {\n DEFAULT_DM_POLICY,\n DEFAULT_GROUP_POLICY,\n DEFAULT_REQUIRE_MENTION,\n resolveChannelDefaults,\n} from './rocketchat/config/defaults'\n\nexport type { ResolvedRocketChatAccount } from './rocketchat/config/resolve_account'\nexport { resolveRocketChatAccount } from './rocketchat/config/resolve_account'\n\nexport type { RocketChatChatType, RocketChatSender } from './rocketchat/policy/chat_types'\nexport type { PolicyDecision } from './rocketchat/policy/policy'\nexport { authorizeCommand, authorizeInboundMessage } from './rocketchat/policy/policy'\n\nexport type { DdpTransport } from './rocketchat/ddp/transport'\nexport { FakeDdpTransport } from './rocketchat/ddp/fake_transport'\nexport type { DdpConnectFrame, DdpPingFrame, DdpPongFrame } from './rocketchat/ddp/protocol'\nexport { installDdpProtocol, makeConnectFrame } from './rocketchat/ddp/protocol'\nexport type { DdpMethodFrame, DdpResultFrame } from './rocketchat/ddp/methods'\nexport { DdpMethodClient } from './rocketchat/ddp/methods'\nexport { ddpLoginWithResume } from './rocketchat/ddp/auth'\nexport type { DdpSubFrame, DdpReadyFrame } from './rocketchat/ddp/subscriptions'\nexport { DdpSubscriptionClient } from './rocketchat/ddp/subscriptions'\n\nexport { RocketChatClient } from './rocketchat/client'\n\nexport type { InboundNormalizeResult, RocketChatInboundMessage } from './rocketchat/inbound/types'\nexport { decodeRoomMessageEvent } from './rocketchat/inbound/decode_message'\nexport { classifyChatType } from './rocketchat/inbound/classify_chat_type'\nexport type { RocketChatSessionConversation } from './rocketchat/inbound/session_keys'\nexport { computeSessionConversation } from './rocketchat/inbound/session_keys'\nexport { processInboundRoomMessage } from './rocketchat/inbound/process_inbound'\n\nexport type {\n RocketChatPostMessageParams,\n RocketChatPostMessageRequest,\n} from './rocketchat/rest/post_message'\nexport { buildPostMessageRequest, postMessage } from './rocketchat/rest/post_message'\n\nexport type { RocketChatReactParams, RocketChatReactRequest } from './rocketchat/rest/react'\nexport { buildReactRequest, reactToMessage } from './rocketchat/rest/react'\n\nexport type { RocketChatMeParams, RocketChatMeRequest } from './rocketchat/rest/me'\nexport { buildMeRequest, getMe } from './rocketchat/rest/me'\n\nexport type {\n RocketChatPrefixCommand,\n RocketChatPrefixCommandName,\n} from './rocketchat/commands/parse'\nexport { parsePrefixCommand } from './rocketchat/commands/parse'\n\nexport type { RocketChatCommandDispatch } from './rocketchat/commands/dispatch'\nexport { dispatchPrefixCommand } from './rocketchat/commands/dispatch'\n\nexport type { CredentialRevalidationOptions } from './rocketchat/security/revalidate'\nexport { startCredentialRevalidation } from './rocketchat/security/revalidate'\nexport { revokeOnUnauthorized } from './rocketchat/security/revocation'\n\nexport type { RocketChatTransportFactory } from './openclaw/runtime/account_runner'\nexport { startRocketChatAccount } from './openclaw/runtime/account_runner'\n\n/**\n * The current version of the package.\n *\n * @tip This constant is replaced during the build process with the actual version of the package.\n */\nexport const version: string = __VERSION__\n\n/**\n * OpenClaw Rocket.Chat channel plugin entrypoint.\n *\n * This is a packaging skeleton only.\n * - No Rocket.Chat networking is implemented yet.\n * - The plugin exists so OpenClaw can discover/install the package as a channel plugin.\n */\nexport default defineChannelPluginEntry({\n id: 'rocketchat',\n name: 'Rocket.Chat',\n description: 'Rocket.Chat channel plugin',\n // Plugin implementation will be wired in later phases.\n // We provide a placeholder to keep the entrypoint valid.\n plugin: null as unknown as never,\n})\n"],"mappings":"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"}
|