@eyeclaw/eyeclaw 1.0.5 β 2.0.2
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/index.ts +4 -92
- package/package.json +1 -1
- package/src/channel.ts +253 -0
- package/src/client.ts +84 -0
- package/src/types.ts +18 -2
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/index.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
|
|
2
2
|
import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk'
|
|
3
|
-
import {
|
|
4
|
-
import type { PluginConfig } from './src/types.js'
|
|
5
|
-
|
|
6
|
-
let client: EyeClawClient | null = null
|
|
3
|
+
import { eyeclawPlugin } from './src/channel.js'
|
|
7
4
|
|
|
8
5
|
/**
|
|
9
6
|
* EyeClaw SDK - OpenClaw Channel Plugin
|
|
@@ -17,94 +14,8 @@ const plugin = {
|
|
|
17
14
|
configSchema: emptyPluginConfigSchema(),
|
|
18
15
|
|
|
19
16
|
register(api: OpenClawPluginApi) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// Try multiple ways to access config
|
|
24
|
-
const runtimeConfig = runtime.config as any
|
|
25
|
-
const apiConfig = (api as any).config
|
|
26
|
-
const pluginConfig = (api as any).pluginConfig
|
|
27
|
-
|
|
28
|
-
// Debug: log entire config structure from different sources
|
|
29
|
-
logger.info('π runtime.config:', JSON.stringify(runtimeConfig, null, 2))
|
|
30
|
-
logger.info('π api.config:', JSON.stringify(apiConfig, null, 2))
|
|
31
|
-
logger.info('π api.pluginConfig:', JSON.stringify(pluginConfig, null, 2))
|
|
32
|
-
logger.info('π api keys:', Object.keys(api).join(', '))
|
|
33
|
-
logger.info('π runtime keys:', Object.keys(runtime).join(', '))
|
|
34
|
-
|
|
35
|
-
// Try to get config from different sources
|
|
36
|
-
let eyeclawConfig: PluginConfig =
|
|
37
|
-
pluginConfig ||
|
|
38
|
-
apiConfig ||
|
|
39
|
-
runtimeConfig?.channels?.eyeclaw ||
|
|
40
|
-
runtimeConfig?.plugins?.eyeclaw ||
|
|
41
|
-
{}
|
|
42
|
-
|
|
43
|
-
// Debug: log eyeclaw config
|
|
44
|
-
logger.info('π EyeClaw config:', JSON.stringify(eyeclawConfig, null, 2))
|
|
45
|
-
logger.info('π botId type:', typeof eyeclawConfig.botId)
|
|
46
|
-
logger.info('π botId value:', eyeclawConfig.botId)
|
|
47
|
-
logger.info('π sdkToken type:', typeof eyeclawConfig.sdkToken)
|
|
48
|
-
logger.info('π sdkToken exists:', !!eyeclawConfig.sdkToken)
|
|
49
|
-
|
|
50
|
-
// Check if enabled
|
|
51
|
-
if (eyeclawConfig.enabled === false) {
|
|
52
|
-
logger.info('Plugin disabled in config')
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Validate required fields with detailed logging
|
|
57
|
-
if (!eyeclawConfig.botId) {
|
|
58
|
-
logger.warn('β botId is missing or falsy:', eyeclawConfig.botId)
|
|
59
|
-
logger.warn('Configure with: openclaw config set channels.eyeclaw.botId "YOUR_BOT_ID"')
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!eyeclawConfig.sdkToken) {
|
|
64
|
-
logger.warn('β sdkToken is missing or falsy')
|
|
65
|
-
logger.warn('Configure with: openclaw config set channels.eyeclaw.sdkToken "YOUR_SDK_TOKEN"')
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
logger.info('β
Config validation passed')
|
|
70
|
-
|
|
71
|
-
// Convert botId to string (OpenClaw may auto-convert "1" to number 1)
|
|
72
|
-
eyeclawConfig.botId = String(eyeclawConfig.botId)
|
|
73
|
-
|
|
74
|
-
// Set defaults
|
|
75
|
-
eyeclawConfig.serverUrl = eyeclawConfig.serverUrl || 'http://localhost:3000'
|
|
76
|
-
eyeclawConfig.reconnectInterval = eyeclawConfig.reconnectInterval || 5000
|
|
77
|
-
eyeclawConfig.heartbeatInterval = eyeclawConfig.heartbeatInterval || 30000
|
|
78
|
-
|
|
79
|
-
logger.info('π¦ Starting EyeClaw SDK...', { botId: eyeclawConfig.botId, serverUrl: eyeclawConfig.serverUrl })
|
|
80
|
-
|
|
81
|
-
// Create logger adapter for client (simple console logger)
|
|
82
|
-
const clientLogger = {
|
|
83
|
-
debug: (msg: string) => logger.debug(msg),
|
|
84
|
-
info: (msg: string) => logger.info(msg),
|
|
85
|
-
warn: (msg: string) => logger.warn(msg),
|
|
86
|
-
error: (msg: string) => logger.error(msg),
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Create and connect WebSocket client
|
|
90
|
-
client = new EyeClawClient(eyeclawConfig, clientLogger)
|
|
91
|
-
|
|
92
|
-
client.connect().then(() => {
|
|
93
|
-
logger.info('β
Successfully connected to EyeClaw platform')
|
|
94
|
-
}).catch((error: Error) => {
|
|
95
|
-
logger.error('Failed to connect to EyeClaw', { error: error.message })
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
// Handle shutdown
|
|
99
|
-
const shutdown = () => {
|
|
100
|
-
if (client) {
|
|
101
|
-
logger.info('π Shutting down EyeClaw SDK...')
|
|
102
|
-
client.disconnect()
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
process.on('SIGINT', shutdown)
|
|
107
|
-
process.on('SIGTERM', shutdown)
|
|
17
|
+
// Register EyeClaw as a channel plugin
|
|
18
|
+
api.registerChannel({ plugin: eyeclawPlugin })
|
|
108
19
|
},
|
|
109
20
|
}
|
|
110
21
|
|
|
@@ -113,3 +24,4 @@ export default plugin
|
|
|
113
24
|
// Re-export for direct usage
|
|
114
25
|
export { EyeClawClient } from './src/client.js'
|
|
115
26
|
export * from './src/types.js'
|
|
27
|
+
export { eyeclawPlugin } from './src/channel.js'
|
package/package.json
CHANGED
package/src/channel.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import type { ChannelPlugin } from 'openclaw/plugin-sdk'
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk'
|
|
3
|
+
import type { EyeClawConfig, ResolvedEyeClawAccount } from './types.js'
|
|
4
|
+
import { EyeClawClient } from './client.js'
|
|
5
|
+
|
|
6
|
+
// Active clients map (accountId -> client)
|
|
7
|
+
const clients = new Map<string, EyeClawClient>()
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve EyeClaw account configuration
|
|
11
|
+
*/
|
|
12
|
+
function resolveEyeClawAccount(cfg: any, accountId: string): ResolvedEyeClawAccount {
|
|
13
|
+
const eyeclawConfig: EyeClawConfig = cfg?.channels?.eyeclaw || {}
|
|
14
|
+
|
|
15
|
+
// Default account uses top-level config
|
|
16
|
+
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
17
|
+
return {
|
|
18
|
+
accountId: DEFAULT_ACCOUNT_ID,
|
|
19
|
+
enabled: eyeclawConfig.enabled !== false,
|
|
20
|
+
configured: !!(eyeclawConfig.botId && eyeclawConfig.sdkToken),
|
|
21
|
+
name: 'Default',
|
|
22
|
+
config: eyeclawConfig,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Named accounts not supported yet
|
|
27
|
+
throw new Error(`Named accounts not yet supported for EyeClaw`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* List all EyeClaw account IDs
|
|
32
|
+
*/
|
|
33
|
+
function listEyeClawAccountIds(cfg: any): string[] {
|
|
34
|
+
return [DEFAULT_ACCOUNT_ID]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* EyeClaw Channel Plugin
|
|
39
|
+
*/
|
|
40
|
+
export const eyeclawPlugin: ChannelPlugin<ResolvedEyeClawAccount> = {
|
|
41
|
+
id: 'eyeclaw',
|
|
42
|
+
|
|
43
|
+
meta: {
|
|
44
|
+
id: 'eyeclaw',
|
|
45
|
+
label: 'EyeClaw',
|
|
46
|
+
selectionLabel: 'EyeClaw Platform',
|
|
47
|
+
docsPath: '/channels/eyeclaw',
|
|
48
|
+
docsLabel: 'eyeclaw',
|
|
49
|
+
blurb: 'EyeClaw platform integration via WebSocket.',
|
|
50
|
+
order: 100,
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
capabilities: {
|
|
54
|
+
chatTypes: ['direct', 'channel'],
|
|
55
|
+
polls: false,
|
|
56
|
+
threads: false,
|
|
57
|
+
media: false,
|
|
58
|
+
reactions: false,
|
|
59
|
+
edit: false,
|
|
60
|
+
reply: false,
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
reload: { configPrefixes: ['channels.eyeclaw'] },
|
|
64
|
+
|
|
65
|
+
configSchema: {
|
|
66
|
+
schema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
additionalProperties: true,
|
|
69
|
+
properties: {
|
|
70
|
+
enabled: {
|
|
71
|
+
type: 'boolean',
|
|
72
|
+
description: 'Enable/disable the plugin',
|
|
73
|
+
},
|
|
74
|
+
botId: {
|
|
75
|
+
type: ['string', 'number'],
|
|
76
|
+
description: 'Bot ID from EyeClaw platform',
|
|
77
|
+
},
|
|
78
|
+
sdkToken: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description: 'SDK token for authentication',
|
|
81
|
+
},
|
|
82
|
+
serverUrl: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'EyeClaw server URL',
|
|
85
|
+
},
|
|
86
|
+
reconnectInterval: {
|
|
87
|
+
type: 'number',
|
|
88
|
+
description: 'Reconnect interval in milliseconds',
|
|
89
|
+
},
|
|
90
|
+
heartbeatInterval: {
|
|
91
|
+
type: 'number',
|
|
92
|
+
description: 'Heartbeat interval in milliseconds',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
config: {
|
|
99
|
+
listAccountIds: (cfg) => listEyeClawAccountIds(cfg),
|
|
100
|
+
resolveAccount: (cfg, accountId) => resolveEyeClawAccount(cfg, accountId || DEFAULT_ACCOUNT_ID),
|
|
101
|
+
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
status: {
|
|
105
|
+
defaultRuntime: {
|
|
106
|
+
accountId: DEFAULT_ACCOUNT_ID,
|
|
107
|
+
running: false,
|
|
108
|
+
lastStartAt: null,
|
|
109
|
+
lastStopAt: null,
|
|
110
|
+
lastError: null,
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
buildChannelSummary: ({ snapshot }) => ({
|
|
114
|
+
configured: snapshot.configured ?? false,
|
|
115
|
+
running: snapshot.running ?? false,
|
|
116
|
+
lastStartAt: snapshot.lastStartAt ?? null,
|
|
117
|
+
lastStopAt: snapshot.lastStopAt ?? null,
|
|
118
|
+
lastError: snapshot.lastError ?? null,
|
|
119
|
+
}),
|
|
120
|
+
|
|
121
|
+
probeAccount: async ({ account }) => {
|
|
122
|
+
// Simple probe - check if configured
|
|
123
|
+
return {
|
|
124
|
+
ok: account.configured,
|
|
125
|
+
message: account.configured ? 'Configured' : 'Not configured',
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
buildAccountSnapshot: ({ account, runtime, probe }) => ({
|
|
130
|
+
accountId: account.accountId,
|
|
131
|
+
enabled: account.enabled,
|
|
132
|
+
configured: account.configured,
|
|
133
|
+
name: account.name,
|
|
134
|
+
running: runtime?.running ?? false,
|
|
135
|
+
lastStartAt: runtime?.lastStartAt ?? null,
|
|
136
|
+
lastStopAt: runtime?.lastStopAt ?? null,
|
|
137
|
+
lastError: runtime?.lastError ?? null,
|
|
138
|
+
probe,
|
|
139
|
+
}),
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
gateway: {
|
|
143
|
+
startAccount: async (ctx) => {
|
|
144
|
+
const account = resolveEyeClawAccount(ctx.cfg, ctx.accountId)
|
|
145
|
+
|
|
146
|
+
if (!account.configured || !account.config) {
|
|
147
|
+
throw new Error('EyeClaw not configured. Please set botId and sdkToken.')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const config = account.config
|
|
151
|
+
|
|
152
|
+
// Validate required fields
|
|
153
|
+
if (!config.botId || !config.sdkToken) {
|
|
154
|
+
throw new Error('botId and sdkToken are required')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Set defaults
|
|
158
|
+
const clientConfig = {
|
|
159
|
+
botId: String(config.botId),
|
|
160
|
+
sdkToken: config.sdkToken,
|
|
161
|
+
serverUrl: config.serverUrl || 'http://localhost:3000',
|
|
162
|
+
reconnectInterval: config.reconnectInterval || 5000,
|
|
163
|
+
heartbeatInterval: config.heartbeatInterval || 30000,
|
|
164
|
+
enabled: true,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
ctx.log?.info(`π¦ Starting EyeClaw SDK... botId=${clientConfig.botId}, serverUrl=${clientConfig.serverUrl}`)
|
|
168
|
+
|
|
169
|
+
// Create logger adapter
|
|
170
|
+
const logger = {
|
|
171
|
+
debug: (msg: string) => ctx.log?.debug?.(msg),
|
|
172
|
+
info: (msg: string) => ctx.log?.info(msg),
|
|
173
|
+
warn: (msg: string) => ctx.log?.warn(msg),
|
|
174
|
+
error: (msg: string) => ctx.log?.error(msg),
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Create and connect client
|
|
178
|
+
const client = new EyeClawClient(clientConfig, logger)
|
|
179
|
+
clients.set(ctx.accountId, client)
|
|
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 CLI (since ctx.sendAgent doesn't exist)
|
|
187
|
+
const { spawn } = await import('child_process')
|
|
188
|
+
const { promisify } = await import('util')
|
|
189
|
+
const exec = promisify((await import('child_process')).exec)
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
// Call openclaw agent CLI to process the message
|
|
193
|
+
const result = await exec(
|
|
194
|
+
`openclaw agent --session-id eyeclaw-web-chat --message ${JSON.stringify(message)} --json`,
|
|
195
|
+
{ timeout: 60000 }
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if (result.stdout) {
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(result.stdout)
|
|
201
|
+
// OpenClaw CLI returns: { result: { payloads: [{ text: "...", mediaUrl: null }] } }
|
|
202
|
+
const response = parsed.result?.payloads?.[0]?.text || 'Agent completed (no text)'
|
|
203
|
+
ctx.log?.info(`β
Agent response: ${response.substring(0, 100)}...`)
|
|
204
|
+
client.sendLog('info', response)
|
|
205
|
+
} catch (e) {
|
|
206
|
+
// If not JSON, just send the stdout
|
|
207
|
+
ctx.log?.info(`β
Agent response (raw): ${result.stdout.substring(0, 100)}...`)
|
|
208
|
+
client.sendLog('info', result.stdout.trim())
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
client.sendLog('info', 'β
Agent completed successfully')
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
215
|
+
ctx.log?.error(`Failed to execute openclaw agent: ${errorMsg}`)
|
|
216
|
+
client.sendLog('error', `β Agent error: ${errorMsg}`)
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
220
|
+
ctx.log?.error(`Failed to call OpenClaw Agent: ${errorMsg}`)
|
|
221
|
+
client.sendLog('error', `β Failed to call agent: ${errorMsg}`)
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
await client.connect()
|
|
227
|
+
ctx.log?.info('β
Successfully connected to EyeClaw platform')
|
|
228
|
+
ctx.setStatus({ accountId: ctx.accountId, running: true, lastStartAt: Date.now() })
|
|
229
|
+
|
|
230
|
+
// Wait for abort signal
|
|
231
|
+
await new Promise<void>((resolve) => {
|
|
232
|
+
ctx.abortSignal.addEventListener('abort', () => {
|
|
233
|
+
ctx.log?.info('π Shutting down EyeClaw SDK...')
|
|
234
|
+
client.disconnect()
|
|
235
|
+
clients.delete(ctx.accountId)
|
|
236
|
+
ctx.setStatus({ accountId: ctx.accountId, running: false, lastStopAt: Date.now() })
|
|
237
|
+
resolve()
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
} catch (error) {
|
|
241
|
+
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
242
|
+
ctx.log?.error(`Failed to connect to EyeClaw: ${errorMsg}`)
|
|
243
|
+
ctx.setStatus({
|
|
244
|
+
accountId: ctx.accountId,
|
|
245
|
+
running: false,
|
|
246
|
+
lastError: errorMsg,
|
|
247
|
+
lastStopAt: Date.now(),
|
|
248
|
+
})
|
|
249
|
+
throw error
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -105,6 +105,16 @@ 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
|
|
@@ -114,6 +124,80 @@ export class EyeClawClient {
|
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
126
|
|
|
127
|
+
private handleExecuteCommand(message: Record<string, unknown>): void {
|
|
128
|
+
const command = message.command as string
|
|
129
|
+
const params = (message.params as Record<string, unknown>) || {}
|
|
130
|
+
|
|
131
|
+
this.logger.info(`π₯ Executing command: ${command}`)
|
|
132
|
+
|
|
133
|
+
switch (command) {
|
|
134
|
+
case 'chat': {
|
|
135
|
+
const userMessage = params.message as string
|
|
136
|
+
this.logger.info(`π¬ Chat message: ${userMessage}`)
|
|
137
|
+
|
|
138
|
+
// Send user message acknowledgment
|
|
139
|
+
this.sendLog('info', `ζΆε°ζΆζ―: ${userMessage}`)
|
|
140
|
+
|
|
141
|
+
// Call OpenClaw Agent via callback
|
|
142
|
+
this.handleChatMessage(userMessage)
|
|
143
|
+
break
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
case 'ping': {
|
|
147
|
+
this.sendLog('info', 'π Pong! Bot is responding.')
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
case 'status': {
|
|
152
|
+
this.requestStatus()
|
|
153
|
+
break
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case 'echo': {
|
|
157
|
+
const echoMessage = params.message as string
|
|
158
|
+
this.sendLog('info', `Echo: ${echoMessage}`)
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'help': {
|
|
163
|
+
const helpMessage = [
|
|
164
|
+
'π€ Available Commands:',
|
|
165
|
+
'β’ chat - Send a chat message',
|
|
166
|
+
'β’ ping - Test connection',
|
|
167
|
+
'β’ status - Get bot status',
|
|
168
|
+
'β’ echo - Echo a message',
|
|
169
|
+
'β’ help - Show this help',
|
|
170
|
+
].join('\n')
|
|
171
|
+
this.sendLog('info', helpMessage)
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
default:
|
|
176
|
+
this.logger.warn(`Unknown command: ${command}`)
|
|
177
|
+
this.sendLog('error', `β Unknown command: ${command}`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private handleChatMessage(userMessage: string): void {
|
|
182
|
+
// This will be called by OpenClaw channel plugin via sendAgent
|
|
183
|
+
if (this.sendAgentCallback) {
|
|
184
|
+
this.logger.info('π€ Calling OpenClaw Agent...')
|
|
185
|
+
this.sendAgentCallback(userMessage)
|
|
186
|
+
} else {
|
|
187
|
+
// Fallback: simple echo if not running in OpenClaw context
|
|
188
|
+
this.logger.warn('No OpenClaw Agent available, using echo mode')
|
|
189
|
+
this.sendLog('info', `π¬ Echo: "${userMessage}" (OpenClaw Agent not connected)`)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Callback to send message to OpenClaw Agent (injected by channel plugin)
|
|
194
|
+
private sendAgentCallback: ((message: string) => Promise<void>) | null = null
|
|
195
|
+
|
|
196
|
+
setSendAgentCallback(callback: (message: string) => Promise<void>): void {
|
|
197
|
+
this.sendAgentCallback = callback
|
|
198
|
+
this.logger.info('β
OpenClaw Agent callback registered')
|
|
199
|
+
}
|
|
200
|
+
|
|
117
201
|
private handleStatusResponse(status: BotStatus): void {
|
|
118
202
|
this.logger.info(`Bot status: online=${status.online}, status=${status.status}, sessions=${status.active_sessions}, uptime=${Math.floor(status.uptime / 60)}m`)
|
|
119
203
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
// Type definitions for
|
|
2
|
-
|
|
1
|
+
// Type definitions for EyeClaw Channel Plugin
|
|
2
|
+
|
|
3
|
+
export interface EyeClawConfig {
|
|
4
|
+
enabled?: boolean
|
|
5
|
+
botId: string | number
|
|
6
|
+
sdkToken: string
|
|
7
|
+
serverUrl?: string
|
|
8
|
+
reconnectInterval?: number
|
|
9
|
+
heartbeatInterval?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ResolvedEyeClawAccount {
|
|
13
|
+
accountId: string
|
|
14
|
+
enabled: boolean
|
|
15
|
+
configured: boolean
|
|
16
|
+
name: string
|
|
17
|
+
config?: EyeClawConfig
|
|
18
|
+
}
|
|
3
19
|
|
|
4
20
|
export interface PluginConfig {
|
|
5
21
|
enabled: boolean
|