@eyeclaw/eyeclaw 2.0.2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeclaw/eyeclaw",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
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
@@ -183,33 +183,96 @@ export const eyeclawPlugin: ChannelPlugin<ResolvedEyeClawAccount> = {
183
183
  try {
184
184
  ctx.log?.info(`🤖 Sending message to OpenClaw Agent: ${message}`)
185
185
 
186
- // Call OpenClaw Agent via CLI (since ctx.sendAgent doesn't exist)
186
+ // Call OpenClaw Agent via spawn for streaming output
187
187
  const { spawn } = await import('child_process')
188
- const { promisify } = await import('util')
189
- const exec = promisify((await import('child_process')).exec)
190
188
 
191
189
  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
- )
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
197
 
198
- if (result.stdout) {
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
199
211
  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)
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
205
224
  } 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())
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}`)
209
266
  }
210
- } else {
211
- client.sendLog('info', '✅ Agent completed successfully')
212
- }
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
+
213
276
  } catch (error) {
214
277
  const errorMsg = error instanceof Error ? error.message : String(error)
215
278
  ctx.log?.error(`Failed to execute openclaw agent: ${errorMsg}`)
package/src/client.ts CHANGED
@@ -119,6 +119,12 @@ export class EyeClawClient {
119
119
  this.logger.info(`[Server Log] ${message.level}: ${message.message}`)
120
120
  break
121
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
+
122
128
  default:
123
129
  this.logger.warn(`Unknown message type: ${type}`)
124
130
  }
@@ -274,6 +280,15 @@ export class EyeClawClient {
274
280
  })
275
281
  }
276
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
+
277
292
  sendCommandResult(command: string, result: unknown, error?: string): void {
278
293
  this.sendChannelMessage('command_result', {
279
294
  command,