@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.
- package/package.json +1 -1
- package/server.js +71 -5
package/package.json
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4577
|
-
|
|
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); });
|