@ravi-hq/ravi 0.1.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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +271 -0
  3. package/dist/channels/email.d.ts +221 -0
  4. package/dist/channels/email.d.ts.map +1 -0
  5. package/dist/channels/email.js +143 -0
  6. package/dist/channels/email.js.map +1 -0
  7. package/dist/channels/sms.d.ts +223 -0
  8. package/dist/channels/sms.d.ts.map +1 -0
  9. package/dist/channels/sms.js +141 -0
  10. package/dist/channels/sms.js.map +1 -0
  11. package/dist/cli.d.ts +24 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +390 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/client.d.ts +174 -0
  16. package/dist/client.d.ts.map +1 -0
  17. package/dist/client.js +289 -0
  18. package/dist/client.js.map +1 -0
  19. package/dist/config.d.ts +9 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +9 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/crypto.d.ts +143 -0
  24. package/dist/crypto.d.ts.map +1 -0
  25. package/dist/crypto.js +310 -0
  26. package/dist/crypto.js.map +1 -0
  27. package/dist/index.d.ts +79 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +143 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/service.d.ts +98 -0
  32. package/dist/service.d.ts.map +1 -0
  33. package/dist/service.js +130 -0
  34. package/dist/service.js.map +1 -0
  35. package/dist/sse.d.ts +110 -0
  36. package/dist/sse.d.ts.map +1 -0
  37. package/dist/sse.js +269 -0
  38. package/dist/sse.js.map +1 -0
  39. package/dist/tools/email-send.d.ts +13 -0
  40. package/dist/tools/email-send.d.ts.map +1 -0
  41. package/dist/tools/email-send.js +60 -0
  42. package/dist/tools/email-send.js.map +1 -0
  43. package/dist/tools/feedback.d.ts +13 -0
  44. package/dist/tools/feedback.d.ts.map +1 -0
  45. package/dist/tools/feedback.js +43 -0
  46. package/dist/tools/feedback.js.map +1 -0
  47. package/dist/tools/identity.d.ts +43 -0
  48. package/dist/tools/identity.d.ts.map +1 -0
  49. package/dist/tools/identity.js +59 -0
  50. package/dist/tools/identity.js.map +1 -0
  51. package/dist/tools/inbox.d.ts +22 -0
  52. package/dist/tools/inbox.d.ts.map +1 -0
  53. package/dist/tools/inbox.js +166 -0
  54. package/dist/tools/inbox.js.map +1 -0
  55. package/dist/tools/passwords.d.ts +19 -0
  56. package/dist/tools/passwords.d.ts.map +1 -0
  57. package/dist/tools/passwords.js +165 -0
  58. package/dist/tools/passwords.js.map +1 -0
  59. package/dist/tools/sms-send.d.ts +11 -0
  60. package/dist/tools/sms-send.d.ts.map +1 -0
  61. package/dist/tools/sms-send.js +32 -0
  62. package/dist/tools/sms-send.js.map +1 -0
  63. package/dist/tools/vault.d.ts +15 -0
  64. package/dist/tools/vault.d.ts.map +1 -0
  65. package/dist/tools/vault.js +97 -0
  66. package/dist/tools/vault.js.map +1 -0
  67. package/dist/types.d.ts +137 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +7 -0
  70. package/dist/types.js.map +1 -0
  71. package/dist/utils.d.ts +11 -0
  72. package/dist/utils.d.ts.map +1 -0
  73. package/dist/utils.js +18 -0
  74. package/dist/utils.js.map +1 -0
  75. package/openclaw.plugin.json +33 -0
  76. package/package.json +70 -0
  77. package/skills/CLAUDE.md +36 -0
  78. package/skills/ravi/SKILL.md +46 -0
  79. package/skills/ravi-email-send/SKILL.md +44 -0
  80. package/skills/ravi-feedback/SKILL.md +37 -0
  81. package/skills/ravi-identity/SKILL.md +50 -0
  82. package/skills/ravi-inbox/SKILL.md +58 -0
  83. package/skills/ravi-login/SKILL.md +42 -0
  84. package/skills/ravi-passwords/SKILL.md +48 -0
  85. package/skills/ravi-vault/SKILL.md +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ravi HQ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # @ravi-hq/openclaw-plugin
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@ravi-hq/openclaw-plugin)](https://www.npmjs.com/package/@ravi-hq/openclaw-plugin)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+
6
+ OpenClaw plugin for [Ravi](https://ravi.app) -- identity provider for AI agents.
7
+ Gives your agent a real email address, phone number, password manager, and
8
+ encrypted vault through a single plugin.
9
+
10
+ ## Features
11
+
12
+ - **2 messaging channels** -- real-time email and SMS via Server-Sent Events
13
+ - **17 agent tools** -- manage identities, inboxes, passwords, vault secrets, and more
14
+ - **E2E encryption** -- PIN-based Argon2id + NaCl SealedBox, compatible with Ravi CLI
15
+ - **DM policy controls** -- allowlist or open policy per channel account
16
+ - **Lazy crypto** -- encryption keys derived on first use, not at startup
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ openclaw plugins install @ravi-hq/openclaw-plugin
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Install and authenticate
27
+
28
+ ```bash
29
+ openclaw plugins install @ravi-hq/openclaw-plugin
30
+ openclaw ravi login
31
+ ```
32
+
33
+ This opens a browser for device-code authentication. Copy the access token from the output.
34
+
35
+ ### 2. Add your token and PIN to the plugin config
36
+
37
+ ```yaml
38
+ # In your OpenClaw config
39
+ plugins:
40
+ ravi:
41
+ token: "eyJhbGciOiJIUzI1NiIs..." # From 'openclaw ravi login'
42
+ pin: "123456" # Your 6-digit E2E encryption PIN
43
+ ```
44
+
45
+ ### 3. Run setup to configure channels
46
+
47
+ ```bash
48
+ openclaw ravi setup
49
+ ```
50
+
51
+ This lists your identities and generates a channel configuration snippet you
52
+ can paste into your OpenClaw config.
53
+
54
+ ## Configuration
55
+
56
+ ```yaml
57
+ plugins:
58
+ ravi:
59
+ # Ravi API base URL (default: https://ravi.app)
60
+ apiUrl: "https://ravi.app"
61
+
62
+ # JWT access token (required)
63
+ token: "your-access-token"
64
+
65
+ # 6-digit E2E encryption PIN (required)
66
+ pin: "123456"
67
+
68
+ # Channel accounts (from 'openclaw ravi setup')
69
+ channels:
70
+ ravi-email:
71
+ accounts:
72
+ "identity-uuid-here":
73
+ identityUuid: "identity-uuid-here"
74
+ identityName: "My Agent"
75
+ email: "agent@in.ravi.app"
76
+ dm:
77
+ policy: "allowlist" # "allowlist" or "open"
78
+ allowFrom:
79
+ - "you@gmail.com"
80
+
81
+ ravi-sms:
82
+ accounts:
83
+ "identity-uuid-here":
84
+ identityUuid: "identity-uuid-here"
85
+ identityName: "My Agent"
86
+ phone: "+15551234567"
87
+ dm:
88
+ policy: "allowlist" # "allowlist" or "open"
89
+ allowFrom:
90
+ - "+15559876543"
91
+ ```
92
+
93
+ ## Channels
94
+
95
+ ### Ravi Email
96
+
97
+ Real-time email channel powered by SSE events from the Ravi backend.
98
+
99
+ - **Threading model:** Each email thread maps 1:1 to an OpenClaw session. A new
100
+ `thread_id` creates a new session; subsequent messages in that thread resume it.
101
+ - **Session key format:** `agent:{agentId}:ravi-email:{accountId}:thread:{threadId}`
102
+ - **DM policy:** Set `policy: "allowlist"` to restrict incoming email to specific
103
+ sender addresses, or `"open"` to accept all senders.
104
+ - **Outbound:** Replies are sent via the Ravi API and threaded correctly in the
105
+ original email conversation.
106
+
107
+ ### Ravi SMS
108
+
109
+ Real-time SMS channel powered by the same SSE stream.
110
+
111
+ - **Threading model:** All messages from one phone number form a single session.
112
+ A new `from_number` creates a new session; subsequent messages from that number
113
+ resume it.
114
+ - **Session key format:** `agent:{agentId}:ravi-sms:{accountId}:dm:{phoneNumber}`
115
+ - **DM policy:** Set `policy: "allowlist"` to restrict incoming SMS to specific
116
+ phone numbers (E.164 format), or `"open"` to accept all senders.
117
+ - **Outbound:** Replies are sent via the Ravi API as plain-text SMS.
118
+
119
+ ## Agent Tools
120
+
121
+ The plugin registers 17 tools that agents can invoke.
122
+
123
+ ### Identity
124
+
125
+ | Tool | Description | Parameters |
126
+ |------|-------------|------------|
127
+ | `ravi_identity_list` | List all identities for the authenticated user | -- |
128
+ | `ravi_identity_create` | Create a new identity (provisions email + phone + vault) | `name` (string, required) |
129
+ | `ravi_get_info` | Get the active identity's email and phone number | -- |
130
+
131
+ ### Email Inbox
132
+
133
+ | Tool | Description | Parameters |
134
+ |------|-------------|------------|
135
+ | `ravi_inbox_email` | List email threads, optionally unread only | `unread` (boolean) |
136
+ | `ravi_read_email` | Read all messages in an email thread | `thread_id` (string, required) |
137
+ | `ravi_email_compose` | Compose and send a new email | `to`, `subject`, `body` (all required) |
138
+ | `ravi_email_reply` | Reply to an existing email | `message_id` (number, required), `body` (required), `reply_all` |
139
+
140
+ ### SMS Inbox
141
+
142
+ | Tool | Description | Parameters |
143
+ |------|-------------|------------|
144
+ | `ravi_inbox_sms` | List SMS conversations, optionally unread only | `unread` (boolean) |
145
+ | `ravi_read_sms` | Read all messages in an SMS conversation | `conversation_id` (string, required) |
146
+
147
+ ### Passwords
148
+
149
+ | Tool | Description | Parameters |
150
+ |------|-------------|------------|
151
+ | `ravi_passwords_list` | List all saved passwords (passwords hidden in list view) | -- |
152
+ | `ravi_passwords_get` | Get a specific password entry with decrypted password | `uuid` (string, required) |
153
+ | `ravi_passwords_create` | Create a new password (encrypted) | `domain` (required), `username`, `password`, `notes` |
154
+ | `ravi_passwords_delete` | Delete a password entry | `uuid` (string, required) |
155
+
156
+ ### Vault
157
+
158
+ | Tool | Description | Parameters |
159
+ |------|-------------|------------|
160
+ | `ravi_vault_list` | List all secret keys in the vault | -- |
161
+ | `ravi_vault_get` | Get a secret by key (auto-decrypted) | `key` (string, required) |
162
+ | `ravi_vault_set` | Store a secret (auto-encrypted) | `key` (string, required), `value` (string, required) |
163
+
164
+ ### Feedback
165
+
166
+ | Tool | Description | Parameters |
167
+ |------|-------------|------------|
168
+ | `ravi_feedback` | Submit feedback about Ravi | `message` (required), `category` (bug/feature/help/other) |
169
+
170
+ ## CLI Commands
171
+
172
+ Run these via `openclaw ravi <command>`:
173
+
174
+ | Command | Description |
175
+ |---------|-------------|
176
+ | `login` | Authenticate via device-code flow (opens browser) |
177
+ | `status` | Show auth status and list available identities |
178
+ | `setup` | Generate channel configuration from your identities |
179
+
180
+ ## E2E Encryption
181
+
182
+ All sensitive data (passwords, vault secrets, email content at rest) is encrypted
183
+ end-to-end using a 6-digit PIN.
184
+
185
+ - **Key derivation:** PIN + server-stored salt -> Argon2id (time=3, mem=64MB, threads=1) -> 32-byte seed
186
+ - **Encryption:** NaCl SealedBox (ephemeral X25519 keypair + Poly1305 MAC)
187
+ - **Ciphertext format:** `e2e::<base64>` -- same format used by the Ravi CLI
188
+ - **PIN verification:** On first use, the plugin derives the keypair and verifies
189
+ it against the server's stored verifier before proceeding
190
+
191
+ The crypto layer is lazy-initialized: keys are only derived when a tool actually
192
+ needs encryption or decryption.
193
+
194
+ ## Architecture
195
+
196
+ ```text
197
+ Agent (OpenClaw runtime)
198
+ |
199
+ +-- Channels (ravi-email, ravi-sms)
200
+ | '-- SSE stream --> GET /api/events/stream/
201
+ | Events arrive in plaintext (server decrypts before dispatch)
202
+ |
203
+ +-- Tools (17 agent tools)
204
+ | '-- REST calls --> GET/POST/DELETE /api/...
205
+ | Identity-scoped via X-Ravi-Identity header
206
+ |
207
+ '-- Crypto layer (lazy-initialized)
208
+ PIN + salt --> Argon2id --> NaCl SealedBox
209
+ Decrypts historical data from REST ("e2e::<base64>" fields)
210
+ Encrypts vault/password writes before sending to server
211
+ ```
212
+
213
+ Key points:
214
+
215
+ - **SSE events are plaintext** -- the server decrypts before pushing to the stream.
216
+ No client-side decryption needed for real-time channel messages.
217
+ - **REST data may be encrypted** -- historical email content, passwords, and vault
218
+ secrets can contain `"e2e::<base64>"` ciphertext that the plugin decrypts
219
+ client-side using the PIN-derived keypair.
220
+ - **Authentication** uses unbound JWTs. The `X-Ravi-Identity` header scopes each
221
+ API request to a specific identity.
222
+
223
+ ## Development
224
+
225
+ ### Prerequisites
226
+
227
+ - Node.js 18+
228
+ - npm
229
+
230
+ ### Setup
231
+
232
+ ```bash
233
+ npm install
234
+ ```
235
+
236
+ ### Scripts
237
+
238
+ ```bash
239
+ npm run build # Compile TypeScript to dist/
240
+ npm test # Run tests (vitest, single run)
241
+ npm run test:watch # Run tests in watch mode
242
+ npm run lint # Type-check without emitting (tsc --noEmit)
243
+ ```
244
+
245
+ ### Project structure
246
+
247
+ ```text
248
+ src/
249
+ index.ts # Plugin entry point and registration
250
+ types.ts # TypeScript interfaces for API responses and SSE events
251
+ client.ts # RaviClient -- typed HTTP client for all REST endpoints
252
+ sse.ts # RaviSSEClient -- SSE with auto-reconnect and heartbeat
253
+ crypto.ts # E2E encryption (Argon2id + NaCl SealedBox)
254
+ service.ts # Background listener service (manages SSE connections)
255
+ cli.ts # CLI commands (login, status, setup)
256
+ channels/
257
+ email.ts # ravi-email channel definition
258
+ sms.ts # ravi-sms channel definition
259
+ tools/
260
+ identity.ts # Identity management tools
261
+ inbox.ts # Email and SMS inbox tools
262
+ email-send.ts # Email compose and reply tools
263
+ passwords.ts # Password manager tools
264
+ vault.ts # Vault secret tools
265
+ feedback.ts # Feedback tool
266
+ __tests__/ # Test files (*.test.ts)
267
+ ```
268
+
269
+ ## License
270
+
271
+ MIT
@@ -0,0 +1,221 @@
1
+ import type { RaviClient } from "../client.js";
2
+ import type { EmailEvent } from "../types.js";
3
+ /** Configuration for a single email account within the ravi-email channel. */
4
+ export interface EmailAccountConfig {
5
+ /** The identity UUID this account is bound to. */
6
+ identityUuid: string;
7
+ /** Human-readable identity name for display purposes. */
8
+ identityName: string;
9
+ /** The email address provisioned for this identity. */
10
+ email: string;
11
+ }
12
+ /**
13
+ * Thread context passed alongside outbound messages so the channel
14
+ * can thread replies correctly in the original email conversation.
15
+ */
16
+ export interface EmailThreadContext {
17
+ /** The email thread identifier (maps 1:1 to an OpenClaw session). */
18
+ threadId: string;
19
+ /** Numeric ID of the last message in the thread (used for reply targeting). */
20
+ lastMessageId: number;
21
+ /** Subject line of the email thread. */
22
+ subject: string;
23
+ }
24
+ /** Normalized inbound message envelope produced by {@link normalizeInbound}. */
25
+ export interface EmailInboundEnvelope {
26
+ /** Sender's email address (used as the sender ID). */
27
+ senderId: string;
28
+ /** Plain-text body extracted from the email (falls back to subject). */
29
+ text: string;
30
+ /** Thread ID that maps to the OpenClaw session key. */
31
+ threadId: string;
32
+ /** Additional email metadata passed through to the agent. */
33
+ metadata: {
34
+ senderType: string;
35
+ readOnly: boolean;
36
+ subject: string;
37
+ fromDisplayName: string;
38
+ htmlContent: string;
39
+ attachments: number;
40
+ inReplyTo: string | null;
41
+ lastMessageId: number;
42
+ };
43
+ }
44
+ /**
45
+ * Creates the ravi-email channel definition for OpenClaw.
46
+ *
47
+ * Each email thread maps to exactly one OpenClaw session (1:1):
48
+ * - A new `thread_id` triggers creation of a new session.
49
+ * - A known `thread_id` resumes the existing session.
50
+ *
51
+ * The channel is a plain object (not a class) following OpenClaw's
52
+ * declarative channel convention.
53
+ *
54
+ * @returns The ravi-email channel definition object.
55
+ */
56
+ export declare function createEmailChannel(): {
57
+ id: "ravi-email";
58
+ meta: {
59
+ id: string;
60
+ label: string;
61
+ selectionLabel: string;
62
+ blurb: string;
63
+ aliases: string[];
64
+ };
65
+ capabilities: {
66
+ chatTypes: readonly ["direct"];
67
+ };
68
+ config: {
69
+ /**
70
+ * List all configured account IDs (identity UUIDs) for the ravi-email channel.
71
+ *
72
+ * @param cfg - The full OpenClaw plugin configuration object.
73
+ * @returns Array of identity UUID strings.
74
+ */
75
+ listAccountIds: (cfg: Record<string, unknown>) => string[];
76
+ /**
77
+ * Resolve an account configuration by its ID (identity UUID).
78
+ *
79
+ * Returns a default empty config if the account is not found rather than
80
+ * throwing, so callers can handle missing accounts gracefully.
81
+ *
82
+ * @param cfg - The full OpenClaw plugin configuration object.
83
+ * @param accountId - The identity UUID to look up.
84
+ * @returns The resolved {@link EmailAccountConfig}.
85
+ */
86
+ resolveAccount: (cfg: Record<string, unknown>, accountId: string) => EmailAccountConfig;
87
+ };
88
+ /**
89
+ * Normalize an inbound SSE email event into a channel message envelope.
90
+ *
91
+ * All messages are passed through — the backend classifies senders via
92
+ * `sender_type`. External senders are marked read-only so the agent
93
+ * treats them as context without responding or using write tools.
94
+ *
95
+ * @param event - The raw {@link EmailEvent} from the SSE stream.
96
+ * @param account - The {@link EmailAccountConfig} for the receiving identity.
97
+ * @returns A normalized {@link EmailInboundEnvelope} (never null).
98
+ */
99
+ normalizeInbound(event: EmailEvent, account: EmailAccountConfig): EmailInboundEnvelope | null;
100
+ outbound: {
101
+ deliveryMode: "direct";
102
+ /**
103
+ * Send a reply to the email thread that triggered this session.
104
+ *
105
+ * Wraps the plain-text body in HTML and calls {@link RaviClient.replyToEmail}
106
+ * using the thread context to target the correct message for threading.
107
+ *
108
+ * @param params.text - Plain-text reply body.
109
+ * @param params.client - An authenticated {@link RaviClient} instance.
110
+ * @param params.threadContext - Thread context from the inbound event.
111
+ * @returns A result object indicating success or failure.
112
+ */
113
+ sendText: (params: {
114
+ text: string;
115
+ client: RaviClient;
116
+ threadContext?: EmailThreadContext;
117
+ }) => Promise<{
118
+ ok: boolean;
119
+ error?: string;
120
+ }>;
121
+ };
122
+ /**
123
+ * Generate the session key for this channel.
124
+ *
125
+ * The key uniquely identifies a session by combining the agent, account,
126
+ * and thread. OpenClaw uses this to route inbound events to the correct
127
+ * session and persist session state.
128
+ *
129
+ * Format: `agent:{agentId}:ravi-email:{accountId}:thread:{threadId}`
130
+ *
131
+ * @param agentId - The OpenClaw agent identifier.
132
+ * @param accountId - The identity UUID (account ID).
133
+ * @param threadId - The email thread identifier.
134
+ * @returns The deterministic session key string.
135
+ */
136
+ getSessionKey(agentId: string, accountId: string, threadId: string): string;
137
+ };
138
+ /** The singleton ravi-email channel instance. */
139
+ export declare const raviEmailChannel: {
140
+ id: "ravi-email";
141
+ meta: {
142
+ id: string;
143
+ label: string;
144
+ selectionLabel: string;
145
+ blurb: string;
146
+ aliases: string[];
147
+ };
148
+ capabilities: {
149
+ chatTypes: readonly ["direct"];
150
+ };
151
+ config: {
152
+ /**
153
+ * List all configured account IDs (identity UUIDs) for the ravi-email channel.
154
+ *
155
+ * @param cfg - The full OpenClaw plugin configuration object.
156
+ * @returns Array of identity UUID strings.
157
+ */
158
+ listAccountIds: (cfg: Record<string, unknown>) => string[];
159
+ /**
160
+ * Resolve an account configuration by its ID (identity UUID).
161
+ *
162
+ * Returns a default empty config if the account is not found rather than
163
+ * throwing, so callers can handle missing accounts gracefully.
164
+ *
165
+ * @param cfg - The full OpenClaw plugin configuration object.
166
+ * @param accountId - The identity UUID to look up.
167
+ * @returns The resolved {@link EmailAccountConfig}.
168
+ */
169
+ resolveAccount: (cfg: Record<string, unknown>, accountId: string) => EmailAccountConfig;
170
+ };
171
+ /**
172
+ * Normalize an inbound SSE email event into a channel message envelope.
173
+ *
174
+ * All messages are passed through — the backend classifies senders via
175
+ * `sender_type`. External senders are marked read-only so the agent
176
+ * treats them as context without responding or using write tools.
177
+ *
178
+ * @param event - The raw {@link EmailEvent} from the SSE stream.
179
+ * @param account - The {@link EmailAccountConfig} for the receiving identity.
180
+ * @returns A normalized {@link EmailInboundEnvelope} (never null).
181
+ */
182
+ normalizeInbound(event: EmailEvent, account: EmailAccountConfig): EmailInboundEnvelope | null;
183
+ outbound: {
184
+ deliveryMode: "direct";
185
+ /**
186
+ * Send a reply to the email thread that triggered this session.
187
+ *
188
+ * Wraps the plain-text body in HTML and calls {@link RaviClient.replyToEmail}
189
+ * using the thread context to target the correct message for threading.
190
+ *
191
+ * @param params.text - Plain-text reply body.
192
+ * @param params.client - An authenticated {@link RaviClient} instance.
193
+ * @param params.threadContext - Thread context from the inbound event.
194
+ * @returns A result object indicating success or failure.
195
+ */
196
+ sendText: (params: {
197
+ text: string;
198
+ client: RaviClient;
199
+ threadContext?: EmailThreadContext;
200
+ }) => Promise<{
201
+ ok: boolean;
202
+ error?: string;
203
+ }>;
204
+ };
205
+ /**
206
+ * Generate the session key for this channel.
207
+ *
208
+ * The key uniquely identifies a session by combining the agent, account,
209
+ * and thread. OpenClaw uses this to route inbound events to the correct
210
+ * session and persist session state.
211
+ *
212
+ * Format: `agent:{agentId}:ravi-email:{accountId}:thread:{threadId}`
213
+ *
214
+ * @param agentId - The OpenClaw agent identifier.
215
+ * @param accountId - The identity UUID (account ID).
216
+ * @param threadId - The email thread identifier.
217
+ * @returns The deterministic session key string.
218
+ */
219
+ getSessionKey(agentId: string, accountId: string, threadId: string): string;
220
+ };
221
+ //# sourceMappingURL=email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/channels/email.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;CAEf;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,QAAQ,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB;;;;;;;;;;;;;QAkB5B;;;;;WAKG;8BACmB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,EAAE;QAOxD;;;;;;;;;WASG;8BAEI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,aACjB,MAAM,KAChB,kBAAkB;;IAgBvB;;;;;;;;;;OAUG;4BAEM,UAAU,WACR,kBAAkB,GAC1B,oBAAoB,GAAG,IAAI;;;QAuB5B;;;;;;;;;;WAUG;2BACsB;YACvB,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,EAAE,UAAU,CAAC;YACnB,aAAa,CAAC,EAAE,kBAAkB,CAAC;SACpC,KAAG,OAAO,CAAC;YAAE,EAAE,EAAE,OAAO,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;;IAoB9C;;;;;;;;;;;;;OAaG;2BACoB,MAAM,aAAa,MAAM,YAAY,MAAM,GAAG,MAAM;EAI9E;AAED,iDAAiD;AACjD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;QAvIvB;;;;;WAKG;8BACmB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,EAAE;QAOxD;;;;;;;;;WASG;8BAEI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,aACjB,MAAM,KAChB,kBAAkB;;IAgBvB;;;;;;;;;;OAUG;4BAEM,UAAU,WACR,kBAAkB,GAC1B,oBAAoB,GAAG,IAAI;;;QAuB5B;;;;;;;;;;WAUG;2BACsB;YACvB,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,EAAE,UAAU,CAAC;YACnB,aAAa,CAAC,EAAE,kBAAkB,CAAC;SACpC,KAAG,OAAO,CAAC;YAAE,EAAE,EAAE,OAAO,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;;IAoB9C;;;;;;;;;;;;;OAaG;2BACoB,MAAM,aAAa,MAAM,YAAY,MAAM,GAAG,MAAM;CAO3B,CAAC"}
@@ -0,0 +1,143 @@
1
+ import { wrapInHtml } from "../utils.js";
2
+ // ─── Channel Definition ──────────────────────────────────────────────────────
3
+ /**
4
+ * Creates the ravi-email channel definition for OpenClaw.
5
+ *
6
+ * Each email thread maps to exactly one OpenClaw session (1:1):
7
+ * - A new `thread_id` triggers creation of a new session.
8
+ * - A known `thread_id` resumes the existing session.
9
+ *
10
+ * The channel is a plain object (not a class) following OpenClaw's
11
+ * declarative channel convention.
12
+ *
13
+ * @returns The ravi-email channel definition object.
14
+ */
15
+ export function createEmailChannel() {
16
+ return {
17
+ id: "ravi-email",
18
+ meta: {
19
+ id: "ravi-email",
20
+ label: "Ravi Email",
21
+ selectionLabel: "Ravi Email (AI Agent Identity)",
22
+ blurb: "Email channel via Ravi identity provider. Each email thread becomes a separate agent session.",
23
+ aliases: ["ravi-mail"],
24
+ },
25
+ capabilities: {
26
+ chatTypes: ["direct"],
27
+ },
28
+ config: {
29
+ /**
30
+ * List all configured account IDs (identity UUIDs) for the ravi-email channel.
31
+ *
32
+ * @param cfg - The full OpenClaw plugin configuration object.
33
+ * @returns Array of identity UUID strings.
34
+ */
35
+ listAccountIds: (cfg) => {
36
+ const channels = cfg.channels;
37
+ return Object.keys(channels?.["ravi-email"]?.accounts ?? {});
38
+ },
39
+ /**
40
+ * Resolve an account configuration by its ID (identity UUID).
41
+ *
42
+ * Returns a default empty config if the account is not found rather than
43
+ * throwing, so callers can handle missing accounts gracefully.
44
+ *
45
+ * @param cfg - The full OpenClaw plugin configuration object.
46
+ * @param accountId - The identity UUID to look up.
47
+ * @returns The resolved {@link EmailAccountConfig}.
48
+ */
49
+ resolveAccount: (cfg, accountId) => {
50
+ const channels = cfg.channels;
51
+ const found = channels?.["ravi-email"]?.accounts?.[accountId];
52
+ if (!found) {
53
+ console.warn(`[ravi] Email account ${accountId} not found in config — using empty defaults`);
54
+ }
55
+ return found ?? {
56
+ identityUuid: accountId,
57
+ identityName: "",
58
+ email: "",
59
+ };
60
+ },
61
+ },
62
+ /**
63
+ * Normalize an inbound SSE email event into a channel message envelope.
64
+ *
65
+ * All messages are passed through — the backend classifies senders via
66
+ * `sender_type`. External senders are marked read-only so the agent
67
+ * treats them as context without responding or using write tools.
68
+ *
69
+ * @param event - The raw {@link EmailEvent} from the SSE stream.
70
+ * @param account - The {@link EmailAccountConfig} for the receiving identity.
71
+ * @returns A normalized {@link EmailInboundEnvelope} (never null).
72
+ */
73
+ normalizeInbound(event, account) {
74
+ const isReadOnly = event.sender_type !== "owner" && event.sender_type !== "self";
75
+ return {
76
+ senderId: event.from_email,
77
+ text: event.text_content || event.subject,
78
+ threadId: event.thread_id,
79
+ metadata: {
80
+ senderType: event.sender_type,
81
+ readOnly: isReadOnly,
82
+ subject: event.subject,
83
+ fromDisplayName: event.from_display_name,
84
+ htmlContent: event.html_content,
85
+ attachments: event.attachments,
86
+ inReplyTo: event.in_reply_to,
87
+ lastMessageId: Number(event.id) || 0,
88
+ },
89
+ };
90
+ },
91
+ outbound: {
92
+ deliveryMode: "direct",
93
+ /**
94
+ * Send a reply to the email thread that triggered this session.
95
+ *
96
+ * Wraps the plain-text body in HTML and calls {@link RaviClient.replyToEmail}
97
+ * using the thread context to target the correct message for threading.
98
+ *
99
+ * @param params.text - Plain-text reply body.
100
+ * @param params.client - An authenticated {@link RaviClient} instance.
101
+ * @param params.threadContext - Thread context from the inbound event.
102
+ * @returns A result object indicating success or failure.
103
+ */
104
+ sendText: async (params) => {
105
+ const { text, client, threadContext } = params;
106
+ if (!threadContext?.lastMessageId) {
107
+ return {
108
+ ok: false,
109
+ error: "No email thread context — cannot reply without a message to reply to",
110
+ };
111
+ }
112
+ try {
113
+ await client.replyToEmail(threadContext.lastMessageId, wrapInHtml(text));
114
+ return { ok: true };
115
+ }
116
+ catch (error) {
117
+ const message = error instanceof Error ? error.message : "Unknown error";
118
+ return { ok: false, error: `Failed to send email reply: ${message}` };
119
+ }
120
+ },
121
+ },
122
+ /**
123
+ * Generate the session key for this channel.
124
+ *
125
+ * The key uniquely identifies a session by combining the agent, account,
126
+ * and thread. OpenClaw uses this to route inbound events to the correct
127
+ * session and persist session state.
128
+ *
129
+ * Format: `agent:{agentId}:ravi-email:{accountId}:thread:{threadId}`
130
+ *
131
+ * @param agentId - The OpenClaw agent identifier.
132
+ * @param accountId - The identity UUID (account ID).
133
+ * @param threadId - The email thread identifier.
134
+ * @returns The deterministic session key string.
135
+ */
136
+ getSessionKey(agentId, accountId, threadId) {
137
+ return `agent:${agentId}:ravi-email:${accountId}:thread:${threadId}`;
138
+ },
139
+ };
140
+ }
141
+ /** The singleton ravi-email channel instance. */
142
+ export const raviEmailChannel = createEmailChannel();
143
+ //# sourceMappingURL=email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/channels/email.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAmDzC,gFAAgF;AAEhF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,EAAE,EAAE,YAAqB;QAEzB,IAAI,EAAE;YACJ,EAAE,EAAE,YAAY;YAChB,KAAK,EAAE,YAAY;YACnB,cAAc,EAAE,gCAAgC;YAChD,KAAK,EACH,+FAA+F;YACjG,OAAO,EAAE,CAAC,WAAW,CAAC;SACvB;QAED,YAAY,EAAE;YACZ,SAAS,EAAE,CAAC,QAAQ,CAAU;SAC/B;QAED,MAAM,EAAE;YACN;;;;;eAKG;YACH,cAAc,EAAE,CAAC,GAA4B,EAAY,EAAE;gBACzD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAER,CAAC;gBACd,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;YAC/D,CAAC;YAED;;;;;;;;;eASG;YACH,cAAc,EAAE,CACd,GAA4B,EAC5B,SAAiB,EACG,EAAE;gBACtB,MAAM,QAAQ,GAAG,GAAG,CAAC,QAER,CAAC;gBACd,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAC;gBAC9D,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,OAAO,CAAC,IAAI,CAAC,wBAAwB,SAAS,6CAA6C,CAAC,CAAC;gBAC/F,CAAC;gBACD,OAAO,KAAK,IAAI;oBACd,YAAY,EAAE,SAAS;oBACvB,YAAY,EAAE,EAAE;oBAChB,KAAK,EAAE,EAAE;iBACV,CAAC;YACJ,CAAC;SACF;QAED;;;;;;;;;;WAUG;QACH,gBAAgB,CACd,KAAiB,EACjB,OAA2B;YAE3B,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,KAAK,OAAO,IAAI,KAAK,CAAC,WAAW,KAAK,MAAM,CAAC;YAEjF,OAAO;gBACL,QAAQ,EAAE,KAAK,CAAC,UAAU;gBAC1B,IAAI,EAAE,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,OAAO;gBACzC,QAAQ,EAAE,KAAK,CAAC,SAAS;gBACzB,QAAQ,EAAE;oBACR,UAAU,EAAE,KAAK,CAAC,WAAW;oBAC7B,QAAQ,EAAE,UAAU;oBACpB,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,eAAe,EAAE,KAAK,CAAC,iBAAiB;oBACxC,WAAW,EAAE,KAAK,CAAC,YAAY;oBAC/B,WAAW,EAAE,KAAK,CAAC,WAAW;oBAC9B,SAAS,EAAE,KAAK,CAAC,WAAW;oBAC5B,aAAa,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC;iBACrC;aACF,CAAC;QACJ,CAAC;QAED,QAAQ,EAAE;YACR,YAAY,EAAE,QAAiB;YAE/B;;;;;;;;;;eAUG;YACH,QAAQ,EAAE,KAAK,EAAE,MAIhB,EAA4C,EAAE;gBAC7C,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC;gBAE/C,IAAI,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC;oBAClC,OAAO;wBACL,EAAE,EAAE,KAAK;wBACT,KAAK,EAAE,sEAAsE;qBAC9E,CAAC;gBACJ,CAAC;gBAED,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,YAAY,CAAC,aAAa,CAAC,aAAa,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;oBACzE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;gBACtB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;oBACzE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,OAAO,EAAE,EAAE,CAAC;gBACxE,CAAC;YACH,CAAC;SACF;QAED;;;;;;;;;;;;;WAaG;QACH,aAAa,CAAC,OAAe,EAAE,SAAiB,EAAE,QAAgB;YAChE,OAAO,SAAS,OAAO,eAAe,SAAS,WAAW,QAAQ,EAAE,CAAC;QACvE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,iDAAiD;AACjD,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,EAAE,CAAC"}