@clawchatsai/connector 0.0.43 → 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 +71 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawchatsai/connector",
3
- "version": "0.0.43",
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
@@ -1437,7 +1437,10 @@ function handleServeFile(req, res, query) {
1437
1437
  if (!filePath) return sendError(res, 400, 'Missing path parameter');
1438
1438
 
1439
1439
  // Resolve to prevent traversal attacks
1440
- const resolved = path.resolve(filePath);
1440
+ // Relative paths (./filename) resolve against the workspace directory
1441
+ const resolved = (filePath.startsWith('./') || filePath.startsWith('../'))
1442
+ ? path.resolve(MEMORY_CONFIG.workspaceDir, filePath)
1443
+ : path.resolve(filePath);
1441
1444
 
1442
1445
  // Security: only serve files from allowed directories
1443
1446
  const allowed = ALLOWED_FILE_DIRS.some(dir => resolved.startsWith(dir + '/') || resolved === dir);
@@ -3966,6 +3969,7 @@ export function createApp(config = {}) {
3966
3969
  this._externalBroadcastTargets = [];
3967
3970
  this.streamState = new Map();
3968
3971
  this.activityLogs = new Map();
3972
+ this._pendingMediaUrls = new Map(); // sessionKey → string[] of MEDIA: paths captured during streaming
3969
3973
  setInterval(() => {
3970
3974
  const cutoff = Date.now() - 10 * 60 * 1000;
3971
3975
  for (const [runId, log] of this.activityLogs) {
@@ -4062,6 +4066,42 @@ export function createApp(config = {}) {
4062
4066
  if (!content || !content.trim()) { console.log(`Skipping empty assistant response for thread ${parsed.threadId}`); return; }
4063
4067
  const now = Date.now();
4064
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
+
4065
4105
  // Check for pending activity message
4066
4106
  const pendingMsg = db.prepare(`
4067
4107
  SELECT id, metadata FROM messages
@@ -4081,13 +4121,15 @@ export function createApp(config = {}) {
4081
4121
  if (lastAssistantIdx >= 0) metadata.activityLog.splice(lastAssistantIdx, 1);
4082
4122
  metadata.activitySummary = this.generateActivitySummary(metadata.activityLog);
4083
4123
  }
4124
+ if (attachments) metadata.attachments = attachments;
4084
4125
  db.prepare('UPDATE messages SET content = ?, metadata = ?, timestamp = ? WHERE id = ?')
4085
4126
  .run(content, JSON.stringify(metadata), now, pendingMsg.id);
4086
4127
  messageId = pendingMsg.id;
4087
4128
  } else {
4088
4129
  // No pending activity — normal INSERT (simple responses, no tools)
4089
4130
  messageId = seq != null ? `gw-${parsed.threadId}-${seq}` : `gw-${parsed.threadId}-${now}`;
4090
- 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);
4091
4133
  }
4092
4134
 
4093
4135
  try {
@@ -4129,6 +4171,8 @@ export function createApp(config = {}) {
4129
4171
  } catch (e) { console.error(`Failed to save error marker:`, e.message); }
4130
4172
  }
4131
4173
 
4174
+ // _scanRecentWorkspaceFiles removed — MEDIA: paths captured from delta stream instead
4175
+
4132
4176
  generateThreadTitle(db, threadId, workspace, skipHeuristic = false) {
4133
4177
  const thread = db.prepare('SELECT title FROM threads WHERE id = ?').get(threadId);
4134
4178
  if (!thread) return;
@@ -4179,7 +4223,25 @@ export function createApp(config = {}) {
4179
4223
  if (!this.activityLogs.has(runId)) this.activityLogs.set(runId, { sessionKey, steps: [], startTime: Date.now() });
4180
4224
  const log = this.activityLogs.get(runId);
4181
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));
4182
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));
4183
4245
  if (text) {
4184
4246
  let currentSegment = log._currentAssistantSegment;
4185
4247
  if (!currentSegment || currentSegment._sealed) {
@@ -4206,6 +4268,7 @@ export function createApp(config = {}) {
4206
4268
  if (log._currentAssistantSegment && !log._currentAssistantSegment._sealed) { log._currentAssistantSegment._sealed = true; }
4207
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 };
4208
4270
  if (data?.phase === 'result') {
4271
+ if (data?.name === 'exec') console.log('[ClawChats] exec tool result meta:', JSON.stringify(data?.meta));
4209
4272
  const existing = log.steps.findLast(s => s.toolCallId === data.toolCallId && (s.phase === 'start' || s.phase === 'running'));
4210
4273
  if (existing) { existing.phase = 'done'; existing.resultMeta = data?.meta; existing.isError = data?.isError || false; existing.durationMs = Date.now() - existing.timestamp; }
4211
4274
  else { step.phase = 'done'; log.steps.push(step); }
@@ -4218,6 +4281,7 @@ export function createApp(config = {}) {
4218
4281
  }
4219
4282
  if (stream === 'lifecycle') {
4220
4283
  if (data?.phase === 'end' || data?.phase === 'error') {
4284
+ // MEDIA: paths already captured during delta streaming — nothing to do here
4221
4285
  if (log._currentAssistantSegment && !log._currentAssistantSegment._sealed) log._currentAssistantSegment._sealed = true;
4222
4286
  const lastAssistantIdx = log.steps.findLastIndex(s => s.type === 'assistant');
4223
4287
  if (lastAssistantIdx >= 0) log.steps.splice(lastAssistantIdx, 1);
@@ -4544,7 +4608,7 @@ export function createApp(config = {}) {
4544
4608
  }
4545
4609
 
4546
4610
  // ── Browser WebSocket setup (shared logic for standalone and plugin) ────────
4547
- function _setupBrowserWs(wssInstance) {
4611
+ function _setupBrowserWs(wssInstance) {
4548
4612
  wssInstance.on('connection', (ws) => {
4549
4613
  console.log('Browser client connected');
4550
4614
  _gatewayClient.addBrowserClient(ws);
@@ -4554,6 +4618,7 @@ export function createApp(config = {}) {
4554
4618
  ws.on('message', (data) => {
4555
4619
  const msgStr = data.toString();
4556
4620
  _debugLogger.logFrame('BR→SRV', msgStr);
4621
+ let forwardStr = msgStr;
4557
4622
  try {
4558
4623
  const msg = JSON.parse(msgStr);
4559
4624
  if (msg.type === 'req' && msg.method === 'connect') {
@@ -4573,8 +4638,9 @@ export function createApp(config = {}) {
4573
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; }
4574
4639
  if (msg.action === 'debug-dump') { const { sessionId, files } = _debugLogger.saveDump(msg); ws.send(JSON.stringify({ type: 'clawchats', event: 'debug-saved', sessionId, files })); return; }
4575
4640
  }
4576
- } catch { /* Not JSON or not a ClawChats message, forward to gateway */ }
4577
- _gatewayClient.sendToGateway(msgStr);
4641
+ // (no hint injection mediaUrls are captured from agent events directly)
4642
+ } catch { /* Not JSON or not a ClawChats message, forward as-is */ }
4643
+ _gatewayClient.sendToGateway(forwardStr);
4578
4644
  });
4579
4645
 
4580
4646
  ws.on('close', () => { console.log('Browser client disconnected'); _debugLogger.handleClientDisconnect(ws); _gatewayClient.removeBrowserClient(ws); });