acp-ts 1.2.5 → 1.2.7
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 +225 -60
- 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;
|
|
@@ -350,7 +376,7 @@ async function ensureGroupClient(instance) {
|
|
|
350
376
|
},
|
|
351
377
|
onJoinApproved(groupId, groupAddress) {
|
|
352
378
|
utils_1.logger.log(`[Group] onJoinApproved: group=${groupId} address=${groupAddress}`);
|
|
353
|
-
//
|
|
379
|
+
// 审核通过:获取群信息、添加本地存储、注册在线,完成后再通知浏览器
|
|
354
380
|
(async () => {
|
|
355
381
|
try {
|
|
356
382
|
if (!instance.agentCP.groupOps) {
|
|
@@ -370,12 +396,13 @@ async function ensureGroupClient(instance) {
|
|
|
370
396
|
catch (e) {
|
|
371
397
|
utils_1.logger.error(`[Group] onJoinApproved processing failed: group=${groupId}`, e.message);
|
|
372
398
|
}
|
|
399
|
+
// 本地存储就绪后再通知浏览器刷新群列表
|
|
400
|
+
broadcastToBrowser({
|
|
401
|
+
type: 'join_approved',
|
|
402
|
+
group_id: groupId,
|
|
403
|
+
group_address: groupAddress,
|
|
404
|
+
});
|
|
373
405
|
})();
|
|
374
|
-
broadcastToBrowser({
|
|
375
|
-
type: 'join_approved',
|
|
376
|
-
group_id: groupId,
|
|
377
|
-
group_address: groupAddress,
|
|
378
|
-
});
|
|
379
406
|
},
|
|
380
407
|
onJoinRejected(groupId, reason) {
|
|
381
408
|
utils_1.logger.log(`[Group] onJoinRejected: group=${groupId} reason=${reason}`);
|
|
@@ -432,15 +459,27 @@ async function ensureGroupClient(instance) {
|
|
|
432
459
|
utils_1.logger.warn('[Group] syncGroupList error:', e.message);
|
|
433
460
|
}
|
|
434
461
|
}
|
|
435
|
-
//
|
|
462
|
+
// 注册上线 + 将群组加入在线列表(不阻塞)
|
|
436
463
|
const groups = instance.agentCP.getLocalGroupList();
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
464
|
+
if (groups.length > 0) {
|
|
465
|
+
await instance.agentCP.groupRegisterOnline();
|
|
466
|
+
for (const group of groups) {
|
|
467
|
+
instance.agentCP.addOnlineGroup(group.group_id);
|
|
468
|
+
}
|
|
469
|
+
instance.agentCP.ensureGroupHeartbeat();
|
|
470
|
+
utils_1.logger.log(`[Group] 已注册上线,在线群组: ${groups.length}`);
|
|
471
|
+
// 后台异步拉取历史消息,不阻塞启动流程
|
|
472
|
+
Promise.all(groups.map(async (group) => {
|
|
473
|
+
try {
|
|
474
|
+
const lastMsgId = instance.agentCP.getGroupLastMsgId(group.group_id);
|
|
475
|
+
await instance.agentCP.pullAndStoreGroupMessages(group.group_id, lastMsgId, 50);
|
|
476
|
+
}
|
|
477
|
+
catch (e) {
|
|
478
|
+
utils_1.logger.warn(`[Group] background sync failed: ${group.group_id}`, e.message);
|
|
479
|
+
}
|
|
480
|
+
})).then(() => {
|
|
481
|
+
utils_1.logger.log(`[Group] 后台消息同步完成,共 ${groups.length} 个群组`);
|
|
482
|
+
});
|
|
444
483
|
}
|
|
445
484
|
instance.groupInitialized = true;
|
|
446
485
|
instance.groupSessionId = groupSessionId;
|
|
@@ -769,7 +808,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
769
808
|
function updateApSelect() {
|
|
770
809
|
var sel = document.getElementById('apSelect');
|
|
771
810
|
if (sel && sel.options.length === 0) {
|
|
772
|
-
const options = ['agentcp.io', '
|
|
811
|
+
const options = ['agentcp.io', 'agentid.pub'];
|
|
773
812
|
options.forEach(function(op) {
|
|
774
813
|
var opt = document.createElement('option');
|
|
775
814
|
opt.value = op;
|
|
@@ -870,6 +909,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
870
909
|
var data = await res.json();
|
|
871
910
|
if (data.success) {
|
|
872
911
|
showStatus(aid + ' 已上线,正在进入聊天...', 'success');
|
|
912
|
+
sessionStorage.setItem('chatEntry','1');
|
|
873
913
|
window.location.href = '/chat';
|
|
874
914
|
} else {
|
|
875
915
|
showStatus(data.error || '上线失败', 'error');
|
|
@@ -881,7 +921,7 @@ const indexHtml = `<!DOCTYPE html>
|
|
|
881
921
|
}
|
|
882
922
|
}
|
|
883
923
|
|
|
884
|
-
function enterChat(aid) { window.location.href = '/chat'; }
|
|
924
|
+
function enterChat(aid) { sessionStorage.setItem('chatEntry','1'); window.location.href = '/chat'; }
|
|
885
925
|
|
|
886
926
|
async function goOffline(aid) {
|
|
887
927
|
try {
|
|
@@ -1194,6 +1234,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1194
1234
|
<div class="group-info-bar" id="groupInfoBar" style="display:none;">
|
|
1195
1235
|
<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
1236
|
<span id="groupInfoText">群组</span>
|
|
1237
|
+
<span id="groupMemberStats" style="font-size:11px;color:var(--t2);margin-left:4px;"></span>
|
|
1197
1238
|
<span class="copy-link" id="groupInviteBtn" onclick="generateInviteLink()" title="生成邀请链接" style="display:none;">生成邀请链接</span>
|
|
1198
1239
|
<span class="copy-link" id="groupCopyLinkBtn" onclick="copyGroupLink()" title="复制群链接" style="display:none;">复制群链接</span>
|
|
1199
1240
|
<span class="copy-link" onclick="showGroupMembers()" title="查看成员">成员</span>
|
|
@@ -1384,14 +1425,39 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1384
1425
|
if (type === 'human') return '/assets/human.png';
|
|
1385
1426
|
return '/assets/agent.png';
|
|
1386
1427
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1428
|
+
// 批量 agent info 请求:攒批 50ms 后合并发送一次
|
|
1429
|
+
var _agentInfoBatchQueue=[];
|
|
1430
|
+
var _agentInfoBatchTimer=null;
|
|
1431
|
+
function _flushAgentInfoBatch(){
|
|
1432
|
+
_agentInfoBatchTimer=null;
|
|
1433
|
+
var queue=_agentInfoBatchQueue;
|
|
1434
|
+
_agentInfoBatchQueue=[];
|
|
1435
|
+
var aids=queue.map(function(q){ return q.aid; });
|
|
1436
|
+
fetch('/api/agent-info-batch',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({aids:aids})})
|
|
1437
|
+
.then(function(r){ return r.json(); })
|
|
1438
|
+
.then(function(d){
|
|
1439
|
+
if(d.success&&d.data){
|
|
1440
|
+
queue.forEach(function(q){
|
|
1441
|
+
var info=d.data[q.aid]||{type:'',name:'',description:'',tags:[]};
|
|
1442
|
+
if(info.type||info.name) agentInfoCache[q.aid]=info;
|
|
1443
|
+
q.resolve(info);
|
|
1444
|
+
});
|
|
1445
|
+
} else {
|
|
1446
|
+
var empty={type:'',name:'',description:'',tags:[]};
|
|
1447
|
+
queue.forEach(function(q){ q.resolve(empty); });
|
|
1448
|
+
}
|
|
1449
|
+
})
|
|
1450
|
+
.catch(function(){
|
|
1451
|
+
var empty={type:'',name:'',description:'',tags:[]};
|
|
1452
|
+
queue.forEach(function(q){ q.resolve(empty); });
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
function fetchAgentInfo(aid){
|
|
1456
|
+
if(agentInfoCache[aid]) return Promise.resolve(agentInfoCache[aid]);
|
|
1457
|
+
return new Promise(function(resolve){
|
|
1458
|
+
_agentInfoBatchQueue.push({aid:aid,resolve:resolve});
|
|
1459
|
+
if(!_agentInfoBatchTimer) _agentInfoBatchTimer=setTimeout(_flushAgentInfoBatch,50);
|
|
1460
|
+
});
|
|
1395
1461
|
}
|
|
1396
1462
|
async function deleteSession(e, sessionId){
|
|
1397
1463
|
e.stopPropagation();
|
|
@@ -1429,6 +1495,9 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1429
1495
|
function hideNewMsgTip(){ if(D.newMsgTip) D.newMsgTip.style.display='none'; }
|
|
1430
1496
|
|
|
1431
1497
|
async function init(){
|
|
1498
|
+
// 刷新页面时强制回到身份管理页面
|
|
1499
|
+
if(!sessionStorage.getItem('chatEntry')){ window.location.href='/'; return; }
|
|
1500
|
+
sessionStorage.removeItem('chatEntry');
|
|
1432
1501
|
initDom();
|
|
1433
1502
|
// 配置 marked:支持换行、GFM
|
|
1434
1503
|
if(typeof marked!=='undefined'&&marked.setOptions){
|
|
@@ -1994,6 +2063,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
1994
2063
|
D.title.textContent=name;
|
|
1995
2064
|
D.groupInfoBar.style.display='flex';
|
|
1996
2065
|
D.groupInfoText.textContent=name;
|
|
2066
|
+
$('groupMemberStats').textContent='';
|
|
1997
2067
|
D.input.disabled=false;
|
|
1998
2068
|
D.input.placeholder='输入群消息...';
|
|
1999
2069
|
D.input.focus();
|
|
@@ -2009,7 +2079,7 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
2009
2079
|
gmsg.textContent='选择群组...';
|
|
2010
2080
|
await fetch('/api/group/select',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({groupId:groupId,aid:S.aid})});
|
|
2011
2081
|
} catch(e){}
|
|
2012
|
-
//
|
|
2082
|
+
// 获取群信息判断是否为创建者,同时获取成员统计
|
|
2013
2083
|
try {
|
|
2014
2084
|
gmsg.textContent='获取群信息...';
|
|
2015
2085
|
var r=await fetch('/api/group/info?groupId='+encodeURIComponent(groupId)+'&aid='+encodeURIComponent(S.aid));
|
|
@@ -2022,6 +2092,14 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
2022
2092
|
} else {
|
|
2023
2093
|
$('groupCopyLinkBtn').style.display='';
|
|
2024
2094
|
}
|
|
2095
|
+
// 展示成员统计
|
|
2096
|
+
if(d.member_stats){
|
|
2097
|
+
var ms=d.member_stats;
|
|
2098
|
+
var parts=[ms.total+'人'];
|
|
2099
|
+
if(ms.human) parts.push(ms.human+'人类');
|
|
2100
|
+
if(ms.agent) parts.push(ms.agent+'Agent');
|
|
2101
|
+
$('groupMemberStats').textContent='('+parts.join(' / ')+')';
|
|
2102
|
+
}
|
|
2025
2103
|
} catch(e){
|
|
2026
2104
|
// 获取失败时默认显示复制群链接
|
|
2027
2105
|
$('groupCopyLinkBtn').style.display='';
|
|
@@ -2234,16 +2312,33 @@ const chatHtml = `<!DOCTYPE html>
|
|
|
2234
2312
|
} else {
|
|
2235
2313
|
D.msgs.scrollTop=prevScrollTop;
|
|
2236
2314
|
}
|
|
2237
|
-
// 异步加载未缓存的 agent info
|
|
2315
|
+
// 异步加载未缓存的 agent info,加载完成后局部更新头像和名字
|
|
2238
2316
|
var unique=needFetch.filter(function(v,i,a){ return a.indexOf(v)===i; });
|
|
2239
|
-
unique.
|
|
2240
|
-
fetchAgentInfo(
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2317
|
+
if(unique.length){
|
|
2318
|
+
fetchAgentInfo(unique[0]); // 触发批量请求(攒批机制会合并)
|
|
2319
|
+
unique.forEach(function(aid){
|
|
2320
|
+
fetchAgentInfo(aid).then(function(info){
|
|
2321
|
+
if(!info||S.tab!=='group') return;
|
|
2322
|
+
// 局部更新 DOM:找到该 sender 的所有消息,更新头像和名字
|
|
2323
|
+
var avatars=D.msgs.querySelectorAll('.msg-avatar[title="'+escH(aid)+'"]');
|
|
2324
|
+
var newSrc=getAvatarSrc(info.type);
|
|
2325
|
+
var displayName=(info.name)||aid;
|
|
2326
|
+
avatars.forEach(function(img){
|
|
2327
|
+
img.src=newSrc;
|
|
2328
|
+
img.title=displayName;
|
|
2329
|
+
// 更新同一消息里的名字
|
|
2330
|
+
var msgEl=img.closest('.message');
|
|
2331
|
+
if(msgEl){
|
|
2332
|
+
var meta=msgEl.querySelector('.msg-meta');
|
|
2333
|
+
if(meta&&!msgEl.classList.contains('sent')){
|
|
2334
|
+
var timeStr=meta.textContent.split(' · ')[1]||'';
|
|
2335
|
+
meta.textContent=displayName+' · '+timeStr;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
});
|
|
2339
|
+
});
|
|
2245
2340
|
});
|
|
2246
|
-
}
|
|
2341
|
+
}
|
|
2247
2342
|
}
|
|
2248
2343
|
|
|
2249
2344
|
// Group modals
|
|
@@ -2738,6 +2833,31 @@ async function handleRequest(req, res) {
|
|
|
2738
2833
|
sendJson(res, info);
|
|
2739
2834
|
return;
|
|
2740
2835
|
}
|
|
2836
|
+
// 批量获取 agent info,优先读本地缓存,未缓存的后台预热
|
|
2837
|
+
if (pathname === '/api/agent-info-batch' && method === 'POST') {
|
|
2838
|
+
try {
|
|
2839
|
+
const body = await parseBody(req);
|
|
2840
|
+
const aids = body.aids || [];
|
|
2841
|
+
const result = {};
|
|
2842
|
+
const empty = { type: '', name: '', description: '', tags: [] };
|
|
2843
|
+
for (const aid of aids) {
|
|
2844
|
+
const cached = agentInfoCache.get(aid);
|
|
2845
|
+
if (cached && Date.now() - cached.cachedAt < AGENT_INFO_CACHE_TTL) {
|
|
2846
|
+
result[aid] = { type: cached.type, name: cached.name, description: cached.description, tags: cached.tags || [] };
|
|
2847
|
+
}
|
|
2848
|
+
else {
|
|
2849
|
+
result[aid] = cached ? { type: cached.type, name: cached.name, description: cached.description, tags: cached.tags || [] } : empty;
|
|
2850
|
+
// 后台异步拉取,下次请求就有了
|
|
2851
|
+
getAgentInfo(aid).catch(() => { });
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
sendJson(res, { success: true, data: result });
|
|
2855
|
+
}
|
|
2856
|
+
catch (e) {
|
|
2857
|
+
sendJson(res, { success: false, error: e.message });
|
|
2858
|
+
}
|
|
2859
|
+
return;
|
|
2860
|
+
}
|
|
2741
2861
|
// 获取远程 agent.md 原始内容
|
|
2742
2862
|
if (pathname === '/api/agent-md-raw' && method === 'GET') {
|
|
2743
2863
|
const aid = parsedUrl.query.aid;
|
|
@@ -3201,15 +3321,13 @@ async function handleRequest(req, res) {
|
|
|
3201
3321
|
}
|
|
3202
3322
|
const instance = await ensureOnline(aid);
|
|
3203
3323
|
await ensureGroupClient(instance);
|
|
3204
|
-
//
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
utils_1.logger.warn('[Group] syncGroupList error:', syncErr.message);
|
|
3212
|
-
}
|
|
3324
|
+
// 每次都从服务端同步群组列表,确保审核通过等变更及时反映
|
|
3325
|
+
try {
|
|
3326
|
+
await instance.agentCP.syncGroupList();
|
|
3327
|
+
instance.groupListSynced = true;
|
|
3328
|
+
}
|
|
3329
|
+
catch (syncErr) {
|
|
3330
|
+
utils_1.logger.warn('[Group] syncGroupList error:', syncErr.message);
|
|
3213
3331
|
}
|
|
3214
3332
|
const groups = instance.agentCP.getLocalGroupList();
|
|
3215
3333
|
sendJson(res, { success: true, groups, activeGroupId: instance.activeGroupId });
|
|
@@ -3250,8 +3368,33 @@ async function handleRequest(req, res) {
|
|
|
3250
3368
|
}
|
|
3251
3369
|
const instance = await ensureOnline(aid);
|
|
3252
3370
|
await ensureGroupClient(instance);
|
|
3253
|
-
const info = await
|
|
3254
|
-
|
|
3371
|
+
const [info, membersResult] = await Promise.all([
|
|
3372
|
+
instance.agentCP.groupOps.getGroupInfo(instance.groupTargetAid, groupId),
|
|
3373
|
+
instance.agentCP.groupOps.getMembers(instance.groupTargetAid, groupId).catch(() => ({ members: [] })),
|
|
3374
|
+
]);
|
|
3375
|
+
// 只用本地缓存统计成员类型,不发远程请求,不阻塞响应
|
|
3376
|
+
let humanCount = 0, agentCount = 0;
|
|
3377
|
+
const members = membersResult.members || [];
|
|
3378
|
+
for (const m of members) {
|
|
3379
|
+
const memberAid = m.agent_id || '';
|
|
3380
|
+
if (!memberAid)
|
|
3381
|
+
continue;
|
|
3382
|
+
const cached = agentInfoCache.get(memberAid);
|
|
3383
|
+
if (cached) {
|
|
3384
|
+
if (cached.type === 'human')
|
|
3385
|
+
humanCount++;
|
|
3386
|
+
else if (cached.type === 'agent')
|
|
3387
|
+
agentCount++;
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
sendJson(res, Object.assign(Object.assign({ success: true }, info), { member_stats: { total: members.length, human: humanCount, agent: agentCount } }));
|
|
3391
|
+
// 后台异步预热未缓存的 agent info,下次请求就有了
|
|
3392
|
+
for (const m of members) {
|
|
3393
|
+
const memberAid = m.agent_id || '';
|
|
3394
|
+
if (memberAid && !agentInfoCache.has(memberAid)) {
|
|
3395
|
+
getAgentInfo(memberAid).catch(() => { });
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3255
3398
|
}
|
|
3256
3399
|
catch (e) {
|
|
3257
3400
|
sendJson(res, { success: false, error: e.message });
|
|
@@ -3284,6 +3427,7 @@ async function handleRequest(req, res) {
|
|
|
3284
3427
|
sendJson(res, Object.assign({ success: true }, result));
|
|
3285
3428
|
}
|
|
3286
3429
|
catch (e) {
|
|
3430
|
+
utils_1.logger.error(`[API] /api/group/send FAILED: error=${e.message}`);
|
|
3287
3431
|
sendJson(res, { success: false, error: e.message });
|
|
3288
3432
|
}
|
|
3289
3433
|
return;
|
|
@@ -3462,6 +3606,27 @@ async function handleRequest(req, res) {
|
|
|
3462
3606
|
}
|
|
3463
3607
|
const instance = await ensureOnline(aid);
|
|
3464
3608
|
await ensureGroupClient(instance);
|
|
3609
|
+
// 同步服务端群列表到本地存储,确保新审核通过的群也能在左侧列表显示
|
|
3610
|
+
try {
|
|
3611
|
+
await instance.agentCP.syncGroupList();
|
|
3612
|
+
instance.groupListSynced = true;
|
|
3613
|
+
}
|
|
3614
|
+
catch (syncErr) {
|
|
3615
|
+
utils_1.logger.warn('[Group] my-groups syncGroupList error:', syncErr.message);
|
|
3616
|
+
}
|
|
3617
|
+
// 确保所有本地群都已注册在线
|
|
3618
|
+
const localGroups = instance.agentCP.getLocalGroupList();
|
|
3619
|
+
const onlineGroups = new Set(instance.agentCP.getOnlineGroups());
|
|
3620
|
+
for (const g of localGroups) {
|
|
3621
|
+
if (!onlineGroups.has(g.group_id)) {
|
|
3622
|
+
try {
|
|
3623
|
+
await instance.agentCP.joinGroupSession(g.group_id);
|
|
3624
|
+
}
|
|
3625
|
+
catch (e) {
|
|
3626
|
+
utils_1.logger.warn(`[Group] my-groups joinGroupSession failed: ${g.group_id}`, e.message);
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3465
3630
|
const ops = instance.agentCP.groupOps;
|
|
3466
3631
|
const target = instance.groupTargetAid;
|
|
3467
3632
|
const result = await ops.listMyGroups(target);
|