@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeclaw/eyeclaw",
3
- "version": "2.0.13",
3
+ "version": "2.0.15",
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",
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 = 'eyeclaw-web-chat'
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 {