@clawchatsai/connector 0.0.44 → 0.0.45

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.js +64 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawchatsai/connector",
3
- "version": "0.0.44",
3
+ "version": "0.0.45",
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
@@ -3969,6 +3969,7 @@ export function createApp(config = {}) {
3969
3969
  this._externalBroadcastTargets = [];
3970
3970
  this.streamState = new Map();
3971
3971
  this.activityLogs = new Map();
3972
+ this._pendingMediaUrls = new Map(); // sessionKey → string[] of MEDIA: paths captured during streaming
3972
3973
  setInterval(() => {
3973
3974
  const cutoff = Date.now() - 10 * 60 * 1000;
3974
3975
  for (const [runId, log] of this.activityLogs) {
@@ -4065,6 +4066,42 @@ export function createApp(config = {}) {
4065
4066
  if (!content || !content.trim()) { console.log(`Skipping empty assistant response for thread ${parsed.threadId}`); return; }
4066
4067
  const now = Date.now();
4067
4068
 
4069
+ // Check if the final assembled message content contains MEDIA: lines
4070
+ console.log('[ClawChats] saveAssistantMessage hasMedia:', content.includes('MEDIA:'), 'msgKeys:', Object.keys(message||{}), 'msgMediaUrls:', JSON.stringify(message?.mediaUrls), 'msgMediaUrl:', message?.mediaUrl);
4071
+
4072
+ // Extract MEDIA: paths from the final complete message content (if gateway preserves them here)
4073
+ let attachments = null;
4074
+ const mediaMatches = [...content.matchAll(/^MEDIA:\s*(\S+)/gm)].map(m => m[1].trim()).filter(p => /\.\w{1,10}$/.test(p));
4075
+ if (mediaMatches.length > 0) {
4076
+ console.log('[ClawChats] MEDIA paths from final content:', JSON.stringify(mediaMatches));
4077
+ attachments = mediaMatches.map(filePath => {
4078
+ const name = path.basename(filePath);
4079
+ const ext = (name.split('.').pop() || '').toLowerCase();
4080
+ const type = ['jpg','jpeg','png','gif','webp','svg','bmp','ico'].includes(ext) ? 'image'
4081
+ : ['mp3','wav','ogg','aac','m4a','flac','opus'].includes(ext) ? 'audio'
4082
+ : ['mp4','webm','mov','avi','mkv'].includes(ext) ? 'video' : 'file';
4083
+ return { path: filePath, name, type };
4084
+ });
4085
+ }
4086
+
4087
+ // Fallback: use MEDIA: paths captured from delta stream
4088
+ if (!attachments) {
4089
+ const paths = this._pendingMediaUrls.get(sessionKey);
4090
+ this._pendingMediaUrls.delete(sessionKey);
4091
+ if (paths?.length) {
4092
+ attachments = paths.map(filePath => {
4093
+ const name = path.basename(filePath);
4094
+ const ext = (name.split('.').pop() || '').toLowerCase();
4095
+ const type = ['jpg','jpeg','png','gif','webp','svg','bmp','ico'].includes(ext) ? 'image'
4096
+ : ['mp3','wav','ogg','aac','m4a','flac','opus'].includes(ext) ? 'audio'
4097
+ : ['mp4','webm','mov','avi','mkv'].includes(ext) ? 'video' : 'file';
4098
+ return { path: filePath, name, type };
4099
+ });
4100
+ }
4101
+ } else {
4102
+ this._pendingMediaUrls.delete(sessionKey); // clean up either way
4103
+ }
4104
+
4068
4105
  // Check for pending activity message
4069
4106
  const pendingMsg = db.prepare(`
4070
4107
  SELECT id, metadata FROM messages
@@ -4084,13 +4121,15 @@ export function createApp(config = {}) {
4084
4121
  if (lastAssistantIdx >= 0) metadata.activityLog.splice(lastAssistantIdx, 1);
4085
4122
  metadata.activitySummary = this.generateActivitySummary(metadata.activityLog);
4086
4123
  }
4124
+ if (attachments) metadata.attachments = attachments;
4087
4125
  db.prepare('UPDATE messages SET content = ?, metadata = ?, timestamp = ? WHERE id = ?')
4088
4126
  .run(content, JSON.stringify(metadata), now, pendingMsg.id);
4089
4127
  messageId = pendingMsg.id;
4090
4128
  } else {
4091
4129
  // No pending activity — normal INSERT (simple responses, no tools)
4092
4130
  messageId = seq != null ? `gw-${parsed.threadId}-${seq}` : `gw-${parsed.threadId}-${now}`;
4093
- 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);
4131
+ const metaStr = attachments ? JSON.stringify({ attachments }) : null;
4132
+ 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, metaStr, now, now);
4094
4133
  }
4095
4134
 
4096
4135
  try {
@@ -4132,6 +4171,8 @@ export function createApp(config = {}) {
4132
4171
  } catch (e) { console.error(`Failed to save error marker:`, e.message); }
4133
4172
  }
4134
4173
 
4174
+ // _scanRecentWorkspaceFiles removed — MEDIA: paths captured from delta stream instead
4175
+
4135
4176
  generateThreadTitle(db, threadId, workspace, skipHeuristic = false) {
4136
4177
  const thread = db.prepare('SELECT title FROM threads WHERE id = ?').get(threadId);
4137
4178
  if (!thread) return;
@@ -4182,7 +4223,25 @@ export function createApp(config = {}) {
4182
4223
  if (!this.activityLogs.has(runId)) this.activityLogs.set(runId, { sessionKey, steps: [], startTime: Date.now() });
4183
4224
  const log = this.activityLogs.get(runId);
4184
4225
  if (stream === 'assistant') {
4226
+ // Check data.mediaUrls — gateway populates this via splitMediaFromOutput on each streaming event.
4227
+ // Partial path events (e.g. ["/"]) fail the extension filter; complete-path events pass.
4228
+ const incomingUrls = Array.isArray(data?.mediaUrls) ? data.mediaUrls : (data?.mediaUrl ? [data.mediaUrl] : []);
4229
+ for (const p of incomingUrls) {
4230
+ if (typeof p === 'string' && /\.\w{1,10}$/.test(p)) {
4231
+ if (!log._seenMediaPaths) log._seenMediaPaths = new Set();
4232
+ if (!log._seenMediaPaths.has(p)) {
4233
+ log._seenMediaPaths.add(p);
4234
+ if (!this._pendingMediaUrls.has(sessionKey)) this._pendingMediaUrls.set(sessionKey, []);
4235
+ this._pendingMediaUrls.get(sessionKey).push(p);
4236
+ console.log('[ClawChats] MEDIA path captured from mediaUrls:', p);
4237
+ }
4238
+ }
4239
+ }
4240
+ if (incomingUrls.length > 0) console.log('[ClawChats] mediaUrls on event:', JSON.stringify(incomingUrls));
4185
4241
  const text = data?.text || '';
4242
+ const delta = data?.delta || '';
4243
+ // Log if a MEDIA: directive appears in text or delta (must look like an actual path, not just the word)
4244
+ if (/MEDIA:\s*[./~a-zA-Z]/.test(text.slice(-80)) || /MEDIA:\s*[./~a-zA-Z]/.test(delta)) console.log('[ClawChats] MEDIA in stream! text tail:', JSON.stringify(text.slice(-80)), 'delta:', JSON.stringify(delta));
4186
4245
  if (text) {
4187
4246
  let currentSegment = log._currentAssistantSegment;
4188
4247
  if (!currentSegment || currentSegment._sealed) {
@@ -4209,6 +4268,7 @@ export function createApp(config = {}) {
4209
4268
  if (log._currentAssistantSegment && !log._currentAssistantSegment._sealed) { log._currentAssistantSegment._sealed = true; }
4210
4269
  const step = { type: 'tool', timestamp: Date.now(), name: data?.name || 'unknown', phase: data?.phase || 'start', toolCallId: data?.toolCallId, meta: data?.meta, isError: data?.isError || false };
4211
4270
  if (data?.phase === 'result') {
4271
+ if (data?.name === 'exec') console.log('[ClawChats] exec tool result meta:', JSON.stringify(data?.meta));
4212
4272
  const existing = log.steps.findLast(s => s.toolCallId === data.toolCallId && (s.phase === 'start' || s.phase === 'running'));
4213
4273
  if (existing) { existing.phase = 'done'; existing.resultMeta = data?.meta; existing.isError = data?.isError || false; existing.durationMs = Date.now() - existing.timestamp; }
4214
4274
  else { step.phase = 'done'; log.steps.push(step); }
@@ -4221,6 +4281,7 @@ export function createApp(config = {}) {
4221
4281
  }
4222
4282
  if (stream === 'lifecycle') {
4223
4283
  if (data?.phase === 'end' || data?.phase === 'error') {
4284
+ // MEDIA: paths already captured during delta streaming — nothing to do here
4224
4285
  if (log._currentAssistantSegment && !log._currentAssistantSegment._sealed) log._currentAssistantSegment._sealed = true;
4225
4286
  const lastAssistantIdx = log.steps.findLastIndex(s => s.type === 'assistant');
4226
4287
  if (lastAssistantIdx >= 0) log.steps.splice(lastAssistantIdx, 1);
@@ -4547,10 +4608,7 @@ export function createApp(config = {}) {
4547
4608
  }
4548
4609
 
4549
4610
  // ── Browser WebSocket setup (shared logic for standalone and plugin) ────────
4550
- // Track which sessions have already received the ClawChats file hint (once per session)
4551
- const _clawchatsHintedSessions = new Set();
4552
-
4553
- function _setupBrowserWs(wssInstance) {
4611
+ function _setupBrowserWs(wssInstance) {
4554
4612
  wssInstance.on('connection', (ws) => {
4555
4613
  console.log('Browser client connected');
4556
4614
  _gatewayClient.addBrowserClient(ws);
@@ -4580,15 +4638,7 @@ export function createApp(config = {}) {
4580
4638
  if (msg.action === 'debug-start') { const result = _debugLogger.start(msg.ts, ws); if (result.error === 'already-active') ws.send(JSON.stringify({ type: 'clawchats', event: 'debug-error', error: 'Recording already active in another tab', sessionId: result.sessionId })); else ws.send(JSON.stringify({ type: 'clawchats', event: 'debug-started', sessionId: result.sessionId })); return; }
4581
4639
  if (msg.action === 'debug-dump') { const { sessionId, files } = _debugLogger.saveDump(msg); ws.send(JSON.stringify({ type: 'clawchats', event: 'debug-saved', sessionId, files })); return; }
4582
4640
  }
4583
- // Inject ClawChats file hint once per session (first message only, ~15 tokens)
4584
- if (msg.type === 'req' && msg.method === 'chat.send' && typeof msg.params?.message === 'string') {
4585
- const sk = msg.params.sessionKey;
4586
- if (sk && !_clawchatsHintedSessions.has(sk)) {
4587
- _clawchatsHintedSessions.add(sk);
4588
- msg.params.message += '\n[ClawChats context: MEDIA: tags are stripped by the gateway and will NOT display. To show images/files inline, use markdown syntax: ![alt](./filename.ext) for workspace files or ![alt](/abs/path.ext) for any path. Always use this instead of MEDIA:]';
4589
- forwardStr = JSON.stringify(msg);
4590
- }
4591
- }
4641
+ // (no hint injection mediaUrls are captured from agent events directly)
4592
4642
  } catch { /* Not JSON or not a ClawChats message, forward as-is */ }
4593
4643
  _gatewayClient.sendToGateway(forwardStr);
4594
4644
  });