@clawchatsai/connector 0.0.44 → 0.0.46

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 +65 -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.46",
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,43 @@ 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 rawContent:', JSON.stringify(content.slice(0, 300)), 'rawMsgContent:', JSON.stringify(JSON.stringify(message?.content)?.slice(0, 300)));
4071
+ console.log('[ClawChats] saveAssistantMessage msgKeys:', Object.keys(message||{}), 'msgMediaUrls:', JSON.stringify(message?.mediaUrls), 'msgMediaUrl:', message?.mediaUrl);
4072
+
4073
+ // Extract MEDIA: paths from the final complete message content (if gateway preserves them here)
4074
+ let attachments = null;
4075
+ const mediaMatches = [...content.matchAll(/^MEDIA:\s*(\S+)/gm)].map(m => m[1].trim()).filter(p => /\.\w{1,10}$/.test(p));
4076
+ if (mediaMatches.length > 0) {
4077
+ console.log('[ClawChats] MEDIA paths from final content:', JSON.stringify(mediaMatches));
4078
+ attachments = mediaMatches.map(filePath => {
4079
+ const name = path.basename(filePath);
4080
+ const ext = (name.split('.').pop() || '').toLowerCase();
4081
+ const type = ['jpg','jpeg','png','gif','webp','svg','bmp','ico'].includes(ext) ? 'image'
4082
+ : ['mp3','wav','ogg','aac','m4a','flac','opus'].includes(ext) ? 'audio'
4083
+ : ['mp4','webm','mov','avi','mkv'].includes(ext) ? 'video' : 'file';
4084
+ return { path: filePath, name, type };
4085
+ });
4086
+ }
4087
+
4088
+ // Fallback: use MEDIA: paths captured from delta stream
4089
+ if (!attachments) {
4090
+ const paths = this._pendingMediaUrls.get(sessionKey);
4091
+ this._pendingMediaUrls.delete(sessionKey);
4092
+ if (paths?.length) {
4093
+ attachments = paths.map(filePath => {
4094
+ const name = path.basename(filePath);
4095
+ const ext = (name.split('.').pop() || '').toLowerCase();
4096
+ const type = ['jpg','jpeg','png','gif','webp','svg','bmp','ico'].includes(ext) ? 'image'
4097
+ : ['mp3','wav','ogg','aac','m4a','flac','opus'].includes(ext) ? 'audio'
4098
+ : ['mp4','webm','mov','avi','mkv'].includes(ext) ? 'video' : 'file';
4099
+ return { path: filePath, name, type };
4100
+ });
4101
+ }
4102
+ } else {
4103
+ this._pendingMediaUrls.delete(sessionKey); // clean up either way
4104
+ }
4105
+
4068
4106
  // Check for pending activity message
4069
4107
  const pendingMsg = db.prepare(`
4070
4108
  SELECT id, metadata FROM messages
@@ -4084,13 +4122,15 @@ export function createApp(config = {}) {
4084
4122
  if (lastAssistantIdx >= 0) metadata.activityLog.splice(lastAssistantIdx, 1);
4085
4123
  metadata.activitySummary = this.generateActivitySummary(metadata.activityLog);
4086
4124
  }
4125
+ if (attachments) metadata.attachments = attachments;
4087
4126
  db.prepare('UPDATE messages SET content = ?, metadata = ?, timestamp = ? WHERE id = ?')
4088
4127
  .run(content, JSON.stringify(metadata), now, pendingMsg.id);
4089
4128
  messageId = pendingMsg.id;
4090
4129
  } else {
4091
4130
  // No pending activity — normal INSERT (simple responses, no tools)
4092
4131
  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);
4132
+ const metaStr = attachments ? JSON.stringify({ attachments }) : null;
4133
+ 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
4134
  }
4095
4135
 
4096
4136
  try {
@@ -4132,6 +4172,8 @@ export function createApp(config = {}) {
4132
4172
  } catch (e) { console.error(`Failed to save error marker:`, e.message); }
4133
4173
  }
4134
4174
 
4175
+ // _scanRecentWorkspaceFiles removed — MEDIA: paths captured from delta stream instead
4176
+
4135
4177
  generateThreadTitle(db, threadId, workspace, skipHeuristic = false) {
4136
4178
  const thread = db.prepare('SELECT title FROM threads WHERE id = ?').get(threadId);
4137
4179
  if (!thread) return;
@@ -4182,7 +4224,25 @@ export function createApp(config = {}) {
4182
4224
  if (!this.activityLogs.has(runId)) this.activityLogs.set(runId, { sessionKey, steps: [], startTime: Date.now() });
4183
4225
  const log = this.activityLogs.get(runId);
4184
4226
  if (stream === 'assistant') {
4227
+ // Check data.mediaUrls — gateway populates this via splitMediaFromOutput on each streaming event.
4228
+ // Partial path events (e.g. ["/"]) fail the extension filter; complete-path events pass.
4229
+ const incomingUrls = Array.isArray(data?.mediaUrls) ? data.mediaUrls : (data?.mediaUrl ? [data.mediaUrl] : []);
4230
+ for (const p of incomingUrls) {
4231
+ if (typeof p === 'string' && /\.\w{1,10}$/.test(p)) {
4232
+ if (!log._seenMediaPaths) log._seenMediaPaths = new Set();
4233
+ if (!log._seenMediaPaths.has(p)) {
4234
+ log._seenMediaPaths.add(p);
4235
+ if (!this._pendingMediaUrls.has(sessionKey)) this._pendingMediaUrls.set(sessionKey, []);
4236
+ this._pendingMediaUrls.get(sessionKey).push(p);
4237
+ console.log('[ClawChats] MEDIA path captured from mediaUrls:', p);
4238
+ }
4239
+ }
4240
+ }
4241
+ if (incomingUrls.length > 0) console.log('[ClawChats] mediaUrls on event:', JSON.stringify(incomingUrls));
4185
4242
  const text = data?.text || '';
4243
+ const delta = data?.delta || '';
4244
+ // Log if a MEDIA: directive appears in text or delta (must look like an actual path, not just the word)
4245
+ 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
4246
  if (text) {
4187
4247
  let currentSegment = log._currentAssistantSegment;
4188
4248
  if (!currentSegment || currentSegment._sealed) {
@@ -4209,6 +4269,7 @@ export function createApp(config = {}) {
4209
4269
  if (log._currentAssistantSegment && !log._currentAssistantSegment._sealed) { log._currentAssistantSegment._sealed = true; }
4210
4270
  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
4271
  if (data?.phase === 'result') {
4272
+ if (data?.name === 'exec') console.log('[ClawChats] exec tool result meta:', JSON.stringify(data?.meta));
4212
4273
  const existing = log.steps.findLast(s => s.toolCallId === data.toolCallId && (s.phase === 'start' || s.phase === 'running'));
4213
4274
  if (existing) { existing.phase = 'done'; existing.resultMeta = data?.meta; existing.isError = data?.isError || false; existing.durationMs = Date.now() - existing.timestamp; }
4214
4275
  else { step.phase = 'done'; log.steps.push(step); }
@@ -4221,6 +4282,7 @@ export function createApp(config = {}) {
4221
4282
  }
4222
4283
  if (stream === 'lifecycle') {
4223
4284
  if (data?.phase === 'end' || data?.phase === 'error') {
4285
+ // MEDIA: paths already captured during delta streaming — nothing to do here
4224
4286
  if (log._currentAssistantSegment && !log._currentAssistantSegment._sealed) log._currentAssistantSegment._sealed = true;
4225
4287
  const lastAssistantIdx = log.steps.findLastIndex(s => s.type === 'assistant');
4226
4288
  if (lastAssistantIdx >= 0) log.steps.splice(lastAssistantIdx, 1);
@@ -4547,10 +4609,7 @@ export function createApp(config = {}) {
4547
4609
  }
4548
4610
 
4549
4611
  // ── 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) {
4612
+ function _setupBrowserWs(wssInstance) {
4554
4613
  wssInstance.on('connection', (ws) => {
4555
4614
  console.log('Browser client connected');
4556
4615
  _gatewayClient.addBrowserClient(ws);
@@ -4580,15 +4639,7 @@ export function createApp(config = {}) {
4580
4639
  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
4640
  if (msg.action === 'debug-dump') { const { sessionId, files } = _debugLogger.saveDump(msg); ws.send(JSON.stringify({ type: 'clawchats', event: 'debug-saved', sessionId, files })); return; }
4582
4641
  }
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
- }
4642
+ // (no hint injection mediaUrls are captured from agent events directly)
4592
4643
  } catch { /* Not JSON or not a ClawChats message, forward as-is */ }
4593
4644
  _gatewayClient.sendToGateway(forwardStr);
4594
4645
  });