@clawchatsai/connector 0.0.50 → 0.0.52

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 (3) hide show
  1. package/dist/index.js +19 -6
  2. package/package.json +1 -1
  3. package/server.js +27 -7
package/dist/index.js CHANGED
@@ -325,15 +325,28 @@ async function stopClawChats(ctx) {
325
325
  * guard checks MediaPath/MediaPaths but not inline base64 attachments).
326
326
  * Injecting a minimal placeholder ensures the agent run proceeds.
327
327
  */
328
+ // Track which ClawChats thread sessions have received the file-hint (once per session).
329
+ const _hintedSessions = new Set();
328
330
  function normalizeGatewayPayload(raw) {
329
331
  try {
330
332
  const parsed = JSON.parse(raw);
331
- if (parsed.method === 'chat.send' &&
332
- Array.isArray(parsed.params?.attachments) &&
333
- parsed.params.attachments.length > 0 &&
334
- !parsed.params.message?.trim()) {
335
- parsed.params.message = '[Image]';
336
- return JSON.stringify(parsed);
333
+ if (parsed.method === 'chat.send' && typeof parsed.params?.message === 'string') {
334
+ // Fix image-only messages: inject placeholder so gateway doesn't reject empty body.
335
+ if (Array.isArray(parsed.params?.attachments) &&
336
+ parsed.params.attachments.length > 0 &&
337
+ !parsed.params.message?.trim()) {
338
+ parsed.params.message = '[Image]';
339
+ return JSON.stringify(parsed);
340
+ }
341
+ // Hint injection: teach the agent to emit MEDIA:/path via exec after creating files.
342
+ // Injected once per ClawChats thread session (~15 tokens, invisible to user).
343
+ const sk = parsed.params.sessionKey || '';
344
+ if (sk.includes(':chat:') && !_hintedSessions.has(sk)) {
345
+ _hintedSessions.add(sk);
346
+ parsed.params.message += '\n[ClawChats: after creating or saving any file, run: exec echo "MEDIA:/absolute/path/to/file" — this delivers it inline to the chat]';
347
+ console.log(`[clawchats] hint-injected for session ${sk}`);
348
+ return JSON.stringify(parsed);
349
+ }
337
350
  }
338
351
  }
339
352
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawchatsai/connector",
3
- "version": "0.0.50",
3
+ "version": "0.0.52",
4
4
  "type": "module",
5
5
  "description": "ClawChats OpenClaw plugin — P2P tunnel + local API bridge",
6
6
  "main": "dist/index.js",
package/server.js CHANGED
@@ -4064,15 +4064,31 @@ export function createApp(config = {}) {
4064
4064
  let content = extractContent(message);
4065
4065
  if (!content || !content.trim()) { console.log(`Skipping empty assistant response for thread ${parsed.threadId}`); return; }
4066
4066
 
4067
- // Attach any images captured by the after_tool_call hook (MEDIA: lines from exec stdout).
4068
- // Always clear the stash entry regardless of whether we find paths, to prevent leaking across turns.
4067
+ // Attach media captured by the after_tool_call hook (MEDIA: lines from exec stdout).
4068
+ // Classify by extension: images inline markdown, audio/docs metadata.attachments.
4069
+ // Always clear the stash regardless of whether paths were found (prevents cross-turn leaks).
4069
4070
  const _mediaStash = config.mediaStash;
4070
4071
  const _pendingPaths = _mediaStash?.get(sessionKey) ?? [];
4071
4072
  _mediaStash?.delete(sessionKey);
4073
+ const _IMAGE_EXTS = new Set(['png','jpg','jpeg','gif','webp','bmp','svg','ico','avif','tiff']);
4074
+ const _AUDIO_EXTS = new Set(['mp3','wav','ogg','m4a','flac','aac','opus','wma']);
4075
+ const _pendingAttachments = []; // non-image files → metadata.attachments
4076
+ const imagePaths = []; // images → inline markdown (declared outside if for broadcast scope)
4072
4077
  if (_pendingPaths.length > 0) {
4073
- const imageMarkdown = _pendingPaths.map(p => `![image](${p})`).join('\n');
4074
- content = content.trimEnd() + '\n\n' + imageMarkdown;
4075
- console.log(`[clawchats] media-attach: appended ${_pendingPaths.length} image(s) to message for ${sessionKey}`);
4078
+ for (const p of _pendingPaths) {
4079
+ const ext = (p.split('.').pop() || '').toLowerCase();
4080
+ if (_IMAGE_EXTS.has(ext)) {
4081
+ imagePaths.push(p);
4082
+ } else {
4083
+ const name = p.split('/').pop();
4084
+ const type = _AUDIO_EXTS.has(ext) ? 'audio' : 'file';
4085
+ _pendingAttachments.push({ path: p, name, type });
4086
+ }
4087
+ }
4088
+ if (imagePaths.length > 0) {
4089
+ content = content.trimEnd() + '\n\n' + imagePaths.map(p => `![image](${p})`).join('\n');
4090
+ }
4091
+ console.log(`[clawchats] media-attach: ${imagePaths.length} image(s) inline, ${_pendingAttachments.length} attachment(s) for ${sessionKey}`);
4076
4092
  }
4077
4093
 
4078
4094
  const now = Date.now();
@@ -4096,13 +4112,17 @@ export function createApp(config = {}) {
4096
4112
  if (lastAssistantIdx >= 0) metadata.activityLog.splice(lastAssistantIdx, 1);
4097
4113
  metadata.activitySummary = this.generateActivitySummary(metadata.activityLog);
4098
4114
  }
4115
+ if (_pendingAttachments.length > 0) {
4116
+ metadata.attachments = [...(metadata.attachments || []), ..._pendingAttachments];
4117
+ }
4099
4118
  db.prepare('UPDATE messages SET content = ?, metadata = ?, timestamp = ? WHERE id = ?')
4100
4119
  .run(content, JSON.stringify(metadata), now, pendingMsg.id);
4101
4120
  messageId = pendingMsg.id;
4102
4121
  } else {
4103
4122
  // No pending activity — normal INSERT (simple responses, no tools)
4104
4123
  messageId = seq != null ? `gw-${parsed.threadId}-${seq}` : `gw-${parsed.threadId}-${now}`;
4105
- db.prepare(`INSERT INTO messages (id, thread_id, role, content, status, timestamp, created_at) VALUES (?, ?, 'assistant', ?, 'sent', ?, ?) ON CONFLICT(id) DO UPDATE SET content = excluded.content, timestamp = excluded.timestamp`).run(messageId, parsed.threadId, content, now, now);
4124
+ const newMeta = _pendingAttachments.length > 0 ? JSON.stringify({ attachments: _pendingAttachments }) : null;
4125
+ db.prepare(`INSERT INTO messages (id, thread_id, role, content, status, metadata, timestamp, created_at) VALUES (?, ?, 'assistant', ?, 'sent', ?, ?, ?) ON CONFLICT(id) DO UPDATE SET content = excluded.content, metadata = COALESCE(excluded.metadata, metadata), timestamp = excluded.timestamp`).run(messageId, parsed.threadId, content, newMeta, now, now);
4106
4126
  }
4107
4127
 
4108
4128
  try {
@@ -4112,7 +4132,7 @@ export function createApp(config = {}) {
4112
4132
  const threadInfo = db.prepare('SELECT title FROM threads WHERE id = ?').get(parsed.threadId);
4113
4133
  const unreadCount = db.prepare('SELECT COUNT(*) as c FROM unread_messages WHERE thread_id = ?').get(parsed.threadId).c;
4114
4134
  const preview = content.length > 120 ? content.substring(0, 120) + '...' : content;
4115
- this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'message-saved', threadId: parsed.threadId, workspace: parsed.workspace, messageId, timestamp: now, title: threadInfo?.title || 'Chat', preview, unreadCount }));
4135
+ this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'message-saved', threadId: parsed.threadId, workspace: parsed.workspace, messageId, timestamp: now, title: threadInfo?.title || 'Chat', preview, unreadCount, updatedContent: imagePaths.length > 0 ? content : undefined, updatedAttachments: _pendingAttachments.length > 0 ? _pendingAttachments : undefined }));
4116
4136
  const workspaceUnreadTotal = db.prepare('SELECT COALESCE(SUM(unread_count), 0) as total FROM threads').get().total;
4117
4137
  this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'unread-update', workspace: parsed.workspace, threadId: parsed.threadId, messageId, action: 'new', unreadCount, workspaceUnreadTotal, title: threadInfo?.title || 'Chat', preview, timestamp: now }));
4118
4138
  console.log(`Saved assistant message to ${parsed.workspace}/${parsed.threadId} (${pendingMsg ? 'merged into pending' : 'seq: ' + seq})`);