@yeego/yeego-openclaw 1.0.2 → 1.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.
package/QUICKSTART.md CHANGED
@@ -1,82 +1,179 @@
1
1
  # Quick Start Guide
2
2
 
3
- ## 🚀 Get Started in 3 Steps
3
+ ## 🚀 Get Started in 4 Steps
4
4
 
5
- ### Step 1: Get Your Configuration
5
+ ### Step 1: Install the Plugin
6
+
7
+ ```bash
8
+ openclaw plugins install @yeego/yeego-openclaw
9
+ ```
10
+
11
+ ### Step 2: Get Your Configuration from Yeego App
6
12
 
7
13
  1. Open Yeego mobile app
8
14
  2. Go to your profile → Settings
9
15
  3. Tap "Connect to OpenClaw" 🦞
10
- 4. Tap "Copy Configuration"
11
-
12
- You'll get something like:
13
-
16
+ 4. Copy the configuration provided
17
+
18
+ You'll get JSON configuration like:
19
+
20
+ ```json
21
+ {
22
+ "token": "eyJhbG...",
23
+ "profileId": "abc123...",
24
+ "baseUrl": "https://yeego.app",
25
+ "sidecarUrl": "https://yeego.app/_sidecar",
26
+ "connectUrl": "https://yeego.app/_sidecar/public/openclaw/connect"
27
+ }
14
28
  ```
15
- # Yeego OpenClaw Plugin Configuration
16
- YEEGO_TOKEN=eyJhbG...
17
- YEEGO_PROFILE_ID=abc123...
18
- YEEGO_BASE_URL=http://localhost:8091
19
- YEEGO_PLUGIN_URL=http://localhost:8092/openclaw-plugin
20
- ```
21
-
22
- ### Step 2: Save Configuration
23
29
 
24
- ```bash
25
- cd openclaw-plugin
26
-
27
- # Paste your config and save
28
- bun run setup "YEEGO_TOKEN=eyJhbG...
29
- YEEGO_PROFILE_ID=abc123...
30
- YEEGO_BASE_URL=http://localhost:8091"
30
+ ### Step 3: Configure OpenClaw
31
+
32
+ Add the configuration to `~/.openclaw/openclaw.json`:
33
+
34
+ ```json
35
+ {
36
+ "channels": {
37
+ "yeego": {
38
+ "enabled": true,
39
+ "config": {
40
+ "token": "eyJhbG...",
41
+ "profileId": "abc123...",
42
+ "baseUrl": "https://yeego.app",
43
+ "sidecarUrl": "https://yeego.app/_sidecar",
44
+ "connectUrl": "https://yeego.app/_sidecar/public/openclaw/connect"
45
+ }
46
+ }
47
+ },
48
+ "hooks": {
49
+ "token": "YOUR_WEBHOOK_TOKEN_HERE"
50
+ }
51
+ }
31
52
  ```
32
53
 
33
- Configuration saved to `~/.yeego/config.json`
54
+ **Important**: Set a secure webhook token. This is used for the poller to communicate with OpenClaw Gateway.
34
55
 
35
- ### Step 3: Start Polling
56
+ ### Step 4: Restart OpenClaw Gateway
36
57
 
37
58
  ```bash
38
- bun run start
59
+ openclaw gateway restart
39
60
  ```
40
61
 
41
- That's it! The plugin will:
42
- - Connect to your Yeego profile
43
- - Poll for new messages every 5 seconds
44
- - Process them through OpenClaw
45
- - Send AI responses back to Yeego
62
+ The plugin will automatically:
63
+ - Connect to your Yeego profile
64
+ - Start polling for new messages
65
+ - Process them through OpenClaw agent
66
+ - Send AI responses back to Yeego
46
67
 
47
68
  ## 📱 Test It
48
69
 
49
70
  1. Open Yeego app
50
71
  2. Send a message to your AI persona
51
- 3. Watch the poller process it
72
+ 3. OpenClaw will receive and process it
52
73
  4. See the AI response appear in the app
53
74
 
54
- ## 🔧 Troubleshooting
75
+ ## 🔧 How It Works
76
+
77
+ The Yeego plugin uses a **webhook-based architecture**:
78
+
79
+ 1. **Poller** detects new messages in Yeego
80
+ 2. Sends message to OpenClaw via `/hooks/agent` webhook
81
+ 3. **Gateway** processes message through your agent
82
+ 4. Calls `channel.sendText` to send response
83
+ 5. **Channel** creates message in Yeego via PocketBase API
84
+
85
+ ## 🔍 Troubleshooting
55
86
 
56
87
  **No messages being processed?**
57
- - Make sure you sent a message in the Yeego app
58
- - Check the profile_id matches your actual profile
88
+ - Check OpenClaw Gateway is running: `openclaw gateway status`
89
+ - Verify webhook token is configured in both places
90
+ - Check logs: `tail -f ~/.openclaw/logs/gateway.log`
59
91
 
60
92
  **Connection errors?**
61
- - Verify backend is running (`http://localhost:8091`)
93
+ - Verify your Yeego backend is accessible
62
94
  - Check your token hasn't expired (30 days validity)
95
+ - Test the connection endpoint manually
96
+
97
+ **Webhook authentication failed?**
98
+ - Make sure `hooks.token` in `~/.openclaw/openclaw.json` matches
99
+ - The poller reads this token to authenticate webhook calls
63
100
 
64
101
  **Want to reset?**
65
102
  ```bash
66
- rm -rf ~/.yeego
67
- # Then run setup again
103
+ # Clear processed messages cache
104
+ rm ~/.yeego/processed.json
105
+
106
+ # Clear session mappings
107
+ rm -rf ~/.openclaw/yeego-sessions/
108
+
109
+ # Restart gateway
110
+ openclaw gateway restart
68
111
  ```
69
112
 
70
- ## 📦 Production Deployment
113
+ ## 📝 Configuration Reference
71
114
 
72
- When ready to deploy:
115
+ ### Yeego Channel Config
73
116
 
74
- ```bash
75
- # Build standalone executable
76
- bun run build
117
+ - `token`: JWT token from Yeego (30-day expiry)
118
+ - `profileId`: Your Yeego profile ID
119
+ - `baseUrl`: Yeego backend URL (PocketBase)
120
+ - `sidecarUrl`: Yeego sidecar URL (for connect endpoint)
121
+ - `connectUrl`: Full URL to the connect endpoint
122
+
123
+ ### Webhook Config
124
+
125
+ - `hooks.token`: Secure token for webhook authentication
126
+ - Used by poller to call `/hooks/agent`
127
+ - Should be a long random string
128
+ - Example: `openssl rand -hex 32`
129
+
130
+ ## 🚀 Advanced
131
+
132
+ ### Development Mode
133
+
134
+ For local development with localhost URLs:
135
+
136
+ ```json
137
+ {
138
+ "channels": {
139
+ "yeego": {
140
+ "config": {
141
+ "baseUrl": "http://localhost:8091",
142
+ "sidecarUrl": "http://localhost:8092",
143
+ "connectUrl": "http://localhost:8092/public/openclaw/connect"
144
+ }
145
+ }
146
+ }
147
+ }
148
+ ```
77
149
 
78
- # Run it anywhere
79
- ./yeego-poller start
150
+ ### Custom Gateway Port
151
+
152
+ If your Gateway runs on a different port:
153
+
154
+ 1. Find your Gateway port: `openclaw config get gateway.port`
155
+ 2. Update poller code or use environment variable
156
+
157
+ ### Multiple Profiles
158
+
159
+ You can configure multiple Yeego profiles by using different channel IDs:
160
+
161
+ ```json
162
+ {
163
+ "channels": {
164
+ "yeego-work": { ... },
165
+ "yeego-personal": { ... }
166
+ }
167
+ }
80
168
  ```
81
169
 
82
- No dependencies needed - it's a single binary! 🎉
170
+ ## 📦 Production Tips
171
+
172
+ - **Security**: Use HTTPS URLs in production
173
+ - **Token Rotation**: Tokens expire after 30 days - regenerate in Yeego app
174
+ - **Monitoring**: Check `~/.openclaw/logs/` for errors
175
+ - **Performance**: Poller polls every 5 seconds - adjust if needed
176
+
177
+ ---
178
+
179
+ 🦞 **Happy chatting with Yeego + OpenClaw!**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeego/yeego-openclaw",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Yeego plugin for OpenClaw - Connect your AI personas",
5
5
  "main": "index.ts",
6
6
  "type": "module",
package/poller.ts CHANGED
@@ -10,7 +10,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
10
10
  import { join } from 'path';
11
11
  import { homedir } from 'os';
12
12
  import { spawn, ChildProcess } from 'child_process';
13
- import { setSessionConversation, setPlaceholderId } from './src/sessionMapping.js';
13
+ import { setSessionConversation } from './src/sessionMapping.js';
14
14
 
15
15
  // ============================================================================
16
16
  // Types
@@ -172,52 +172,20 @@ class YeegoPoller {
172
172
  message: PocketBaseMessage,
173
173
  conversation: PocketBaseConversation
174
174
  ): Promise<void> {
175
- let placeholderId: string | null = null;
176
-
177
175
  try {
178
176
  console.log(`[Yeego] Processing message: ${message.id}`);
179
177
 
180
- // Create placeholder message to show loading state
181
- const placeholder = await this.pb.collection('profile_chat_messages').create({
182
- conversation: conversation.id,
183
- message: '',
184
- by: 'persona',
185
- is_ai_generated: true,
186
- has_finished_generating: false,
187
- source: 'openclaw',
188
- });
189
- placeholderId = placeholder.id;
190
- console.log(`[Yeego] Created placeholder: ${placeholderId}`);
191
-
192
- // Save placeholder ID in memory cache for channel to use
193
- setPlaceholderId(conversation.id, placeholderId);
194
-
195
- // Save session mapping before calling OpenClaw
178
+ // Save session mapping
196
179
  this.saveSessionMapping(conversation.id);
197
180
 
198
- // Call OpenClaw agent - it will update the placeholder via channel.sendText
199
- await this.callOpenClawAgent(message, conversation);
181
+ // Call OpenClaw via webhook API
182
+ await this.sendToOpenClawWebhook(message, conversation);
200
183
 
201
- // Workaround: Set messageChannel in session file after agent creates it
202
- setTimeout(() => {
203
- this.setSessionMessageChannel(conversation.id, 'yeego');
204
- }, SESSION_METADATA_DELAY_MS);
205
-
206
- console.log(`[Yeego] ✓ Processed message ${message.id}`);
184
+ console.log(`[Yeego] Sent message to OpenClaw webhook`);
207
185
  } catch (error) {
208
186
  console.error(`[Yeego] Error processing message:`, error);
209
187
 
210
- // On error, clean up placeholder and create error message
211
- if (placeholderId) {
212
- try {
213
- await this.pb.collection('profile_chat_messages').delete(placeholderId);
214
- console.log(`[Yeego] Deleted placeholder: ${placeholderId}`);
215
- } catch (deleteError) {
216
- console.error(`[Yeego] Failed to delete placeholder:`, deleteError);
217
- }
218
- }
219
-
220
- // Create error message
188
+ // Create error message if webhook call fails
221
189
  try {
222
190
  await this.pb.collection('profile_chat_messages').create({
223
191
  conversation: conversation.id,
@@ -240,103 +208,56 @@ class YeegoPoller {
240
208
  setSessionConversation(`yeego:${conversationId}`, conversationId);
241
209
  }
242
210
 
243
- private async callOpenClawAgent(
211
+ private async sendToOpenClawWebhook(
244
212
  message: PocketBaseMessage,
245
213
  conversation: PocketBaseConversation
246
214
  ): Promise<void> {
247
- console.log(`[Yeego] Calling OpenClaw agent for conversation: ${conversation.id}`);
248
-
249
- const proc = spawn('openclaw', [
250
- 'agent',
251
- '--session-id',
252
- conversation.id,
253
- '--reply-channel',
254
- 'yeego',
255
- '--reply-to',
256
- conversation.id,
257
- '--message',
258
- message.message,
259
- ], {
260
- stdio: ['ignore', 'pipe', 'pipe'],
261
- });
215
+ console.log(`[Yeego] Sending message to OpenClaw webhook for conversation: ${conversation.id}`);
262
216
 
263
- const { stdout, stderr, exitCode } = await this.captureProcessOutput(proc);
264
-
265
- if (exitCode !== 0) {
266
- console.error(`[Yeego] OpenClaw agent failed with exit code ${exitCode}`);
267
- console.error(`[Yeego] stderr:`, stderr);
268
- throw new Error(`OpenClaw agent failed: ${stderr}`);
269
- }
217
+ // Get webhook token from OpenClaw config
218
+ const webhookToken = await this.getOpenClawWebhookToken();
270
219
 
271
- console.log(`[Yeego] OpenClaw agent completed successfully`);
272
- if (stdout) {
273
- console.log(`[Yeego] stdout:`, stdout);
220
+ if (!webhookToken) {
221
+ throw new Error('No webhook token configured in OpenClaw. Please set hooks.token in ~/.openclaw/openclaw.json');
274
222
  }
275
- }
276
-
277
- private captureProcessOutput(proc: ChildProcess): Promise<{
278
- stdout: string;
279
- stderr: string;
280
- exitCode: number;
281
- }> {
282
- return new Promise((resolve) => {
283
- let stdout = '';
284
- let stderr = '';
285
-
286
- proc.stdout?.on('data', (data) => {
287
- stdout += data.toString();
288
- });
289
223
 
290
- proc.stderr?.on('data', (data) => {
291
- stderr += data.toString();
292
- });
293
-
294
- proc.on('close', (code) => {
295
- resolve({ stdout, stderr, exitCode: code || 0 });
296
- });
224
+ // Call OpenClaw webhook API
225
+ // Note: channel should be just "yeego", the conversation ID will be passed via session routing
226
+ const response = await fetch('http://localhost:4242/hooks/agent', {
227
+ method: 'POST',
228
+ headers: {
229
+ 'Content-Type': 'application/json',
230
+ 'Authorization': `Bearer ${webhookToken}`,
231
+ },
232
+ body: JSON.stringify({
233
+ message: message.message,
234
+ sessionKey: conversation.id,
235
+ channel: 'yeego',
236
+ }),
297
237
  });
298
- }
299
238
 
239
+ if (!response.ok) {
240
+ const errorText = await response.text();
241
+ console.error(`[Yeego] Webhook call failed: ${response.status} ${errorText}`);
242
+ throw new Error(`OpenClaw webhook failed: ${response.status} ${errorText}`);
243
+ }
300
244
 
301
- // --------------------------------------------------------------------------
302
- // Session Metadata Workaround
303
- // --------------------------------------------------------------------------
245
+ console.log(`[Yeego] Webhook call successful (${response.status})`);
246
+ }
304
247
 
305
- /**
306
- * Sets messageChannel in OpenClaw session metadata file.
307
- * This is a workaround because --reply-channel CLI flag doesn't set the session's messageChannel property.
308
- */
309
- private setSessionMessageChannel(sessionId: string, channel: string): boolean {
248
+ private async getOpenClawWebhookToken(): Promise<string | null> {
310
249
  try {
311
- const sessionDir = join(homedir(), '.openclaw', 'agents', 'main', 'sessions');
312
- const sessionFile = join(sessionDir, `${sessionId}.jsonl`);
313
-
314
- if (!existsSync(sessionFile)) {
315
- console.log(`[Yeego] Session file not found yet: ${sessionFile}`);
316
- return false;
317
- }
318
-
319
- const lines = readFileSync(sessionFile, 'utf-8')
320
- .split('\n')
321
- .filter(l => l.trim());
322
-
323
- if (lines.length === 0) {
324
- return false;
325
- }
326
-
327
- const firstLine = JSON.parse(lines[0]);
328
- if (firstLine.type === 'session') {
329
- firstLine.messageChannel = channel;
330
- lines[0] = JSON.stringify(firstLine);
331
- writeFileSync(sessionFile, lines.join('\n') + '\n', 'utf-8');
332
- console.log(`[Yeego] ✓ Set messageChannel="${channel}" for session ${sessionId}`);
333
- return true;
250
+ const configPath = join(homedir(), '.openclaw', 'openclaw.json');
251
+ if (!existsSync(configPath)) {
252
+ console.error(`[Yeego] OpenClaw config not found: ${configPath}`);
253
+ return null;
334
254
  }
335
255
 
336
- return false;
256
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
257
+ return config?.hooks?.token || null;
337
258
  } catch (error) {
338
- console.error(`[Yeego] Failed to set messageChannel:`, error);
339
- return false;
259
+ console.error(`[Yeego] Failed to read OpenClaw config:`, error);
260
+ return null;
340
261
  }
341
262
  }
342
263
 
package/src/channel.ts CHANGED
@@ -22,7 +22,6 @@ import { createYeegoTools } from "./tools/index.js";
22
22
  import {
23
23
  setSessionConversation,
24
24
  getSessionConversation,
25
- consumePlaceholderId,
26
25
  } from "./sessionMapping.js";
27
26
 
28
27
  // ============================================================================
@@ -309,57 +308,17 @@ export const yeegoPlugin: ChannelPlugin<YeegoConfig> = {
309
308
  const pb = new PocketBase(config.base_url);
310
309
  pb.authStore.save(config.token);
311
310
 
312
- // First, try to get the placeholder ID from memory cache (most reliable)
313
- const cachedPlaceholderId = consumePlaceholderId(conversationId);
314
- let placeholderMessage = null;
315
-
316
- if (cachedPlaceholderId) {
317
- // We have the exact placeholder ID from poller
318
- try {
319
- placeholderMessage = await pb.collection('profile_chat_messages').getOne(cachedPlaceholderId);
320
- console.log(`[Yeego] Found placeholder from cache: ${placeholderMessage.id}`);
321
- } catch (error) {
322
- console.warn(`[Yeego] Cached placeholder not found, falling back to query`);
323
- }
324
- }
325
-
326
- // Fallback: search for placeholder message if cache miss
327
- if (!placeholderMessage) {
328
- try {
329
- const recentMessages = await pb.collection('profile_chat_messages').getList(1, 1, {
330
- filter: `conversation="${conversationId}" && by="persona" && has_finished_generating=false && source="openclaw"`,
331
- sort: '-created',
332
- });
333
-
334
- if (recentMessages.items.length > 0) {
335
- placeholderMessage = recentMessages.items[0];
336
- console.log(`[Yeego] Found placeholder via query: ${placeholderMessage.id}`);
337
- }
338
- } catch (error) {
339
- console.log(`[Yeego] No placeholder found, will create new message`);
340
- }
341
- }
311
+ // Simply create a new message - no placeholder logic needed
312
+ const message = await pb.collection('profile_chat_messages').create({
313
+ conversation: conversationId,
314
+ message: text,
315
+ by: 'persona',
316
+ is_ai_generated: true,
317
+ has_finished_generating: true,
318
+ source: 'openclaw',
319
+ });
342
320
 
343
- let message;
344
- if (placeholderMessage) {
345
- // Update existing placeholder
346
- message = await pb.collection('profile_chat_messages').update(placeholderMessage.id, {
347
- message: text,
348
- has_finished_generating: true,
349
- });
350
- console.log(`[Yeego] Updated placeholder: ${message.id}`);
351
- } else {
352
- // Create new message if no placeholder exists
353
- message = await pb.collection('profile_chat_messages').create({
354
- conversation: conversationId,
355
- message: text,
356
- by: 'persona',
357
- is_ai_generated: true,
358
- has_finished_generating: true,
359
- source: 'openclaw',
360
- });
361
- console.log(`[Yeego] Created new message: ${message.id}`);
362
- }
321
+ console.log(`[Yeego] Created message: ${message.id}`);
363
322
 
364
323
  return {
365
324
  channel: "yeego",
@@ -56,58 +56,6 @@ function saveMappings(mappings: SessionMappings): void {
56
56
  }
57
57
  }
58
58
 
59
- // ============================================================================
60
- // In-Memory Placeholder Cache
61
- // ============================================================================
62
-
63
- // Cache placeholder message IDs temporarily (in memory only)
64
- // Use array to support multiple concurrent messages per conversation
65
- const placeholderCache = new Map<string, string[]>();
66
-
67
- /**
68
- * Sets the placeholder message ID for a conversation (in-memory cache).
69
- * This is used to pass the placeholder ID from poller to channel.
70
- * Supports multiple placeholders per conversation for concurrent messages.
71
- */
72
- export function setPlaceholderId(conversationId: string, placeholderId: string): void {
73
- const existing = placeholderCache.get(conversationId) || [];
74
- existing.push(placeholderId);
75
- placeholderCache.set(conversationId, existing);
76
-
77
- // Auto-expire after 30 seconds to avoid memory leaks
78
- setTimeout(() => {
79
- const current = placeholderCache.get(conversationId);
80
- if (current) {
81
- const filtered = current.filter(id => id !== placeholderId);
82
- if (filtered.length === 0) {
83
- placeholderCache.delete(conversationId);
84
- } else {
85
- placeholderCache.set(conversationId, filtered);
86
- }
87
- }
88
- }, 30000);
89
- }
90
-
91
- /**
92
- * Gets and removes the oldest placeholder message ID for a conversation (FIFO).
93
- * This ensures concurrent messages are handled in order.
94
- */
95
- export function consumePlaceholderId(conversationId: string): string | undefined {
96
- const placeholders = placeholderCache.get(conversationId);
97
- if (!placeholders || placeholders.length === 0) {
98
- return undefined;
99
- }
100
-
101
- // Remove and return the first (oldest) placeholder
102
- const placeholderId = placeholders.shift();
103
-
104
- if (placeholders.length === 0) {
105
- placeholderCache.delete(conversationId);
106
- }
107
-
108
- return placeholderId;
109
- }
110
-
111
59
  // ============================================================================
112
60
  // Public API
113
61
  // ============================================================================
package/yeego-poller CHANGED
Binary file