@clawchatsai/connector 0.1.8 → 0.1.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server/gateway.js +36 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawchatsai/connector",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "ClawChats OpenClaw plugin — P2P tunnel + local API bridge",
6
6
  "main": "dist/index.js",
package/server/gateway.js CHANGED
@@ -11,9 +11,13 @@ export class GatewayClient {
11
11
  this.debugLogger = debugLogger;
12
12
  this.gatewayWsUrl = gatewayWsUrl;
13
13
  this.authToken = authToken;
14
- // Per-session buffer of MEDIA: paths extracted from exec tool args (echo "MEDIA:/...").
14
+ // Per-session buffer of attachment paths captured during a run. Drained by saveAssistantMessage.
15
+ // Sources: (1) MEDIA: paths from exec tool's echo args, (2) Write/Edit tool args.path.
15
16
  // Lives on the stable singleton — safe from plugin re-registration that broke the old closure-based stash.
16
17
  this._runMediaPaths = new Map();
18
+ // Per-toolCallId staging for Write/Edit: path captured at phase:start, promoted to _runMediaPaths
19
+ // at phase:result if the call succeeded. Keyed by toolCallId; value: { sessionKey, path }.
20
+ this._runWriteBuffers = new Map();
17
21
 
18
22
  this.ws = null;
19
23
  this.connected = false;
@@ -292,7 +296,7 @@ export class GatewayClient {
292
296
  if (!db.prepare('SELECT id FROM threads WHERE id = ?').get(parsed.threadId)) return;
293
297
  const now = Date.now();
294
298
  try {
295
- db.prepare('INSERT INTO messages (id, thread_id, role, content, status, metadata, timestamp, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(`gw-error-${parsed.threadId}-${now}`, parsed.threadId, 'system', `[error] ${gwErrorMessage || message?.error || message?.content || 'Unknown error'}`, 'sent', '{"transient":true}', now, now);
299
+ db.prepare('INSERT INTO messages (id, thread_id, role, content, status, metadata, timestamp, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(`gw-error-${parsed.threadId}-${now}`, parsed.threadId, 'system', `[error] ${message?.error || message?.content || 'Unknown error'}`, 'sent', '{"transient":true}', now, now);
296
300
  // Clear stale pending flag so browsers reloading the chat don't re-derive "thinking..." state.
297
301
  db.prepare("UPDATE messages SET metadata = json_remove(metadata, '$.pending') WHERE thread_id = ? AND role = 'assistant' AND json_extract(metadata, '$.pending') = 1").run(parsed.threadId);
298
302
  } catch (e) { console.error('Failed to save error marker:', e.message); }
@@ -379,6 +383,31 @@ export class GatewayClient {
379
383
  this._runMediaPaths.set(sessionKey, [...new Set([...existing, ...paths])]);
380
384
  }
381
385
  }
386
+ // Capture Write/Edit tool paths. The split pane (and existing attachment chip render) handles
387
+ // any text-ish or PDF file, so allow that broad set; image/audio extensions also flow through
388
+ // — saveAssistantMessage routes them by ext (image → markdown, audio → audio chip).
389
+ // Buffer at phase:start when args.path is present; promote to _runMediaPaths at phase:result
390
+ // only if the call succeeded — failed writes shouldn't surface a chip for a file that may not exist.
391
+ const VIEWABLE_EXTS = new Set([
392
+ 'pdf','md','mdx','rst','txt','log',
393
+ 'csv','tsv','json','xml','yaml','yml','toml','ini','env','conf',
394
+ 'js','jsx','mjs','cjs','ts','tsx','py','rb','go','rs','java','c','cpp','h','hpp','cs','php',
395
+ 'html','htm','css','scss','sass','less','sh','bash','zsh','sql','pl','lua','r','swift','kt','dart',
396
+ 'png','jpg','jpeg','gif','webp','bmp','svg','ico','avif','tiff',
397
+ 'mp3','wav','ogg','m4a','flac','aac','opus','wma'
398
+ ]);
399
+ if (sessionKey && data?.toolCallId && /^(write|edit|multiedit|notebookedit)$/i.test(data?.name || '') && data?.phase === 'start' && typeof data?.args?.path === 'string' && data.args.path.length > 0) {
400
+ const ext = (data.args.path.split('.').pop() || '').toLowerCase();
401
+ if (VIEWABLE_EXTS.has(ext)) this._runWriteBuffers.set(data.toolCallId, { sessionKey, path: data.args.path });
402
+ }
403
+ if (data?.toolCallId && data?.phase === 'result' && this._runWriteBuffers.has(data.toolCallId)) {
404
+ const buf = this._runWriteBuffers.get(data.toolCallId);
405
+ this._runWriteBuffers.delete(data.toolCallId);
406
+ if (!data.isError && buf.sessionKey === sessionKey) {
407
+ const existing = this._runMediaPaths.get(buf.sessionKey) ?? [];
408
+ this._runMediaPaths.set(buf.sessionKey, [...new Set([...existing, buf.path])]);
409
+ }
410
+ }
382
411
  const step = { type: 'tool', timestamp: Date.now(), name: data?.name || 'unknown', phase: data?.phase || 'start', toolCallId: data?.toolCallId, meta: data?.meta || (argsMeta ? String(argsMeta) : undefined), isError: data?.isError || false };
383
412
  if (data?.phase === 'result') {
384
413
  const existing = log.steps.findLast(s => s.toolCallId === data.toolCallId && (s.phase === 'start' || s.phase === 'running'));
@@ -428,6 +457,11 @@ export class GatewayClient {
428
457
  this._syntheticErrorRuns.add(runId);
429
458
  const parsed = parseSessionKey(sessionKey);
430
459
  if (parsed) {
460
+ // Mirror the state:'error' path — writeActivityToDb just set metadata.pending=true,
461
+ // and without this the flag survives: loadHistory re-derives has_pending on reconnect,
462
+ // "thinking…" sticks, and sendMessage's isStreaming() guard queues future sends into
463
+ // _pendingSend forever.
464
+ this.saveErrorMarker(sessionKey, { error: data?.error || 'Agent failed before reply' });
431
465
  this.broadcastToBrowsers(JSON.stringify({
432
466
  type: 'clawchats',
433
467
  event: 'streaming-end',