acp-ts 1.2.5 → 1.2.6
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/agentcp.d.ts +13 -0
- package/dist/agentcp.js +29 -4
- package/dist/agentmd.d.ts +1 -1
- package/dist/agentmd.js +2 -2
- package/dist/server.js +190 -45
- package/package.json +1 -1
package/dist/agentcp.d.ts
CHANGED
|
@@ -177,6 +177,19 @@ declare class AgentCP implements IAgentCP {
|
|
|
177
177
|
* @param limit 每次拉取数量上限,0 表示使用服务端默认值
|
|
178
178
|
*/
|
|
179
179
|
pullAndStoreGroupMessages(groupId: string, afterMsgId?: number, limit?: number): Promise<GroupMessage[]>;
|
|
180
|
+
/**
|
|
181
|
+
* 将群组加入在线列表(不发送网络请求)。
|
|
182
|
+
*/
|
|
183
|
+
addOnlineGroup(groupId: string): void;
|
|
184
|
+
/**
|
|
185
|
+
* 确保群组心跳定时器已启动(公开方法)。
|
|
186
|
+
*/
|
|
187
|
+
ensureGroupHeartbeat(): void;
|
|
188
|
+
/**
|
|
189
|
+
* 向 group.ap 发送一次 register_online,告知当前客户端在线。
|
|
190
|
+
* 不带群组 ID,只需在启动或重连时调用一次。
|
|
191
|
+
*/
|
|
192
|
+
groupRegisterOnline(): Promise<void>;
|
|
180
193
|
/**
|
|
181
194
|
* 加入群组会话(完整生命周期):
|
|
182
195
|
* 1. register_online → 告知 group.ap 在线
|
package/dist/agentcp.js
CHANGED
|
@@ -701,6 +701,29 @@ class AgentCP {
|
|
|
701
701
|
// ============================================================
|
|
702
702
|
// Group Session Lifecycle (register_online / pull / heartbeat / unregister)
|
|
703
703
|
// ============================================================
|
|
704
|
+
/**
|
|
705
|
+
* 将群组加入在线列表(不发送网络请求)。
|
|
706
|
+
*/
|
|
707
|
+
addOnlineGroup(groupId) {
|
|
708
|
+
this._onlineGroups.add(groupId);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* 确保群组心跳定时器已启动(公开方法)。
|
|
712
|
+
*/
|
|
713
|
+
ensureGroupHeartbeat() {
|
|
714
|
+
this._ensureHeartbeat();
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* 向 group.ap 发送一次 register_online,告知当前客户端在线。
|
|
718
|
+
* 不带群组 ID,只需在启动或重连时调用一次。
|
|
719
|
+
*/
|
|
720
|
+
async groupRegisterOnline() {
|
|
721
|
+
if (!this.groupOps || !this._groupTargetAid) {
|
|
722
|
+
throw new Error('群组客户端未初始化,请先调用 initGroupClient');
|
|
723
|
+
}
|
|
724
|
+
await this.groupOps.registerOnline(this._groupTargetAid);
|
|
725
|
+
utils_1.logger.log(`[Group] registerOnline: 已通知 group.ap 在线`);
|
|
726
|
+
}
|
|
704
727
|
/**
|
|
705
728
|
* 加入群组会话(完整生命周期):
|
|
706
729
|
* 1. register_online → 告知 group.ap 在线
|
|
@@ -711,11 +734,13 @@ class AgentCP {
|
|
|
711
734
|
if (!this.groupOps || !this._groupTargetAid) {
|
|
712
735
|
throw new Error('群组客户端未初始化,请先调用 initGroupClient');
|
|
713
736
|
}
|
|
714
|
-
//
|
|
715
|
-
|
|
737
|
+
// 如果还没有在线群组,说明是首次加入,需要先 registerOnline
|
|
738
|
+
if (this._onlineGroups.size === 0) {
|
|
739
|
+
await this.groupOps.registerOnline(this._groupTargetAid);
|
|
740
|
+
}
|
|
716
741
|
this._onlineGroups.add(groupId);
|
|
717
742
|
utils_1.logger.log(`[Group] joinGroupSession: group=${groupId}`);
|
|
718
|
-
//
|
|
743
|
+
// 冷启动同步 — 拉取历史消息对齐,再进入批推送接收
|
|
719
744
|
try {
|
|
720
745
|
const lastMsgId = this.getGroupLastMsgId(groupId);
|
|
721
746
|
await this.pullAndStoreGroupMessages(groupId, lastMsgId, 50);
|
|
@@ -723,7 +748,7 @@ class AgentCP {
|
|
|
723
748
|
catch (e) {
|
|
724
749
|
utils_1.logger.warn(`[Group] cold-start sync failed: group=${groupId}`, e.message || e);
|
|
725
750
|
}
|
|
726
|
-
//
|
|
751
|
+
// 启动心跳定时器(首次加入群组时启动)
|
|
727
752
|
this._ensureHeartbeat();
|
|
728
753
|
}
|
|
729
754
|
/**
|
package/dist/agentmd.d.ts
CHANGED
package/dist/agentmd.js
CHANGED
|
@@ -7,10 +7,10 @@ exports.extractDisplayName = extractDisplayName;
|
|
|
7
7
|
exports.generateAgentMd = generateAgentMd;
|
|
8
8
|
/**
|
|
9
9
|
* 从 AID 中提取显示名称
|
|
10
|
-
* 例如: "alice.
|
|
10
|
+
* 例如: "alice.agentcp.io" -> "alice"
|
|
11
11
|
*/
|
|
12
12
|
function extractDisplayName(aid) {
|
|
13
|
-
const suffixes = ['.agentcp.io', '.
|
|
13
|
+
const suffixes = ['.agentcp.io', '.agentid.pub'];
|
|
14
14
|
for (const suffix of suffixes) {
|
|
15
15
|
if (aid.endsWith(suffix)) {
|
|
16
16
|
return aid.slice(0, -suffix.length);
|
package/dist/server.js
CHANGED
|
@@ -151,25 +151,27 @@ async function doEnsureOnline(aid) {
|
|
|
151
151
|
instance.agentWS.acceptInviteFromHeartbeat(invite.sessionId, invite.inviterAgentId, invite.inviteCode);
|
|
152
152
|
}
|
|
153
153
|
});
|
|
154
|
-
// 心跳重连成功后,自动触发 WebSocket 重连 +
|
|
154
|
+
// 心跳重连成功后,自动触发 WebSocket 重连 + 重新注册上线
|
|
155
155
|
hb.onReconnect(() => {
|
|
156
156
|
if (instance.agentWS) {
|
|
157
157
|
utils_1.logger.log('[Server] 心跳重连成功,触发 WebSocket 重连...');
|
|
158
158
|
instance.agentWS.reconnect().then(async () => {
|
|
159
|
-
// WebSocket 重连成功后,重新注册所有在线群组
|
|
160
|
-
// 断线期间 group.ap 会将在线状态过期,必须重新 register_online 才能收到推送
|
|
161
159
|
const onlineGroups = instance.agentCP.getOnlineGroups();
|
|
162
160
|
if (onlineGroups.length > 0) {
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
await instance.agentCP.groupRegisterOnline();
|
|
162
|
+
utils_1.logger.log(`[Server] WebSocket 重连成功,已重新注册上线,在线群组: ${onlineGroups.length}`);
|
|
163
|
+
// 后台异步拉取断线期间可能漏掉的消息
|
|
164
|
+
Promise.all(onlineGroups.map(async (groupId) => {
|
|
165
165
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
const lastMsgId = instance.agentCP.getGroupLastMsgId(groupId);
|
|
167
|
+
await instance.agentCP.pullAndStoreGroupMessages(groupId, lastMsgId, 50);
|
|
168
168
|
}
|
|
169
169
|
catch (e) {
|
|
170
|
-
utils_1.logger.warn(`[Server]
|
|
170
|
+
utils_1.logger.warn(`[Server] reconnect sync failed: ${groupId}`, e.message || e);
|
|
171
171
|
}
|
|
172
|
-
}
|
|
172
|
+
})).then(() => {
|
|
173
|
+
utils_1.logger.log(`[Server] 重连后台消息同步完成`);
|
|
174
|
+
});
|
|
173
175
|
}
|
|
174
176
|
}).catch((err) => {
|
|
175
177
|
utils_1.logger.error('[Server] WebSocket 重连失败:', err);
|
|
@@ -184,19 +186,22 @@ async function doEnsureOnline(aid) {
|
|
|
184
186
|
instance.connectionConfig = newConnConfig;
|
|
185
187
|
utils_1.logger.log('[Server] 重新鉴权成功,使用新 signature 重连 WebSocket...');
|
|
186
188
|
await instance.agentWS.reconnect(newConnConfig.messageServer, newConnConfig.messageSignature);
|
|
187
|
-
// 重连成功后重新注册所有在线群组
|
|
188
189
|
const onlineGroups = instance.agentCP.getOnlineGroups();
|
|
189
190
|
if (onlineGroups.length > 0) {
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
await instance.agentCP.groupRegisterOnline();
|
|
192
|
+
utils_1.logger.log(`[Server] 重新鉴权重连成功,已重新注册上线,在线群组: ${onlineGroups.length}`);
|
|
193
|
+
// 后台异步拉取断线期间可能漏掉的消息
|
|
194
|
+
Promise.all(onlineGroups.map(async (groupId) => {
|
|
192
195
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
const lastMsgId = instance.agentCP.getGroupLastMsgId(groupId);
|
|
197
|
+
await instance.agentCP.pullAndStoreGroupMessages(groupId, lastMsgId, 50);
|
|
195
198
|
}
|
|
196
199
|
catch (e) {
|
|
197
|
-
utils_1.logger.warn(`[Server]
|
|
200
|
+
utils_1.logger.warn(`[Server] reconnect sync failed: ${groupId}`, e.message || e);
|
|
198
201
|
}
|
|
199
|
-
}
|
|
202
|
+
})).then(() => {
|
|
203
|
+
utils_1.logger.log(`[Server] 重连后台消息同步完成`);
|
|
204
|
+
});
|
|
200
205
|
}
|
|
201
206
|
}
|
|
202
207
|
catch (err) {
|
|
@@ -284,12 +289,33 @@ async function doEnsureOnline(aid) {
|
|
|
284
289
|
return instance;
|
|
285
290
|
}
|
|
286
291
|
// 确保群组客户端已初始化
|
|
292
|
+
// 群组客户端初始化锁,防止并发请求重复初始化
|
|
293
|
+
const groupInitPromises = new Map();
|
|
287
294
|
async function ensureGroupClient(instance) {
|
|
288
295
|
if (instance.groupInitialized && instance.agentCP.groupClient)
|
|
289
296
|
return;
|
|
290
297
|
if (!instance.agentWS)
|
|
291
298
|
throw new Error('WebSocket 未连接');
|
|
292
299
|
const aid = instance.aid;
|
|
300
|
+
// 如果已有初始化在进行中,等待它完成
|
|
301
|
+
const existing = groupInitPromises.get(aid);
|
|
302
|
+
if (existing) {
|
|
303
|
+
await existing;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const initPromise = doInitGroupClient(instance);
|
|
307
|
+
groupInitPromises.set(aid, initPromise);
|
|
308
|
+
try {
|
|
309
|
+
await initPromise;
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
groupInitPromises.delete(aid);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async function doInitGroupClient(instance) {
|
|
316
|
+
if (!instance.agentWS)
|
|
317
|
+
throw new Error('WebSocket 未连接');
|
|
318
|
+
const aid = instance.aid;
|
|
293
319
|
// 计算 group target AID: group.{issuer}
|
|
294
320
|
const parts = aid.split('.', 1);
|
|
295
321
|
const issuer = aid.substring(parts[0].length + 1) || aid;
|
|
@@ -432,15 +458,27 @@ async function ensureGroupClient(instance) {
|
|
|
432
458
|
utils_1.logger.warn('[Group] syncGroupList error:', e.message);
|
|
433
459
|
}
|
|
434
460
|
}
|
|
435
|
-
//
|
|
461
|
+
// 注册上线 + 将群组加入在线列表(不阻塞)
|
|
436
462
|
const groups = instance.agentCP.getLocalGroupList();
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
463
|
+
if (groups.length > 0) {
|
|
464
|
+
await instance.agentCP.groupRegisterOnline();
|
|
465
|
+
for (const group of groups) {
|
|
466
|
+
instance.agentCP.addOnlineGroup(group.group_id);
|
|
467
|
+
}
|
|
468
|
+
instance.agentCP.ensureGroupHeartbeat();
|
|
469
|
+
utils_1.logger.log(`[Group] 已注册上线,在线群组: ${groups.length}`);
|
|
470
|
+
// 后台异步拉取历史消息,不阻塞启动流程
|
|
471
|
+
Promise.all(groups.map(async (group) => {
|
|
472
|
+
try {
|
|
473
|
+
const lastMsgId = instance.agentCP.getGroupLastMsgId(group.group_id);
|
|
474
|
+
await instance.agentCP.pullAndStoreGroupMessages(group.group_id, lastMsgId, 50);
|
|
475
|
+
}
|
|
476
|
+
catch (e) {
|
|
477
|
+
utils_1.logger.warn(`[Group] background sync failed: ${group.group_id}`, e.message);
|
|
478
|
+
}
|
|
479
|
+
})).then(() => {
|
|
480
|
+
utils_1.logger.log(`[Group] 后台消息同步完成,共 ${groups.length} 个群组`);
|
|
481
|
+
});
|
|
444
482
|
}
|
|
445
483
|
instance.groupInitialized = true;
|
|
446
484
|
instance.groupSessionId = groupSessionId;
|
|
@@ -769,7 +807,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
769
807
|
function updateApSelect() {
|
|
770
808
|
var sel = document.getElementById('apSelect');
|
|
771
809
|
if (sel && sel.options.length === 0) {
|
|
772
|
-
const options = ['agentcp.io', '
|
|
810
|
+
const options = ['agentcp.io', 'agentid.pub'];
|
|
773
811
|
options.forEach(function(op) {
|
|
774
812
|
var opt = document.createElement('option');
|
|
775
813
|
opt.value = op;
|
|
@@ -870,6 +908,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
870
908
|
var data = await res.json();
|
|
871
909
|
if (data.success) {
|
|
872
910
|
showStatus(aid + ' 已上线,正在进入聊天...', 'success');
|
|
911
|
+
sessionStorage.setItem('chatEntry','1');
|
|
873
912
|
window.location.href = '/chat';
|
|
874
913
|
} else {
|
|
875
914
|
showStatus(data.error || '上线失败', 'error');
|
|
@@ -881,7 +920,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
881
920
|
}
|
|
882
921
|
}
|
|
883
922
|
|
|
884
|
-
function enterChat(aid) { window.location.href = '/chat'; }
|
|
923
|
+
function enterChat(aid) { sessionStorage.setItem('chatEntry','1'); window.location.href = '/chat'; }
|
|
885
924
|
|
|
886
925
|
async function goOffline(aid) {
|
|
887
926
|
try {
|
|
@@ -1194,6 +1233,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1194
1233
|
<div class="group-info-bar" id="groupInfoBar" style="display:none;">
|
|
1195
1234
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
|
1196
1235
|
<span id="groupInfoText">群组</span>
|
|
1236
|
+
<span id="groupMemberStats" style="font-size:11px;color:var(--t2);margin-left:4px;"></span>
|
|
1197
1237
|
<span class="copy-link" id="groupInviteBtn" onclick="generateInviteLink()" title="生成邀请链接" style="display:none;">生成邀请链接</span>
|
|
1198
1238
|
<span class="copy-link" id="groupCopyLinkBtn" onclick="copyGroupLink()" title="复制群链接" style="display:none;">复制群链接</span>
|
|
1199
1239
|
<span class="copy-link" onclick="showGroupMembers()" title="查看成员">成员</span>
|
|
@@ -1384,14 +1424,39 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1384
1424
|
if (type === 'human') return '/assets/human.png';
|
|
1385
1425
|
return '/assets/agent.png';
|
|
1386
1426
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1427
|
+
// 批量 agent info 请求:攒批 50ms 后合并发送一次
|
|
1428
|
+
var _agentInfoBatchQueue=[];
|
|
1429
|
+
var _agentInfoBatchTimer=null;
|
|
1430
|
+
function _flushAgentInfoBatch(){
|
|
1431
|
+
_agentInfoBatchTimer=null;
|
|
1432
|
+
var queue=_agentInfoBatchQueue;
|
|
1433
|
+
_agentInfoBatchQueue=[];
|
|
1434
|
+
var aids=queue.map(function(q){ return q.aid; });
|
|
1435
|
+
fetch('/api/agent-info-batch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({aids:aids})})
|
|
1436
|
+
.then(function(r){ return r.json(); })
|
|
1437
|
+
.then(function(d){
|
|
1438
|
+
if(d.success&&d.data){
|
|
1439
|
+
queue.forEach(function(q){
|
|
1440
|
+
var info=d.data[q.aid]||{type:'',name:'',description:'',tags:[]};
|
|
1441
|
+
if(info.type||info.name) agentInfoCache[q.aid]=info;
|
|
1442
|
+
q.resolve(info);
|
|
1443
|
+
});
|
|
1444
|
+
} else {
|
|
1445
|
+
var empty={type:'',name:'',description:'',tags:[]};
|
|
1446
|
+
queue.forEach(function(q){ q.resolve(empty); });
|
|
1447
|
+
}
|
|
1448
|
+
})
|
|
1449
|
+
.catch(function(){
|
|
1450
|
+
var empty={type:'',name:'',description:'',tags:[]};
|
|
1451
|
+
queue.forEach(function(q){ q.resolve(empty); });
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
function fetchAgentInfo(aid){
|
|
1455
|
+
if(agentInfoCache[aid]) return Promise.resolve(agentInfoCache[aid]);
|
|
1456
|
+
return new Promise(function(resolve){
|
|
1457
|
+
_agentInfoBatchQueue.push({aid:aid,resolve:resolve});
|
|
1458
|
+
if(!_agentInfoBatchTimer) _agentInfoBatchTimer=setTimeout(_flushAgentInfoBatch,50);
|
|
1459
|
+
});
|
|
1395
1460
|
}
|
|
1396
1461
|
async function deleteSession(e, sessionId){
|
|
1397
1462
|
e.stopPropagation();
|
|
@@ -1429,6 +1494,9 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1429
1494
|
function hideNewMsgTip(){ if(D.newMsgTip) D.newMsgTip.style.display='none'; }
|
|
1430
1495
|
|
|
1431
1496
|
async function init(){
|
|
1497
|
+
// 刷新页面时强制回到身份管理页面
|
|
1498
|
+
if(!sessionStorage.getItem('chatEntry')){ window.location.href='/'; return; }
|
|
1499
|
+
sessionStorage.removeItem('chatEntry');
|
|
1432
1500
|
initDom();
|
|
1433
1501
|
// 配置 marked:支持换行、GFM
|
|
1434
1502
|
if(typeof marked!=='undefined'&&marked.setOptions){
|
|
@@ -1994,6 +2062,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1994
2062
|
D.title.textContent=name;
|
|
1995
2063
|
D.groupInfoBar.style.display='flex';
|
|
1996
2064
|
D.groupInfoText.textContent=name;
|
|
2065
|
+
$('groupMemberStats').textContent='';
|
|
1997
2066
|
D.input.disabled=false;
|
|
1998
2067
|
D.input.placeholder='输入群消息...';
|
|
1999
2068
|
D.input.focus();
|
|
@@ -2009,7 +2078,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
2009
2078
|
gmsg.textContent='选择群组...';
|
|
2010
2079
|
await fetch('/api/group/select',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({groupId:groupId,aid:S.aid})});
|
|
2011
2080
|
} catch(e){}
|
|
2012
|
-
//
|
|
2081
|
+
// 获取群信息判断是否为创建者,同时获取成员统计
|
|
2013
2082
|
try {
|
|
2014
2083
|
gmsg.textContent='获取群信息...';
|
|
2015
2084
|
var r=await fetch('/api/group/info?groupId='+encodeURIComponent(groupId)+'&aid='+encodeURIComponent(S.aid));
|
|
@@ -2022,6 +2091,14 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
2022
2091
|
} else {
|
|
2023
2092
|
$('groupCopyLinkBtn').style.display='';
|
|
2024
2093
|
}
|
|
2094
|
+
// 展示成员统计
|
|
2095
|
+
if(d.member_stats){
|
|
2096
|
+
var ms=d.member_stats;
|
|
2097
|
+
var parts=[ms.total+'人'];
|
|
2098
|
+
if(ms.human) parts.push(ms.human+'人类');
|
|
2099
|
+
if(ms.agent) parts.push(ms.agent+'Agent');
|
|
2100
|
+
$('groupMemberStats').textContent='('+parts.join(' / ')+')';
|
|
2101
|
+
}
|
|
2025
2102
|
} catch(e){
|
|
2026
2103
|
// 获取失败时默认显示复制群链接
|
|
2027
2104
|
$('groupCopyLinkBtn').style.display='';
|
|
@@ -2234,16 +2311,33 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
2234
2311
|
} else {
|
|
2235
2312
|
D.msgs.scrollTop=prevScrollTop;
|
|
2236
2313
|
}
|
|
2237
|
-
// 异步加载未缓存的 agent info
|
|
2314
|
+
// 异步加载未缓存的 agent info,加载完成后局部更新头像和名字
|
|
2238
2315
|
var unique=needFetch.filter(function(v,i,a){ return a.indexOf(v)===i; });
|
|
2239
|
-
unique.
|
|
2240
|
-
fetchAgentInfo(
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2316
|
+
if(unique.length){
|
|
2317
|
+
fetchAgentInfo(unique[0]); // 触发批量请求(攒批机制会合并)
|
|
2318
|
+
unique.forEach(function(aid){
|
|
2319
|
+
fetchAgentInfo(aid).then(function(info){
|
|
2320
|
+
if(!info||S.tab!=='group') return;
|
|
2321
|
+
// 局部更新 DOM:找到该 sender 的所有消息,更新头像和名字
|
|
2322
|
+
var avatars=D.msgs.querySelectorAll('.msg-avatar[title="'+escH(aid)+'"]');
|
|
2323
|
+
var newSrc=getAvatarSrc(info.type);
|
|
2324
|
+
var displayName=(info.name)||aid;
|
|
2325
|
+
avatars.forEach(function(img){
|
|
2326
|
+
img.src=newSrc;
|
|
2327
|
+
img.title=displayName;
|
|
2328
|
+
// 更新同一消息里的名字
|
|
2329
|
+
var msgEl=img.closest('.message');
|
|
2330
|
+
if(msgEl){
|
|
2331
|
+
var meta=msgEl.querySelector('.msg-meta');
|
|
2332
|
+
if(meta&&!msgEl.classList.contains('sent')){
|
|
2333
|
+
var timeStr=meta.textContent.split(' · ')[1]||'';
|
|
2334
|
+
meta.textContent=displayName+' · '+timeStr;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
});
|
|
2245
2339
|
});
|
|
2246
|
-
}
|
|
2340
|
+
}
|
|
2247
2341
|
}
|
|
2248
2342
|
|
|
2249
2343
|
// Group modals
|
|
@@ -2738,6 +2832,31 @@ async function handleRequest(req, res) {
|
|
|
2738
2832
|
sendJson(res, info);
|
|
2739
2833
|
return;
|
|
2740
2834
|
}
|
|
2835
|
+
// 批量获取 agent info,优先读本地缓存,未缓存的后台预热
|
|
2836
|
+
if (pathname === '/api/agent-info-batch' && method === 'POST') {
|
|
2837
|
+
try {
|
|
2838
|
+
const body = await parseBody(req);
|
|
2839
|
+
const aids = body.aids || [];
|
|
2840
|
+
const result = {};
|
|
2841
|
+
const empty = { type: '', name: '', description: '', tags: [] };
|
|
2842
|
+
for (const aid of aids) {
|
|
2843
|
+
const cached = agentInfoCache.get(aid);
|
|
2844
|
+
if (cached && Date.now() - cached.cachedAt < AGENT_INFO_CACHE_TTL) {
|
|
2845
|
+
result[aid] = { type: cached.type, name: cached.name, description: cached.description, tags: cached.tags || [] };
|
|
2846
|
+
}
|
|
2847
|
+
else {
|
|
2848
|
+
result[aid] = cached ? { type: cached.type, name: cached.name, description: cached.description, tags: cached.tags || [] } : empty;
|
|
2849
|
+
// 后台异步拉取,下次请求就有了
|
|
2850
|
+
getAgentInfo(aid).catch(() => { });
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
sendJson(res, { success: true, data: result });
|
|
2854
|
+
}
|
|
2855
|
+
catch (e) {
|
|
2856
|
+
sendJson(res, { success: false, error: e.message });
|
|
2857
|
+
}
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2741
2860
|
// 获取远程 agent.md 原始内容
|
|
2742
2861
|
if (pathname === '/api/agent-md-raw' && method === 'GET') {
|
|
2743
2862
|
const aid = parsedUrl.query.aid;
|
|
@@ -3250,8 +3369,33 @@ async function handleRequest(req, res) {
|
|
|
3250
3369
|
}
|
|
3251
3370
|
const instance = await ensureOnline(aid);
|
|
3252
3371
|
await ensureGroupClient(instance);
|
|
3253
|
-
const info = await
|
|
3254
|
-
|
|
3372
|
+
const [info, membersResult] = await Promise.all([
|
|
3373
|
+
instance.agentCP.groupOps.getGroupInfo(instance.groupTargetAid, groupId),
|
|
3374
|
+
instance.agentCP.groupOps.getMembers(instance.groupTargetAid, groupId).catch(() => ({ members: [] })),
|
|
3375
|
+
]);
|
|
3376
|
+
// 只用本地缓存统计成员类型,不发远程请求,不阻塞响应
|
|
3377
|
+
let humanCount = 0, agentCount = 0;
|
|
3378
|
+
const members = membersResult.members || [];
|
|
3379
|
+
for (const m of members) {
|
|
3380
|
+
const memberAid = m.agent_id || '';
|
|
3381
|
+
if (!memberAid)
|
|
3382
|
+
continue;
|
|
3383
|
+
const cached = agentInfoCache.get(memberAid);
|
|
3384
|
+
if (cached) {
|
|
3385
|
+
if (cached.type === 'human')
|
|
3386
|
+
humanCount++;
|
|
3387
|
+
else if (cached.type === 'agent')
|
|
3388
|
+
agentCount++;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
sendJson(res, Object.assign(Object.assign({ success: true }, info), { member_stats: { total: members.length, human: humanCount, agent: agentCount } }));
|
|
3392
|
+
// 后台异步预热未缓存的 agent info,下次请求就有了
|
|
3393
|
+
for (const m of members) {
|
|
3394
|
+
const memberAid = m.agent_id || '';
|
|
3395
|
+
if (memberAid && !agentInfoCache.has(memberAid)) {
|
|
3396
|
+
getAgentInfo(memberAid).catch(() => { });
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3255
3399
|
}
|
|
3256
3400
|
catch (e) {
|
|
3257
3401
|
sendJson(res, { success: false, error: e.message });
|
|
@@ -3284,6 +3428,7 @@ async function handleRequest(req, res) {
|
|
|
3284
3428
|
sendJson(res, Object.assign({ success: true }, result));
|
|
3285
3429
|
}
|
|
3286
3430
|
catch (e) {
|
|
3431
|
+
utils_1.logger.error(`[API] /api/group/send FAILED: error=${e.message}`);
|
|
3287
3432
|
sendJson(res, { success: false, error: e.message });
|
|
3288
3433
|
}
|
|
3289
3434
|
return;
|