@eyeclaw/eyeclaw 2.0.8 → 2.0.10
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/index.ts +5 -1
- package/package.json +4 -2
- package/src/channel.ts +65 -74
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
|
|
2
2
|
import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk'
|
|
3
|
-
import { eyeclawPlugin } from './src/channel.js'
|
|
3
|
+
import { eyeclawPlugin, setRuntime } from './src/channel.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* EyeClaw SDK - OpenClaw Channel Plugin
|
|
@@ -14,6 +14,10 @@ const plugin = {
|
|
|
14
14
|
configSchema: emptyPluginConfigSchema(),
|
|
15
15
|
|
|
16
16
|
register(api: OpenClawPluginApi) {
|
|
17
|
+
// Save runtime for message processing (like WeCom plugin does)
|
|
18
|
+
// This allows channel.ts to access runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher
|
|
19
|
+
setRuntime(api.runtime)
|
|
20
|
+
|
|
17
21
|
// Register EyeClaw as a channel plugin
|
|
18
22
|
api.registerChannel({ plugin: eyeclawPlugin })
|
|
19
23
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eyeclaw/eyeclaw",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"description": "EyeClaw channel plugin for OpenClaw - Connect your local OpenClaw instance to EyeClaw platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://eyeclaw.io",
|
|
34
34
|
"openclaw": {
|
|
35
|
-
"extensions": [
|
|
35
|
+
"extensions": [
|
|
36
|
+
"./index.ts"
|
|
37
|
+
],
|
|
36
38
|
"channel": {
|
|
37
39
|
"id": "eyeclaw",
|
|
38
40
|
"label": "EyeClaw",
|
package/src/channel.ts
CHANGED
|
@@ -6,6 +6,20 @@ import { EyeClawClient } from './client.js'
|
|
|
6
6
|
// Active clients map (accountId -> client)
|
|
7
7
|
const clients = new Map<string, EyeClawClient>()
|
|
8
8
|
|
|
9
|
+
// Store runtime for use in gateway.startAccount (set during plugin registration)
|
|
10
|
+
let _runtime: any = null
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Set the plugin runtime (called during plugin registration)
|
|
14
|
+
*/
|
|
15
|
+
export function setRuntime(runtime: any) {
|
|
16
|
+
_runtime = runtime
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getRuntime() {
|
|
20
|
+
return _runtime
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
/**
|
|
10
24
|
* Resolve EyeClaw account configuration
|
|
11
25
|
*/
|
|
@@ -138,9 +152,9 @@ export const eyeclawPlugin: ChannelPlugin<ResolvedEyeClawAccount> = {
|
|
|
138
152
|
probe,
|
|
139
153
|
}),
|
|
140
154
|
},
|
|
141
|
-
|
|
155
|
+
|
|
142
156
|
gateway: {
|
|
143
|
-
startAccount: async (ctx) => {
|
|
157
|
+
startAccount: async (ctx: any) => {
|
|
144
158
|
const account = resolveEyeClawAccount(ctx.cfg, ctx.accountId)
|
|
145
159
|
|
|
146
160
|
if (!account.configured || !account.config) {
|
|
@@ -174,99 +188,76 @@ export const eyeclawPlugin: ChannelPlugin<ResolvedEyeClawAccount> = {
|
|
|
174
188
|
error: (msg: string) => ctx.log?.error(msg),
|
|
175
189
|
}
|
|
176
190
|
|
|
191
|
+
// Get runtime from module-level storage (set during register)
|
|
192
|
+
const runtime = getRuntime()
|
|
193
|
+
if (!runtime) {
|
|
194
|
+
throw new Error('OpenClaw runtime not available - did you install the plugin correctly?')
|
|
195
|
+
}
|
|
196
|
+
|
|
177
197
|
// Create and connect client
|
|
178
198
|
const client = new EyeClawClient(clientConfig, logger)
|
|
179
199
|
clients.set(ctx.accountId, client)
|
|
180
200
|
|
|
181
201
|
// Register OpenClaw Agent callback for chat messages
|
|
202
|
+
// Use runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher for true streaming
|
|
182
203
|
client.setSendAgentCallback(async (message: string) => {
|
|
183
|
-
const { spawn } = await import('child_process')
|
|
184
204
|
const streamId = Date.now().toString()
|
|
205
|
+
const streamKey = 'eyeclaw-web-chat'
|
|
185
206
|
|
|
186
207
|
try {
|
|
187
|
-
ctx.log?.info(`🤖
|
|
188
|
-
|
|
189
|
-
// Spawn openclaw agent process for streaming output
|
|
190
|
-
const agentProcess = spawn('openclaw', [
|
|
191
|
-
'agent',
|
|
192
|
-
'--session-id', 'eyeclaw-web-chat',
|
|
193
|
-
'--message', message,
|
|
194
|
-
// No --json flag: use plain text streaming output
|
|
195
|
-
])
|
|
208
|
+
ctx.log?.info(`🤖 Processing message via OpenClaw dispatchReply: ${message}`)
|
|
196
209
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// Send stream_start event
|
|
210
|
+
// 发送 stream_start
|
|
200
211
|
client.sendStreamChunk('stream_start', streamId, '')
|
|
201
212
|
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
// 使用 OpenClaw 的 dispatchReplyWithBufferedBlockDispatcher 实现真正的流式
|
|
214
|
+
// 这和 WeCom 插件使用的方式完全相同
|
|
215
|
+
// API 路径: runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher
|
|
216
|
+
await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
217
|
+
ctx: {
|
|
218
|
+
// Inbound message context
|
|
219
|
+
sessionKey: `eyeclaw:${streamKey}`,
|
|
220
|
+
messageKey: `eyeclaw:${streamKey}:${streamId}`,
|
|
221
|
+
peerKey: streamKey,
|
|
222
|
+
message: {
|
|
223
|
+
role: 'user',
|
|
224
|
+
content: message,
|
|
225
|
+
roleDetail: 'user',
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
cfg: ctx.cfg,
|
|
229
|
+
dispatcherOptions: {
|
|
230
|
+
// 流式回调 - LLM 每生成一块文本就实时调用
|
|
231
|
+
deliver: async (payload: any, info: any) => {
|
|
232
|
+
const text = payload.text || ''
|
|
233
|
+
if (text) {
|
|
234
|
+
ctx.log?.debug(`Delivering chunk: ${text.substring(0, 50)}...`)
|
|
235
|
+
client.sendStreamChunk('stream_chunk', streamId, text)
|
|
215
236
|
}
|
|
216
|
-
}
|
|
217
|
-
} catch (error) {
|
|
218
|
-
// Catch any errors in data processing to prevent WebSocket disconnect
|
|
219
|
-
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
220
|
-
ctx.log?.error(`Error processing stdout data: ${errorMsg}`)
|
|
221
|
-
}
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
// Handle stderr (errors)
|
|
225
|
-
agentProcess.stderr?.on('data', (data: Buffer) => {
|
|
226
|
-
const errorText = data.toString()
|
|
227
|
-
ctx.log?.error(`Agent stderr: ${errorText}`)
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
// Wait for process to complete
|
|
231
|
-
await new Promise<void>((resolve, reject) => {
|
|
232
|
-
// Handle process completion
|
|
233
|
-
agentProcess.on('close', (code: number) => {
|
|
234
|
-
try {
|
|
235
|
-
// Send stream_end event
|
|
236
|
-
client.sendStreamChunk('stream_end', streamId, '')
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
} else {
|
|
242
|
-
ctx.log?.error(`Agent exited with code ${code}`)
|
|
243
|
-
client.sendLog('error', `❌ Agent error: process exited with code ${code}`)
|
|
244
|
-
reject(new Error(`Agent exited with code ${code}`))
|
|
238
|
+
// 当主响应完成时记录
|
|
239
|
+
if (info.kind === 'final') {
|
|
240
|
+
ctx.log?.info('Main response complete')
|
|
245
241
|
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
// Handle process errors
|
|
255
|
-
agentProcess.on('error', (error: Error) => {
|
|
256
|
-
ctx.log?.error(`Failed to start agent process: ${error.message}`)
|
|
257
|
-
client.sendStreamChunk('stream_error', streamId, error.message)
|
|
258
|
-
client.sendLog('error', `❌ Failed to start agent: ${error.message}`)
|
|
259
|
-
reject(error)
|
|
260
|
-
})
|
|
242
|
+
},
|
|
243
|
+
onError: async (error: any, info: any) => {
|
|
244
|
+
ctx.log?.error(`Reply failed: ${error.message}`)
|
|
245
|
+
client.sendStreamChunk('stream_error', streamId, error.message)
|
|
246
|
+
},
|
|
247
|
+
},
|
|
261
248
|
})
|
|
262
249
|
|
|
250
|
+
// 发送 stream_end
|
|
251
|
+
client.sendStreamChunk('stream_end', streamId, '')
|
|
252
|
+
ctx.log?.info(`✅ Message processed successfully`)
|
|
253
|
+
|
|
263
254
|
} catch (error) {
|
|
264
255
|
const errorMsg = error instanceof Error ? error.message : String(error)
|
|
265
|
-
ctx.log?.error(`Failed to
|
|
266
|
-
//
|
|
256
|
+
ctx.log?.error(`Failed to process message: ${errorMsg}`)
|
|
257
|
+
// 发送错误通知
|
|
267
258
|
try {
|
|
268
259
|
client.sendStreamChunk('stream_error', streamId, errorMsg)
|
|
269
|
-
client.sendLog('error', `❌
|
|
260
|
+
client.sendLog('error', `❌ Error: ${errorMsg}`)
|
|
270
261
|
} catch (sendError) {
|
|
271
262
|
ctx.log?.error(`Failed to send error notification: ${sendError}`)
|
|
272
263
|
}
|