@clawchatsai/connector 0.0.51 → 0.0.53
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/dist/index.js +47 -6
- package/package.json +1 -1
- package/server.js +27 -7
package/dist/index.js
CHANGED
|
@@ -37,6 +37,8 @@ let signaling = null;
|
|
|
37
37
|
let webrtcPeer = null;
|
|
38
38
|
let healthServer = null;
|
|
39
39
|
let _stopRequested = false;
|
|
40
|
+
/** Model IDs that have 'input' explicitly set without 'image' support. */
|
|
41
|
+
let _imageRestrictedModels = [];
|
|
40
42
|
// ---------------------------------------------------------------------------
|
|
41
43
|
// Config helpers
|
|
42
44
|
// ---------------------------------------------------------------------------
|
|
@@ -325,15 +327,38 @@ async function stopClawChats(ctx) {
|
|
|
325
327
|
* guard checks MediaPath/MediaPaths but not inline base64 attachments).
|
|
326
328
|
* Injecting a minimal placeholder ensures the agent run proceeds.
|
|
327
329
|
*/
|
|
330
|
+
// Track which ClawChats thread sessions have received the file-hint (once per session).
|
|
331
|
+
const _hintedSessions = new Set();
|
|
328
332
|
function normalizeGatewayPayload(raw) {
|
|
329
333
|
try {
|
|
330
334
|
const parsed = JSON.parse(raw);
|
|
331
|
-
if (parsed.method === 'chat.send' &&
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
if (parsed.method === 'chat.send' && typeof parsed.params?.message === 'string') {
|
|
336
|
+
// Warn user in-chat if they're sending image attachments to an image-restricted model.
|
|
337
|
+
// The warning is appended to the message so the AI echoes it back — impossible to miss.
|
|
338
|
+
if (Array.isArray(parsed.params?.attachments) &&
|
|
339
|
+
parsed.params.attachments.length > 0 &&
|
|
340
|
+
_imageRestrictedModels.length > 0) {
|
|
341
|
+
parsed.params.message = (parsed.params.message || '').trimEnd() +
|
|
342
|
+
'\n\n[⚠️ ClawChats: image attachment not delivered — your model config is missing "image" input support. ' +
|
|
343
|
+
'Fix: add "image" to the input array for your model in ~/.openclaw/openclaw.json, then restart the gateway.]';
|
|
344
|
+
return JSON.stringify(parsed);
|
|
345
|
+
}
|
|
346
|
+
// Fix image-only messages: inject placeholder so gateway doesn't reject empty body.
|
|
347
|
+
if (Array.isArray(parsed.params?.attachments) &&
|
|
348
|
+
parsed.params.attachments.length > 0 &&
|
|
349
|
+
!parsed.params.message?.trim()) {
|
|
350
|
+
parsed.params.message = '[Image]';
|
|
351
|
+
return JSON.stringify(parsed);
|
|
352
|
+
}
|
|
353
|
+
// Hint injection: teach the agent to emit MEDIA:/path via exec after creating files.
|
|
354
|
+
// Injected once per ClawChats thread session (~15 tokens, invisible to user).
|
|
355
|
+
const sk = parsed.params.sessionKey || '';
|
|
356
|
+
if (sk.includes(':chat:') && !_hintedSessions.has(sk)) {
|
|
357
|
+
_hintedSessions.add(sk);
|
|
358
|
+
parsed.params.message += '\n[ClawChats: after creating or saving any file, run: exec echo "MEDIA:/absolute/path/to/file" — this delivers it inline to the chat]';
|
|
359
|
+
console.log(`[clawchats] hint-injected for session ${sk}`);
|
|
360
|
+
return JSON.stringify(parsed);
|
|
361
|
+
}
|
|
337
362
|
}
|
|
338
363
|
}
|
|
339
364
|
catch {
|
|
@@ -384,6 +409,14 @@ function setupDataChannelHandler(dc, connectionId, ctx) {
|
|
|
384
409
|
// Auth succeeded — add to broadcast clients and inform gateway
|
|
385
410
|
connectedClients.set(connectionId, dc);
|
|
386
411
|
ctx.logger.info(`Browser authenticated: ${connectionId}`);
|
|
412
|
+
// Warn if any models have image support disabled in openclaw.json
|
|
413
|
+
if (_imageRestrictedModels.length > 0) {
|
|
414
|
+
dc.send(JSON.stringify({
|
|
415
|
+
type: 'clawchats',
|
|
416
|
+
event: 'image-capability-warning',
|
|
417
|
+
models: _imageRestrictedModels,
|
|
418
|
+
}));
|
|
419
|
+
}
|
|
387
420
|
// Persist backup code changes if any were consumed
|
|
388
421
|
if (authConfig.backupCodeHashes && config.backupCodeHashes) {
|
|
389
422
|
config.backupCodeHashes = authConfig.backupCodeHashes;
|
|
@@ -667,6 +700,14 @@ async function handleSetup(token) {
|
|
|
667
700
|
const openclawConfigPath = path.join(process.env.HOME || '/root', '.openclaw', 'openclaw.json');
|
|
668
701
|
const openclawConfig = JSON.parse(fs.readFileSync(openclawConfigPath, 'utf8'));
|
|
669
702
|
gatewayToken = openclawConfig.gateway?.auth?.token || openclawConfig.auth?.token || openclawConfig.token || '';
|
|
703
|
+
// Check for model definitions that have 'input' set without 'image' — these silently drop image attachments.
|
|
704
|
+
const modelDefs = openclawConfig?.models?.definitions ?? [];
|
|
705
|
+
_imageRestrictedModels = modelDefs
|
|
706
|
+
.filter(def => Array.isArray(def.input) && !def.input.includes('image'))
|
|
707
|
+
.map(def => def.id ?? '(unknown)');
|
|
708
|
+
if (_imageRestrictedModels.length > 0) {
|
|
709
|
+
console.warn(`[clawchats] image-restricted models detected: ${_imageRestrictedModels.join(', ')}`);
|
|
710
|
+
}
|
|
670
711
|
}
|
|
671
712
|
catch {
|
|
672
713
|
console.error('Could not read gateway token from ~/.openclaw/openclaw.json');
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -4064,15 +4064,31 @@ export function createApp(config = {}) {
|
|
|
4064
4064
|
let content = extractContent(message);
|
|
4065
4065
|
if (!content || !content.trim()) { console.log(`Skipping empty assistant response for thread ${parsed.threadId}`); return; }
|
|
4066
4066
|
|
|
4067
|
-
// Attach
|
|
4068
|
-
//
|
|
4067
|
+
// Attach media captured by the after_tool_call hook (MEDIA: lines from exec stdout).
|
|
4068
|
+
// Classify by extension: images → inline markdown, audio/docs → metadata.attachments.
|
|
4069
|
+
// Always clear the stash regardless of whether paths were found (prevents cross-turn leaks).
|
|
4069
4070
|
const _mediaStash = config.mediaStash;
|
|
4070
4071
|
const _pendingPaths = _mediaStash?.get(sessionKey) ?? [];
|
|
4071
4072
|
_mediaStash?.delete(sessionKey);
|
|
4073
|
+
const _IMAGE_EXTS = new Set(['png','jpg','jpeg','gif','webp','bmp','svg','ico','avif','tiff']);
|
|
4074
|
+
const _AUDIO_EXTS = new Set(['mp3','wav','ogg','m4a','flac','aac','opus','wma']);
|
|
4075
|
+
const _pendingAttachments = []; // non-image files → metadata.attachments
|
|
4076
|
+
const imagePaths = []; // images → inline markdown (declared outside if for broadcast scope)
|
|
4072
4077
|
if (_pendingPaths.length > 0) {
|
|
4073
|
-
const
|
|
4074
|
-
|
|
4075
|
-
|
|
4078
|
+
for (const p of _pendingPaths) {
|
|
4079
|
+
const ext = (p.split('.').pop() || '').toLowerCase();
|
|
4080
|
+
if (_IMAGE_EXTS.has(ext)) {
|
|
4081
|
+
imagePaths.push(p);
|
|
4082
|
+
} else {
|
|
4083
|
+
const name = p.split('/').pop();
|
|
4084
|
+
const type = _AUDIO_EXTS.has(ext) ? 'audio' : 'file';
|
|
4085
|
+
_pendingAttachments.push({ path: p, name, type });
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
if (imagePaths.length > 0) {
|
|
4089
|
+
content = content.trimEnd() + '\n\n' + imagePaths.map(p => ``).join('\n');
|
|
4090
|
+
}
|
|
4091
|
+
console.log(`[clawchats] media-attach: ${imagePaths.length} image(s) inline, ${_pendingAttachments.length} attachment(s) for ${sessionKey}`);
|
|
4076
4092
|
}
|
|
4077
4093
|
|
|
4078
4094
|
const now = Date.now();
|
|
@@ -4096,13 +4112,17 @@ export function createApp(config = {}) {
|
|
|
4096
4112
|
if (lastAssistantIdx >= 0) metadata.activityLog.splice(lastAssistantIdx, 1);
|
|
4097
4113
|
metadata.activitySummary = this.generateActivitySummary(metadata.activityLog);
|
|
4098
4114
|
}
|
|
4115
|
+
if (_pendingAttachments.length > 0) {
|
|
4116
|
+
metadata.attachments = [...(metadata.attachments || []), ..._pendingAttachments];
|
|
4117
|
+
}
|
|
4099
4118
|
db.prepare('UPDATE messages SET content = ?, metadata = ?, timestamp = ? WHERE id = ?')
|
|
4100
4119
|
.run(content, JSON.stringify(metadata), now, pendingMsg.id);
|
|
4101
4120
|
messageId = pendingMsg.id;
|
|
4102
4121
|
} else {
|
|
4103
4122
|
// No pending activity — normal INSERT (simple responses, no tools)
|
|
4104
4123
|
messageId = seq != null ? `gw-${parsed.threadId}-${seq}` : `gw-${parsed.threadId}-${now}`;
|
|
4105
|
-
|
|
4124
|
+
const newMeta = _pendingAttachments.length > 0 ? JSON.stringify({ attachments: _pendingAttachments }) : null;
|
|
4125
|
+
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, newMeta, now, now);
|
|
4106
4126
|
}
|
|
4107
4127
|
|
|
4108
4128
|
try {
|
|
@@ -4112,7 +4132,7 @@ export function createApp(config = {}) {
|
|
|
4112
4132
|
const threadInfo = db.prepare('SELECT title FROM threads WHERE id = ?').get(parsed.threadId);
|
|
4113
4133
|
const unreadCount = db.prepare('SELECT COUNT(*) as c FROM unread_messages WHERE thread_id = ?').get(parsed.threadId).c;
|
|
4114
4134
|
const preview = content.length > 120 ? content.substring(0, 120) + '...' : content;
|
|
4115
|
-
this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'message-saved', threadId: parsed.threadId, workspace: parsed.workspace, messageId, timestamp: now, title: threadInfo?.title || 'Chat', preview, unreadCount, updatedContent:
|
|
4135
|
+
this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'message-saved', threadId: parsed.threadId, workspace: parsed.workspace, messageId, timestamp: now, title: threadInfo?.title || 'Chat', preview, unreadCount, updatedContent: imagePaths.length > 0 ? content : undefined, updatedAttachments: _pendingAttachments.length > 0 ? _pendingAttachments : undefined }));
|
|
4116
4136
|
const workspaceUnreadTotal = db.prepare('SELECT COALESCE(SUM(unread_count), 0) as total FROM threads').get().total;
|
|
4117
4137
|
this.broadcastToBrowsers(JSON.stringify({ type: 'clawchats', event: 'unread-update', workspace: parsed.workspace, threadId: parsed.threadId, messageId, action: 'new', unreadCount, workspaceUnreadTotal, title: threadInfo?.title || 'Chat', preview, timestamp: now }));
|
|
4118
4138
|
console.log(`Saved assistant message to ${parsed.workspace}/${parsed.threadId} (${pendingMsg ? 'merged into pending' : 'seq: ' + seq})`);
|