@eyeclaw/eyeclaw 2.0.0 β 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/package.json +1 -1
- package/src/channel.ts +107 -0
- package/src/client.ts +99 -0
package/README.md
CHANGED
|
@@ -212,6 +212,112 @@ client.disconnect()
|
|
|
212
212
|
- **Issues**: [https://github.com/eyeclaw/eyeclaw/issues](https://github.com/eyeclaw/eyeclaw/issues)
|
|
213
213
|
- **Discord**: [https://discord.gg/eyeclaw](https://discord.gg/eyeclaw)
|
|
214
214
|
|
|
215
|
+
## π¬ Chat Functionality
|
|
216
|
+
|
|
217
|
+
The EyeClaw SDK includes built-in chat functionality that allows you to interact with your bot through the web dashboard.
|
|
218
|
+
|
|
219
|
+
### Testing Chat
|
|
220
|
+
|
|
221
|
+
1. Start your bot with the EyeClaw plugin installed
|
|
222
|
+
2. Go to your bot's detail page on EyeClaw Dashboard
|
|
223
|
+
3. Click **"Test Chat"** button
|
|
224
|
+
4. Send messages - the bot will respond with smart replies
|
|
225
|
+
5. Use quick test buttons:
|
|
226
|
+
- **Ping** - Test bot connectivity
|
|
227
|
+
- **Status** - Get bot status (online, sessions, uptime)
|
|
228
|
+
- **Echo** - Echo back a test message
|
|
229
|
+
- **Help** - Show available commands
|
|
230
|
+
|
|
231
|
+
### Supported Commands
|
|
232
|
+
|
|
233
|
+
The SDK automatically handles these commands from the web dashboard:
|
|
234
|
+
|
|
235
|
+
- **`chat`** - Receive and respond to chat messages
|
|
236
|
+
- Params: `{ message: string }`
|
|
237
|
+
- The bot uses pattern matching to generate smart responses
|
|
238
|
+
- Supports greetings, questions, thanks, farewells in Chinese and English
|
|
239
|
+
|
|
240
|
+
- **`ping`** - Test bot connectivity
|
|
241
|
+
- Response: "π Pong! Bot is responding."
|
|
242
|
+
|
|
243
|
+
- **`status`** - Get current bot status
|
|
244
|
+
- Returns: online status, active sessions, total sessions, uptime
|
|
245
|
+
|
|
246
|
+
- **`echo`** - Echo back a message
|
|
247
|
+
- Params: `{ message: string }`
|
|
248
|
+
- Response: Echoes the message back
|
|
249
|
+
|
|
250
|
+
- **`help`** - Show available commands
|
|
251
|
+
- Response: Lists all available commands and their usage
|
|
252
|
+
|
|
253
|
+
### Customizing Bot Responses
|
|
254
|
+
|
|
255
|
+
You can customize the bot's chat responses by modifying `generateBotReply()` in `sdk/src/client.ts`:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
private generateBotReply(userMessage: string): string {
|
|
259
|
+
const lowerMessage = userMessage.toLowerCase()
|
|
260
|
+
|
|
261
|
+
// Add your custom patterns
|
|
262
|
+
if (lowerMessage.includes('weather')) {
|
|
263
|
+
return 'π€οΈ The weather is great today!'
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (lowerMessage.includes('time')) {
|
|
267
|
+
return `π Current time: ${new Date().toLocaleTimeString()}`
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Default response
|
|
271
|
+
return `π¬ You said: "${userMessage}"`
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Integrating AI/LLM Services
|
|
276
|
+
|
|
277
|
+
For production use, integrate your preferred AI service:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import OpenAI from 'openai'
|
|
281
|
+
|
|
282
|
+
private async generateBotReply(userMessage: string): Promise<string> {
|
|
283
|
+
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
|
|
284
|
+
|
|
285
|
+
const completion = await openai.chat.completions.create({
|
|
286
|
+
model: 'gpt-4',
|
|
287
|
+
messages: [
|
|
288
|
+
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
289
|
+
{ role: 'user', content: userMessage }
|
|
290
|
+
]
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
return completion.choices[0].message.content || 'Sorry, I could not generate a response.'
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Message Flow
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
User (Web Dashboard)
|
|
301
|
+
β
|
|
302
|
+
βΌ
|
|
303
|
+
DashboardChannel ββexecute_commandβββΊ BotChannel
|
|
304
|
+
β β
|
|
305
|
+
β βΌ
|
|
306
|
+
β SDK receives command
|
|
307
|
+
β β
|
|
308
|
+
β βΌ
|
|
309
|
+
β handleExecuteCommand()
|
|
310
|
+
β β
|
|
311
|
+
β βΌ
|
|
312
|
+
β generateBotReply()
|
|
313
|
+
β β
|
|
314
|
+
β βΌ
|
|
315
|
+
βββββββββββ sendLog() ββββββββββsendLog('info', reply)
|
|
316
|
+
β
|
|
317
|
+
βΌ
|
|
318
|
+
Chat Controller displays message
|
|
319
|
+
```
|
|
320
|
+
|
|
215
321
|
## License
|
|
216
322
|
|
|
217
323
|
MIT Β© EyeClaw Team
|
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -178,6 +178,113 @@ export const eyeclawPlugin: ChannelPlugin<ResolvedEyeClawAccount> = {
|
|
|
178
178
|
const client = new EyeClawClient(clientConfig, logger)
|
|
179
179
|
clients.set(ctx.accountId, client)
|
|
180
180
|
|
|
181
|
+
// Register OpenClaw Agent callback for chat messages
|
|
182
|
+
client.setSendAgentCallback(async (message: string) => {
|
|
183
|
+
try {
|
|
184
|
+
ctx.log?.info(`π€ Sending message to OpenClaw Agent: ${message}`)
|
|
185
|
+
|
|
186
|
+
// Call OpenClaw Agent via spawn for streaming output
|
|
187
|
+
const { spawn } = await import('child_process')
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
// Spawn openclaw agent process for streaming output
|
|
191
|
+
const agentProcess = spawn('openclaw', [
|
|
192
|
+
'agent',
|
|
193
|
+
'--session-id', 'eyeclaw-web-chat',
|
|
194
|
+
'--message', message,
|
|
195
|
+
'--json'
|
|
196
|
+
])
|
|
197
|
+
|
|
198
|
+
let outputBuffer = ''
|
|
199
|
+
let streamId = Date.now().toString()
|
|
200
|
+
|
|
201
|
+
// Send stream_start event
|
|
202
|
+
client.sendStreamChunk('stream_start', streamId, '')
|
|
203
|
+
|
|
204
|
+
// Handle stdout (streaming response)
|
|
205
|
+
agentProcess.stdout?.on('data', (data: Buffer) => {
|
|
206
|
+
const text = data.toString()
|
|
207
|
+
outputBuffer += text
|
|
208
|
+
|
|
209
|
+
// For streaming text output, send each chunk immediately
|
|
210
|
+
// Try to parse as JSON first
|
|
211
|
+
try {
|
|
212
|
+
const parsed = JSON.parse(outputBuffer)
|
|
213
|
+
// If we can parse the complete JSON, extract the text
|
|
214
|
+
const response = parsed.result?.payloads?.[0]?.text
|
|
215
|
+
if (response) {
|
|
216
|
+
// Send the text in chunks
|
|
217
|
+
const chunkSize = 50 // Send ~50 chars at a time
|
|
218
|
+
for (let i = 0; i < response.length; i += chunkSize) {
|
|
219
|
+
const chunk = response.substring(i, i + chunkSize)
|
|
220
|
+
client.sendStreamChunk('stream_chunk', streamId, chunk)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
outputBuffer = '' // Clear buffer after successful parse
|
|
224
|
+
} catch (e) {
|
|
225
|
+
// Not valid JSON yet, check if we have complete lines to send
|
|
226
|
+
const lines = outputBuffer.split('\n')
|
|
227
|
+
// Keep last incomplete line in buffer
|
|
228
|
+
if (lines.length > 1) {
|
|
229
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
230
|
+
if (lines[i].trim()) {
|
|
231
|
+
client.sendStreamChunk('stream_chunk', streamId, lines[i] + '\n')
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
outputBuffer = lines[lines.length - 1]
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// Handle stderr (errors)
|
|
240
|
+
agentProcess.stderr?.on('data', (data: Buffer) => {
|
|
241
|
+
const errorText = data.toString()
|
|
242
|
+
ctx.log?.error(`Agent stderr: ${errorText}`)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// Handle process completion
|
|
246
|
+
agentProcess.on('close', (code: number) => {
|
|
247
|
+
// Send any remaining buffered content
|
|
248
|
+
if (outputBuffer.trim()) {
|
|
249
|
+
try {
|
|
250
|
+
const parsed = JSON.parse(outputBuffer)
|
|
251
|
+
const response = parsed.result?.payloads?.[0]?.text || outputBuffer.trim()
|
|
252
|
+
client.sendStreamChunk('stream_chunk', streamId, response)
|
|
253
|
+
} catch (e) {
|
|
254
|
+
client.sendStreamChunk('stream_chunk', streamId, outputBuffer.trim())
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Send stream_end event
|
|
259
|
+
client.sendStreamChunk('stream_end', streamId, '')
|
|
260
|
+
|
|
261
|
+
if (code === 0) {
|
|
262
|
+
ctx.log?.info(`β
Agent completed successfully`)
|
|
263
|
+
} else {
|
|
264
|
+
ctx.log?.error(`Agent exited with code ${code}`)
|
|
265
|
+
client.sendLog('error', `β Agent error: process exited with code ${code}`)
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// Handle process errors
|
|
270
|
+
agentProcess.on('error', (error: Error) => {
|
|
271
|
+
ctx.log?.error(`Failed to start agent process: ${error.message}`)
|
|
272
|
+
client.sendStreamChunk('stream_error', streamId, error.message)
|
|
273
|
+
client.sendLog('error', `β Failed to start agent: ${error.message}`)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
} catch (error) {
|
|
277
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
278
|
+
ctx.log?.error(`Failed to execute openclaw agent: ${errorMsg}`)
|
|
279
|
+
client.sendLog('error', `β Agent error: ${errorMsg}`)
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
283
|
+
ctx.log?.error(`Failed to call OpenClaw Agent: ${errorMsg}`)
|
|
284
|
+
client.sendLog('error', `β Failed to call agent: ${errorMsg}`)
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
|
|
181
288
|
try {
|
|
182
289
|
await client.connect()
|
|
183
290
|
ctx.log?.info('β
Successfully connected to EyeClaw platform')
|
package/src/client.ts
CHANGED
|
@@ -105,15 +105,105 @@ export class EyeClawClient {
|
|
|
105
105
|
this.logger.info(`Command received by server: ${message.command}`)
|
|
106
106
|
break
|
|
107
107
|
|
|
108
|
+
case 'execute_command':
|
|
109
|
+
this.handleExecuteCommand(message)
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
case 'ping':
|
|
113
|
+
this.logger.debug('Received ping from dashboard')
|
|
114
|
+
// Send pong response
|
|
115
|
+
this.sendLog('info', 'π Pong! Bot is alive.')
|
|
116
|
+
break
|
|
117
|
+
|
|
108
118
|
case 'log':
|
|
109
119
|
this.logger.info(`[Server Log] ${message.level}: ${message.message}`)
|
|
110
120
|
break
|
|
111
121
|
|
|
122
|
+
case 'stream_chunk':
|
|
123
|
+
// Forward stream chunks to handler (will be handled by web UI)
|
|
124
|
+
const chunkPreview = typeof message.chunk === 'string' ? message.chunk.substring(0, 50) : String(message.chunk || '').substring(0, 50)
|
|
125
|
+
this.logger.debug(`Stream chunk: ${message.type} - ${chunkPreview}...`)
|
|
126
|
+
break
|
|
127
|
+
|
|
112
128
|
default:
|
|
113
129
|
this.logger.warn(`Unknown message type: ${type}`)
|
|
114
130
|
}
|
|
115
131
|
}
|
|
116
132
|
|
|
133
|
+
private handleExecuteCommand(message: Record<string, unknown>): void {
|
|
134
|
+
const command = message.command as string
|
|
135
|
+
const params = (message.params as Record<string, unknown>) || {}
|
|
136
|
+
|
|
137
|
+
this.logger.info(`π₯ Executing command: ${command}`)
|
|
138
|
+
|
|
139
|
+
switch (command) {
|
|
140
|
+
case 'chat': {
|
|
141
|
+
const userMessage = params.message as string
|
|
142
|
+
this.logger.info(`π¬ Chat message: ${userMessage}`)
|
|
143
|
+
|
|
144
|
+
// Send user message acknowledgment
|
|
145
|
+
this.sendLog('info', `ζΆε°ζΆζ―: ${userMessage}`)
|
|
146
|
+
|
|
147
|
+
// Call OpenClaw Agent via callback
|
|
148
|
+
this.handleChatMessage(userMessage)
|
|
149
|
+
break
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case 'ping': {
|
|
153
|
+
this.sendLog('info', 'π Pong! Bot is responding.')
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
case 'status': {
|
|
158
|
+
this.requestStatus()
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'echo': {
|
|
163
|
+
const echoMessage = params.message as string
|
|
164
|
+
this.sendLog('info', `Echo: ${echoMessage}`)
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
case 'help': {
|
|
169
|
+
const helpMessage = [
|
|
170
|
+
'π€ Available Commands:',
|
|
171
|
+
'β’ chat - Send a chat message',
|
|
172
|
+
'β’ ping - Test connection',
|
|
173
|
+
'β’ status - Get bot status',
|
|
174
|
+
'β’ echo - Echo a message',
|
|
175
|
+
'β’ help - Show this help',
|
|
176
|
+
].join('\n')
|
|
177
|
+
this.sendLog('info', helpMessage)
|
|
178
|
+
break
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
default:
|
|
182
|
+
this.logger.warn(`Unknown command: ${command}`)
|
|
183
|
+
this.sendLog('error', `β Unknown command: ${command}`)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private handleChatMessage(userMessage: string): void {
|
|
188
|
+
// This will be called by OpenClaw channel plugin via sendAgent
|
|
189
|
+
if (this.sendAgentCallback) {
|
|
190
|
+
this.logger.info('π€ Calling OpenClaw Agent...')
|
|
191
|
+
this.sendAgentCallback(userMessage)
|
|
192
|
+
} else {
|
|
193
|
+
// Fallback: simple echo if not running in OpenClaw context
|
|
194
|
+
this.logger.warn('No OpenClaw Agent available, using echo mode')
|
|
195
|
+
this.sendLog('info', `π¬ Echo: "${userMessage}" (OpenClaw Agent not connected)`)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Callback to send message to OpenClaw Agent (injected by channel plugin)
|
|
200
|
+
private sendAgentCallback: ((message: string) => Promise<void>) | null = null
|
|
201
|
+
|
|
202
|
+
setSendAgentCallback(callback: (message: string) => Promise<void>): void {
|
|
203
|
+
this.sendAgentCallback = callback
|
|
204
|
+
this.logger.info('β
OpenClaw Agent callback registered')
|
|
205
|
+
}
|
|
206
|
+
|
|
117
207
|
private handleStatusResponse(status: BotStatus): void {
|
|
118
208
|
this.logger.info(`Bot status: online=${status.online}, status=${status.status}, sessions=${status.active_sessions}, uptime=${Math.floor(status.uptime / 60)}m`)
|
|
119
209
|
}
|
|
@@ -190,6 +280,15 @@ export class EyeClawClient {
|
|
|
190
280
|
})
|
|
191
281
|
}
|
|
192
282
|
|
|
283
|
+
sendStreamChunk(type: string, streamId: string, chunk: string): void {
|
|
284
|
+
this.sendChannelMessage('stream_chunk', {
|
|
285
|
+
type,
|
|
286
|
+
stream_id: streamId,
|
|
287
|
+
chunk,
|
|
288
|
+
timestamp: new Date().toISOString(),
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
193
292
|
sendCommandResult(command: string, result: unknown, error?: string): void {
|
|
194
293
|
this.sendChannelMessage('command_result', {
|
|
195
294
|
command,
|