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 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
- // Step 1: register_online(仅通知 group.ap 在线,不再返回游标)
715
- await this.groupOps.registerOnline(this._groupTargetAid);
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
- // Step 2: 冷启动同步 — 拉取历史消息对齐,再进入批推送接收
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
- // Step 3: 启动心跳定时器(首次加入群组时启动)
751
+ // 启动心跳定时器(首次加入群组时启动)
727
752
  this._ensureHeartbeat();
728
753
  }
729
754
  /**
package/dist/agentmd.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface AgentMdOptions {
17
17
  }
18
18
  /**
19
19
  * 从 AID 中提取显示名称
20
- * 例如: "alice.aid.show" -> "alice"
20
+ * 例如: "alice.agentcp.io" -> "alice"
21
21
  */
22
22
  export declare function extractDisplayName(aid: string): string;
23
23
  /**
package/dist/agentmd.js CHANGED
@@ -7,10 +7,10 @@ exports.extractDisplayName = extractDisplayName;
7
7
  exports.generateAgentMd = generateAgentMd;
8
8
  /**
9
9
  * 从 AID 中提取显示名称
10
- * 例如: "alice.aid.show" -> "alice"
10
+ * 例如: "alice.agentcp.io" -> "alice"
11
11
  */
12
12
  function extractDisplayName(aid) {
13
- const suffixes = ['.agentcp.io', '.aid.show', '.agentid.pub'];
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
- utils_1.logger.log(`[Server] WebSocket 重连成功,重新注册 ${onlineGroups.length} 个在线群组...`);
164
- for (const groupId of onlineGroups) {
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
- await instance.agentCP.joinGroupSession(groupId);
167
- utils_1.logger.log(`[Server] 群组重新注册成功: ${groupId}`);
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] 群组重新注册失败: ${groupId}`, e.message || e);
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
- utils_1.logger.log(`[Server] 重新鉴权重连成功,重新注册 ${onlineGroups.length} 个在线群组...`);
191
- for (const groupId of onlineGroups) {
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
- await instance.agentCP.joinGroupSession(groupId);
194
- utils_1.logger.log(`[Server] 群组重新注册成功: ${groupId}`);
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] 群组重新注册失败: ${groupId}`, e.message || e);
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
- // 审核通过:获取群信息、添加本地存储、注册到 Home AP
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
- // 为所有已加入群组注册上线(register_online + 拉取未读 + 启动心跳)
462
+ // 注册上线 + 将群组加入在线列表(不阻塞)
436
463
  const groups = instance.agentCP.getLocalGroupList();
437
- for (const group of groups) {
438
- try {
439
- await instance.agentCP.joinGroupSession(group.group_id);
440
- }
441
- catch (e) {
442
- utils_1.logger.warn(`[Group] joinGroupSession failed: ${group.group_id}`, e.message);
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', 'aid.show', 'agentid.pub'];
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
- async function fetchAgentInfo(aid) {
1388
- if (agentInfoCache[aid]) return agentInfoCache[aid];
1389
- try {
1390
- var r = await fetch('/api/agent-info?aid=' + encodeURIComponent(aid));
1391
- var d = await r.json();
1392
- if (d.type || d.name) { agentInfoCache[aid] = d; }
1393
- return d;
1394
- } catch(e) { return { type:'', name:'', description:'', tags:[] }; }
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.forEach(function(aid){
2240
- fetchAgentInfo(aid).then(function(){
2241
- if(S.tab!=='group') return;
2242
- _lastGroupMsgSig='';
2243
- _lastGroupMsgs._forceRender=true;
2244
- renderGroupMsgs(_lastGroupMsgs);
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
- if (!instance.groupListSynced) {
3206
- try {
3207
- await instance.agentCP.syncGroupList();
3208
- instance.groupListSynced = true;
3209
- }
3210
- catch (syncErr) {
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 instance.agentCP.groupOps.getGroupInfo(instance.groupTargetAid, groupId);
3254
- sendJson(res, Object.assign({ success: true }, info));
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acp-ts",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "基于 ACP智能体通信协议 的智能体通信库,提供智能体身份管理和实时通信功能",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",