@eyeclaw/eyeclaw 2.0.13 → 2.0.15
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/package.json +1 -1
- package/src/channel.ts +44 -14
- package/src/client.ts +34 -0
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -202,7 +202,7 @@ export const eyeclawPlugin: ChannelPlugin<ResolvedEyeClawAccount> = {
|
|
|
202
202
|
// Use runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher for true streaming
|
|
203
203
|
client.setSendAgentCallback(async (message: string) => {
|
|
204
204
|
const streamId = Date.now().toString()
|
|
205
|
-
const streamKey =
|
|
205
|
+
const streamKey = `eyeclaw-${ctx.accountId}`
|
|
206
206
|
|
|
207
207
|
try {
|
|
208
208
|
ctx.log?.info(`🤖 Processing message via OpenClaw dispatchReply: ${message}`)
|
|
@@ -210,21 +210,51 @@ export const eyeclawPlugin: ChannelPlugin<ResolvedEyeClawAccount> = {
|
|
|
210
210
|
// 发送 stream_start
|
|
211
211
|
client.sendStreamChunk('stream_start', streamId, '')
|
|
212
212
|
|
|
213
|
+
// Build message context using OpenClaw's proper API (like WeCom plugin)
|
|
214
|
+
const runtime = getRuntime()
|
|
215
|
+
const core = runtime.channel
|
|
216
|
+
|
|
217
|
+
// Get envelope format options
|
|
218
|
+
const envelopeOptions = core.reply.resolveEnvelopeFormatOptions(ctx.cfg)
|
|
219
|
+
|
|
220
|
+
// Format the message envelope (like WeCom does)
|
|
221
|
+
const body = core.reply.formatAgentEnvelope({
|
|
222
|
+
channel: 'EyeClaw Web',
|
|
223
|
+
from: 'web_user',
|
|
224
|
+
timestamp: Date.now(),
|
|
225
|
+
previousTimestamp: undefined,
|
|
226
|
+
envelope: envelopeOptions,
|
|
227
|
+
body: message,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// Build the inbound context payload
|
|
231
|
+
const ctxBase = {
|
|
232
|
+
Body: body,
|
|
233
|
+
RawBody: message,
|
|
234
|
+
CommandBody: message,
|
|
235
|
+
From: `eyeclaw:${streamKey}`,
|
|
236
|
+
To: `eyeclaw:${ctx.accountId}`,
|
|
237
|
+
SessionKey: `eyeclaw:${streamKey}`,
|
|
238
|
+
AccountId: ctx.accountId,
|
|
239
|
+
ChatType: 'direct',
|
|
240
|
+
ConversationLabel: streamKey,
|
|
241
|
+
SenderName: 'web_user',
|
|
242
|
+
SenderId: streamKey,
|
|
243
|
+
Provider: 'eyeclaw',
|
|
244
|
+
Surface: 'eyeclaw',
|
|
245
|
+
OriginatingChannel: 'eyeclaw',
|
|
246
|
+
OriginatingTo: ctx.accountId,
|
|
247
|
+
CommandAuthorized: true,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Finalize the context payload
|
|
251
|
+
const ctxPayload = core.reply.finalizeInboundContext(ctxBase)
|
|
252
|
+
|
|
253
|
+
ctx.log?.info(`Prepared context: SessionKey=${ctxPayload.SessionKey}, Body=${ctxPayload.Body?.substring(0, 50)}`)
|
|
254
|
+
|
|
213
255
|
// 使用 OpenClaw 的 dispatchReplyWithBufferedBlockDispatcher 实现真正的流式
|
|
214
|
-
// 这和 WeCom 插件使用的方式完全相同
|
|
215
|
-
// API 路径: runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher
|
|
216
256
|
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
|
-
},
|
|
257
|
+
ctx: ctxPayload,
|
|
228
258
|
cfg: ctx.cfg,
|
|
229
259
|
dispatcherOptions: {
|
|
230
260
|
// 流式回调 - LLM 每生成一块文本就实时调用
|
package/src/client.ts
CHANGED
|
@@ -296,20 +296,54 @@ export class EyeClawClient {
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
sendLog(level: string, message: string): void {
|
|
299
|
+
// Send to BotChannel
|
|
299
300
|
this.sendChannelMessage('log', {
|
|
300
301
|
level,
|
|
301
302
|
message,
|
|
302
303
|
timestamp: new Date().toISOString(),
|
|
303
304
|
})
|
|
305
|
+
|
|
306
|
+
// Also broadcast directly to bot_{id} stream as backup
|
|
307
|
+
this.send({
|
|
308
|
+
command: 'message',
|
|
309
|
+
identifier: JSON.stringify({
|
|
310
|
+
channel: `bot_${this.config.botId}`,
|
|
311
|
+
}),
|
|
312
|
+
data: JSON.stringify({
|
|
313
|
+
action: 'log',
|
|
314
|
+
level,
|
|
315
|
+
message,
|
|
316
|
+
timestamp: new Date().toISOString(),
|
|
317
|
+
}),
|
|
318
|
+
})
|
|
304
319
|
}
|
|
305
320
|
|
|
306
321
|
sendStreamChunk(type: string, streamId: string, chunk: string): void {
|
|
322
|
+
this.logger.info(`📤 Sending stream_chunk: type=${type}, streamId=${streamId}, chunk="${chunk.substring(0, 30)}..."`)
|
|
323
|
+
|
|
324
|
+
// Send to BotChannel which will broadcast to bot_{id} for Rails to receive
|
|
307
325
|
this.sendChannelMessage('stream_chunk', {
|
|
308
326
|
type,
|
|
309
327
|
stream_id: streamId,
|
|
310
328
|
chunk,
|
|
311
329
|
timestamp: new Date().toISOString(),
|
|
312
330
|
})
|
|
331
|
+
|
|
332
|
+
// Also broadcast directly to bot_{id} stream as backup (Rails expects stream_type)
|
|
333
|
+
this.logger.info(`📤 Also sending to bot_${this.config.botId} channel`)
|
|
334
|
+
this.send({
|
|
335
|
+
command: 'message',
|
|
336
|
+
identifier: JSON.stringify({
|
|
337
|
+
channel: `bot_${this.config.botId}`,
|
|
338
|
+
}),
|
|
339
|
+
data: JSON.stringify({
|
|
340
|
+
action: 'stream_chunk',
|
|
341
|
+
stream_type: type, // Rails expects 'stream_type'
|
|
342
|
+
stream_id: streamId,
|
|
343
|
+
chunk,
|
|
344
|
+
timestamp: new Date().toISOString(),
|
|
345
|
+
}),
|
|
346
|
+
})
|
|
313
347
|
}
|
|
314
348
|
|
|
315
349
|
sendCommandResult(command: string, result: unknown, error?: string): void {
|