@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.
- package/package.json +1 -1
- package/server.js +64 -14
package/package.json
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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:  for workspace files or  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
|
});
|