aicq-chat-plugin 3.8.0 → 3.9.0
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/index.js +11 -1
- package/lib/chat.js +258 -36
- package/package.json +1 -1
- package/src/channel.js +27 -3
package/index.js
CHANGED
|
@@ -132,7 +132,7 @@ async function handleGatewayMethod(method, kwargs = {}) {
|
|
|
132
132
|
return {
|
|
133
133
|
state: _serverClient.connected ? "connected" : "disconnected",
|
|
134
134
|
agent_id: currentAgentId,
|
|
135
|
-
version: "3.
|
|
135
|
+
version: "3.9.0",
|
|
136
136
|
architecture: "channel",
|
|
137
137
|
};
|
|
138
138
|
case "aicq.friends.list":
|
|
@@ -278,6 +278,14 @@ async function handleGatewayMethod(method, kwargs = {}) {
|
|
|
278
278
|
);
|
|
279
279
|
return { success: true, file: b64Result };
|
|
280
280
|
}
|
|
281
|
+
case "aicq.userfiles.list": {
|
|
282
|
+
if (!_chat) return { error: "Chat not initialized" };
|
|
283
|
+
return { files: _chat.listUserfiles(), directory: _chat.getUserfilesDir() };
|
|
284
|
+
}
|
|
285
|
+
case "aicq.userfiles.getPath": {
|
|
286
|
+
if (!_chat) return { error: "Chat not initialized" };
|
|
287
|
+
return { directory: _chat.getUserfilesDir() };
|
|
288
|
+
}
|
|
281
289
|
case "aicq.sessions.list":
|
|
282
290
|
return { sessions: [] };
|
|
283
291
|
default:
|
|
@@ -332,6 +340,8 @@ async function registerFull(api) {
|
|
|
332
340
|
"aicq.chat.sendFile",
|
|
333
341
|
"aicq.chat.sendImage",
|
|
334
342
|
"aicq.chat.sendFileFromBase64",
|
|
343
|
+
"aicq.userfiles.list",
|
|
344
|
+
"aicq.userfiles.getPath",
|
|
335
345
|
"aicq.groups.list",
|
|
336
346
|
"aicq.groups.create",
|
|
337
347
|
"aicq.groups.join",
|
package/lib/chat.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AICQ Chat Manager — Send/receive messages, group chat, file/image handling
|
|
3
3
|
*
|
|
4
|
-
* v3.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* v3.9.0: File/image receiving redesigned.
|
|
5
|
+
* Incoming files are saved to userfiles/ directory first,
|
|
6
|
+
* then a simulated user message is dispatched to the AI agent
|
|
7
|
+
* telling it about the uploaded file with full path info.
|
|
8
|
+
* The agent can then read and process the file.
|
|
8
9
|
*/
|
|
9
10
|
const { encryptMessage, decryptMessage } = require('./crypto');
|
|
10
11
|
const fs = require('fs');
|
|
@@ -22,10 +23,16 @@ class ChatManager {
|
|
|
22
23
|
this.uploadsDir = uploadsDir;
|
|
23
24
|
this._onNewMessage = null;
|
|
24
25
|
|
|
25
|
-
//
|
|
26
|
+
// userfiles/ directory — where received files are saved for agent processing
|
|
27
|
+
this.userfilesDir = path.join(path.dirname(uploadsDir), 'userfiles');
|
|
28
|
+
|
|
29
|
+
// Ensure directories exist
|
|
26
30
|
if (!fs.existsSync(uploadsDir)) {
|
|
27
31
|
fs.mkdirSync(uploadsDir, { recursive: true });
|
|
28
32
|
}
|
|
33
|
+
if (!fs.existsSync(this.userfilesDir)) {
|
|
34
|
+
fs.mkdirSync(this.userfilesDir, { recursive: true });
|
|
35
|
+
}
|
|
29
36
|
|
|
30
37
|
// Listen for incoming messages via WS
|
|
31
38
|
this.server.onMessage('relay', (data) => this._handleIncoming(data));
|
|
@@ -350,22 +357,10 @@ class ChatManager {
|
|
|
350
357
|
|
|
351
358
|
// Check if this is a file/image message in the new format
|
|
352
359
|
if (typeof data === 'object' && data.data && typeof data.data === 'object' && data.data.file_info) {
|
|
353
|
-
// This is a structured message with file info
|
|
360
|
+
// This is a structured message with file info — save to userfiles and notify agent
|
|
354
361
|
const fileInfo = data.data.file_info;
|
|
355
362
|
const msgType = fileInfo.isImage ? 'image' : 'file';
|
|
356
|
-
|
|
357
|
-
agent_id: agentId,
|
|
358
|
-
target_id: fromId,
|
|
359
|
-
from_id: fromId,
|
|
360
|
-
to_id: agentId,
|
|
361
|
-
type: msgType,
|
|
362
|
-
content: data.data.content || (fileInfo.isImage ? '[图片]' : `[文件] ${fileInfo.fileName}`),
|
|
363
|
-
file_url: data.data.file_url || '',
|
|
364
|
-
file_name: fileInfo.fileName || '',
|
|
365
|
-
is_group: 0,
|
|
366
|
-
status: 'delivered',
|
|
367
|
-
});
|
|
368
|
-
if (this._onNewMessage) this._onNewMessage(msg);
|
|
363
|
+
this._saveToUserfilesAndNotify(agentId, fromId, fileInfo, data.data, { isGroup: false });
|
|
369
364
|
return;
|
|
370
365
|
}
|
|
371
366
|
|
|
@@ -405,26 +400,14 @@ class ChatManager {
|
|
|
405
400
|
const msgType = data.msgType || data.msg_type || 'text';
|
|
406
401
|
|
|
407
402
|
if (msgType === 'file' || msgType === 'image') {
|
|
408
|
-
// File/image in group message
|
|
403
|
+
// File/image in group message — save to userfiles and notify agent
|
|
409
404
|
let fileInfo = {};
|
|
410
405
|
try {
|
|
411
406
|
fileInfo = typeof content === 'string' ? JSON.parse(content) : content;
|
|
412
407
|
} catch (e) {}
|
|
413
408
|
|
|
414
409
|
if (fileInfo.file_info) {
|
|
415
|
-
|
|
416
|
-
agent_id: agentId,
|
|
417
|
-
target_id: groupId,
|
|
418
|
-
from_id: fromId,
|
|
419
|
-
to_id: groupId,
|
|
420
|
-
type: msgType,
|
|
421
|
-
content: fileInfo.content || (fileInfo.file_info.isImage ? '[图片]' : `[文件] ${fileInfo.file_info.fileName}`),
|
|
422
|
-
file_url: fileInfo.file_url || '',
|
|
423
|
-
file_name: fileInfo.file_info.fileName || '',
|
|
424
|
-
is_group: 1,
|
|
425
|
-
status: 'delivered',
|
|
426
|
-
});
|
|
427
|
-
if (this._onNewMessage) this._onNewMessage(msg);
|
|
410
|
+
this._saveToUserfilesAndNotify(agentId, groupId, fileInfo.file_info, fileInfo, { isGroup: true, fromId });
|
|
428
411
|
return;
|
|
429
412
|
}
|
|
430
413
|
}
|
|
@@ -532,8 +515,18 @@ class ChatManager {
|
|
|
532
515
|
const isImage = meta.isImage || this._isImageExt(ext);
|
|
533
516
|
const msgType = isImage ? 'image' : 'file';
|
|
534
517
|
|
|
535
|
-
// Save
|
|
518
|
+
// Save the assembled file to userfiles/ and notify the agent
|
|
536
519
|
if (agentId) {
|
|
520
|
+
// Move from uploads/ to userfiles/ for agent access
|
|
521
|
+
const userfilesPath = path.join(this.userfilesDir, localFileName);
|
|
522
|
+
try {
|
|
523
|
+
// Copy to userfiles (keep original in uploads for HTTP serving)
|
|
524
|
+
fs.copyFileSync(localPath, userfilesPath);
|
|
525
|
+
} catch (e) {
|
|
526
|
+
console.warn(`[Chat] Could not copy to userfiles: ${e.message}`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Save message to chat history
|
|
537
530
|
const msg = this.db.saveMessage({
|
|
538
531
|
agent_id: agentId,
|
|
539
532
|
target_id: fromId || '',
|
|
@@ -546,14 +539,45 @@ class ChatManager {
|
|
|
546
539
|
is_group: 0,
|
|
547
540
|
status: 'delivered',
|
|
548
541
|
});
|
|
549
|
-
|
|
542
|
+
|
|
543
|
+
// Notify agent with file path info
|
|
544
|
+
if (this._onNewMessage) {
|
|
545
|
+
this._notifyAgentAboutFile(agentId, fromId || '', meta.fileName, userfilesPath, msgType, isImage, { isGroup: false });
|
|
546
|
+
this._onNewMessage(msg);
|
|
547
|
+
}
|
|
550
548
|
}
|
|
551
549
|
|
|
552
|
-
console.log(`[Chat] File assembled: ${meta.fileName} (${fileBuffer.length} bytes)`);
|
|
550
|
+
console.log(`[Chat] File assembled and saved to userfiles: ${meta.fileName} (${fileBuffer.length} bytes)`);
|
|
553
551
|
} catch (e) {
|
|
554
552
|
console.error(`[Chat] File assembly failed for ${fileId}:`, e.message);
|
|
555
553
|
} finally {
|
|
556
554
|
this._incomingFiles.delete(fileId);
|
|
555
|
+
|
|
556
|
+
// Check if there's a pending notification for this file
|
|
557
|
+
if (this._pendingFileNotifications && this._pendingFileNotifications.has(fileId)) {
|
|
558
|
+
const pending = this._pendingFileNotifications.get(fileId);
|
|
559
|
+
this._pendingFileNotifications.delete(fileId);
|
|
560
|
+
|
|
561
|
+
// The file is now assembled in uploads/ — copy to userfiles/
|
|
562
|
+
const ext = this._extFromMime(meta?.mimeType) || path.extname(meta?.fileName || '') || '.bin';
|
|
563
|
+
const localFileName = `${fileId}${ext}`;
|
|
564
|
+
const localPath = path.join(this.uploadsDir, localFileName);
|
|
565
|
+
const userfilesPath = path.join(this.userfilesDir, pending.safeName);
|
|
566
|
+
|
|
567
|
+
if (fs.existsSync(localPath)) {
|
|
568
|
+
try {
|
|
569
|
+
fs.copyFileSync(localPath, userfilesPath);
|
|
570
|
+
this._notifyAgentAboutFile(
|
|
571
|
+
pending.agentId, pending.fromId, pending.originalName,
|
|
572
|
+
userfilesPath, pending.msgType, pending.isImage,
|
|
573
|
+
{ isGroup: pending.isGroup }
|
|
574
|
+
);
|
|
575
|
+
console.log(`[Chat] Pending notification sent for assembled file: ${pending.originalName}`);
|
|
576
|
+
} catch (e2) {
|
|
577
|
+
console.warn(`[Chat] Failed to copy assembled file to userfiles: ${e2.message}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
557
581
|
}
|
|
558
582
|
}
|
|
559
583
|
|
|
@@ -710,6 +734,204 @@ class ChatManager {
|
|
|
710
734
|
return msg;
|
|
711
735
|
}
|
|
712
736
|
|
|
737
|
+
// ─── Userfiles: save received file and notify agent ─────────────
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Save a received file to userfiles/ directory and notify the AI agent
|
|
741
|
+
* that a file was uploaded. This creates a simulated user message
|
|
742
|
+
* telling the agent about the file with its full path.
|
|
743
|
+
*
|
|
744
|
+
* @param {string} agentId - The local agent ID
|
|
745
|
+
* @param {string} chatId - The chat/session ID (friend or group)
|
|
746
|
+
* @param {object} fileInfo - File metadata { fileName, fileSize, mimeType, isImage }
|
|
747
|
+
* @param {object} rawData - The raw message data (may contain base64 content)
|
|
748
|
+
* @param {object} opts - { isGroup, fromId }
|
|
749
|
+
*/
|
|
750
|
+
_saveToUserfilesAndNotify(agentId, chatId, fileInfo, rawData, opts = {}) {
|
|
751
|
+
const { isGroup = false, fromId = chatId } = opts;
|
|
752
|
+
const originalName = fileInfo.fileName || 'unknown';
|
|
753
|
+
const isImage = fileInfo.isImage || this._isImageExt(path.extname(originalName));
|
|
754
|
+
const msgType = isImage ? 'image' : 'file';
|
|
755
|
+
|
|
756
|
+
// Generate a safe filename: timestamp_originalname to avoid collisions
|
|
757
|
+
const safeName = `${Date.now()}_${originalName.replace(/[^a-zA-Z0-9._-]/g, '_')}`;
|
|
758
|
+
const userfilesPath = path.join(this.userfilesDir, safeName);
|
|
759
|
+
|
|
760
|
+
// Try to extract file data from the message
|
|
761
|
+
let saved = false;
|
|
762
|
+
|
|
763
|
+
// Case 1: file_info message may include base64 data in rawData.data
|
|
764
|
+
if (rawData.data && typeof rawData.data === 'string') {
|
|
765
|
+
try {
|
|
766
|
+
const buffer = Buffer.from(rawData.data, 'base64');
|
|
767
|
+
fs.writeFileSync(userfilesPath, buffer);
|
|
768
|
+
saved = true;
|
|
769
|
+
console.log(`[Chat] File saved to userfiles from base64 data: ${safeName} (${buffer.length} bytes)`);
|
|
770
|
+
} catch (e) {
|
|
771
|
+
console.warn(`[Chat] Failed to save file from base64 data: ${e.message}`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Case 2: file_url might point to a local file in uploads/
|
|
776
|
+
if (!saved && rawData.file_url) {
|
|
777
|
+
const localFileName = path.basename(rawData.file_url);
|
|
778
|
+
const localPath = path.join(this.uploadsDir, localFileName);
|
|
779
|
+
if (fs.existsSync(localPath)) {
|
|
780
|
+
try {
|
|
781
|
+
fs.copyFileSync(localPath, userfilesPath);
|
|
782
|
+
saved = true;
|
|
783
|
+
console.log(`[Chat] File copied from uploads to userfiles: ${safeName}`);
|
|
784
|
+
} catch (e) {
|
|
785
|
+
console.warn(`[Chat] Failed to copy file from uploads: ${e.message}`);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Case 3: If we have file_info with chunks, the file may still be
|
|
791
|
+
// downloading via file_chunk messages. In that case, we set up a
|
|
792
|
+
// pending notification that will be sent when _assembleFile completes.
|
|
793
|
+
if (!saved && fileInfo.fileId) {
|
|
794
|
+
// Store the notification info for when the file is fully assembled
|
|
795
|
+
if (!this._pendingFileNotifications) {
|
|
796
|
+
this._pendingFileNotifications = new Map();
|
|
797
|
+
}
|
|
798
|
+
this._pendingFileNotifications.set(fileInfo.fileId, {
|
|
799
|
+
agentId, chatId, fromId, originalName, msgType, isImage, isGroup, safeName,
|
|
800
|
+
});
|
|
801
|
+
console.log(`[Chat] File ${originalName} pending assembly (fileId=${fileInfo.fileId}), notification queued`);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Save message to chat history
|
|
805
|
+
const msg = this.db.saveMessage({
|
|
806
|
+
agent_id: agentId,
|
|
807
|
+
target_id: chatId,
|
|
808
|
+
from_id: fromId,
|
|
809
|
+
to_id: agentId,
|
|
810
|
+
type: msgType,
|
|
811
|
+
content: isImage ? `[图片] ${originalName}` : `[文件] ${originalName}`,
|
|
812
|
+
file_url: rawData.file_url || `/userfiles/${safeName}`,
|
|
813
|
+
file_name: originalName,
|
|
814
|
+
is_group: isGroup ? 1 : 0,
|
|
815
|
+
status: saved ? 'delivered' : 'pending',
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
// Notify the agent about the file
|
|
819
|
+
if (saved) {
|
|
820
|
+
this._notifyAgentAboutFile(agentId, fromId, originalName, userfilesPath, msgType, isImage, opts);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (this._onNewMessage) this._onNewMessage(msg);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Notify the AI agent about a received file by sending a simulated
|
|
828
|
+
* user message that describes the file and its location.
|
|
829
|
+
*
|
|
830
|
+
* @param {string} agentId - The local agent ID
|
|
831
|
+
* @param {string} fromId - The sender ID
|
|
832
|
+
* @param {string} fileName - Original file name
|
|
833
|
+
* @param {string} filePath - Absolute path to the saved file in userfiles/
|
|
834
|
+
* @param {string} msgType - 'image' or 'file'
|
|
835
|
+
* @param {boolean} isImage - Whether this is an image
|
|
836
|
+
* @param {object} opts - { isGroup, caption }
|
|
837
|
+
*/
|
|
838
|
+
_notifyAgentAboutFile(agentId, fromId, fileName, filePath, msgType, isImage, opts = {}) {
|
|
839
|
+
const { isGroup = false, caption = '' } = opts;
|
|
840
|
+
const fileSize = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0;
|
|
841
|
+
const mimeType = this._getMimeType(fileName);
|
|
842
|
+
|
|
843
|
+
// Build a descriptive message for the AI agent
|
|
844
|
+
let agentMessage;
|
|
845
|
+
if (isImage) {
|
|
846
|
+
agentMessage = [
|
|
847
|
+
`[用户上传了图片]`,
|
|
848
|
+
`文件名: ${fileName}`,
|
|
849
|
+
`文件路径: ${filePath}`,
|
|
850
|
+
`文件大小: ${this._formatFileSize(fileSize)}`,
|
|
851
|
+
`文件类型: ${mimeType}`,
|
|
852
|
+
caption ? `说明: ${caption}` : '',
|
|
853
|
+
`请查看并处理这张图片。`,
|
|
854
|
+
].filter(Boolean).join('\n');
|
|
855
|
+
} else {
|
|
856
|
+
agentMessage = [
|
|
857
|
+
`[用户上传了文件]`,
|
|
858
|
+
`文件名: ${fileName}`,
|
|
859
|
+
`文件路径: ${filePath}`,
|
|
860
|
+
`文件大小: ${this._formatFileSize(fileSize)}`,
|
|
861
|
+
`文件类型: ${mimeType}`,
|
|
862
|
+
caption ? `说明: ${caption}` : '',
|
|
863
|
+
`请读取并处理这个文件。`,
|
|
864
|
+
].filter(Boolean).join('\n');
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Save this as a text message in chat history so the agent sees it
|
|
868
|
+
const msg = this.db.saveMessage({
|
|
869
|
+
agent_id: agentId,
|
|
870
|
+
target_id: fromId,
|
|
871
|
+
from_id: fromId,
|
|
872
|
+
to_id: agentId,
|
|
873
|
+
type: 'text',
|
|
874
|
+
content: agentMessage,
|
|
875
|
+
file_url: filePath,
|
|
876
|
+
file_name: fileName,
|
|
877
|
+
is_group: isGroup ? 1 : 0,
|
|
878
|
+
status: 'delivered',
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
// Trigger the onNewMessage callback so the channel.js inbound handler
|
|
882
|
+
// picks it up and dispatches it to the AI agent
|
|
883
|
+
if (this._onNewMessage) {
|
|
884
|
+
this._onNewMessage(msg);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
console.log(`[Chat] Agent notification sent: ${msgType} ${fileName} at ${filePath}`);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Format file size in human-readable format.
|
|
892
|
+
*/
|
|
893
|
+
_formatFileSize(bytes) {
|
|
894
|
+
if (bytes === 0) return '0 B';
|
|
895
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
896
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
897
|
+
return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + units[i];
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* List all files in the userfiles directory.
|
|
902
|
+
* @returns {Array} Array of file info objects
|
|
903
|
+
*/
|
|
904
|
+
listUserfiles() {
|
|
905
|
+
if (!fs.existsSync(this.userfilesDir)) return [];
|
|
906
|
+
return fs.readdirSync(this.userfilesDir)
|
|
907
|
+
.filter(name => !name.startsWith('.'))
|
|
908
|
+
.map(name => {
|
|
909
|
+
const fullPath = path.join(this.userfilesDir, name);
|
|
910
|
+
try {
|
|
911
|
+
const stat = fs.statSync(fullPath);
|
|
912
|
+
return {
|
|
913
|
+
name,
|
|
914
|
+
path: fullPath,
|
|
915
|
+
size: stat.size,
|
|
916
|
+
mimeType: this._getMimeType(name),
|
|
917
|
+
isImage: this._isImageExt(path.extname(name)),
|
|
918
|
+
modifiedAt: stat.mtime.toISOString(),
|
|
919
|
+
};
|
|
920
|
+
} catch (e) {
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
})
|
|
924
|
+
.filter(Boolean);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Get the userfiles directory path.
|
|
929
|
+
* @returns {string} Absolute path to userfiles directory
|
|
930
|
+
*/
|
|
931
|
+
getUserfilesDir() {
|
|
932
|
+
return this.userfilesDir;
|
|
933
|
+
}
|
|
934
|
+
|
|
713
935
|
// ─── Helpers ────────────────────────────────────────────────────
|
|
714
936
|
|
|
715
937
|
_isImageExt(ext) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aicq-chat-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"description": "AICQ End-to-end Encrypted Chat Channel Plugin for OpenClaw — In-process Channel SDK architecture with friend management, group chat, file transfer, and AI agent communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/src/channel.js
CHANGED
|
@@ -186,6 +186,8 @@ const _plugin = createChatChannelPlugin({
|
|
|
186
186
|
"aicq.chat.sendFile",
|
|
187
187
|
"aicq.chat.sendImage",
|
|
188
188
|
"aicq.chat.sendFileFromBase64",
|
|
189
|
+
"aicq.userfiles.list",
|
|
190
|
+
"aicq.userfiles.getPath",
|
|
189
191
|
"aicq.groups.list",
|
|
190
192
|
"aicq.groups.create",
|
|
191
193
|
"aicq.groups.join",
|
|
@@ -362,14 +364,36 @@ _plugin.gateway = {
|
|
|
362
364
|
const data = msg.data || msg;
|
|
363
365
|
const fromId = data.from || data.fromId || data.sender_id || msg.from || msg.fromId;
|
|
364
366
|
const isGroup = !!(data.isGroup || data.groupId || msg.isGroup || msg.groupId);
|
|
365
|
-
const text = data.content || data.text || data.payload || msg.content || msg.text || msg.payload || "";
|
|
366
367
|
|
|
367
|
-
|
|
368
|
+
// Detect file/image messages
|
|
369
|
+
const msgType = data.msgType || data.msg_type || (data.data && data.data.file_info ? (data.data.file_info.isImage ? 'image' : 'file') : 'text');
|
|
370
|
+
const isFileMessage = msgType === 'file' || msgType === 'image';
|
|
368
371
|
|
|
369
|
-
|
|
372
|
+
// For file/image messages, chat.js already handles saving to userfiles/
|
|
373
|
+
// and creating a notification message via _notifyAgentAboutFile().
|
|
374
|
+
// That notification triggers _onNewMessage which calls this inboundHandler
|
|
375
|
+
// again with the text message. So we just need to handle text here.
|
|
376
|
+
let text = data.content || data.text || data.payload || msg.content || msg.text || msg.payload || "";
|
|
377
|
+
|
|
378
|
+
console.log(`[AICQ Channel] Inbound message from=${fromId} isGroup=${isGroup} type=${msgType} text=${(text || "").substring(0, 80)}`);
|
|
379
|
+
|
|
380
|
+
if (!fromId) {
|
|
370
381
|
return; // Skip system messages (online_ack, presence, etc.)
|
|
371
382
|
}
|
|
372
383
|
|
|
384
|
+
// For file/image messages from the WS, chat.js _handleIncoming/_handleGroupIncoming
|
|
385
|
+
// will handle the userfiles saving and create a notification text message.
|
|
386
|
+
// The notification will trigger _onNewMessage which calls this handler again
|
|
387
|
+
// as a text message. So we skip dispatching file messages directly to the agent.
|
|
388
|
+
if (isFileMessage && !(data.file_url && data.type === 'text')) {
|
|
389
|
+
console.log(`[AICQ Channel] File/image message received, chat.js will handle userfiles saving and notify agent`);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!text) {
|
|
394
|
+
return; // Skip empty messages
|
|
395
|
+
}
|
|
396
|
+
|
|
373
397
|
// Skip our own messages
|
|
374
398
|
if (fromId === runtime.serverClient?.serverAccountId || fromId === agentId) {
|
|
375
399
|
return;
|